Skip to content

feat(cli): CI/CD flags, fix legacy TS runner, restore device params and tokens (cascades on #31)#32

Merged
vitorfdl merged 11 commits intofeat/cli-refactoring-testsfrom
feat/cli-refactoring-ci-cd
Apr 27, 2026
Merged

feat(cli): CI/CD flags, fix legacy TS runner, restore device params and tokens (cascades on #31)#32
vitorfdl merged 11 commits intofeat/cli-refactoring-testsfrom
feat/cli-refactoring-ci-cd

Conversation

@mateuscardosodeveloper
Copy link
Copy Markdown
Collaborator

@mateuscardosodeveloper mateuscardosodeveloper commented Apr 22, 2026

This PR started with the deploy --all / -t/--token flags for CI/CD and grew to cover two more items surfaced while smoke-testing the branch end-to-end: tagoio run was broken for legacy CJS analyses, and device restore was silently dropping params and tokens.

1. tagoio deploy CI/CD flags

--all flag

Deploys every analysis registered in tagoconfig.json without opening any prompt. Previously, tagoio deploy all opened an interactive picker — despite the help text suggesting otherwise. The positional "all" is now rejected with a helpful pointer to --all, which is a bug fix, not a breaking change.

-t, --token <profile-token> flag

Supplies a profile token for the current invocation, bypassing the local .tago-lock.*.lock file. This is the missing piece for CI/CD — runners have no lock file, and tagoio init/tagoio login are interactive and can't run headlessly.

Together, tagoio deploy --all --env prod -t $TOKEN --silent runs end-to-end without any prompt and without requiring init/login first.

Code-quality cleanups in deploy.ts

No behavior change:

  • The if (options.all) { /* empty */ } else if (...) pattern was inverted to if (!options.all) { ... }, removing a branch whose only purpose was to block entry into the interactive selection.
  • A dead return after errorHandler(...) was removed (TypeScript flagged it as unreachable — errorHandler is typed never), bringing the file in line with the 5+ other call sites that already omit the return.

2. tagoio run: tsx loader for CJS-compatible TS execution

Bug

tagoio run <name> was broken for every legacy analysis project that uses "module": "CommonJS" + "moduleResolution": "Node" in their tsconfig.json and imports subpaths without a .js extension (e.g. import { Data } from "@tago-io/sdk/lib/types"). Commit 60c5792 had swapped the runner to node --experimental-transform-types --watch, but Node's built-in TS execution routes those files through the ESM loader, which rejects extensionless specifiers with ERR_MODULE_NOT_FOUND:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../node_modules/@tago-io/sdk/lib/types'
Did you mean to import "@tago-io/sdk/lib/types.js"?

Every legacy customer analysis failed at import time. Investigation confirmed there's no Node 24 flag that fixes this — --experimental-specifier-resolution=node was removed in Node 19 and has no replacement; --experimental-transform-types only strips types, it doesn't downlevel import to require; and no flag forces CJS loader to accept ESM syntax.

Fix

_buildCMD now invokes tsx's CLI via the binary shipped inside the CLI's own node_modules:

node <cli-path>/node_modules/tsx/dist/cli.mjs watch <script>

tsx pairs esbuild (TS transpile) with oxc-resolver and CJS-aware module resolution, so the same classic imports resolve cleanly. Watch mode is provided by tsx watch instead of node --watch, and --inspect / --clear continue to work.

Why tsx over reviving @swc-node/register

  • Install footprint: ~12 MB per platform (esbuild single native binary) vs ~25-36 MB for @swc/core + oxc-resolver
  • One native binary to ship, not two
  • Watch + REPL built-in
  • Same resolver internally (oxc-resolver), so no correctness difference
  • 10x the weekly npm downloads and stronger maintenance signal
  • oxc-resolver alone does not solve the problem — it resolves paths but does not transpile TS syntax or plug into Node's loader hooks

--tsnd (ts-node-dev) and --deno flags are unchanged.

3. tagoio backup device restore: params and serial tokens

Device restore was only recreating the device record itself. Two pieces of state that users expect to survive a backup/restore cycle were silently dropped:

  • Configuration parameters (device.params) — required by most integrations.
  • Tokens tied to a physical device by serie_number — without these, the device cannot communicate after restore.

Adds three helpers in devices.ts:

  • stripDeviceFields centralizes the list of fields the TagoIO API rejects on create/edit (ids, timestamps, profile, and the two restored-separately fields params and tokens). Both processCreateTask and processEditTask delegate to it, removing previously duplicated destructuring.
  • restoreDeviceParams replays the backup's params via the dedicated paramSet endpoint.
  • restoreDeviceTokens recreates only tokens with a serie_number (the identifying attribute for physical devices); ephemeral tokens without one are intentionally skipped. On the edit path it fetches the current token list first and skips serials already present, so re-runs are idempotent. Token values cannot be preserved because the backup stores them masked — callers must update integrations that relied on the old value.

Tests cover: param restoration on create/edit, serial-token recreation, skipping tokens without a serial, idempotency when a serial already exists on the target device, and graceful handling of a failed tokenCreate (logged, restore continues).

Acceptance criteria

Deploy CI/CD (original scope)

  • --all flag added
  • -t, --token <profile-token> flag added (deploy-only, not global)
  • tagoio deploy --all deploys every analysis without any prompt
  • tagoio deploy --all --env <env> -t $TOKEN --silent works end-to-end with no lock file
  • Help text updated with --all and -t examples including CI/CD usage
  • README documents CI/CD workflow using deploy only (no init/login prerequisite)
  • No breaking changes to tagoio deploy <name> or tagoio deploy (no-arg picker)
  • No changes to init, login, or other commands

Run fix

  • Legacy CJS analyses with extensionless subpath imports execute successfully
  • Watch, --inspect, --clear continue to work
  • --tsnd and --deno unchanged
  • Tests updated to assert the new tsx CLI path

Backup device restore

  • Device params are restored via paramSet
  • Tokens with serie_number are recreated on restore
  • Tokens without serie_number are skipped
  • Edit-path token restore is idempotent
  • Failed tokenCreate is logged and does not abort the restore

Verification

  • npx tsc --noEmit → clean
  • npx vitest run → 436/436 (up from 426 — 10 new tests for the backup changes)
  • npm run linter → 0/0
  • npm run build → clean
  • Smoke test of the original bug: handler.ts from a real CJS analysis project (analysis-prosentry, tsconfig with "module": "CommonJS", imports @tago-io/sdk + @tago-io/sdk/lib/types without .js) now resolves and runs successfully under the new tsx-based runner. Same file under the previous --experimental-transform-types command fails with ERR_MODULE_NOT_FOUND.

Commit layout

  1. feat(deploy)--all + -t/--token flag implementation + help text refresh
  2. docs(readme) — CI/CD pipelines section with GitHub Actions example
  3. test(deploy) — 4 tests covering --all, -t override, --all + -t end-to-end, and the legacy "all" rejection
  4. fix(run) — switch to tsx loader for CJS-compatible TS execution, update _buildCMD tests
  5. feat(backup) — restore device params and serial tokens on restore, 5 new tests

Test plan

  • CI green on this branch.
  • Smoke-test tagoio deploy --all --env <env> -t $TOKEN --silent against a real project with tagoconfig.json configured.
  • Confirm tagoio deploy all produces the --all pointer error.
  • Verify tagoio deploy <name> and tagoio deploy (no args) still behave as before.
  • Smoke-test tagoio run <name> on a legacy CJS analysis with extensionless subpath imports.
  • Smoke-test tagoio run <name> -d (debug) and -c (clear).
  • Backup a profile that includes devices with params and serial-number tokens, restore it, and confirm params and tokens are present.
  • Re-run the restore (idempotency check) and confirm existing tokens are not duplicated.

Adds --all to deploy every analysis in tagoconfig.json without any
prompt, and -t/--token to supply a profile token at invocation time
(bypassing the lock file, which doesn't exist in CI runners).

Also rejects the legacy "deploy all" positional with a helpful
pointer to --all. The "all" positional has never worked as the help
text suggested — it opened an interactive prompt — so dropping it
is a bug fix, not a breaking change.

Help examples updated: remove the misleading "deploy all" line and
add pipeline-friendly usages covering --all, --all + stage-env,
and the full CI combination (--all -e prod -t $TAGOIO_TOKEN --silent).

Surgical scope: no changes to init, login, or any shared
infrastructure. Only deploy.ts and analysis/index.ts are touched.
Documents the deploy flow for CI runners using --all + -e + -t +
--silent, including the install step for @tago-io/cli and
@tago-io/builder (deploy shells out to analysis-builder, so the
builder needs to be on PATH).

Notes that -t accepts either a profile token or an external-analysis
token with the proper Access Management permissions, and that a
pre-configured tagoconfig.json in the repository is required.
Adds coverage for the four new behaviors:
- legacy positional "all" is rejected with a pointer to --all
- --all deploys every analysis with no prompt
- -t/--token overrides the lock-file token for the run
- --all + -t end-to-end works with no lock file (CI flow)

Also updates the existing "env missing" test to reflect the new
error message ("No profile token found" instead of "Environment not
found") and introduces a defaultOptions() helper to DRY up the test
setup now that IDeployOptions has 5 required fields.
…gister

`tagoio run <name>` was broken on every Node run: the command called
`node -r <swc-register-path>` via `resolveCLIPath`, but that package
was removed during Phase 1 when SWC was ripped out. Every invocation
failed with `Cannot find module '.../node_modules/@swc-node/register/index'`.

Switches the Node path in `_buildCMD` to `node --experimental-strip-types
--watch`. Node 24+ strips TypeScript type annotations natively, so no
external loader is required. The flag is stable on Node 24 and does
not emit a runtime warning (unlike `--experimental-transform-types`,
which does). Limitation: strip-types does not handle `enum` or
`namespace` — not used in customer analyses today.

Also:
- Drops the now-unused `resolveCLIPath` import from run-analysis.ts.
- Updates the three `_buildCMD` tests that asserted the old SWC path.
- README: removes the `.swcrc` / `.swcrc.example` note from the
  Analysis Runner section (the debugger no longer needs source-map
  config — Node's strip-types preserves line numbers).
- README (CI/CD): documents the Access Management rule required when
  using an external-analysis token in `-t, --token` (Access Analysis
  + Edit Analysis + Upload Analysis Script), so least-privilege
  pipelines don't hit Authorization Denied at deploy time.
- `analysis/index.ts`: spells out `--env` instead of `-e` in the help
  examples for consistency with the option name shown above the list.
`tagoio run <name>` was broken for legacy analysis projects that use
`"module": "CommonJS"` + `"moduleResolution": "Node"` in their tsconfig
and import subpaths without a `.js` extension (e.g.
`import { Data } from "@tago-io/sdk/lib/types"`). Commit 60c5792 had
swapped the runner to `node --experimental-transform-types --watch`, but
Node's built-in TS execution routes those files through the ESM loader,
which rejects extensionless specifiers with `ERR_MODULE_NOT_FOUND`.
Every legacy analysis failed at import time.

Switches `_buildCMD` to invoke tsx's CLI via the cached binary shipped
inside the CLI's own node_modules:
`node <cli-path>/node_modules/tsx/dist/cli.mjs watch <script>`. tsx
pairs esbuild (TS transpile) with oxc-resolver and CJS-aware module
resolution, so the same classic imports resolve cleanly. Watch mode is
provided by `tsx watch` instead of `node --watch`, and `--inspect` /
`--clear` continue to work.

Why tsx over reviving `@swc-node/register`:
- Install footprint: ~12 MB per platform (esbuild single native binary)
  vs ~25-36 MB for @swc/core + oxc-resolver
- One native binary to ship, not two
- Watch + REPL ship built-in
- Same resolver internally (oxc-resolver), so no correctness difference
- 10x the weekly npm downloads and stronger maintenance signal

Also:
- Adds `tsx@^4.21.0` to dependencies.
- Reintroduces the `resolveCLIPath` import removed in 60c5792.
- Updates the three `_buildCMD` tests that asserted the Node native
  flags to assert the tsx CLI path and `watch` subcommand instead.

The `--tsnd` and `--deno` flags are unchanged.
Device restore was only recreating the device record itself. Two
pieces of state that users expect to survive a backup/restore cycle
were silently dropped:

- Configuration parameters (`device.params`) — these carry per-device
  runtime settings and are required by most integrations.
- Tokens tied to a physical device by `serie_number` — without these,
  the device cannot communicate after restore.

Adds three helpers in `devices.ts`:

- `stripDeviceFields` centralizes the list of fields the TagoIO API
  rejects on create/edit (ids, timestamps, profile, and the two
  restored-separately fields `params` and `tokens`). Both
  `processCreateTask` and `processEditTask` now delegate to it,
  removing the previously duplicated destructuring.
- `restoreDeviceParams` replays the backup's params via the dedicated
  `paramSet` endpoint.
- `restoreDeviceTokens` recreates only tokens with a `serie_number`
  (the identifying attribute for physical devices); ephemeral tokens
  without one are intentionally skipped. On the edit path it fetches
  the current token list first and skips serials already present, so
  re-runs are idempotent. Token values cannot be preserved because
  the backup stores them masked — callers must update integrations
  that relied on the old value.

Tests cover: param restoration on create/edit, serial-token
recreation, skipping tokens without a serial, idempotency when a
serial already exists on the target device, and graceful handling of
a failed `tokenCreate` (logged, restore continues).
@mateuscardosodeveloper mateuscardosodeveloper changed the title feat(deploy): add --all and -t/--token flags for CI/CD (cascades on #31) feat: CI/CD flags, tsx runner fix, device restore params/tokens Apr 24, 2026
@mateuscardosodeveloper mateuscardosodeveloper changed the title feat: CI/CD flags, tsx runner fix, device restore params/tokens feat(cli): CI/CD flags, fix legacy TS runner, restore device params and tokens (cascades on #31) Apr 24, 2026
@vitorfdl vitorfdl merged commit 8bd56d6 into feat/cli-refactoring-tests Apr 27, 2026
1 check passed
@vitorfdl vitorfdl deleted the feat/cli-refactoring-ci-cd branch April 27, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants