AssetIT

AssetIT Asset Importer Setup & Development Guide for Microsoft Intune and Jamf Pro Integration

🚀 AssetIT Asset Importer — Setup & Development Guide

Forge app (Jira admin page) that imports devices from Microsoft Intune and Jamf Pro into AssetIT.

🧬 Clone repository

Bash
git clone <https://<your-repo-url>>.git
cd <your-project-folder>

🔑 Bitbucket app password

Create an app password: 👉 https://bitbucket.org/account/settings/app-passwords/new

⚙️ Node version

CI builds on Node 22 (node:22.22.0). Use a 22.x release locally:

Bash
node -v   # v22.x

📦 Install

Bash
npm i -g pnpm
pnpm install
pnpm generate        # prisma generate

🧪 Development

Do you need the backend running locally? No (working on the Forge UI / resolver / mapping logic) — skip steps 1–2 and step 5: the committed manifest.yml points at the deployed staging backend, which is sufficient for everyday work. Yes (changing anything under src/backend) — complete all steps.

▶️ 1. Start the local databases — only for a local backend

Bash
docker compose up -d

Service

Details

Postgres 17

on 5432 (assetsimporter / assetsimporter)

Redis

on 6380 (container port 6379)

pnpm studio opens Prisma Studio against DATABASE_URL.

🔧 2. Configure src/backend/.envonly for a local backend

Gitignored; local only:

DATABASE_URL=postgres://<user>:<pass>@localhost:<port>/<db>
JWT_SECRET=<any local value>
BE_SECRET_SECURED=<any local value>
REDIS_HOST=localhost
REDIS_PORT=6380
NODE_ENV=development

DATABASE_URL must point at an AssetIT database, not an empty one. The Prisma schema maps onto AssetIT's tables (assets, api_keys, AddonSettings, models, …); the importer does not own its schema.

🚀 3. Run everything

Bash
pnpm dev

Turbo starts four persistent tasks: frontend (vite, 5173), resolver (nodemon → tsup), backend (nodemon → tsup → node, 3001), and open-api.

To restart a single task: pnpm dev -- --filter=forge-resolvers.

Stop with Ctrl+C in the terminal. Using an IDE stop button sends the signal only to child processes; turbo then reports a task failure that is not an actual error.

🛠 4. Forge CLI

Bash
npm i -g @forge/cli
forge --version

Action

Notes

Generate an API token

https://id.atlassian.com/manage/api-tokens

forge login

forge login

Ask an app admin to add you as a Forge App Developer

👉 https://developer.atlassian.com/console/myapps/8004a698-ad73-4cba-8f5b-a079761ece2e/manage/contributors

Install into your site

Use forge install to install the app into your site.

Bash
forge install     # select Jira, then enter your instance domain

🔗 5. Which backend does the resolver talk to?

🌐 Option A — staging backend (default)

manifest.yml as committed points both remotes at the deployed staging backend:

YAML
baseUrl: https://assetit-assets-importer-dev-dot-devsamurai-dev.ue.r.appspot.com

Leave it as is to develop the Forge UI and resolver without Docker, without src/backend/.env, and without running the backend. AssetIT data comes from staging.

🏠 Option B — local backend

Use this only when changing src/backend. The resolver needs a public HTTPS URL — localhost will not work. Expose :3001 (e.g., ngrok) and place that URL in two locations in manifest.yml:

YAML
remotes:
  connect:
    key: connect
    baseUrl: https://<your-tunnel>.ngrok-free.app  # + connect-eu
    permissions:
      external:
        fetch:
          backend:
            - address: https://<your-tunnel>.ngrok-free.app

Do not commit a tunnel URL. Restore the staging URL before pushing — check git diff manifest.yml each time.

📦 6. Tunnel

Bash
# Terminal 1
pnpm dev

# Terminal 2
forge tunnel

forge tunnel runs the resolver locally against the development environment. Resolver logs appear in this terminal.

🔐 7. Microsoft client secret (once per environment)

