Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,30 @@ node bin/workflow.js # run the CLI directly (same as `npm run dev` / `npm star
- **`build` is a no-op** (`echo 'Build not needed for now'`) — nothing to compile.
- **There is no test suite, linter, or formatter configured.** Do not invent `npm test`/`npm run lint` commands; they will fail. Verify changes by running the CLI against a real vNext project directory.

The CLI always treats `process.cwd()` as the project root and requires a `vnext.config.json` in that directory. To exercise it, `cd` into a vNext workspace (not this repo) before running.
The CLI always treats `process.cwd()` as the project root and requires at least one solution file (`vnext.config.json` or `vnext.{domain}.config.json`) in that directory. To exercise it, `cd` into a vNext workspace (not this repo) before running. A two-solution testbed lives at `../vnext-example` (`core` + `partner`); copy it into a scratch directory before running write-mode commands against it.

## Two distinct config systems (do not conflate)

1. **`vnext.config.json`** — lives in the *user's project root*, read fresh each run. Defines `domain` and `paths` (which folders hold which component types). Handled by [src/lib/vnextConfig.js](src/lib/vnextConfig.js) with a single-entry cache.
1. **Solution files** — `vnext.config.json` (default) and any `vnext.{domain}.config.json`, all in the *user's project root*, read fresh each run. Each defines a `domain` and `paths` (which folders hold which component types); every solution has its **own `componentsRoot`**. [src/lib/vnextConfig.js](src/lib/vnextConfig.js) is a stateless parser; [src/lib/solutions.js](src/lib/solutions.js) discovers the files and turns each into a **solution object** (`domain`, `fileName`, `projectRoot`, `componentsRoot`, `componentTypes`, `profile`, `warnings`). The `domain` *field* is authoritative; the `{domain}` in the file name is only used for discovery (mismatch → warning).
2. **CLI config** — global, stored in `~/.config/vnext-workflow-cli/config.json` via the `conf` package. Holds API/DB connection settings, structured as **domain profiles** (`ACTIVE_DOMAIN` + `DOMAINS[]`). Handled by [src/lib/config.js](src/lib/config.js).

`config.js` auto-migrates the old flat config format to the domain-aware format on module load (`migrateConfig`), and exposes a virtual `PROJECT_ROOT` key that always returns `process.cwd()` (it is never persisted). `DOMAIN_NAME`, `ACTIVE_DOMAIN`, and `PROJECT_ROOT` are reserved and cannot be set via `config.set` — they have dedicated domain commands.

**Auto domain resolution:** the `preAction` hook in [bin/workflow.js](bin/workflow.js) calls `config.resolveWorkspaceDomain(cwd)` before every command *except* `domain`. It reads the `domain` field from the project's `vnext.config.json` and, if a matching CLI domain profile exists, silently switches `ACTIVE_DOMAIN` to it. This is why running a command in a project folder uses that project's connection settings without a manual `wf domain use`.
**Solution → profile resolution (run-scoped, never persisted):** `solutions.loadSolutions` attaches `profile = config.getDomainConfig(solution.domain)` to every solution. Commands build their connection settings from that profile via `config.buildDbConfig(profile)` / `config.buildApiConfig(profile)` — they never call `config.get('API_BASE_URL')` etc. `ACTIVE_DOMAIN` is **not** touched by workspace commands; it only matters for `wf domain use/active` and `wf config get/set`. A solution whose domain has no profile is skipped with a `wf domain add` hint (except by `csx`, which needs no profile).

**`--domain <name>`** is a *global* Commander option on `program`; the `preAction` hook in [bin/workflow.js](bin/workflow.js) forwards it to the subcommand (`actionCommand.setOptionValue('domain', …)`) so every handler reads `options.domain`. It has no short flag (`-d` belongs to `update --folder`).

## Architecture

`bin/workflow.js` wires up Commander, registers commands, and installs the `preAction` domain-resolution + banner hook. Each command in `src/commands/` orchestrates the shared libraries in `src/lib/`:
`bin/workflow.js` wires up Commander, registers commands, and installs the `preAction` hook that forwards the global `--domain`. Each command in `src/commands/` orchestrates the shared libraries in `src/lib/`:

