@starci/hfs 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +25 -21
- package/bin/hfs.mjs +59 -37
- package/lint/run.mjs +70 -39
- package/package.json +2 -2
- package/runtime/engine/admission.mjs +3 -3
- package/runtime/engine/ledger-db.mjs +2 -2
- package/runtime/engine/machine-db.mjs +90 -9
- package/runtime/engine/migrations/machine/0002-worktrees-no-workflow-kind.sql +13 -0
- package/runtime/engine/migrations/runtime/0005-ended-workflow-views.sql +93 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +28 -9
- package/runtime/knowledge/hfs/peer-integrations.yaml +18 -0
- package/runtime/knowledge/hfs/slots.yaml +193 -128
- package/runtime/knowledge/patterns/fe/folder.yaml +36 -36
- package/runtime/modules/kernel/failure-codes.yaml +23 -32
- package/runtime/scripts/checks/architecture/backend.mjs +1 -1
- package/runtime/scripts/checks/architecture/config.mjs +31 -11
- package/runtime/scripts/checks/architecture/contracts.mjs +4 -4
- package/runtime/scripts/checks/architecture/fe-slot-allows.mjs +7 -3
- package/runtime/scripts/checks/architecture/framework-pinned.mjs +5 -47
- package/runtime/scripts/checks/architecture/frontend.mjs +6 -4
- package/runtime/scripts/checks/architecture/hfs.mjs +104 -66
- package/runtime/scripts/checks/architecture/next-data.mjs +3 -2
- package/runtime/scripts/checks/architecture/registration.mjs +1 -1
- package/runtime/scripts/checks/architecture/symbols.mjs +13 -2
- package/runtime/scripts/checks/architecture/test-world-files.mjs +83 -45
- package/runtime/scripts/checks/architecture/typescript.mjs +45 -20
- package/runtime/scripts/checks/typescript-programs.mjs +2 -2
- package/runtime/scripts/lib/hfs-check.mjs +156 -141
- package/runtime/scripts/lib/hfs-path-findings.mjs +13 -2
- package/runtime/scripts/lib/hfs-rules/contract.mjs +15 -42
- package/runtime/scripts/lib/hfs-rules/frontend.mjs +37 -36
- package/runtime/scripts/lib/hfs-rules/peer-integrations.mjs +44 -0
- package/runtime/scripts/lib/hfs-rules/proof-commands.mjs +11 -8
- package/runtime/scripts/lib/hfs-slots.mjs +244 -61
- package/runtime/scripts/lib/hfs-view.mjs +9 -7
- package/runtime/scripts/lib/language.mjs +11 -1
- package/runtime/scripts/lib/safe-remove.mjs +95 -10
- package/scaffold/app.mjs +179 -0
- package/scaffold/service.mjs +26 -16
- package/sync/cli.mjs +1 -1
- package/sync/hygiene.mjs +11 -8
- package/sync/index.mjs +109 -111
- package/sync/managed.mjs +9 -8
- package/sync/sonar-key.mjs +20 -22
- package/templates/{be → app}/ci-workflows/github/workflows/ci.yml +4 -2
- package/templates/app/gitignore +6 -0
- package/templates/app/hooks/husky/pre-commit +25 -0
- package/templates/app/hooks/husky/pre-push +7 -0
- package/templates/app/package-scripts/package.json +22 -0
- package/templates/{be → app}/quality-config/sonar-project.properties +3 -2
- package/templates/app/skeleton/.editorconfig +15 -0
- package/templates/app/skeleton/.gitattributes +2 -0
- package/templates/app/skeleton/.nvmrc +1 -0
- package/templates/app/skeleton/.starciwork/features/index.yaml +7 -0
- package/templates/app/skeleton/.starciwork/workspace.yaml +9 -0
- package/templates/app/skeleton/README.md +36 -0
- package/templates/app/skeleton/scripts/codegen.mjs +4 -0
- package/templates/{fe → app}/tool-config/prettierignore +4 -1
- package/templates/be/skeleton/.sops.yaml +2 -0
- package/templates/be/skeleton/.starcistacks/application-stacks.yaml +10 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +27 -6
- package/templates/be/skeleton/apps/__app__/src/main.ts +4 -1
- package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +11 -0
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +25 -0
- package/templates/be/skeleton/src/modules/domain/identity/errors/identity.error.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +11 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +14 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/identity/index.ts +6 -0
- package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +8 -1
- package/templates/be/skeleton/src/modules/platform/composition/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +71 -23
- package/templates/be/skeleton/src/modules/platform/config/errors/config.error.ts +14 -16
- package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/config/server.config.ts +2 -12
- package/templates/be/skeleton/src/modules/platform/errors/domain.error.ts +16 -7
- package/templates/be/skeleton/src/modules/platform/errors/errors/errors.error.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.contracts.ts +33 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.decorators.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.ts +32 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.log-events.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.options.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.service.spec.ts +94 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.service.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/errors/http-status.policy.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/errors/index.ts +4 -1
- package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +17 -0
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +31 -0
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.ts +68 -0
- package/templates/be/skeleton/src/modules/platform/i18n/bundle-message-catalog.service.spec.ts +66 -0
- package/templates/be/skeleton/src/modules/platform/i18n/bundle-message-catalog.service.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.contracts.ts +18 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.decorators.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module.ts +24 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.options.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.port.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/i18n/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/i18n/request-locale.service.spec.ts +45 -0
- package/templates/be/skeleton/src/modules/platform/i18n/request-locale.service.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/logging/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +85 -68
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +14 -10
- package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.port.ts +0 -1
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.contracts.ts +25 -0
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.ts +24 -0
- package/templates/fe/skeleton/apps/__app__/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/error.tsx +5 -17
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +11 -22
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/not-found.tsx +3 -12
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +6 -24
- package/templates/fe/skeleton/apps/__app__/src/app/global-error.tsx +4 -18
- package/templates/fe/skeleton/apps/__app__/src/app/globals.css +5 -0
- package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +28 -0
- package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +35 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +21 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +17 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +26 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +15 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +25 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +15 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +27 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +32 -0
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/config/index.ts +12 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +2 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +6 -0
- package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/request.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +4 -0
- package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/proxy.ts +1 -1
- package/sync/skeleton.mjs +0 -76
- package/templates/be/gitignore +0 -2
- package/templates/be/hooks/husky/pre-commit +0 -13
- package/templates/be/hooks/husky/pre-push +0 -6
- package/templates/be/package-scripts/package.json +0 -19
- package/templates/be/skeleton/scripts/.gitkeep +0 -0
- package/templates/be/skeleton/src/modules/platform/config/config.decorators.ts +0 -9
- package/templates/be/skeleton/src/modules/platform/errors/error.filter.ts +0 -20
- package/templates/be/tool-config/prettierignore +0 -8
- package/templates/fe/ci-workflows/github/workflows/ci.yml +0 -40
- package/templates/fe/gitignore +0 -3
- package/templates/fe/hooks/husky/pre-commit +0 -16
- package/templates/fe/hooks/husky/pre-push +0 -5
- package/templates/fe/package-scripts/package.json +0 -13
- package/templates/fe/parts/api-client.ts +0 -44
- package/templates/fe/parts/api-outcome.ts +0 -7
- package/templates/fe/quality-config/sonar-project.properties +0 -8
- package/templates/fe/skeleton/scripts/.gitkeep +0 -0
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/client.ts +0 -1
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/index.ts +0 -3
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/outcome.ts +0 -1
- package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/index.ts +0 -4
- package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/navigation.ts +0 -5
- package/templates/fe/skeleton-shared/apps/__app__/next.config.ts +0 -12
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/api/index.ts +0 -2
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/index.ts +0 -9
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/request.ts +0 -5
- package/templates/fe/skeleton-shared/apps/__app__/src/proxy.ts +0 -5
- package/templates/fe/skeleton-shared/packages/__family__-api/package.json +0 -12
- package/templates/fe/skeleton-shared/packages/__family__-api/src/client.ts +0 -1
- package/templates/fe/skeleton-shared/packages/__family__-api/src/index.ts +0 -3
- package/templates/fe/skeleton-shared/packages/__family__-api/src/outcome.ts +0 -1
- package/templates/fe/skeleton-shared/packages/__family__-api/tsconfig.json +0 -5
- package/templates/fe/skeleton-shared/packages/__family__-i18n/package.json +0 -18
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/app.ts +0 -19
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/index.ts +0 -2
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/proxy.ts +0 -12
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/request.ts +0 -15
- package/templates/fe/skeleton-shared/packages/__family__-i18n/tsconfig.json +0 -5
- package/templates/fe/tool-config/prettierrc +0 -1
- /package/templates/{be → app}/ci-workflows/github/workflows/e2e.yml +0 -0
- /package/templates/{be → app}/starciwork.gitignore +0 -0
- /package/templates/{be → app}/tool-config/prettierrc +0 -0
- /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/next.config.ts +0 -0
- /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/config.ts +0 -0
- /package/templates/fe/{skeleton-app → skeleton}/apps/__app__/src/modules/i18n/routing.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.0 - 2026-10-01
|
|
4
|
+
|
|
5
|
+
- Added: the scaffolded root `package.json` (and its lockfile root entry) pins `@starci/test-world` from `knowledge/hfs/canon-pins.yaml` (1.0.0), the package whose `starci-test-stack` bin the managed `test:stack` script runs.
|
|
6
|
+
- Changed: R47 `test-world-files` reads `test-world.config.ts` in the shape `@starci/test-world` defines and in its one named form `export const { useTestWorld, useSandbox } = defineTestWorld({ ... })` (the form R89 allows): `stack` names the environment, a stack service is faked only by its own `stacks` entry `{ fakedBy, reason }`, and a `fakes` entry of the declaration that fakes a stack service is refused like a `fakes/<provider>/` folder. A config with no readable named declaration (a default export included) is refused. The top-level `fakedBy` and the `stacks` list of environment names are no longer read.
|
|
7
|
+
- New (lane SHAPE): `HFS_PEER_INTEGRATION_MISSING` (R111). The app root package.json that depends on every package of a pair of `knowledge/hfs/peer-integrations.yaml` (at the named major) declares the pair's peer in its dependencies; the first pair is `@nestjs/apollo` on `@nestjs/platform-express` 11, which requires `@as-integrations/express5`.
|
|
8
|
+
- Changed (lane SHAPE): `HFS_PROOF_COMMAND_FILE_MISSING` (R105) judges the records naming a side (`repository: be` or `fe`) from the app root, where their proof commands run; a record naming another repository is still not judged.
|
|
9
|
+
- Changed (lane SHAPE): the managed `typecheck` script of an app with fe packages runs `npm run build --workspaces --if-present` before it type-checks the fe apps, which import the packages from `dist/`.
|
|
10
|
+
- Changed (lane SHAPE): `hfs scaffold app` writes `.starciwork/workspace.yaml` with the repositories `[{role: be, name: be}, {role: fe, name: fe}]`, the form of the examples.
|
|
11
|
+
- Fixed (lane SHAPE): slot be.tests.world allows `fakes/<provider>/server.ts`, so `BE_SOURCE_FORM` reads the fake server of a provider as its role instead of refusing the path `test-world-files` accepts.
|
|
12
|
+
- Breaking: the app monorepo standard. A product is ONE app repository `<app>/`: the root holds the one `package.json` (every dependency of both sides and every script; npm workspaces only for `fe/packages/*`), the one lockfile and `node_modules`, `hfs.json` of kind `app`, README, CI, `.husky`, `scripts/` and `.starciwork`; `be/` and `fe/` hold what the old back-end and front-end repository roots held except `package.json` and the lockfile. `hfs.json` is `{ "hfs": 2, "kind": "app", "project", "sides": { "be": { "apps", "optionalSlots", "connections" }, "fe": { "apps", "optionalSlots", "reads": ["be/contracts/"] } } }` (manifest major 2). The standalone `be` and `fe` repository kinds (`profile`, `stacks`) are deleted, with no alias.
|
|
13
|
+
- Breaking: `hfs init` is deleted; `hfs scaffold app <name> [--into <dir>]` writes the root, the `be/` and `fe/` skeletons and the app hfs.json, and refuses an existing directory. The fresh app lints clean (0 findings): the be skeleton is default-deny (throttler, CSRF origin guard, AuthGuard of `domain/identity`), carries the required platform capabilities (config, logging, errors, primitives, clock, i18n, cqrs, http-security) and the health feature; the fe skeleton mounts one pages feature per route slot, drawn with `@starci/grammar`.
|
|
14
|
+
- Breaking: `hfs lint`, `hfs check` and `hfs sync` run at the app root. The lint runs ESLint once per side (be/ with the BE canon, fe/ with the FE canon, each from its side folder with its one-line config), stylelint over fe/, the root checks once and the side checks and the architecture machine per side ("side view": each side judged with its folder as the root), and writes one `starci/lint@1` report and one Sonar import with app-relative paths. `--changed` keeps working with app-relative files. `hfs sync` renders the root files (package.json scripts `dev:be`, `dev:fe`, `build:be`, `build:fe`, `start:<app>`, `migrate`, `lint`, `test*`, `codegen`, `contract:emit`, `typecheck`, the hooks, CI, Sonar, prettier, the .gitignore block) and each side's managed files.
|
|
15
|
+
- Breaking: the front end reads `be/contracts/` in place for its codegen (`sides.fe.reads`); the front-end contract copy and its `HFS_CONTRACT_SNAPSHOT_DRIFT` hash comparison are gone.
|
|
16
|
+
- Changed: one version per dependency in the one package.json: a conflict between the sides takes its canon pin, otherwise the higher version, pinned in `knowledge/hfs/canon-pins.yaml` with a why (`graphql` 16.14.2).
|
|
17
|
+
- Changed: a side finding names every path from the app root, in its message as in its path (`fe/apps/web/...`, `be/src/...`), in `hfs check` and in the ESLint and stylelint findings of `hfs lint`.
|
|
18
|
+
- Fixed: the architecture machine gives each file to the deepest tsconfig project that holds it, so a route file importing its feature through the app alias `@/` mounts its owner (`FE_ROUTE_FILES_THIN` reported "0 feature owners"); the skeleton route slots import through the alias. A route file binding a declaration to a Next.js segment export (`generateMetadata`, ...) is not `HFS_ALIAS_REEXPORT`.
|
|
19
|
+
|
|
3
20
|
## 3.0.0 - 2026-10-01
|
|
4
21
|
|
|
5
22
|
- Breaking: one entry, `hfs lint`. It runs the repository's eslint, the `hfs check` repository pass and, for a front end, stylelint, and writes one `starci/lint@1` report; `--sonar <file>` writes the single Sonar import (engines starci-hfs, eslint and stylelint in one document); `--changed <files...> --format json` is the land-gate shape. `hfs report`, `hfs check --sonar` and the managed scripts `lint:check`, `lint:report`, `hfs:check` and `hfs:report` are deleted; the managed `lint` script is `hfs lint`, and CI, hooks and the Sonar template read `reports/lint.sonar.json`.
|
package/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# @starci/hfs
|
|
2
2
|
|
|
3
|
-
The HFS command line of a StarCi product
|
|
3
|
+
The HFS command line of a StarCi app: the one repository of a product, `<app>/` with its root (the one `package.json`, lockfile and `node_modules`, `hfs.json` of kind `app`, CI, hooks, `.starciwork`) and its two sides `be/` and `fe/`. It is installed from the npm registry at the exact version in `knowledge/hfs/canon-pins.yaml` (see [`packages/README.md`](../README.md)),
|
|
4
4
|
and it is self-contained: `runtime/` carries the slot manifest, the canon pins, the Vietnamese why
|
|
5
5
|
catalog slice, the loader and the architecture machine with every file it imports, so it runs where there is no runtime
|
|
6
|
-
checkout. The machine loads `typescript` from the
|
|
6
|
+
checkout. The machine loads `typescript` from the app it checks (never its own copy), so run `npm ci` first.
|
|
7
|
+
|
|
8
|
+
Every command runs at the app root. A side is judged with its folder as the root it was when products were split in two repositories (the "side view" of `scripts/lib/hfs-slots.mjs`): the root slots (`app.*`) are judged once, the `be.*` slots under `be/` and the `fe.*` slots under `fe/`, and nothing crosses sides except `sides.fe.reads` (`be/contracts/`, the input of the front end's codegen). Every finding path is app-relative (`be/src/...`, `fe/apps/...`).
|
|
7
9
|
|
|
8
10
|
```sh
|
|
9
|
-
npx hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>] [--stylelint <glob>] # THE lint entry (`npm run lint`): exit 0 clean, 1 findings, 2 a tool could not run
|
|
11
|
+
npx hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>] [--stylelint <glob>] # THE lint entry (`npm run lint`): ESLint over be/ with the BE canon and over fe/ with the FE canon, stylelint over fe/, the hfs checks; exit 0 clean, 1 findings, 2 a tool could not run
|
|
10
12
|
npx hfs check [--repo <dir>] [--json] [--fast] [--base <ref>] # the repository pass alone (what `hfs lint` runs for the findings that have no TypeScript file); exit 1 on any error-level finding
|
|
11
|
-
npx hfs
|
|
12
|
-
npx hfs emit-contracts [--repo <dir>] # write contracts/<app>/schema.graphql of every api app that serves GraphQL and contracts/<app>/openapi.json of every api app with a typed operation table (the managed script contract:emit)
|
|
13
|
+
npx hfs scaffold app <name> [--into <dir>] # a new app <dir>/<name>/: the root, be/ and fe/ skeletons and the app hfs.json; refuses an existing directory
|
|
14
|
+
npx hfs emit-contracts [--repo <dir>] # write be/contracts/<app>/schema.graphql of every api app that serves GraphQL and be/contracts/<app>/openapi.json of every api app with a typed operation table (the managed script contract:emit)
|
|
13
15
|
npx hfs explain <path> [--repo <dir>] [--json]
|
|
14
16
|
npx hfs sync (--check | --write) [--root <dir>] # generated files: the managedBy slots of slots.yaml, the .gitignore block (sync/, templates/)
|
|
15
17
|
npx hfs work-hygiene # pre-commit guard: staged .starciwork / .starcistacks paths, and the secrets guard over every staged file (read from the index)
|
|
@@ -26,8 +28,8 @@ Only `*.service.ts` files are unit-tested (unit test standard), each with exactl
|
|
|
26
28
|
|
|
27
29
|
The spec skeleton is `Test.createTestingModule({ providers: [Service, { provide: TOKEN, useValue: double }, ...] }).compile()` and `moduleRef.get(Service)`, with one provider per constructor dependency and nothing else, every double imported from `@starci/jest-preset` (the root), and one placeholder `it` per public method. The double of a token comes from `ruleParams.be.specDoubles` of the slot manifest, the table the lint law `spec-infra-double-from-kit` holds a spec to (`*_ENTITY_MANAGER` -> `mockEntityManager()`, `CLOCK` -> `new FakeClock(...)`, `OUTBOX` -> `recordingOutbox()`, `CACHE` -> `fakeCache(clock)`, lock, lease, fence and hold -> `fakeLock(clock)`, ids -> `fakeIds()`, `*_OPTIONS` -> a literal to fill in, everything else and every class -> `mock<T>()`), so the skeleton satisfies the law by construction: no cast, no `new` of the service, no ambient clock. A back end only (a front end has no services); the files are written for prettier (print width 120).
|
|
28
30
|
|
|
29
|
-
`hfs check` reads the
|
|
30
|
-
whole architecture machine over
|
|
31
|
+
`hfs check` reads the app's `hfs.json` and the tracked paths (`git ls-files`), checks the work tree (the root rules once, the side rules per side), and then runs the
|
|
32
|
+
whole architecture machine over each side folder. Every finding carries a why code and its Vietnamese text
|
|
31
33
|
(`modules/kernel/failure-codes.yaml`).
|
|
32
34
|
|
|
33
35
|
Its own checks (`scripts/lib/hfs-check.mjs`, `scripts/lib/hfs-rules/`; the rendered-file checks (`sync/managed.mjs`) and the prettier check (`sync/format.mjs`) live in `sync/` because they read the templates and the repository's own install, and reach `hfs check` as `extraFindings`; the jest / vitest preset the coverage exclusions come from and prettier are read from the repository's `node_modules`, so run `npm ci` first):
|
|
@@ -55,12 +57,13 @@ Its own checks (`scripts/lib/hfs-check.mjs`, `scripts/lib/hfs-rules/`; the rende
|
|
|
55
57
|
| `HFS_STACKS_SHAPE` | error | a `.starcistacks` path outside the standard shape (a sealed file outside `<env>/secrets/`, `runtime/files/`, root `DESIGN.md` or `k8s/`), a local Sonar not owned by the host, a service still rooted at `.stacks` (R10) |
|
|
56
58
|
| `HFS_CI_MISSING_CANON` | error | `ci.yml` without a `run:` step of `hfs lint` (`npm run lint`, or `npx hfs lint` at the pinned version), or `.husky/pre-push` without `npm run typecheck` and `npm run lint` (R13) |
|
|
57
59
|
| `HFS_DEP_VERSION_SKEW` | error | a dependency at two specs across the root and workspace `package.json` files, a dependency declared at another version than the root `overrides` pin, or a nested copy of a declared dependency in `package-lock.json` (R14) |
|
|
58
|
-
| `HFS_CONTRACT_SNAPSHOT_DRIFT` | error
|
|
60
|
+
| `HFS_CONTRACT_SNAPSHOT_DRIFT` | error | a back-end api app serving GraphQL without `be/contracts/<app>/schema.graphql` (R23); the front end reads that snapshot in place, so there is no copy to drift |
|
|
59
61
|
| `BE_TEST_TOPOLOGY` | error | a `*.test.*` file, a `testing/` folder, a second jest configuration or a `jest` key in `package.json` (R47; `int-spec`, `harness-spec` and the retired test folders are the machine's `HFS_TEST_KIND_RETIRED`) |
|
|
60
62
|
| `BE_SPEC_PLACEMENT` | error | a `*.spec.*`, `*.test.*` or `*-spec.*` file outside the four test layers, `scripts/` and `tools/` included (R102) |
|
|
61
63
|
| `HFS_REPO_LOCAL_CHECK` | error | a `check-*` file in `scripts/` or `tools/`, an `eslint-local-rules*` file or local eslint plugin, or a script that runs a local check (R103) |
|
|
62
64
|
| `HFS_LINT_SUPPRESSION_FILE` | error | an `eslint.suppressions*` file, a `lint:suppressions` script, an eslint suppress flag or a suppressions config (R104) |
|
|
63
65
|
| `HFS_PROOF_COMMAND_FILE_MISSING` | error | a `.starciwork` `requiresProof.<kind>.command` that runs a file the repository does not hold (R105) |
|
|
66
|
+
| `HFS_PEER_INTEGRATION_MISSING` | error | the app root `package.json` depends on a driver integration (a pair of `knowledge/hfs/peer-integrations.yaml`) without its runtime peer, e.g. `@nestjs/apollo` on `@nestjs/platform-express` 11 without `@as-integrations/express5` (R111) |
|
|
64
67
|
| `FE_WIRE_GENERATED` | error | a contract copy with no `codegen` script wired before `build` and `typecheck`, or generated types older than the copy (R52) |
|
|
65
68
|
| `FE_I18N_PLACEMENT` | error | no `next-intl`, no `src/proxy.ts`, a `middleware.ts`, a route file outside `[locale]`, no `vi.json` catalog (R59) |
|
|
66
69
|
| `FE_I18N_CATALOG` | error | a locale catalog lacking a key another locale has (R60) |
|
|
@@ -74,9 +77,8 @@ Its own checks (`scripts/lib/hfs-check.mjs`, `scripts/lib/hfs-rules/`; the rende
|
|
|
74
77
|
The managed files are judged by `sync/managed.mjs` and `sync/ts-strict.mjs` (the package renders the templates; the runtime copy does not). The list of managed
|
|
75
78
|
files is the `managedBy` slots of `slots.yaml`; `hfs sync --write` renders them and `hfs check` compares them, so a hand edit and a forgotten `sync` are the same finding.
|
|
76
79
|
Each finding is reported once: the eslint and stylelint one-liners under R17, `tsconfig.json` under R22 when it names a flag, a workflow or hook that lost a canon step under R13 (or R19 for the format step), everything else under R05.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
(`fe.tool-config-repo`): it carries the repository's task graph, which no preset can render.
|
|
80
|
+
The root and both sides are rendered by the one mechanism. The root: `package.json` `scripts` (`dev:be`, `dev:fe`, `build:be`, `build:fe`, `start:<app>`, `lint`, `lint:fix`, `test`, `test:integration`, `test:e2e`, `test:contract`, `test:stack`, `codegen`, `contract:emit`, `migrate`, `typecheck`, ...; a be script runs from `be/`, where its tsconfig and jest configuration are), `.prettierrc`, `.prettierignore`, the `.husky` hooks, the CI workflows, the Sonar file and the `.gitignore` block. A side: its `tsconfig.json` (resolving `@starci/tsconfig` from the root `node_modules`), its `eslint.config.mjs` one-liner, and for be `tsconfig.build.json`, `src/tests/tsconfig.json` and `jest.config.js`, for fe `stylelint.config.mjs`. `lint` is the one lint gate, `hfs lint`: ESLint over each side with its canon, the app check and stylelint over fe; `lint:fix` is the same with `--fix`. The front end has no test script, no test configuration and no e2e or coverage file: it has no tests, and `FE_NO_TESTS` (R97) refuses any spec, e2e file, test tool or test script under `fe/`. `turbo.json` stays the app's own
|
|
81
|
+
(`fe.tool-config-repo`): it carries the task graph, which no preset can render.
|
|
80
82
|
|
|
81
83
|
The architecture machine (`scripts/checks/architecture.mjs` of the runtime, the same code bundled here): tiers and import
|
|
82
84
|
direction, owner public API, cycles, module registration and composition, clones, dead exports, required files,
|
|
@@ -90,26 +92,26 @@ of the changed source files without clones and dead exports, and no file-system
|
|
|
90
92
|
checks still cover the whole tree. With no merge-base `--fast` is a refusal (exit 2) that names the fix, never a silent full
|
|
91
93
|
pass.
|
|
92
94
|
|
|
93
|
-
Exit codes: 0 clean, 1 an error finding, 2 a refusal (not a Git work tree, bad flag, `--fast` with no merge-base).
|
|
94
|
-
|
|
95
|
+
Exit codes: 0 clean, 1 an error finding, 2 a refusal (not a Git work tree, bad flag, `--fast` with no merge-base). `hfs check` and `hfs lint` never write to the app
|
|
96
|
+
(`hfs lint --fix` lets ESLint and stylelint fix in place).
|
|
95
97
|
|
|
96
98
|
## Sonar
|
|
97
99
|
|
|
98
|
-
One mechanism
|
|
99
|
-
fails while any is open. Nothing is configured per
|
|
100
|
+
One mechanism for both sides: every finding of the canon is imported into Sonar, and the quality gate
|
|
101
|
+
fails while any is open. Nothing is configured per app; the pieces are managed files (`hfs sync`) and this package.
|
|
100
102
|
|
|
101
|
-
One entry, one report, one file: `npm run lint` is `hfs lint`; `npm run lint -- --sonar reports/lint.sonar.json` writes the ONE Sonar file, read through `sonar.externalIssuesReportPaths`. It carries three engines: `starci-hfs` (the repository findings of `hfs check`, rule id = the finding code), `eslint` (the BE and FE canon plugins alike, rule id = the ESLint rule) and
|
|
103
|
+
One entry, one report, one file: `npm run lint` is `hfs lint`; `npm run lint -- --sonar reports/lint.sonar.json` writes the ONE Sonar file, read through `sonar.externalIssuesReportPaths`. It carries three engines: `starci-hfs` (the repository findings of `hfs check`, rule id = the finding code), `eslint` (the BE and FE canon plugins alike, rule id = the ESLint rule) and `stylelint` over fe (rule id = the stylelint rule). `hfs lint --format json` prints the same findings as the one report `starci/lint@1` (`{ schema, ok, changed, counts.error, engines, errors[], findings[{ engine, rule, code, severity, path, line, column, message }] }`); `--changed <files...>` restricts ESLint and stylelint to those files and keeps of the repository findings the ones on a listed file or on no file. The exit code is 0 clean, 1 findings, 2 a tool could not run (a missing ESLint install is never a pass).
|
|
102
104
|
|
|
103
105
|
`--sonar` writes the findings as a Generic Issue Import document (SonarQube 10.3+ format: `{ rules, issues }`) before the verdict, so a
|
|
104
106
|
failing lint still leaves its report. A rule's name and description are the catalog's English title and Vietnamese title, meaning and next step;
|
|
105
107
|
impacts are HIGH (maintainability). `info` findings (the soft-size backlog) are report-only and not imported. The output is sorted, so two runs
|
|
106
108
|
over one tree are byte-identical. Sonar drops an issue on a file it does not index (a tracked source file or stylesheet under `sonar.sources`), so the ONE placement rule of the three engines files a finding on
|
|
107
109
|
any other path (hfs.json, a workflow, a config file, `e2e/`, a package the sources do not list) and a finding with no path on the first source file, its message starting with the real path.
|
|
108
|
-
Sonar's own ESLint import (`sonar.eslint.reportPaths`) is not used: it drops those issues silently.
|
|
110
|
+
Sonar's own ESLint import (`sonar.eslint.reportPaths`) is not used: it drops those issues silently. `sonar.sources` lists `be/apps`, `be/src` and `fe/apps` (plus `fe/packages` when the front end opts into an `fe.package.*` slot); `sonar.tests` is the back end's.
|
|
109
111
|
|
|
110
112
|
The managed `sonar-project.properties` carries `sonar.externalIssuesReportPaths` (`reports/lint.sonar.json` only) and no `sonar.host.url` (the host is
|
|
111
113
|
`SONAR_HOST_URL`); the managed CI workflow produces the report and runs the scan and the gate action with `!cancelled()`, so a failed lint step still
|
|
112
|
-
reaches Sonar while the job stays failed. There is no `continue-on-error`.
|
|
114
|
+
reaches Sonar while the job stays failed. There is no `continue-on-error`. CI runs `npm run lint -- --sonar reports/lint.sonar.json` once at the app root, before the scan. The duplicate-block threshold (`ruleParams.<side>.duplicateBlock`) has no Sonar property for TypeScript (SonarJS detects
|
|
113
115
|
clones with its own token rule), so the machine enforces it (R21) and its findings are imported like every other.
|
|
114
116
|
|
|
115
117
|
The gate is `knowledge/sonar-gate.yaml`, the one declaration: the new-code conditions (duplication, blocker and critical issues, hotspots; no coverage condition) and an `overall` part (0 open issues on the whole code,
|
|
@@ -117,7 +119,7 @@ duplicated lines density, cognitive complexity through the S3776 rule). A SonarQ
|
|
|
117
119
|
open issue, imported or native; that is stricter than the three imports alone and is intended. `hfs check` reports `HFS_SONAR_CONFIG` (R11) when the
|
|
118
120
|
properties file is not its render or the stack declaration names another gate. R20 and R21 have their Sonar enforcers as conditions of that file.
|
|
119
121
|
|
|
120
|
-
Sonar does not depend on coverage: the managed properties file has no `sonar.*.lcov.reportPaths` and no `sonar.coverage.*`, the gate has no coverage condition, and the managed CI workflow uploads no coverage anywhere (no Codecov).
|
|
122
|
+
Sonar does not depend on coverage: the managed properties file has no `sonar.*.lcov.reportPaths` and no `sonar.coverage.*`, the gate has no coverage condition, and the managed CI workflow uploads no coverage anywhere (no Codecov). The back end's unit coverage is the runner's: the managed `test` script is `jest --selectProjects unit --coverage`, which fails below the per-file 100 threshold on `src/**/*.service.ts`.
|
|
121
123
|
|
|
122
124
|
## Maintaining the bundle
|
|
123
125
|
|
|
@@ -128,8 +130,10 @@ derived from the machine's rule id lists). After changing any of those files, `k
|
|
|
128
130
|
`knowledge/hfs/canon-pins.yaml`, `knowledge/patterns/fe/folder.yaml` or the catalog entries of those codes, run `node packages/hfs/scripts/sync-runtime.mjs`;
|
|
129
131
|
`tests/hfs-cli.spec.mjs` fails on a stale copy. Bump `version` here and in the pin when the behaviour changes.
|
|
130
132
|
|
|
131
|
-
The examples gate `node scripts/checks/check-example-architecture.mjs` runs this CLI
|
|
132
|
-
with an `hfs.json` and fails on any
|
|
133
|
+
The examples gate `node scripts/checks/check-example-architecture.mjs` runs `hfs lint` of this CLI at the root of every `examples/*`
|
|
134
|
+
app with an `hfs.json` (`examples/todo-app`, `examples/ecommerce-app`) and fails on any finding or any tool that could not run;
|
|
135
|
+
`hfs check` alone would miss the machine's source rules, which the canons judge. It is heavy: run it once, by hand, after `npm ci`
|
|
136
|
+
in each app.
|
|
133
137
|
|
|
134
138
|
## Serving knowledge to other packages
|
|
135
139
|
|
package/bin/hfs.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// hfs - the HFS command line of a StarCi
|
|
2
|
+
// hfs - the HFS command line of a StarCi app: one repository, `<app>/`, with one package.json, lockfile and node_modules at its root
|
|
3
|
+
// and two sides, be/ and fe/, declared by the one hfs.json of kind app. Every verb runs at the app root.
|
|
3
4
|
// hfs check [--repo <dir>] [--json] [--fast] [--base <ref>]
|
|
4
5
|
// every tracked path has a slot; required files exist; nothing forbidden or
|
|
5
6
|
// tracked-that-must-be-ignored; pins match; every managed file equals its render
|
|
@@ -13,43 +14,47 @@
|
|
|
13
14
|
// --fast: only what changed since the merge-base with origin/main (else main; --base
|
|
14
15
|
// overrides it); the machine
|
|
15
16
|
// runs on those owners without clones and dead exports. No merge-base is a refusal
|
|
16
|
-
// (exit 2), never a silent full pass. Exit 1 on any error-level finding.
|
|
17
|
-
//
|
|
18
|
-
// hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>]
|
|
19
|
-
// the ONE lint entry (npm run lint): ESLint
|
|
20
|
-
//
|
|
21
|
-
// the one Sonar import file. Exit 0 clean, 1 findings, 2 a tool could not run (lint/run.mjs).
|
|
17
|
+
// (exit 2), never a silent full pass. Exit 1 on any error-level finding. The root checks run once and
|
|
18
|
+
// the side checks and the machine once per side, the side folder as their root; every path is app-relative.
|
|
19
|
+
// hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>]
|
|
20
|
+
// the ONE lint entry (npm run lint): ESLint per side with that side's canon config (per-file rules and
|
|
21
|
+
// the project-graph rules), this check's findings, and stylelint over the fe side, as one starci/lint@1
|
|
22
|
+
// report; --sonar writes the one Sonar import file. Exit 0 clean, 1 findings, 2 a tool could not run (lint/run.mjs).
|
|
23
|
+
// hfs scaffold app <name> [--into <dir>] a new app <dir>/<name>/: the root (hfs.json, package.json, managed files, .starciwork) and the
|
|
24
|
+
// be/ and fe/ skeletons (scaffold/app.mjs). Refuses an existing directory.
|
|
22
25
|
// hfs explain <path> [--repo <dir>] [--json] which slot owns the path, its tier, allowed imports, required tests.
|
|
23
|
-
// hfs emit-contracts [--repo <dir>] write contracts/<app>/schema.graphql of every api app that serves GraphQL (emit/contracts.mjs):
|
|
26
|
+
// hfs emit-contracts [--repo <dir>] write be/contracts/<app>/schema.graphql of every api app that serves GraphQL (emit/contracts.mjs):
|
|
24
27
|
// printSchema(lexicographicSortSchema) of the resolvers the app root composes; no env, no database, no network.
|
|
25
|
-
// hfs sync (--check | --write) [--root <dir>] the
|
|
28
|
+
// hfs sync (--check | --write) [--root <dir>] the managed files of the root (scripts, husky, CI, .gitignore block, sonar, prettier) and
|
|
29
|
+
// of each side (tsconfig, eslint, jest, stylelint); sync/cli.mjs
|
|
26
30
|
// hfs work-hygiene the pre-commit guard: staged .starciwork and .starcistacks paths, and the secrets guard over every staged file (read from the index); sync/cli.mjs
|
|
27
31
|
// hfs new service <dir> <name> [--inject <Decorator>=<module>:<Type> | <Class>=<module>]... [--repo <dir>]
|
|
28
|
-
// a back-end `<name>.service.ts` and its `<name>.service.spec.ts` skeleton (scaffold/service.mjs
|
|
32
|
+
// a back-end `<name>.service.ts` and its `<name>.service.spec.ts` skeleton (scaffold/service.mjs; <dir> is
|
|
33
|
+
// relative to be/): the spec is built
|
|
29
34
|
// with Test.createTestingModule, one provider per constructor dependency (kit doubles from @starci/jest-preset),
|
|
30
35
|
// one placeholder it per public method. Never overwrites a file.
|
|
31
36
|
// hfs new spec <file>.service.ts [--repo <dir>] the spec skeleton of an existing service, read from its constructor with the repository's TypeScript
|
|
32
|
-
// Every finding names a why code and carries its Vietnamese text.
|
|
33
|
-
//
|
|
34
|
-
import fs from 'node:fs';
|
|
37
|
+
// Every finding names a why code and carries its Vietnamese text. check, lint and explain read the app, never write to it. Exit codes:
|
|
38
|
+
// 0 clean, 1 error findings, 2 a refusal or bad usage.
|
|
35
39
|
import path from 'node:path';
|
|
36
40
|
import { fileURLToPath } from 'node:url';
|
|
37
41
|
import { isMain } from '../runtime/scripts/checks/common.mjs';
|
|
38
|
-
import { checkRepository, explainPath,
|
|
39
|
-
import { HfsSlotsError } from '../runtime/scripts/lib/hfs-slots.mjs';
|
|
42
|
+
import { checkRepository, explainPath, trackedFiles } from '../runtime/scripts/lib/hfs-check.mjs';
|
|
43
|
+
import { HfsSlotsError, loadSlotManifest, readRepoDeclaration } from '../runtime/scripts/lib/hfs-slots.mjs';
|
|
40
44
|
import { formatFindings } from '../sync/format.mjs';
|
|
41
45
|
import { main as syncMain } from '../sync/cli.mjs';
|
|
42
|
-
import { SyncError } from '../sync/index.mjs';
|
|
46
|
+
import { SyncError, loadPresets } from '../sync/index.mjs';
|
|
43
47
|
import { managedFindings } from '../sync/managed.mjs';
|
|
44
48
|
import { emitContracts } from '../emit/contracts.mjs';
|
|
45
49
|
import { ScaffoldError, newService, newSpec } from '../scaffold/service.mjs';
|
|
50
|
+
import { scaffoldApp } from '../scaffold/app.mjs';
|
|
46
51
|
import { contractEmitFindings } from '../runtime/scripts/lib/hfs-rules/contract.mjs';
|
|
47
52
|
import { lintRepository, parseLintArgs, printLintText } from '../lint/run.mjs';
|
|
48
53
|
import { writeReport } from '../report/sonar.mjs';
|
|
49
54
|
|
|
50
55
|
const USAGE = `hfs check [--repo <dir>] [--json] [--fast] [--base <ref>]
|
|
51
|
-
hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>]
|
|
52
|
-
hfs
|
|
56
|
+
hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>]
|
|
57
|
+
hfs scaffold app <name> [--into <dir>]
|
|
53
58
|
hfs emit-contracts [--repo <dir>]
|
|
54
59
|
hfs explain <path> [--repo <dir>] [--json]
|
|
55
60
|
hfs sync (--check | --write) [--root <dir>]
|
|
@@ -58,10 +63,10 @@ hfs new service <dir> <name> [--inject <Decorator>=<module>:<Type> | <Class>=<mo
|
|
|
58
63
|
hfs new spec <file>.service.ts [--repo <dir>]
|
|
59
64
|
`;
|
|
60
65
|
const PER_CODE_LIMIT = 25;
|
|
61
|
-
const VALUE_FLAGS = new Set(['--repo', '--base', '--inject']);
|
|
66
|
+
const VALUE_FLAGS = new Set(['--repo', '--base', '--inject', '--into']);
|
|
62
67
|
/** Flags that may repeat: their values are collected in order. */
|
|
63
68
|
const LIST_FLAGS = new Set(['--inject']);
|
|
64
|
-
const BOOL_FLAGS = new Set(['--json', '--
|
|
69
|
+
const BOOL_FLAGS = new Set(['--json', '--fast']);
|
|
65
70
|
|
|
66
71
|
function parse(argv) {
|
|
67
72
|
const opts = { positional: [] };
|
|
@@ -114,20 +119,25 @@ function printExplain(e, out) {
|
|
|
114
119
|
if (e.code) out(` ${e.code}: ${e.titleVi}\n ${e.whyVi}\n`);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* The app pass: slots, managed files, formatter, contract snapshots and the architecture machine's check surface, root and sides.
|
|
124
|
+
* `only` limits the formatter to those (app-relative) files.
|
|
125
|
+
*/
|
|
118
126
|
async function runCheck({ repoRoot, fast = false, base, only, presets, prettier }) {
|
|
119
127
|
const tracked = trackedFiles(repoRoot);
|
|
120
|
-
// Managed files, the .gitignore block and sonar against their render (R04, R05, R11, ...), and prettier
|
|
128
|
+
// Managed files of the root and both sides, the .gitignore block and sonar against their render (R04, R05, R11, ...), and prettier
|
|
129
|
+
// through the app's own install (R19, never under --fast).
|
|
121
130
|
const extraFindings = [...await managedFindings({ repoRoot, tracked, presets }), ...(fast ? [] : await formatFindings({ repoRoot, files: only ?? tracked, prettier }))];
|
|
122
|
-
// R23 against the
|
|
131
|
+
// R23 against the be side itself (full pass only): the committed snapshots equal what `emit-contracts` writes now.
|
|
123
132
|
let contracts = null;
|
|
124
|
-
let
|
|
125
|
-
try {
|
|
126
|
-
if (
|
|
133
|
+
let be = null;
|
|
134
|
+
try { be = readRepoDeclaration(loadSlotManifest(), repoRoot).sides?.be ?? null; } catch { /* checkRepository reports the unreadable declaration */ }
|
|
135
|
+
if (be) {
|
|
127
136
|
if (fast) contracts = { status: 'skipped', reason: '--fast does not emit the apps' };
|
|
128
137
|
else {
|
|
129
|
-
const
|
|
130
|
-
|
|
138
|
+
const files = tracked.filter((file) => file.startsWith('be/')).map((file) => file.slice('be/'.length));
|
|
139
|
+
const emitted = contractEmitFindings({ repoRoot: path.join(repoRoot, 'be'), files, repo: be, emit: emitContracts });
|
|
140
|
+
extraFindings.push(...emitted.findings.map((finding) => ({ ...finding, side: 'be', path: `be/${finding.path}` })));
|
|
131
141
|
contracts = { status: 'checked', apps: emitted.apps };
|
|
132
142
|
}
|
|
133
143
|
}
|
|
@@ -136,7 +146,7 @@ async function runCheck({ repoRoot, fast = false, base, only, presets, prettier
|
|
|
136
146
|
return result;
|
|
137
147
|
}
|
|
138
148
|
|
|
139
|
-
/** `hfs lint`: ESLint, the
|
|
149
|
+
/** `hfs lint`: ESLint per side, the app checks and stylelint over the fe side as one report; see lint/run.mjs. */
|
|
140
150
|
async function lintMain(argv, { stdout, presets, prettier }) {
|
|
141
151
|
const opts = parseLintArgs(argv);
|
|
142
152
|
const repoRoot = path.resolve(opts.repo ?? process.cwd());
|
|
@@ -150,10 +160,20 @@ async function lintMain(argv, { stdout, presets, prettier }) {
|
|
|
150
160
|
return exit;
|
|
151
161
|
}
|
|
152
162
|
|
|
163
|
+
/**
|
|
164
|
+
* The jest preset a new app renders its Sonar exclusions from: the one installed beside this @starci/hfs (`npx -p @starci/hfs -p
|
|
165
|
+
* @starci/jest-preset hfs scaffold app <name>`, or an install that holds both). None is a refusal, never a render without it.
|
|
166
|
+
*/
|
|
167
|
+
async function scaffoldPresets() {
|
|
168
|
+
try { return await loadPresets(path.dirname(fileURLToPath(import.meta.url))); } catch (error) {
|
|
169
|
+
throw new SyncError('HFS_SYNC_PRESET_MISSING', `${error.message.replace(/^[A-Z_]+: /, '')}; run hfs scaffold with @starci/jest-preset installed beside @starci/hfs (npx -p @starci/hfs -p @starci/jest-preset hfs scaffold app <name>)`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
153
173
|
/** `presets` and `prettier` are test seams: the Sonar exclusions sync would load from the repository's installed preset, and the repository's own prettier. */
|
|
154
174
|
export async function main(argv, { stdout = (s) => process.stdout.write(s), stderr = (s) => process.stderr.write(s), presets, prettier } = {}) {
|
|
155
175
|
const [verb, ...rest] = argv;
|
|
156
|
-
if (!['check', 'lint', '
|
|
176
|
+
if (!['check', 'lint', 'scaffold', 'explain', 'sync', 'work-hygiene', 'emit-contracts', 'new'].includes(verb)) { stderr(USAGE); return 2; }
|
|
157
177
|
try {
|
|
158
178
|
if (verb === 'sync' || verb === 'work-hygiene') return await syncMain(argv);
|
|
159
179
|
if (verb === 'lint') return await lintMain(rest, { stdout, presets, prettier });
|
|
@@ -168,9 +188,10 @@ export async function main(argv, { stdout = (s) => process.stdout.write(s), stde
|
|
|
168
188
|
}
|
|
169
189
|
if (verb === 'emit-contracts') {
|
|
170
190
|
if (opts.positional.length) throw new Error('hfs emit-contracts takes no path');
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
const be = readRepoDeclaration(loadSlotManifest(), repoRoot).sides?.be;
|
|
192
|
+
if (!be) throw new Error('hfs emit-contracts runs at the app root (the folder of hfs.json)');
|
|
193
|
+
const { written, skipped, standIns } = emitContracts({ repoRoot: path.join(repoRoot, 'be'), declaration: be });
|
|
194
|
+
for (const file of written) stdout(`wrote be/${file}
|
|
174
195
|
`);
|
|
175
196
|
stdout(`hfs emit-contracts: ${written.length} written${skipped.length ? `, ${skipped.join(', ')} serve no GraphQL` : ''}
|
|
176
197
|
`);
|
|
@@ -190,10 +211,11 @@ export async function main(argv, { stdout = (s) => process.stdout.write(s), stde
|
|
|
190
211
|
`);
|
|
191
212
|
return 0;
|
|
192
213
|
}
|
|
193
|
-
if (verb === '
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
214
|
+
if (verb === 'scaffold') {
|
|
215
|
+
const [kind, name, ...extra] = opts.positional;
|
|
216
|
+
if (kind !== 'app' || !name || extra.length || opts.repo !== undefined) throw new Error('hfs scaffold takes `app <name> [--into <dir>]`');
|
|
217
|
+
const { root: created, files } = scaffoldApp({ name, into: path.resolve(opts.into ?? process.cwd()), presets: presets ?? await scaffoldPresets() });
|
|
218
|
+
stdout(`hfs scaffold app: created ${created} (${files.length} files); next: npm install, then npm run lint\n`);
|
|
197
219
|
return 0;
|
|
198
220
|
}
|
|
199
221
|
if (opts.positional.length !== 1) throw new Error('hfs explain takes exactly one path');
|
package/lint/run.mjs
CHANGED
|
@@ -1,36 +1,41 @@
|
|
|
1
|
-
// hfs lint - the ONE lint entry and the ONE report of a StarCi
|
|
1
|
+
// hfs lint - the ONE lint entry and the ONE report of a StarCi app.
|
|
2
2
|
//
|
|
3
|
-
// hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>]
|
|
3
|
+
// hfs lint [--repo <dir>] [--changed <file>...] [--fix] [--format text|json] [--sonar <file>]
|
|
4
4
|
//
|
|
5
|
-
// It runs, over the
|
|
6
|
-
// 1. ESLint through the
|
|
7
|
-
// rules of the architecture machine, findings on the line of the
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
5
|
+
// It runs at the app root (the folder of hfs.json), over the app (or over the app-relative files named by --changed):
|
|
6
|
+
// 1. ESLint once per side, from the side folder, through the app's own install and that side's eslint.config.mjs (be/ the be canon,
|
|
7
|
+
// fe/ the fe canon: the per-file rules and the project-graph rules of the architecture machine, findings on the line of the
|
|
8
|
+
// offending TypeScript file); each side lints only its own files, so the be rules never see fe/ and the fe rules never see be/;
|
|
9
|
+
// 2. `hfs check` for what has no TypeScript file to sit on (the tree, managed files, pins, CI, contracts, docs, .starciwork), root and sides;
|
|
10
|
+
// 3. stylelint over the fe side's stylesheets (sync STYLE_GLOB), from fe/ with its stylelint.config.mjs.
|
|
11
|
+
// Their findings become one list of one shape (`starci/lint@1`), every path app-relative. `--format json` prints that report on stdout;
|
|
12
|
+
// `--sonar <file>` writes the same findings as THE Sonar Generic Issue Import file (engine ids starci-hfs, eslint, stylelint in one
|
|
13
|
+
// document); the exit code is the land-gate input: 0 no finding, 1 at least one, 2 a tool could not run (never a pass).
|
|
14
|
+
// --changed restricts ESLint and stylelint to the listed files and keeps of the app findings only those on a listed file or on no file.
|
|
14
15
|
import fs from 'node:fs';
|
|
15
16
|
import path from 'node:path';
|
|
16
17
|
import { createRequire } from 'node:module';
|
|
17
18
|
import { spawnSync } from 'node:child_process';
|
|
18
19
|
import { linterReport, mergeReports, sonarReport, sourceRootsOf } from '../report/sonar.mjs';
|
|
20
|
+
import { SIDES, appRelativeMessages, loadSlotManifest, readRepoDeclaration } from '../runtime/scripts/lib/hfs-slots.mjs';
|
|
21
|
+
import { STYLE_GLOB } from '../sync/index.mjs';
|
|
19
22
|
|
|
20
23
|
export const LINT_SCHEMA = 'starci/lint@1';
|
|
24
|
+
/** The side whose stylesheets stylelint judges. */
|
|
25
|
+
const STYLE_SIDE = 'fe';
|
|
21
26
|
const CODE_PREFIX = /^\[([A-Z][A-Z0-9_]+)\] /;
|
|
22
27
|
const posix = (file) => String(file).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
23
28
|
|
|
24
29
|
/** The flags of `hfs lint`; `--changed` takes every argument up to the next flag. */
|
|
25
30
|
export function parseLintArgs(argv) {
|
|
26
|
-
const opts = { changed: null, fix: false, format: 'text', repo: undefined, sonar: undefined
|
|
31
|
+
const opts = { changed: null, fix: false, format: 'text', repo: undefined, sonar: undefined };
|
|
27
32
|
for (let i = 0; i < argv.length; i += 1) {
|
|
28
33
|
const arg = argv[i];
|
|
29
34
|
if (arg === '--changed') {
|
|
30
35
|
opts.changed = [];
|
|
31
36
|
while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) opts.changed.push(posix(argv[++i]));
|
|
32
37
|
} else if (arg === '--fix') opts.fix = true;
|
|
33
|
-
else if (['--format', '--repo', '--sonar'
|
|
38
|
+
else if (['--format', '--repo', '--sonar'].includes(arg)) {
|
|
34
39
|
if (argv[i + 1] === undefined) throw new Error(`${arg} needs a value`);
|
|
35
40
|
opts[arg.slice(2)] = argv[++i];
|
|
36
41
|
} else throw new Error(`unknown argument ${arg}`);
|
|
@@ -39,39 +44,43 @@ export function parseLintArgs(argv) {
|
|
|
39
44
|
return opts;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
/** A linter's json results through the
|
|
43
|
-
function runLinter({
|
|
47
|
+
/** A linter's json results through the app's own install, run from `cwd` (a side folder): `{ results }` or `{ error }`. */
|
|
48
|
+
function runLinter({ cwd, pkg, bin, args }) {
|
|
44
49
|
let entry;
|
|
45
50
|
try {
|
|
46
|
-
const manifest = createRequire(path.join(
|
|
51
|
+
const manifest = createRequire(path.join(cwd, 'package.json')).resolve(`${pkg}/package.json`);
|
|
47
52
|
const declared = JSON.parse(fs.readFileSync(manifest, 'utf8')).bin;
|
|
48
53
|
entry = path.join(path.dirname(manifest), typeof declared === 'string' ? declared : declared[bin]);
|
|
49
54
|
} catch {
|
|
50
|
-
return { error: `${pkg} is not installed
|
|
55
|
+
return { error: `${pkg} is not installed for ${cwd}` };
|
|
51
56
|
}
|
|
52
|
-
const run = spawnSync(process.execPath, [entry, ...args], { cwd
|
|
53
|
-
// The linters exit 1 when they found something; no json at all means they could not run.
|
|
57
|
+
const run = spawnSync(process.execPath, [entry, ...args], { cwd, encoding: 'utf8', maxBuffer: 512 * 1024 * 1024 });
|
|
58
|
+
// The linters exit 1 when they found something; no json at all means they could not run. ESLint prints its report on stdout,
|
|
59
|
+
// stylelint (16 and later) its formatter output on stderr.
|
|
54
60
|
let results;
|
|
55
|
-
try { results = JSON.parse(run.stdout); } catch { return { error: `${pkg} produced no json report (exit ${run.status}): ${String(run.stderr || run.stdout).trim().split('\n')[0]}` }; }
|
|
61
|
+
try { results = JSON.parse(run.stdout.trim() || run.stderr); } catch { return { error: `${pkg} produced no json report in ${path.basename(cwd)}/ (exit ${run.status}): ${String(run.stderr || run.stdout).trim().split('\n')[0]}` }; }
|
|
56
62
|
if (!Array.isArray(results)) return { error: `${pkg} produced a report that is not a result list` };
|
|
57
63
|
return { results };
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
const eslintFindings = (results,
|
|
66
|
+
const eslintFindings = (results, appRoot) => results.flatMap((result) => (result.messages ?? []).map((message) => ({
|
|
61
67
|
engine: 'eslint', rule: message.ruleId ?? 'eslint-error', code: CODE_PREFIX.exec(message.message ?? '')?.[1] ?? null, severity: 'error',
|
|
62
|
-
path: posix(path.relative(
|
|
68
|
+
path: posix(path.relative(appRoot, result.filePath)), line: message.line ?? null, column: message.column ?? null, message: message.message,
|
|
63
69
|
})));
|
|
64
70
|
|
|
65
|
-
const stylelintFindings = (results,
|
|
71
|
+
const stylelintFindings = (results, appRoot) => results.flatMap((result) => (result.warnings ?? []).map((warning) => ({
|
|
66
72
|
engine: 'stylelint', rule: warning.rule || 'stylelint-error', code: null, severity: 'error',
|
|
67
|
-
path: posix(path.relative(
|
|
73
|
+
path: posix(path.relative(appRoot, result.source)), line: warning.line ?? null, column: warning.column ?? null, message: warning.text,
|
|
68
74
|
})));
|
|
69
75
|
|
|
70
76
|
const byLocation = (a, b) => `${a.path ?? ''}:${String(a.line ?? 0).padStart(7, '0')}:${a.rule}`.localeCompare(`${b.path ?? ''}:${String(b.line ?? 0).padStart(7, '0')}:${b.rule}`);
|
|
71
77
|
|
|
78
|
+
/** The files of `changed` (app-relative) below `side`/, relative to the side folder. */
|
|
79
|
+
const onSide = (changed, side) => changed.filter((file) => file.startsWith(`${side}/`)).map((file) => file.slice(side.length + 1));
|
|
80
|
+
|
|
72
81
|
/**
|
|
73
|
-
* Run the whole lint of
|
|
74
|
-
* Returns `{ report, sonar, exit }`; `sonar` is the merged Generic Issue Import document.
|
|
82
|
+
* Run the whole lint of the app at `repoRoot`. `hfsCheck(repoRoot)` returns the `hfs check` result (`{ findings, tracked }`); the CLI
|
|
83
|
+
* injects it. Returns `{ report, sonar, exit }`; `sonar` is the merged Generic Issue Import document.
|
|
75
84
|
*/
|
|
76
85
|
export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles = () => [] }) {
|
|
77
86
|
const changed = opts.changed === null ? null : new Set(opts.changed);
|
|
@@ -81,26 +90,48 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
|
|
|
81
90
|
const raw = { eslint: [], stylelint: [] };
|
|
82
91
|
const engines = {};
|
|
83
92
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
let app = null;
|
|
94
|
+
try { app = readRepoDeclaration(loadSlotManifest(), repoRoot); } catch (error) { errors.push(`hfs lint runs at the app root: ${String(error?.message ?? error)}`); }
|
|
95
|
+
if (app && !app.sides) { errors.push(`${repoRoot} is the ${app.side} side of an app; hfs lint runs at the app root, the folder of hfs.json`); app = null; }
|
|
96
|
+
|
|
97
|
+
// 1. ESLint, once per side, from the side folder with that side's config.
|
|
98
|
+
engines.eslint = { sides: {} };
|
|
99
|
+
for (const side of app ? SIDES : []) {
|
|
100
|
+
const sources = onSide(existing, side).filter((file) => /\.(?:[cm]?[jt]sx?)$/.test(file));
|
|
101
|
+
if (changed !== null && !sources.length) { engines.eslint.sides[side] = { files: 0, skipped: 'no changed source file' }; continue; }
|
|
102
|
+
const linted = runLinter({ cwd: path.join(repoRoot, side), pkg: 'eslint', bin: 'eslint', args: ['--format', 'json', ...(opts.fix ? ['--fix'] : []), ...(changed ? sources : ['.'])] });
|
|
87
103
|
if (linted.error) errors.push(linted.error);
|
|
88
|
-
else {
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
else {
|
|
105
|
+
// The side canon names side-relative paths in its messages; the report names every path from the app root.
|
|
106
|
+
const appRelative = appRelativeMessages(side, path.join(repoRoot, side));
|
|
107
|
+
for (const result of linted.results) for (const message of result.messages ?? []) message.message = appRelative(message.message);
|
|
108
|
+
raw.eslint.push(...linted.results);
|
|
109
|
+
findings.push(...eslintFindings(linted.results, repoRoot));
|
|
110
|
+
}
|
|
111
|
+
engines.eslint.sides[side] = { files: linted.results?.length ?? 0 };
|
|
112
|
+
}
|
|
91
113
|
|
|
92
|
-
|
|
93
|
-
|
|
114
|
+
// 3. stylelint over the fe side's stylesheets.
|
|
115
|
+
if (app) {
|
|
116
|
+
const styles = onSide(existing, STYLE_SIDE).filter((file) => file.endsWith('.css'));
|
|
94
117
|
if (changed === null || styles.length) {
|
|
95
|
-
const linted = runLinter({ repoRoot, pkg: 'stylelint', bin: 'stylelint', args: [...(changed ? styles : [
|
|
118
|
+
const linted = runLinter({ cwd: path.join(repoRoot, STYLE_SIDE), pkg: 'stylelint', bin: 'stylelint', args: [...(changed ? styles : [STYLE_GLOB]), '--formatter', 'json', ...(opts.fix ? ['--fix'] : [])] });
|
|
96
119
|
if (linted.error) errors.push(linted.error);
|
|
97
|
-
else {
|
|
120
|
+
else {
|
|
121
|
+
const appRelative = appRelativeMessages(STYLE_SIDE, path.join(repoRoot, STYLE_SIDE));
|
|
122
|
+
for (const result of linted.results) for (const warning of result.warnings ?? []) warning.text = appRelative(warning.text);
|
|
123
|
+
raw.stylelint = linted.results;
|
|
124
|
+
findings.push(...stylelintFindings(linted.results, repoRoot));
|
|
125
|
+
}
|
|
98
126
|
}
|
|
99
|
-
engines.stylelint = {
|
|
127
|
+
engines.stylelint = { side: STYLE_SIDE, files: raw.stylelint.length };
|
|
100
128
|
}
|
|
101
129
|
|
|
130
|
+
// 2. The app check: root and sides.
|
|
102
131
|
let checked = { findings: [] };
|
|
103
|
-
|
|
132
|
+
if (app) {
|
|
133
|
+
try { checked = await hfsCheck(repoRoot); } catch (error) { errors.push(`hfs check could not run: ${String(error?.message ?? error)}`); }
|
|
134
|
+
}
|
|
104
135
|
const repoErrors = checked.findings.filter((finding) => finding.level === 'error');
|
|
105
136
|
const kept = changed === null ? repoErrors : repoErrors.filter((finding) => !finding.path || changed.has(posix(finding.path)));
|
|
106
137
|
for (const finding of kept) {
|
|
@@ -116,7 +147,7 @@ export async function lintRepository({ repoRoot, opts, hfsCheck, trackedFiles =
|
|
|
116
147
|
const sonar = mergeReports([
|
|
117
148
|
sonarReport(kept, { sourceRoots, tracked }),
|
|
118
149
|
linterReport('eslint', raw.eslint, { root: repoRoot, sourceRoots, tracked }),
|
|
119
|
-
|
|
150
|
+
linterReport('stylelint', raw.stylelint, { root: repoRoot, sourceRoots, tracked }),
|
|
120
151
|
]);
|
|
121
152
|
const report = { schema: LINT_SCHEMA, ok: findings.length === 0 && errors.length === 0, repoRoot, changed: changed ? [...changed].sort() : null, counts: { error: findings.length }, engines, errors, findings };
|
|
122
153
|
return { report, sonar, exit: errors.length ? 2 : findings.length ? 1 : 0 };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starci/hfs",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "The HFS command line of a StarCi
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "The HFS command line of a StarCi app (one repository: the root, be/ and fe/): hfs lint, check, scaffold app, explain, sync and work-hygiene. Self-contained: it carries the runtime files it reads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"private": false,
|
|
@@ -72,9 +72,9 @@ const leasePath=resourceKey=>String(resourceKey??'').startsWith(PATH_LEASE_PREFI
|
|
|
72
72
|
* The spelling two lease paths are compared in. The same file must compare equal however a workflow
|
|
73
73
|
* spelled it (nivo wf-nivo-fe-debt-mug06w7h inc-52a4a5ee5b12: `apps/app/src/messages/vi.json` bare for
|
|
74
74
|
* the fe repository vs `nivo-fe/apps/app/src/messages` repository-prefixed never overlapped). `canonicalOf`
|
|
75
|
-
* (scripts/kernel/lease-canon.mjs) resolves a path to its
|
|
76
|
-
*
|
|
77
|
-
*
|
|
75
|
+
* (scripts/kernel/lease-canon.mjs) resolves a path to its app-relative form in a bound app
|
|
76
|
+
* (be/<path>, fe/<path>) — for a held row through its holder job, so every spelling of one file
|
|
77
|
+
* compares as one key; paths on Windows compare case-insensitively, as its file
|
|
78
78
|
* systems do.
|
|
79
79
|
*/
|
|
80
80
|
export const leaseCompareForm=(leasePathValue,{canonicalOf=null,row=null,platform=process.platform}={})=>{
|
|
@@ -54,7 +54,7 @@ export const JOB_ARTIFACT_ROLES=Object.freeze(['check-output','check-stdout','ch
|
|
|
54
54
|
'direction','prompt','render','redline','critique','capture','dom','screenshot','video','trace','uat-run','metrics','salvage','scan','other']);
|
|
55
55
|
|
|
56
56
|
export const LEDGER_SCHEMA='starci/runtime@1';
|
|
57
|
-
export const LEDGER_VERSION=
|
|
57
|
+
export const LEDGER_VERSION=5;
|
|
58
58
|
|
|
59
59
|
const need=(ok,message,code)=>{if(!ok)throw Object.assign(Error(message),code?{code}:{});};
|
|
60
60
|
const json=value=>value===undefined||value===null?null:JSON.stringify(value);
|
|
@@ -127,7 +127,7 @@ const INIT_SQL_FILE=new URL('./migrations/runtime/0001-init.sql',import.meta.url
|
|
|
127
127
|
const INIT_SQL=fs.readFileSync(INIT_SQL_FILE,'utf8');
|
|
128
128
|
const INIT_SQL_SHA=sha256(INIT_SQL);
|
|
129
129
|
/** Forward migrations after 0001-init, in order; each bumps user_version to its `version` (migrateLedger). */
|
|
130
|
-
const FORWARD_MIGRATIONS=Object.freeze([{version:3,name:'0003-usage-unavailable'},{version:4,name:'0004-attempt-why'}]);
|
|
130
|
+
const FORWARD_MIGRATIONS=Object.freeze([{version:3,name:'0003-usage-unavailable'},{version:4,name:'0004-attempt-why'},{version:5,name:'0005-ended-workflow-views'}]);
|
|
131
131
|
export const LEDGER_BUSY_TIMEOUT_MS=15000;
|
|
132
132
|
/**
|
|
133
133
|
* Writer pragmas. wal_autocheckpoint=0 on EVERY connection except the one checkpointer (openLedger({checkpointer:true}),
|