Ask the Azure app admin for the client secret of the Azure app registration for the environment you are setting up — it is not in this repo and is unrecoverable from the Azure portal after creation. Then:

Bash
forge providers configure microsoft \
  -s "<client secret value from the Azure admin>" \
  -e development --verbose

manifest.yml (dev/staging, clientId 3d7901f7-…) and manifest.prod.yml (clientId 393307ca-…) use different Azure app registrations; each has its own secret. Set the secret separately for development, staging, and production.

Jamf requires no app-level secret: each user enters their Jamf Pro Client ID and Secret in the app; the app stores them with storage.setSecret.


🏗 Manual deploy (development environment)

Bash
pnpm dev-deploy       # turbo run build && forge deploy -> development

Or in two steps: pnpm build then forge deploy. pnpm start runs the built backend (src/backend/dist/index.js) without watch mode.

Useful build filters:

Command

Builds

pnpm build

everything

pnpm build-forge

frontend + resolver + contracts (--filter=!backend)

pnpm build-server

backend only


🚢 Staging & Production (CI)

Deploys use bitbucket-pipelines.ymldo not deploy staging or production manually.

⬆️ develop → Staging

⚙️ envsubst fills the $VARS in src/backend/src/loadEnv.ts and src/resolvers/src/loadEnv.ts from pipeline variables.

📁 app.stg.yaml is rendered as app.yaml and deployed to App Engine: project devsamurai-dev, service assetit-assets-importer-dev.

Bash
pnpm build-forge
forge lint -e staging
forge deploy -e staging
Bash
forge install --upgrade --site $FORGE_STG_SITE --product jira -e staging

📂 If packages/contract/** or src/open-api/** changed, the generated OpenAPI documentation is pushed to the ds-dev-internal-shared GCS bucket.

🚀 main → Production

🌐 Backend deploys to two regions in parallel, both as service assetit-assets-importer-prod:

  • US — app.prod.us.yaml → project atlas-app-03

  • EU — app.prod.eu.yaml → project atlas-app-eu

🗂 Forge renders the production manifest and deploys it:

Bash
cat manifest.prod.yml | envsubst > manifest.yml
forge lint -e production && forge deploy -e production

manifest.prod.yml keeps $US_SERVER_BASE_URL / $EU_SERVER_BASE_URL as placeholders — the committed prod manifest contains no real URLs.

⛔ There is no forge install --upgrade in production: existing installs pick up the new version automatically; a major-scope change requires the customer to re-consent.

📋 Required pipeline variables

Variable

Used for

DEV_KEY_FILE

GCP service account — staging + docs bucket

ATLAS3_KEY_FILE, ATLAS_EU_KEY_FILE

GCP service accounts — prod US / EU

FORGE_DEPLOYER_EMAIL, FORGE_DEPLOYER_KEY

forge deploy credentials

FORGE_STG_SITE

Staging Jira site for forge install --upgrade

DATABASE_URL, JWT_SECRET, BE_SECRET_SECURED, NODE_ENV

Substituted into loadEnv.ts

SERVER_BASE_URL

Substituted into the resolver's loadEnv.ts

US_SERVER_BASE_URL, EU_SERVER_BASE_URL

Substituted into manifest.prod.yml

Redis hosts are not pipeline variables — they are hardcoded per environment in the app.*.yaml files and reached over a VPC connector.


🩺 Debugging

Item

Details

Resolver logs while tunneling

the forge tunnel terminal.

Deployed logs

https://developer.atlassian.com/console/myapps/8004a698-ad73-4cba-8f5b-a079761ece2e/monitoring/logs?environment=production&interval=P1D (switch the environment query parameter for staging or development).

Diagnostic console.error calls

Several diagnostic console.error calls remain commented out next to the Intune and Jamf permission checks — uncomment them when a tenant misbehaves.


🧹 Code quality

Bash
pnpm lint          # biome lint
pnpm lint-fix      # biome check --write