- **discover.js** — given `vnext.config.json` `paths`, locates component folders under `componentsRoot` and globs their JSON/CSX files. Crucially, it **only scans folders declared in `paths`** and always ignores `.meta/`, `*.diagram.json`, `package*.json`, and `*config*.json`.
- **solutions.js** — the multi-solution layer. `loadWorkspace(options)` discovers/loads/filters solution files and reports fatal problems (no solution file, unknown `--domain`); `runForEachSolution(options, runOpts, fn)` is the shared loop every workspace command uses: it prints a banner per solution (`ui.printSolutionBanner`), skips profile-less solutions when `requireProfile` is set, routes `--file` to the owning solution (`findSolutionForPath`, by `componentsRoot` prefix), calls `fn(solution)` sequentially, stamps `domain` onto returned error rows and prints a `WORKSPACE SUMMARY` when more than one solution ran. `update --all` (single confirmation) and `reset` (domain picker) pre-load the workspace with `loadWorkspace` and hand it to the runner via `runOpts.loaded`.
- **discover.js** — given a solution's `componentsRoot`/`componentTypes`, locates component folders and globs their JSON/CSX files. Crucially, it **only scans folders declared in `paths`** and always ignores `.meta/`, `*.diagram.json`, `package*.json`, and `*config*.json` (the shared `JSON_IGNORE_PATTERNS` / `CSX_IGNORE_PATTERNS` constants — use them instead of inlining the list). `resolveFeatureFolders` accepts an exact path only when it lies inside that solution's `componentsRoot`.
- **csx.js** — embeds `.csx` content into the JSON files that reference it. A `.csx` file is matched to JSON by its `location` string (e.g. `./src/Rules/MyRule.csx`), and the JSON's per-reference `encoding` field decides the form: `NAT` writes plain text, `B64`/absent writes Base64 (default). It updates *every* matching `location` in the JSON tree recursively. **CSX→JSON matching is scoped to the CSX file's own component directory** (the parent of its `src/` folder) so that same-named `.csx` files in sibling components don't cross-contaminate.
- **workflow.js** — per-component publish logic: read JSON `key`/`version`/`flow`, map the component type to a `sys-*` flow name (`workflows`→`sys-flows`, `tasks`→`sys-tasks`, etc.), check the DB, delete if present, publish.
- **workflow.js** — per-component helpers: `getJsonMetadata` reads `key`/`version`/`flow`/`domain`; `checkComponentDomain(metadata, solution)` enforces that a component declares `domain` equal to its solution's domain (commands record failures as `DOMAIN_MISMATCH` and skip the component); `detectComponentType` maps the folder to a `sys-*` flow name (`workflows`→`sys-flows`, `tasks`→`sys-tasks`, etc.); `getGitChangedJson(solution)` lists changed files inside that solution's `componentsRoot`.
- **db.js** — PostgreSQL access with two interchangeable backends selected by `USE_DOCKER`: a direct `pg.Client` connection, or `docker exec ... psql` shelling into a container. Queries target `"<schema>"."Instances"` where schema = the flow name with `-`→`_`. Lookups are by `Key` only (version is ignored), newest `CreatedAt` first.
- **api.js** — axios calls: `GET /health`, `POST /api/v1/definitions/publish`, `GET /api/{version}/definitions/re-initialize`. `publishComponent` carefully unwraps RFC 7807 Problem Details (`detail`, `title`, `errors`, `errorCode`, `traceId`) into a structured `apiError` for rich error display.
- **ui.js** — all console output (chalk). `LOG` helpers, the active-domain banner, and two error renderers: `printApiError` (single component, tree-style) and `printErrorSummaryTable` (batch, table with expanded validation errors). Route user-facing output through this module rather than ad-hoc `console.log`.
- **ui.js** — all console output (chalk). `LOG` helpers, the per-solution banner (`printSolutionBanner`), and two error renderers: `printApiError` (single component, tree-style) and `printErrorSummaryTable` (batch, table with expanded validation errors; grows a `Domain` column when rows carry `domain`, and shows `errorCode` from either the row or the API error). Route user-facing output through this module rather than ad-hoc `console.log`.

## Command semantics

Expand All @@ -52,10 +55,11 @@ The four sync commands differ only in their DB/existing-component behavior — k
| `reset` | interactively-chosen folder | delete + publish | publish |
| `csx` | CSX files only | n/a (no DB/API) | n/a |

`update` and `reset` re-initialize the system (`reinitializeSystem`) after a successful batch. `update`/`csx` default to git-changed files: `getGitChangedJson` / `getGitChangedCsx` run `git status --porcelain` from the **git root** (not project root), then filter results back down to `PROJECT_ROOT`.
Every workspace command runs the table above **once per solution, sequentially** (via `runForEachSolution`); `reset` first asks which domain when several exist and no `--domain` is given, and `update --all` confirms once for all domains. `update` and `reset` re-initialize the system (`reinitializeSystem`) after a successful batch, per solution. `update`/`csx` default to git-changed files: `getGitChangedJson` / `getGitChangedCsx` run `git status --porcelain` from the **git root** (not project root), then filter results down to the solution's **`componentsRoot`** — JSONs elsewhere in the repo are never published.

## Conventions

- CommonJS only (`require`/`module.exports`), Node >= 14. No TypeScript, no ESM.
- Library functions take an explicit `projectRoot` argument rather than reading cwd directly; commands resolve it once via `config.get('PROJECT_ROOT')`.
- Library functions (`discover.js`, `workflow.js`, `csx.js`) take a **solution object** (`projectRoot`, `componentsRoot`, `componentTypes`, …) rather than a bare `projectRoot` or reading cwd directly; commands receive it from `runForEachSolution`. Only `config.get('PROJECT_ROOT')` (inside `solutions.loadWorkspace`) reads cwd.
- Commands are split into a thin `xxxCommand(options)` (header, prompts that must happen once, then `runForEachSolution`) and an `xxxSolution(solution, options)` body that returns `{ success, failed, errors }` so the workspace summary can aggregate.
- DB and API helpers swallow connection errors and return `false`/`null` rather than throwing — callers treat a missing instance as "not in DB".
Loading
Loading