@starci/hfs 4.0.10 → 4.1.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 +1 -1
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +3 -2
- package/package.json +1 -1
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +187 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +221 -239
- package/runtime/engine/invalid-config.mjs +5 -5
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +3 -1
- package/runtime/engine/removed-vocabulary.mjs +28 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/runtime-root.mjs +17 -0
- package/runtime/engine/secrets.mjs +35 -31
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/engine/temp-root.mjs +32 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
- package/runtime/knowledge/hfs/rules.yaml +98 -65
- package/runtime/knowledge/hfs/slots.yaml +11 -14
- package/runtime/knowledge/patterns/be/api.yaml +7 -24
- package/runtime/knowledge/patterns/be/cli.yaml +3 -12
- package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
- package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
- package/runtime/modules/kernel/failure-codes.yaml +1 -41
- package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
- package/runtime/modules/models/registry.yaml +25 -359
- package/runtime/modules/models/runtimes.yaml +92 -256
- package/runtime/modules/models/tiers.yaml +65 -0
- package/runtime/scripts/api/fs/claim-file.mjs +30 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
- package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
- package/runtime/scripts/api/git/lib.mjs +34 -1
- package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
- package/runtime/scripts/api/process/run-program.mjs +2 -1
- package/runtime/scripts/api/sops/lib.mjs +198 -120
- package/runtime/scripts/hfs/allows.mjs +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
- package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
- package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
- package/runtime/scripts/hfs/architecture/config.mjs +95 -80
- package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
- package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
- package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
- package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
- package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
- package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
- package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
- package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
- package/runtime/scripts/hfs/architecture/index.mjs +158 -102
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
- package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
- package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
- package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
- package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
- package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
- package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
- package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
- package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
- package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
- package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
- package/runtime/scripts/hfs/architecture.mjs +3 -4
- package/runtime/scripts/hfs/check.mjs +61 -70
- package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
- package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
- package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
- package/runtime/scripts/hfs/edition-slots.mjs +5 -3
- package/runtime/scripts/hfs/linear-text.mjs +30 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
- package/runtime/scripts/hfs/path-findings.mjs +58 -38
- package/runtime/scripts/hfs/pin-findings.mjs +31 -0
- package/runtime/scripts/hfs/repo-identity.mjs +5 -2
- package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
- package/runtime/scripts/hfs/rules/cli.mjs +5 -1
- package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
- package/runtime/scripts/hfs/rules/contract.mjs +35 -27
- package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
- package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
- package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
- package/runtime/scripts/hfs/rules/database.mjs +36 -23
- package/runtime/scripts/hfs/rules/deps.mjs +64 -32
- package/runtime/scripts/hfs/rules/docker.mjs +113 -65
- package/runtime/scripts/hfs/rules/edition.mjs +118 -111
- package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
- package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
- package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
- package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
- package/runtime/scripts/hfs/rules/saga.mjs +128 -61
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
- package/runtime/scripts/hfs/rules/services.mjs +34 -16
- package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
- package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
- package/runtime/scripts/hfs/secret.mjs +59 -48
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +76 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +76 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/hfs/slots.mjs +72 -693
- package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
- package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
- package/runtime/scripts/hfs/tree.mjs +19 -14
- package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
- package/runtime/scripts/hfs/view.mjs +1 -1
- package/runtime/scripts/lib/dockerfile.mjs +56 -33
- package/runtime/scripts/lib/event-contract.mjs +17 -13
- package/runtime/scripts/lib/git.mjs +2 -2
- package/runtime/scripts/lib/graphql-contract.mjs +158 -322
- package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
- package/runtime/scripts/lib/i18n.mjs +3 -2
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +3 -3
- package/runtime/scripts/lib/list.mjs +8 -1
- package/runtime/scripts/lib/path-key.mjs +38 -5
- package/runtime/scripts/lib/pid-alive.mjs +7 -0
- package/runtime/scripts/lib/regex.mjs +1 -1
- package/runtime/scripts/lib/same-text.mjs +1 -1
- package/runtime/scripts/lib/secret-patterns.mjs +6 -2
- package/runtime/scripts/lib/sleep-sync.mjs +2 -2
- package/runtime/scripts/lib/sops-envelope.mjs +95 -48
- package/runtime/scripts/lib/stack-services.mjs +1 -2
- package/runtime/scripts/lib/ts-ast.mjs +9 -0
- package/runtime/scripts/lib/walk.mjs +6 -1
- package/scaffold/add-cli-lite.mjs +2 -1
- package/scaffold/add-table.mjs +2 -2
- package/scaffold/add.mjs +3 -2
- package/scaffold/app.mjs +4 -2
- package/scaffold/edition-gate.mjs +25 -25
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +5 -4
- package/sync/hygiene.mjs +15 -9
- package/sync/index.mjs +7 -3
- package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
- package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
- package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
- package/templates/app/ci-workflows/sonar-steps.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
- package/templates/app/starciwork.gitignore +1 -1
- package/templates/be/image/api/Dockerfile +1 -1
- package/templates/be/image/cli/Dockerfile +1 -1
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
- package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/fe/image/next/Dockerfile +2 -1
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
- package/upgrade/index.mjs +9 -2
- package/runtime/engine/admission.mjs +0 -308
- package/runtime/engine/canonical-json.mjs +0 -10
- package/runtime/engine/db/blob.mjs +0 -315
- package/runtime/engine/db/ledger-paths.mjs +0 -83
- package/runtime/engine/db/ledger.mjs +0 -1118
- package/runtime/engine/db/machine-connection.mjs +0 -135
- package/runtime/engine/db/machine-schema.mjs +0 -84
- package/runtime/engine/db/machine.mjs +0 -1367
- package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
- package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
- package/runtime/engine/db/provider-reservations.mjs +0 -101
- package/runtime/engine/digest.mjs +0 -16
- package/runtime/engine/refuse.mjs +0 -11
- package/runtime/modules/ops/_labels.yaml +0 -53
- package/runtime/scripts/api/node/lib.mjs +0 -14
- package/runtime/scripts/api/node/spawn-node.mjs +0 -6
- package/runtime/scripts/api/process/lib.mjs +0 -111
- package/runtime/scripts/api/process/owned-process.mjs +0 -78
- package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
- package/runtime/scripts/connectors/lib.mjs +0 -488
- package/runtime/scripts/lib/clip.mjs +0 -19
- package/runtime/scripts/lib/display-names.mjs +0 -259
- package/runtime/scripts/lib/example-refs.mjs +0 -158
- package/runtime/scripts/lib/json-schema.mjs +0 -52
- package/runtime/scripts/lib/process-identity.mjs +0 -6
- package/runtime/scripts/lib/read-yaml.mjs +0 -18
- package/runtime/scripts/lib/redact.mjs +0 -161
- package/runtime/scripts/lib/sleep.mjs +0 -7
- package/runtime/scripts/lib/source-phrases.mjs +0 -34
- package/runtime/scripts/lib/sqlite.mjs +0 -21
|
@@ -1,35 +1,18 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { treeOf } from './required-files.mjs';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
* R24 `arch-config-unread` (HFS_ARCH_CONFIG_UNREAD). The machine reads `hfs.json
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - a tracked `architecture.json` (the retired per-repository config; the declaration is hfs.json, the rest is derived
|
|
10
|
-
* from the slot manifest);
|
|
11
|
-
* - a repository whose declaration and tsconfig load but whose program holds no production source file.
|
|
2
|
+
* R24 `arch-config-unread` (HFS_ARCH_CONFIG_UNREAD). The machine reads `hfs.json`; a declaration that yields no analysed
|
|
3
|
+
* file is a machine that judged nothing, and that is red: a repository whose declaration and tsconfig load but whose
|
|
4
|
+
* program holds no production source file.
|
|
12
5
|
*/
|
|
13
6
|
export const CONFIG_UNREAD_RULE_IDS = ['HFS_ARCH_CONFIG_UNREAD'];
|
|
14
7
|
|
|
15
8
|
const RULE = 'HFS_ARCH_CONFIG_UNREAD';
|
|
16
|
-
/** Config file names an earlier machine read and this one never does. */
|
|
17
|
-
const RETIRED_CONFIG_NAMES = ['architecture.json'];
|
|
18
9
|
|
|
19
|
-
export function checkConfigUnread({
|
|
10
|
+
export function checkConfigUnread({ context }) {
|
|
20
11
|
const violations = [];
|
|
21
|
-
const tree = treeOf(config.root);
|
|
22
|
-
let retired = 0;
|
|
23
|
-
for (const file of [...tree.files].sort()) {
|
|
24
|
-
if (!RETIRED_CONFIG_NAMES.includes(path.posix.basename(file))) continue;
|
|
25
|
-
retired += 1;
|
|
26
|
-
violations.push({ ruleId: RULE, path: file, line: 1, column: 1,
|
|
27
|
-
message: `${file} is a config the architecture machine no longer reads. hfs.json declares the repository; owners, roots, tiers and budgets come from the slot manifest. Delete ${file}.` });
|
|
28
|
-
}
|
|
29
12
|
const analysed = context.files.length;
|
|
30
13
|
if (analysed === 0) {
|
|
31
14
|
violations.push({ ruleId: RULE, path: 'hfs.json', line: 1, column: 1,
|
|
32
15
|
message: 'hfs.json and the TypeScript projects load but the machine analysed no production source file, so it judged nothing. Declare the apps that hold source in hfs.json, or fix the tsconfig include so the source is in the program.' });
|
|
33
16
|
}
|
|
34
|
-
return { violations, coverage: { status: 'checked',
|
|
17
|
+
return { violations, coverage: { status: 'checked', analysed } };
|
|
35
18
|
}
|
|
@@ -4,6 +4,7 @@ import { slash } from '../../lib/path-key.mjs';
|
|
|
4
4
|
import { readJsonFile as readJson } from '../../lib/json.mjs';
|
|
5
5
|
import { isInside } from '../../lib/walk.mjs';
|
|
6
6
|
import { openHfs } from '../slots.mjs';
|
|
7
|
+
import { byCodeUnit } from '../../lib/list.mjs';
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
/** A file's identity: its resolved real path, or the resolved path when it does not exist. */
|
|
@@ -13,9 +14,12 @@ function canonical(file) {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function exactKeys(value, allowed, label) {
|
|
16
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw Error(`${label} must be an object.`);
|
|
17
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object.`);
|
|
17
18
|
const unknown = Object.keys(value).filter(key => !allowed.has(key));
|
|
18
|
-
if (unknown.length)
|
|
19
|
+
if (unknown.length) {
|
|
20
|
+
unknown.sort(byCodeUnit);
|
|
21
|
+
throw new Error(`${label} has unsupported fields: ${unknown.join(', ')}.`);
|
|
22
|
+
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
/**
|
|
@@ -61,6 +65,81 @@ function existingRegularFile(root, relative) {
|
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
function admitWorkspace(root, directories, queue, candidate, label = 'local package', strict = false) {
|
|
69
|
+
const absolute = path.resolve(candidate);
|
|
70
|
+
if (!isInside(root, absolute) || absolute === root || !existingDirectory(root, slash(path.relative(root, absolute)))) {
|
|
71
|
+
if (strict) throw new Error(`${label} must resolve to a package directory inside the repository.`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const manifest = path.join(absolute, 'package.json');
|
|
75
|
+
try {
|
|
76
|
+
if (!fs.lstatSync(manifest).isFile() || fs.lstatSync(manifest).isSymbolicLink()) {
|
|
77
|
+
if (strict) throw new Error(`${label} must resolve to a regular package.json inside the repository.`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (strict) throw new Error(error.message.startsWith(label) ? error.message : `${label} must resolve to a regular package.json inside the repository.`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const relative = slash(path.relative(root, absolute));
|
|
85
|
+
if (!directories.has(relative)) { directories.add(relative); queue.push(absolute); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function expandSegment(base, segment) {
|
|
89
|
+
if (segment !== '*') return [path.join(base, segment)];
|
|
90
|
+
if (!fs.existsSync(base) || !fs.lstatSync(base).isDirectory()) return [];
|
|
91
|
+
return fs.readdirSync(base, { withFileTypes: true })
|
|
92
|
+
.filter(item => item.isDirectory())
|
|
93
|
+
.sort((a, b) => byCodeUnit(a.name, b.name))
|
|
94
|
+
.map(item => path.join(base, item.name));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function workspaceCandidates(packageRoot, normalized) {
|
|
98
|
+
const segments = normalized.split('/');
|
|
99
|
+
if (path.isAbsolute(normalized) || segments.some(segment => segment === '..' || (segment.includes('*') && segment !== '*'))) {
|
|
100
|
+
throw new Error(`Unsupported local workspace pattern: ${normalized}.`);
|
|
101
|
+
}
|
|
102
|
+
let candidates = [packageRoot];
|
|
103
|
+
for (const segment of segments) candidates = candidates.flatMap(base => expandSegment(base, segment));
|
|
104
|
+
return { candidates, wildcard: segments.includes('*') };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const admitFileDependency = (walk, section, name, value) => {
|
|
108
|
+
const { root, appPackageRoot, packageRoot, sideRoot, repository, state } = walk;
|
|
109
|
+
if (typeof value !== 'string' || !value.startsWith('file:')) return;
|
|
110
|
+
const absolute = path.resolve(sideRoot ? appPackageRoot : packageRoot, value.slice('file:'.length));
|
|
111
|
+
if (sideRoot && !isInside(root, absolute) && isInside(appPackageRoot, absolute)) return;
|
|
112
|
+
const label = `${section}.${name} file dependency`;
|
|
113
|
+
if (isInside(root, absolute)) { admitWorkspace(root, state.directories, state.queue, absolute, label, true); return; }
|
|
114
|
+
if (!repository || !isInside(repository, absolute) || !existingPackageDirectory(absolute)) {
|
|
115
|
+
throw new Error(`${label} must resolve to a package directory inside the repository.`);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function admitFileDependencies(walk, pkg) {
|
|
120
|
+
for (const section of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
121
|
+
for (const [name, value] of Object.entries(pkg?.[section] ?? {}))
|
|
122
|
+
admitFileDependency(walk, section, name, value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const admitPackageWorkspaces = (walk, pkg) => {
|
|
127
|
+
const { root, packageRoot, sideRoot, side, state } = walk;
|
|
128
|
+
const patterns = Array.isArray(pkg?.workspaces) ? pkg.workspaces : pkg?.workspaces?.packages;
|
|
129
|
+
for (const pattern of Array.isArray(patterns) ? patterns : []) {
|
|
130
|
+
if (typeof pattern !== 'string' || !pattern.trim()) throw new Error('package.json workspace entries must be non-empty paths.');
|
|
131
|
+
const written = slash(pattern.trim()).replace(/^\.\//, '');
|
|
132
|
+
if (sideRoot && !written.startsWith(`${side}/`)) continue;
|
|
133
|
+
const normalized = sideRoot ? written.slice(side.length + 1) : written;
|
|
134
|
+
const { candidates, wildcard } = workspaceCandidates(packageRoot, normalized);
|
|
135
|
+
// npm expands a `*` segment to the directories that hold a package.json and skips the rest (an empty or untracked folder is
|
|
136
|
+
// HFS_EMPTY_DIR / HFS_SLOT_UNDECLARED, never a reason to analyse nothing); a literal workspace path must resolve. A `*`
|
|
137
|
+
// pattern may match nothing: every app declares the same workspaces (fe/apps/*, fe/packages/*, HFS_MONO_WORKSPACES) whether
|
|
138
|
+
// or not it has a package yet, and the root patterns are held to that fixed list by the rule, not here.
|
|
139
|
+
for (const candidate of candidates) admitWorkspace(root, state.directories, state.queue, candidate, `workspace ${normalized}`, !wildcard);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
64
143
|
/**
|
|
65
144
|
* The npm workspaces below `root`. The one package.json of an app is at `packageRoot` (the app root; `root` is its side folder):
|
|
66
145
|
* its workspace patterns under `<side>/` are this side's, read relative to the side folder, and every other pattern is the other
|
|
@@ -71,82 +150,17 @@ function workspaceDirectories(root, { packageRoot: appPackageRoot = root, side =
|
|
|
71
150
|
const queue = [root];
|
|
72
151
|
const visited = new Set();
|
|
73
152
|
const repository = enclosingRepository(root);
|
|
74
|
-
const admit = (candidate, label = 'local package', strict = false) => {
|
|
75
|
-
const absolute = path.resolve(candidate);
|
|
76
|
-
if (!isInside(root, absolute) || absolute === root || !existingDirectory(root, slash(path.relative(root, absolute)))) {
|
|
77
|
-
if (strict) throw Error(`${label} must resolve to a package directory inside the repository.`);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const manifest = path.join(absolute, 'package.json');
|
|
81
|
-
try {
|
|
82
|
-
if (!fs.lstatSync(manifest).isFile() || fs.lstatSync(manifest).isSymbolicLink()) {
|
|
83
|
-
if (strict) throw Error(`${label} must resolve to a regular package.json inside the repository.`);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
} catch (error) {
|
|
87
|
-
if (strict) throw Error(error.message.startsWith(label) ? error.message : `${label} must resolve to a regular package.json inside the repository.`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const relative = slash(path.relative(root, absolute));
|
|
91
|
-
if (!directories.has(relative)) { directories.add(relative); queue.push(absolute); }
|
|
92
|
-
};
|
|
93
153
|
while (queue.length) {
|
|
94
154
|
const packageRoot = queue.shift();
|
|
95
155
|
if (visited.has(packageRoot)) continue;
|
|
96
156
|
visited.add(packageRoot);
|
|
97
157
|
const sideRoot = side !== null && packageRoot === root;
|
|
98
158
|
const pkg = readJson(path.join(sideRoot ? appPackageRoot : packageRoot, 'package.json'));
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const written = slash(pattern.trim()).replace(/^\.\//, '');
|
|
103
|
-
if (sideRoot && !written.startsWith(`${side}/`)) continue;
|
|
104
|
-
const normalized = sideRoot ? written.slice(side.length + 1) : written;
|
|
105
|
-
const segments = normalized.split('/');
|
|
106
|
-
if (path.isAbsolute(normalized) || segments.some(segment => segment === '..' || (segment.includes('*') && segment !== '*'))) {
|
|
107
|
-
throw Error(`Unsupported local workspace pattern: ${normalized}.`);
|
|
108
|
-
}
|
|
109
|
-
let candidates = [packageRoot];
|
|
110
|
-
for (const segment of segments) {
|
|
111
|
-
const next = [];
|
|
112
|
-
for (const base of candidates) {
|
|
113
|
-
if (segment === '*') {
|
|
114
|
-
if (!fs.existsSync(base) || !fs.lstatSync(base).isDirectory()) continue;
|
|
115
|
-
next.push(...fs.readdirSync(base, { withFileTypes: true })
|
|
116
|
-
.filter(item => item.isDirectory())
|
|
117
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
118
|
-
.map(item => path.join(base, item.name)));
|
|
119
|
-
} else next.push(path.join(base, segment));
|
|
120
|
-
}
|
|
121
|
-
candidates = next;
|
|
122
|
-
}
|
|
123
|
-
// npm expands a `*` segment to the directories that hold a package.json and skips the rest (an empty or untracked folder is
|
|
124
|
-
// HFS_EMPTY_DIR / HFS_SLOT_UNDECLARED, never a reason to analyse nothing); a literal workspace path must resolve. A `*`
|
|
125
|
-
// pattern may match nothing: every app declares the same workspaces (fe/apps/*, fe/packages/*, HFS_MONO_WORKSPACES) whether
|
|
126
|
-
// or not it has a package yet, and the root patterns are held to that fixed list by the rule, not here.
|
|
127
|
-
const wildcard = segments.includes('*');
|
|
128
|
-
for (const candidate of candidates) admit(candidate, `workspace ${normalized}`, !wildcard);
|
|
129
|
-
}
|
|
130
|
-
for (const section of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
131
|
-
for (const [name, value] of Object.entries(pkg?.[section] ?? {})) {
|
|
132
|
-
if (typeof value !== 'string' || !value.startsWith('file:')) continue;
|
|
133
|
-
const absolute = path.resolve(sideRoot ? appPackageRoot : packageRoot, value.slice('file:'.length));
|
|
134
|
-
// The app's one package.json also lists the other side's file dependencies (inside the app, outside this side): not this
|
|
135
|
-
// side's. A path that leaves the app is judged like any other: a package of the same repository, or refused.
|
|
136
|
-
if (sideRoot && !isInside(root, absolute) && isInside(appPackageRoot, absolute)) continue;
|
|
137
|
-
const label = `${section}.${name} file dependency`;
|
|
138
|
-
if (isInside(root, absolute)) { admit(absolute, label, true); continue; }
|
|
139
|
-
// A sibling package of the same repository: this project consumes it, so the path is real and
|
|
140
|
-
// resolvable, but it is not part of this project's own source layout and must not become one of
|
|
141
|
-
// its workspaces - collecting it would pull another package's src into these roots. Refusing it
|
|
142
|
-
// instead would fail the whole check closed over an ordinary monorepo shape.
|
|
143
|
-
if (!repository || !isInside(repository, absolute) || !existingPackageDirectory(absolute)) {
|
|
144
|
-
throw Error(`${label} must resolve to a package directory inside the repository.`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
159
|
+
const walk = { root, appPackageRoot, packageRoot, sideRoot, side, repository, state: { directories, queue } };
|
|
160
|
+
admitPackageWorkspaces(walk, pkg);
|
|
161
|
+
admitFileDependencies(walk, pkg);
|
|
148
162
|
}
|
|
149
|
-
return [...directories].sort();
|
|
163
|
+
return [...directories].sort(byCodeUnit);
|
|
150
164
|
}
|
|
151
165
|
|
|
152
166
|
function discoveredProjects(root, workspaces) {
|
|
@@ -201,7 +215,7 @@ function assertFrontendRolesDisjoint(root, frontend) {
|
|
|
201
215
|
const left = entries[index];
|
|
202
216
|
const right = entries[other];
|
|
203
217
|
if (isInside(left.absolute, right.absolute) || isInside(right.absolute, left.absolute)) {
|
|
204
|
-
throw Error(`Architecture frontend role roots must be disjoint; ${left.role} ${left.relative} overlaps ${right.role} ${right.relative}.`);
|
|
218
|
+
throw new Error(`Architecture frontend role roots must be disjoint; ${left.role} ${left.relative} overlaps ${right.role} ${right.relative}.`);
|
|
205
219
|
}
|
|
206
220
|
}
|
|
207
221
|
}
|
|
@@ -226,8 +240,10 @@ function directoriesUnder(root, relative, depth) {
|
|
|
226
240
|
|
|
227
241
|
/** The owner entry a slot instance exposes: index.ts/index.tsx, a package's src/index.ts, an app's app.module.ts. */
|
|
228
242
|
function ownerEntry(root, ownerRoot, tier) {
|
|
229
|
-
|
|
230
|
-
|
|
243
|
+
let candidates;
|
|
244
|
+
if (tier === 'package') candidates = ['src/index.ts', 'src/index.tsx', 'index.ts', 'index.tsx'];
|
|
245
|
+
else if (tier === 'app') candidates = ['app.module.ts'];
|
|
246
|
+
else candidates = ['index.ts', 'index.tsx'];
|
|
231
247
|
for (const candidate of candidates) {
|
|
232
248
|
const relative = `${ownerRoot}/${candidate}`;
|
|
233
249
|
if (existingRegularFile(root, relative)) return relative;
|
|
@@ -279,13 +295,12 @@ function derivedGrammar(root, packageRoot, workspaces, apps = []) {
|
|
|
279
295
|
}
|
|
280
296
|
|
|
281
297
|
/**
|
|
282
|
-
* Resolve the layout contract from hfs.json and the slot manifest (knowledge/hfs/slots.yaml).
|
|
283
|
-
* retired: a repository carries only hfs.json. Owners, roots and the tier of every path come from slots; the contract
|
|
298
|
+
* Resolve the layout contract from hfs.json and the slot manifest (knowledge/hfs/slots.yaml). A repository carries only hfs.json. Owners, roots and the tier of every path come from slots; the contract
|
|
284
299
|
* has no ignore, waiver or baseline field.
|
|
285
300
|
*/
|
|
286
301
|
export function loadArchitectureConfig(repositoryRoot, { hfs } = {}) {
|
|
287
302
|
const root = fs.realpathSync(path.resolve(repositoryRoot));
|
|
288
|
-
if (!fs.lstatSync(root).isDirectory()) throw Error('Repository root must be a directory.');
|
|
303
|
+
if (!fs.lstatSync(root).isDirectory()) throw new Error('Repository root must be a directory.');
|
|
289
304
|
const opened = hfs ?? openHfs({ repoRoot: root });
|
|
290
305
|
const { profile, apps } = opened.repo;
|
|
291
306
|
// A side of an app (the side folder is the root the machine judges) keeps its dependencies in the app root's one package.json.
|
|
@@ -293,10 +308,10 @@ export function loadArchitectureConfig(repositoryRoot, { hfs } = {}) {
|
|
|
293
308
|
const packageRoot = side === null ? root : path.dirname(root);
|
|
294
309
|
const workspaces = workspaceDirectories(root, { packageRoot, side });
|
|
295
310
|
const appDirs = apps.map(app => `apps/${app.name}`);
|
|
296
|
-
const inferred = inferredLayout(root, [...new Set([...workspaces, ...appDirs])].sort());
|
|
311
|
+
const inferred = inferredLayout(root, [...new Set([...workspaces, ...appDirs])].sort(byCodeUnit));
|
|
297
312
|
const kinds = [profile === 'be' ? 'backend' : 'frontend'];
|
|
298
|
-
const projects = discoveredProjects(root, [...new Set([...workspaces, ...appDirs])].sort());
|
|
299
|
-
if (!projects.length) throw Error('The repository has no tsconfig.json to derive a TypeScript project from.');
|
|
313
|
+
const projects = discoveredProjects(root, [...new Set([...workspaces, ...appDirs])].sort(byCodeUnit));
|
|
314
|
+
if (!projects.length) throw new Error('The repository has no tsconfig.json to derive a TypeScript project from.');
|
|
300
315
|
const backend = {
|
|
301
316
|
modules: ['src/modules'],
|
|
302
317
|
// The layered feature root is the api kind (src/features/api/<feature>/: application/ and transport/<protocol>/); the cli
|