@platforma-sdk/block-tools 2.10.13 → 2.10.15
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/dist/cli.js +23 -2
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1944 -100
- package/dist/cli.mjs.map +1 -1
- package/dist/cmd/index.d.ts +10 -4
- package/dist/cmd/structure/check.d.ts +10 -0
- package/dist/cmd/structure/init.d.ts +18 -0
- package/dist/cmd/structure/refresh.d.ts +11 -0
- package/dist/cmd/update-deps.d.ts +7 -0
- package/dist/structure/cli/run-structure.d.ts +19 -0
- package/dist/structure/engine/api.d.ts +55 -0
- package/dist/structure/engine/builders.d.ts +95 -0
- package/dist/structure/engine/content-rules.d.ts +148 -0
- package/dist/structure/engine/ctx.d.ts +34 -0
- package/dist/structure/engine/discovery-fs.d.ts +22 -0
- package/dist/structure/engine/discovery.d.ts +39 -0
- package/dist/structure/engine/flatten.d.ts +3 -0
- package/dist/structure/engine/fs/api.d.ts +27 -0
- package/dist/structure/engine/fs/memory.d.ts +16 -0
- package/dist/structure/engine/fs/node.d.ts +14 -0
- package/dist/structure/engine/glob.d.ts +4 -0
- package/dist/structure/engine/ir.d.ts +124 -0
- package/dist/structure/engine/parsers/json.d.ts +37 -0
- package/dist/structure/engine/parsers/lines.d.ts +9 -0
- package/dist/structure/engine/parsers/yaml.d.ts +18 -0
- package/dist/structure/engine/registry-client.d.ts +34 -0
- package/dist/structure/engine/runner.d.ts +45 -0
- package/dist/structure/engine/templates.d.ts +30 -0
- package/dist/structure/engine/testing.d.ts +34 -0
- package/dist/structure/engine/version.d.ts +18 -0
- package/dist/structure/init-block-constructor.d.ts +32 -0
- package/dist/structure/rules/block-package-json.d.ts +9 -0
- package/dist/structure/rules/block.d.ts +1 -0
- package/dist/structure/rules/migrations.d.ts +2 -0
- package/dist/structure/rules/model-package-json.d.ts +3 -0
- package/dist/structure/rules/model.d.ts +1 -0
- package/dist/structure/rules/root-catalog-bump.d.ts +1 -0
- package/dist/structure/rules/root-gitignore.d.ts +2 -0
- package/dist/structure/rules/root-package-json.d.ts +3 -0
- package/dist/structure/rules/root-pnpm-workspace.d.ts +15 -0
- package/dist/structure/rules/root.d.ts +1 -0
- package/dist/structure/rules/shared/colocated-tests.d.ts +2 -0
- package/dist/structure/rules/shared/key-order.d.ts +1 -0
- package/dist/structure/rules/software-package-json.d.ts +3 -0
- package/dist/structure/rules/software.d.ts +1 -0
- package/dist/structure/rules/test-package-json.d.ts +3 -0
- package/dist/structure/rules/test.d.ts +1 -0
- package/dist/structure/rules/ui-package-json.d.ts +3 -0
- package/dist/structure/rules/ui.d.ts +1 -0
- package/dist/structure/rules/workflow-package-json.d.ts +3 -0
- package/dist/structure/rules/workflow.d.ts +1 -0
- package/dist/structure/structure-definition.d.ts +1 -0
- package/package.json +8 -6
- package/src/cmd/index.ts +10 -4
- package/src/cmd/structure/check.ts +51 -0
- package/src/cmd/structure/init.ts +65 -0
- package/src/cmd/structure/refresh.ts +44 -0
- package/src/cmd/update-deps.ts +15 -3
- package/src/structure/__tests__/colocated-test-wiring.test.ts +107 -0
- package/src/structure/__tests__/floor-version.test.ts +52 -0
- package/src/structure/__tests__/init-catalog-resolution.test.ts +82 -0
- package/src/structure/__tests__/init-check-parity.test.ts +59 -0
- package/src/structure/__tests__/model-package-json.snapshot.test.ts +60 -0
- package/src/structure/__tests__/oxfmt-clean-init.test.ts +64 -0
- package/src/structure/__tests__/root-catalog-bump.test.ts +110 -0
- package/src/structure/__tests__/seed-disjointness.test.ts +79 -0
- package/src/structure/cli/run-structure.ts +89 -0
- package/src/structure/engine/__tests__/content-rules/active-state-guards.test.ts +61 -0
- package/src/structure/engine/__tests__/content-rules/catalog-pin.test.ts +46 -0
- package/src/structure/engine/__tests__/content-rules/enforce-alphabetical-order.test.ts +55 -0
- package/src/structure/engine/__tests__/content-rules/enforce-field-order.test.ts +73 -0
- package/src/structure/engine/__tests__/content-rules/ensure-catalog-absent.test.ts +29 -0
- package/src/structure/engine/__tests__/content-rules/ensure-catalog-latest.test.ts +70 -0
- package/src/structure/engine/__tests__/content-rules/ensure-dep.test.ts +63 -0
- package/src/structure/engine/__tests__/content-rules/ensure-deps-bulk.test.ts +46 -0
- package/src/structure/engine/__tests__/content-rules/ensure-field-entries.test.ts +35 -0
- package/src/structure/engine/__tests__/content-rules/ensure-field.test.ts +56 -0
- package/src/structure/engine/__tests__/content-rules/ensure-script.test.ts +39 -0
- package/src/structure/engine/__tests__/content-rules/ensure-workspace-scope-deps.test.ts +92 -0
- package/src/structure/engine/__tests__/content-rules/gitignore.test.ts +50 -0
- package/src/structure/engine/__tests__/content-rules/pin-catalog-to.test.ts +64 -0
- package/src/structure/engine/__tests__/content-rules/prune-keys.test.ts +59 -0
- package/src/structure/engine/__tests__/content-rules/registry-client.test.ts +116 -0
- package/src/structure/engine/__tests__/content-rules/remove-field.test.ts +49 -0
- package/src/structure/engine/__tests__/content-rules/require-field.test.ts +40 -0
- package/src/structure/engine/__tests__/content-rules/transform-at.test.ts +33 -0
- package/src/structure/engine/__tests__/content-rules/when-inner.test.ts +244 -0
- package/src/structure/engine/__tests__/content-rules/workspace-module-paths.test.ts +53 -0
- package/src/structure/engine/__tests__/discovery.test.ts +160 -0
- package/src/structure/engine/__tests__/fs-memory.test.ts +59 -0
- package/src/structure/engine/__tests__/glob.test.ts +43 -0
- package/src/structure/engine/__tests__/parsers-json.test.ts +189 -0
- package/src/structure/engine/__tests__/parsers-lines.test.ts +44 -0
- package/src/structure/engine/__tests__/parsers-yaml.test.ts +90 -0
- package/src/structure/engine/__tests__/recheck.test.ts +99 -0
- package/src/structure/engine/__tests__/registry-lookup.test.ts +49 -0
- package/src/structure/engine/__tests__/runner-when-inside-managed.test.ts +136 -0
- package/src/structure/engine/__tests__/runner.test.ts +136 -0
- package/src/structure/engine/__tests__/synthetic.test.ts +153 -0
- package/src/structure/engine/__tests__/version.test.ts +44 -0
- package/src/structure/engine/__tests__/when-files-exist.test.ts +89 -0
- package/src/structure/engine/api.ts +131 -0
- package/src/structure/engine/builders.ts +341 -0
- package/src/structure/engine/content-rules.ts +616 -0
- package/src/structure/engine/ctx.ts +94 -0
- package/src/structure/engine/discovery-fs.ts +209 -0
- package/src/structure/engine/discovery.ts +168 -0
- package/src/structure/engine/flatten.ts +117 -0
- package/src/structure/engine/fs/api.ts +40 -0
- package/src/structure/engine/fs/memory.ts +132 -0
- package/src/structure/engine/fs/node.ts +89 -0
- package/src/structure/engine/glob.ts +42 -0
- package/src/structure/engine/ir.ts +149 -0
- package/src/structure/engine/parsers/json.ts +212 -0
- package/src/structure/engine/parsers/lines.ts +42 -0
- package/src/structure/engine/parsers/yaml.ts +72 -0
- package/src/structure/engine/registry-client.ts +140 -0
- package/src/structure/engine/runner.ts +519 -0
- package/src/structure/engine/templates.ts +74 -0
- package/src/structure/engine/testing.ts +93 -0
- package/src/structure/engine/version.ts +60 -0
- package/src/structure/init-block-constructor.ts +164 -0
- package/src/structure/rules/block-package-json.ts +156 -0
- package/src/structure/rules/block.ts +29 -0
- package/src/structure/rules/migrations.ts +63 -0
- package/src/structure/rules/model-package-json.ts +123 -0
- package/src/structure/rules/model.ts +65 -0
- package/src/structure/rules/root-catalog-bump.ts +77 -0
- package/src/structure/rules/root-gitignore.ts +27 -0
- package/src/structure/rules/root-package-json.ts +119 -0
- package/src/structure/rules/root-pnpm-workspace.ts +103 -0
- package/src/structure/rules/root.ts +43 -0
- package/src/structure/rules/shared/colocated-tests.ts +11 -0
- package/src/structure/rules/shared/key-order.ts +34 -0
- package/src/structure/rules/software-package-json.ts +86 -0
- package/src/structure/rules/software.ts +33 -0
- package/src/structure/rules/test-package-json.ts +70 -0
- package/src/structure/rules/test.ts +40 -0
- package/src/structure/rules/ui-package-json.ts +108 -0
- package/src/structure/rules/ui.ts +75 -0
- package/src/structure/rules/workflow-package-json.ts +92 -0
- package/src/structure/rules/workflow.ts +51 -0
- package/src/structure/structure-definition.ts +28 -0
- package/src/structure/templates/static/block/index.d.ts +6 -0
- package/src/structure/templates/static/block/index.js +8 -0
- package/src/structure/templates/static/block/logos/block-logo.png +0 -0
- package/src/structure/templates/static/block/logos/organization-logo.png +0 -0
- package/src/structure/templates/static/model/.oxfmtrc.json +4 -0
- package/src/structure/templates/static/model/.oxlintrc.json +3 -0
- package/src/structure/templates/static/model/src/index.ts +13 -0
- package/src/structure/templates/static/model/tsconfig.json +4 -0
- package/src/structure/templates/static/root/.gitignore +13 -0
- package/src/structure/templates/static/root/.vscode/settings.json +3 -0
- package/src/structure/templates/static/root/turbo.json +35 -0
- package/src/structure/templates/static/test/tsconfig.json +5 -0
- package/src/structure/templates/static/test/vitest.config.mts +8 -0
- package/src/structure/templates/static/ui/.oxfmtrc.json +3 -0
- package/src/structure/templates/static/ui/.oxlintrc.json +3 -0
- package/src/structure/templates/static/ui/index.html +12 -0
- package/src/structure/templates/static/ui/src/main.ts +5 -0
- package/src/structure/templates/static/ui/tsconfig.json +4 -0
- package/src/structure/templates/static/workflow/format.el +42 -0
- package/src/structure/templates/static/workflow/tsconfig.json +16 -0
- package/src/structure/templates/static/workflow/vitest.config.mts +9 -0
- package/src/structure/templates/text/README.md +5 -0
- package/src/structure/templates/text/docs/description.md +3 -0
- package/src/structure/templates/text/ui/src/MainPage.tpl.vue +9 -0
- package/src/structure/templates/text/ui/src/app.tpl.ts +11 -0
- package/src/structure/templates/text/workflow/src/main.tpl.tengo +10 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// `.structure` metadata file — read/write + floor check.
|
|
2
|
+
//
|
|
3
|
+
// Single JSON file at the block root holding the layout-version
|
|
4
|
+
// integer and (in future) any other structurer metadata. Two
|
|
5
|
+
// constants govern lifecycle gates:
|
|
6
|
+
// - STRUCTURE_VERSION : latest known. Initial value 1.
|
|
7
|
+
// - STRUCTURE_MIN_SUPPORTED : floor. Initial value 0.
|
|
8
|
+
// Both are bumped by structurer releases; rule predicates may consult
|
|
9
|
+
// `ctx.version` for one-shot version-gated transitions.
|
|
10
|
+
|
|
11
|
+
import type { FileSystem } from "./fs/api";
|
|
12
|
+
|
|
13
|
+
export const STRUCTURE_VERSION = 1;
|
|
14
|
+
export const STRUCTURE_MIN_SUPPORTED = 0;
|
|
15
|
+
/** Block-relative path of the structurer metadata file. */
|
|
16
|
+
export const STRUCTURE_META_FILE = ".structure";
|
|
17
|
+
|
|
18
|
+
export type StructureMeta = { version: number };
|
|
19
|
+
|
|
20
|
+
/** Read `.structure`. Missing file or invalid payload → 0. */
|
|
21
|
+
export function readStructureVersion(fs: FileSystem): number {
|
|
22
|
+
if (!fs.exists(STRUCTURE_META_FILE)) return 0;
|
|
23
|
+
try {
|
|
24
|
+
const raw = fs.read(STRUCTURE_META_FILE);
|
|
25
|
+
const parsed = JSON.parse(raw) as Partial<StructureMeta>;
|
|
26
|
+
if (typeof parsed?.version === "number") return parsed.version;
|
|
27
|
+
return 0;
|
|
28
|
+
} catch {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Throw if version is below floor. */
|
|
34
|
+
export function assertVersionAboveFloor(
|
|
35
|
+
version: number,
|
|
36
|
+
floor: number = STRUCTURE_MIN_SUPPORTED,
|
|
37
|
+
): void {
|
|
38
|
+
if (version < floor) {
|
|
39
|
+
throw new StructureVersionFloorError(version, floor);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class StructureVersionFloorError extends Error {
|
|
44
|
+
constructor(
|
|
45
|
+
public readonly version: number,
|
|
46
|
+
public readonly floor: number,
|
|
47
|
+
) {
|
|
48
|
+
super(
|
|
49
|
+
`Block .structure version is ${version}, below floor ${floor}. ` +
|
|
50
|
+
`Bootstrap via the legacy-migration skill, then re-run.`,
|
|
51
|
+
);
|
|
52
|
+
this.name = "StructureVersionFloorError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function writeStructureVersion(fs: FileSystem, version: number = STRUCTURE_VERSION): void {
|
|
57
|
+
const payload: StructureMeta = { version };
|
|
58
|
+
// Compact, single-line — this is a tiny machine file, no pretty-print.
|
|
59
|
+
fs.write(STRUCTURE_META_FILE, JSON.stringify(payload));
|
|
60
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// `block-tools structure init` constructor: collects BlockVars from CLI
|
|
2
|
+
// flags (prompting for any missing field unless --non-interactive),
|
|
3
|
+
// assembles `ctx.modules` via the shared `modulesForInit`, and runs the
|
|
4
|
+
// engine in init mode against a NodeFileSystem rooted at the target dir.
|
|
5
|
+
//
|
|
6
|
+
// Replaces the manual `git clone platforma-block-boilerplate` workflow
|
|
7
|
+
// and `pl-dev create-block`. `--platform` is single-valued: a
|
|
8
|
+
// freshly-init'd block has at most one software module.
|
|
9
|
+
|
|
10
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
11
|
+
import { NodeFileSystem } from "./engine/fs/node";
|
|
12
|
+
import { NodeTemplateProvider } from "./engine/templates";
|
|
13
|
+
import { run as engineRun } from "./engine/runner";
|
|
14
|
+
import { createRunContext, modulesForInit } from "./engine/ctx";
|
|
15
|
+
import { STRUCTURE } from "./structure-definition";
|
|
16
|
+
import { STRUCTURE_VERSION } from "./engine/version";
|
|
17
|
+
import { matchesBumpPattern, buildRegistryLookupForNames } from "./engine/registry-client";
|
|
18
|
+
import { SDK_CATALOG_PACKAGES } from "./rules/root-pnpm-workspace";
|
|
19
|
+
import type { BlockVars } from "./engine/api";
|
|
20
|
+
|
|
21
|
+
/** Platform choices `init` can scaffold a software module for. Python is
|
|
22
|
+
* the only platform with a real, buildable scaffold at v1; any other
|
|
23
|
+
* value is rejected rather than scaffolded as a non-buildable placeholder,
|
|
24
|
+
* which would defeat the "init must produce a working block" intent.
|
|
25
|
+
* `softwarePlatform` stays in BlockVars for when another platform gets a
|
|
26
|
+
* real scaffold. (Note: Tengo is the workflow language, not a software
|
|
27
|
+
* platform.) */
|
|
28
|
+
export const SUPPORTED_PLATFORMS = ["python"] as const;
|
|
29
|
+
|
|
30
|
+
export type InitFlagValues = {
|
|
31
|
+
npmOrg?: string;
|
|
32
|
+
orgScope?: string;
|
|
33
|
+
shortName?: string;
|
|
34
|
+
/** Tri-state: true (--with-software), false (--no-software),
|
|
35
|
+
* undefined (unspecified → prompt or, non-interactive, default off). */
|
|
36
|
+
withSoftware?: boolean;
|
|
37
|
+
platform?: string;
|
|
38
|
+
nonInteractive: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type InitInput = {
|
|
42
|
+
/** Resolved BlockVars (from `resolveBlockVars`). */
|
|
43
|
+
vars: BlockVars;
|
|
44
|
+
/** Resolved absolute target directory. */
|
|
45
|
+
blockPath: string;
|
|
46
|
+
/** `<block-tools>/src/structure/templates`. */
|
|
47
|
+
templatesRoot: string;
|
|
48
|
+
log: (msg: string) => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
class InitFlagError extends Error {
|
|
52
|
+
constructor(flag: string) {
|
|
53
|
+
super(
|
|
54
|
+
`Missing required flag --${flag} in --non-interactive mode. ` +
|
|
55
|
+
`Supply every BlockVars flag, or drop --non-interactive to be prompted.`,
|
|
56
|
+
);
|
|
57
|
+
this.name = "InitFlagError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function need(
|
|
62
|
+
value: string | undefined,
|
|
63
|
+
flag: string,
|
|
64
|
+
nonInteractive: boolean,
|
|
65
|
+
prompt: () => Promise<string>,
|
|
66
|
+
): Promise<string> {
|
|
67
|
+
if (value !== undefined && value !== "") return value;
|
|
68
|
+
if (nonInteractive) throw new InitFlagError(flag);
|
|
69
|
+
return prompt();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Resolve the full BlockVars (incl. softwarePlatform) from flags +
|
|
73
|
+
* prompts. */
|
|
74
|
+
export async function resolveBlockVars(flags: InitFlagValues): Promise<BlockVars> {
|
|
75
|
+
const npmOrg = await need(flags.npmOrg, "npm-org", flags.nonInteractive, () =>
|
|
76
|
+
input({ message: "npm org (e.g. @platforma-open):", default: "@platforma-open" }),
|
|
77
|
+
);
|
|
78
|
+
const orgScope = await need(flags.orgScope, "org-scope", flags.nonInteractive, () =>
|
|
79
|
+
input({ message: "org scope (e.g. my-org):" }),
|
|
80
|
+
);
|
|
81
|
+
const shortName = await need(flags.shortName, "short-name", flags.nonInteractive, () =>
|
|
82
|
+
input({ message: "block short name (e.g. mixcr-clonotyping):" }),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Software: explicit flag wins; otherwise prompt (interactive) or
|
|
86
|
+
// default off (non-interactive).
|
|
87
|
+
let hasSoftware: boolean;
|
|
88
|
+
if (flags.withSoftware !== undefined) {
|
|
89
|
+
hasSoftware = flags.withSoftware;
|
|
90
|
+
} else if (flags.nonInteractive) {
|
|
91
|
+
hasSoftware = false;
|
|
92
|
+
} else {
|
|
93
|
+
hasSoftware = await confirm({ message: "Include a software module?", default: false });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let softwarePlatform: string | undefined;
|
|
97
|
+
if (hasSoftware) {
|
|
98
|
+
softwarePlatform = await need(flags.platform, "platform", flags.nonInteractive, () =>
|
|
99
|
+
select({
|
|
100
|
+
message: "Software platform:",
|
|
101
|
+
choices: SUPPORTED_PLATFORMS.map((p) => ({ name: p, value: p })),
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
if (!(SUPPORTED_PLATFORMS as readonly string[]).includes(softwarePlatform)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Unsupported --platform '${softwarePlatform}'. ` +
|
|
107
|
+
`Only ${SUPPORTED_PLATFORMS.join(", ")} ${
|
|
108
|
+
SUPPORTED_PLATFORMS.length === 1 ? "is" : "are"
|
|
109
|
+
} supported at v1.`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
} else if (flags.platform) {
|
|
113
|
+
throw new Error("--platform was given but software is disabled (--no-software).");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const facadeName = `${npmOrg}/${orgScope}.${shortName}`;
|
|
117
|
+
return {
|
|
118
|
+
facadeName,
|
|
119
|
+
baseName: `${orgScope}.${shortName}`,
|
|
120
|
+
npmOrg,
|
|
121
|
+
orgScope,
|
|
122
|
+
shortName,
|
|
123
|
+
softwarePlatform,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function runInit(init: InitInput): Promise<BlockVars> {
|
|
128
|
+
const vars = init.vars;
|
|
129
|
+
const modules = modulesForInit(vars);
|
|
130
|
+
|
|
131
|
+
const fs = new NodeFileSystem(init.blockPath);
|
|
132
|
+
const templates = new NodeTemplateProvider(init.templatesRoot);
|
|
133
|
+
const ctx = createRunContext({
|
|
134
|
+
blockVars: vars,
|
|
135
|
+
modules,
|
|
136
|
+
isSdkInternal: false,
|
|
137
|
+
version: STRUCTURE_VERSION,
|
|
138
|
+
dryRun: false,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Init REQUIRES network: resolve the SDK catalog families to npm latest
|
|
142
|
+
// before the run, so the generated block carries real current versions
|
|
143
|
+
// (no placeholders). The names come from the SDK catalog package list —
|
|
144
|
+
// there is no on-disk pnpm-workspace.yaml to read yet. A registry failure
|
|
145
|
+
// aborts init rather than silently shipping an unresolved placeholder.
|
|
146
|
+
const sdkNames = SDK_CATALOG_PACKAGES.filter(matchesBumpPattern);
|
|
147
|
+
let registryLookup: (name: string) => string | undefined;
|
|
148
|
+
try {
|
|
149
|
+
registryLookup = await buildRegistryLookupForNames(sdkNames);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`init requires network access to resolve SDK catalog versions from npm, ` +
|
|
153
|
+
`but the registry lookup failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
engineRun(STRUCTURE, fs, ctx, { templates, initMode: true, registryLookup });
|
|
158
|
+
init.log(
|
|
159
|
+
`Initialised block '${vars.facadeName}'${
|
|
160
|
+
vars.softwarePlatform ? ` (software: ${vars.softwarePlatform})` : " (no software)"
|
|
161
|
+
} at ${init.blockPath}`,
|
|
162
|
+
);
|
|
163
|
+
return vars;
|
|
164
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Block-level (orchestrator) `package.json`: the initial generator and the
|
|
2
|
+
// drift-correcting body rules, co-located. Workspace-scope deps reflect the
|
|
3
|
+
// discovered modules at run time; the generator pre-computes them and the
|
|
4
|
+
// body rule re-asserts them.
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ensureField,
|
|
8
|
+
ensureScript,
|
|
9
|
+
ensureDevDep,
|
|
10
|
+
ensureWorkspaceScopeDeps,
|
|
11
|
+
enforceAlphabeticalOrder,
|
|
12
|
+
enforceFieldOrder,
|
|
13
|
+
type RunContext,
|
|
14
|
+
} from "../engine/api";
|
|
15
|
+
import { getActiveRunContext } from "../engine/builders";
|
|
16
|
+
import { findModules, scopeDepMap } from "../engine/ctx";
|
|
17
|
+
import { canonicalPackageJsonOrder } from "./shared/key-order";
|
|
18
|
+
|
|
19
|
+
/** The facade's `block.components` map: the dist artifact each module build
|
|
20
|
+
* emits, keyed by role. Fully determined by the discovered modules — the
|
|
21
|
+
* body rule re-asserts this exact value, so it is engine-managed (NOT an
|
|
22
|
+
* author seed). Mirrors the artifact paths produced by each scope's build:
|
|
23
|
+
* workflow → pl-tengo, model → build-model, ui → ts-builder. */
|
|
24
|
+
export function blockComponents(ctx: RunContext): Record<string, string> {
|
|
25
|
+
const components: Record<string, string> = {};
|
|
26
|
+
for (const m of findModules(ctx, "workflow"))
|
|
27
|
+
components.workflow = `${m.name}/dist/tengo/tpl/main.plj.gz`;
|
|
28
|
+
for (const m of findModules(ctx, "model")) components.model = `${m.name}/dist/model.json`;
|
|
29
|
+
for (const m of findModules(ctx, "ui")) components.ui = `${m.name}/dist`;
|
|
30
|
+
return components;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Fail loudly if any required `block.*` field is empty — a missing
|
|
34
|
+
* title / description / logo / organization or empty components map would
|
|
35
|
+
* otherwise produce an init block that `block-tools pack` rejects only
|
|
36
|
+
* later. Asserted at generation time so the failure is immediate. */
|
|
37
|
+
function assertBlockFieldsFilled(block: {
|
|
38
|
+
components: Record<string, string>;
|
|
39
|
+
meta: Record<string, unknown>;
|
|
40
|
+
}): void {
|
|
41
|
+
if (Object.keys(block.components).length === 0) {
|
|
42
|
+
throw new Error("block.components is empty — no workflow/model/ui module was discovered");
|
|
43
|
+
}
|
|
44
|
+
const meta = block.meta as {
|
|
45
|
+
title?: unknown;
|
|
46
|
+
description?: unknown;
|
|
47
|
+
logo?: unknown;
|
|
48
|
+
organization?: { name?: unknown; url?: unknown; logo?: unknown };
|
|
49
|
+
};
|
|
50
|
+
const required: [string, unknown][] = [
|
|
51
|
+
["block.meta.title", meta.title],
|
|
52
|
+
["block.meta.description", meta.description],
|
|
53
|
+
["block.meta.logo", meta.logo],
|
|
54
|
+
["block.meta.organization.name", meta.organization?.name],
|
|
55
|
+
["block.meta.organization.url", meta.organization?.url],
|
|
56
|
+
["block.meta.organization.logo", meta.organization?.logo],
|
|
57
|
+
];
|
|
58
|
+
for (const [path, value] of required) {
|
|
59
|
+
if (typeof value !== "string" || value === "") {
|
|
60
|
+
throw new Error(`required block field '${path}' is empty`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function blockPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
|
|
66
|
+
const v = ctx.blockVars;
|
|
67
|
+
// The facade depends on its model / ui / workflow modules — not on its
|
|
68
|
+
// software module (the workflow owns that dep) and not on its test module
|
|
69
|
+
// (the test package depends on the facade, so a reciprocal dep would be a
|
|
70
|
+
// pnpm/turbo cycle).
|
|
71
|
+
const deps: Record<string, string> = {
|
|
72
|
+
...scopeDepMap(ctx, "model"),
|
|
73
|
+
...scopeDepMap(ctx, "ui"),
|
|
74
|
+
...scopeDepMap(ctx, "workflow"),
|
|
75
|
+
};
|
|
76
|
+
// `block.components` is engine-managed (re-asserted by the body rule);
|
|
77
|
+
// `block.meta` is an author SEED — written once at init, never touched on
|
|
78
|
+
// refresh, so the author owns title / description / organization / logo.
|
|
79
|
+
// `block-tools pack` requires both. The author edits the placeholder meta
|
|
80
|
+
// before publishing.
|
|
81
|
+
const block = {
|
|
82
|
+
components: blockComponents(ctx),
|
|
83
|
+
meta: {
|
|
84
|
+
title: v.shortName,
|
|
85
|
+
description: `${v.shortName} block`,
|
|
86
|
+
logo: "file:logos/block-logo.png",
|
|
87
|
+
organization: {
|
|
88
|
+
name: v.orgScope,
|
|
89
|
+
url: "https://example.com",
|
|
90
|
+
logo: "file:logos/organization-logo.png",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
assertBlockFieldsFilled(block);
|
|
95
|
+
return {
|
|
96
|
+
name: `${v.facadeName}.block`,
|
|
97
|
+
version: "1.0.0",
|
|
98
|
+
// type:"module" intentionally omitted.
|
|
99
|
+
files: ["index.d.ts", "index.js"],
|
|
100
|
+
scripts: {
|
|
101
|
+
build: "shx rm -rf ./block-pack && block-tools pack",
|
|
102
|
+
"mark-stable":
|
|
103
|
+
"block-tools mark-stable -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'",
|
|
104
|
+
prepublishOnly:
|
|
105
|
+
"block-tools pack && block-tools publish -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'",
|
|
106
|
+
"do-pack": "shx rm -f *.tgz && block-tools pack && pnpm pack && shx mv *.tgz package.tgz",
|
|
107
|
+
},
|
|
108
|
+
dependencies: deps,
|
|
109
|
+
devDependencies: { "@platforma-sdk/block-tools": "sdk:", shx: "catalog:" },
|
|
110
|
+
block,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function blockPackageJsonRules(): void {
|
|
115
|
+
// type:"module" intentionally omitted: the facade's index.js is the
|
|
116
|
+
// CommonJS dev-block descriptor (module.exports / __dirname); ESM would
|
|
117
|
+
// break it. The facade has no TS sources to compile.
|
|
118
|
+
ensureField("files", ["index.d.ts", "index.js"]);
|
|
119
|
+
|
|
120
|
+
ensureScript("build", "shx rm -rf ./block-pack && block-tools pack");
|
|
121
|
+
ensureScript(
|
|
122
|
+
"mark-stable",
|
|
123
|
+
"block-tools mark-stable -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'",
|
|
124
|
+
);
|
|
125
|
+
ensureScript(
|
|
126
|
+
"prepublishOnly",
|
|
127
|
+
"block-tools pack && block-tools publish -r 's3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1'",
|
|
128
|
+
);
|
|
129
|
+
ensureScript(
|
|
130
|
+
"do-pack",
|
|
131
|
+
"shx rm -f *.tgz && block-tools pack && pnpm pack && shx mv *.tgz package.tgz",
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// One workspace dep per discovered module in the facade's scopes. The
|
|
135
|
+
// facade depends on model / ui / workflow — not software (the workflow
|
|
136
|
+
// owns it) and not test (a reciprocal dep would be a pnpm/turbo cycle).
|
|
137
|
+
ensureWorkspaceScopeDeps("model");
|
|
138
|
+
ensureWorkspaceScopeDeps("ui");
|
|
139
|
+
ensureWorkspaceScopeDeps("workflow");
|
|
140
|
+
|
|
141
|
+
ensureDevDep("@platforma-sdk/block-tools", "sdk:");
|
|
142
|
+
// shx powers the cross-platform build / do-pack scripts above.
|
|
143
|
+
ensureDevDep("shx", "catalog:");
|
|
144
|
+
|
|
145
|
+
// `block.components` is fully determined by the discovered modules — keep
|
|
146
|
+
// it in sync on refresh. `block.meta` is deliberately NOT touched here: it
|
|
147
|
+
// is an author-owned seed (set once in the init package.json), so refresh
|
|
148
|
+
// must never overwrite the author's title / description / logo.
|
|
149
|
+
ensureField("block.components", blockComponents(getActiveRunContext()));
|
|
150
|
+
|
|
151
|
+
enforceAlphabeticalOrder("dependencies");
|
|
152
|
+
enforceAlphabeticalOrder("devDependencies");
|
|
153
|
+
enforceAlphabeticalOrder("peerDependencies");
|
|
154
|
+
enforceAlphabeticalOrder("optionalDependencies");
|
|
155
|
+
enforceFieldOrder([...canonicalPackageJsonOrder]);
|
|
156
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Block-scope rules — the orchestrator package at `block/`.
|
|
2
|
+
// Workspace-scope deps in the body resolve from ctx.modules.
|
|
3
|
+
|
|
4
|
+
import { scope, fixed, managed, seed, file, binaryFile, generate } from "../engine/api";
|
|
5
|
+
import { getActiveRunContext } from "../engine/builders";
|
|
6
|
+
import { blockPackageJsonInitial, blockPackageJsonRules } from "./block-package-json";
|
|
7
|
+
|
|
8
|
+
export function blockRules(): void {
|
|
9
|
+
scope("block", () => {
|
|
10
|
+
fixed("index.js", file("block/index.js"));
|
|
11
|
+
fixed("index.d.ts", file("block/index.d.ts"));
|
|
12
|
+
|
|
13
|
+
// Placeholder logos referenced by `block.meta` (logo + organization.logo).
|
|
14
|
+
// Binary seeds: written once at init, author-owned thereafter — refresh
|
|
15
|
+
// never overwrites them, so a custom logo survives. `block-tools pack`
|
|
16
|
+
// embeds these into the block pack; without them pack would fail on the
|
|
17
|
+
// `file:logos/...` meta references.
|
|
18
|
+
seed("logos/block-logo.png", binaryFile("block/logos/block-logo.png"));
|
|
19
|
+
seed("logos/organization-logo.png", binaryFile("block/logos/organization-logo.png"));
|
|
20
|
+
|
|
21
|
+
managed(
|
|
22
|
+
"package.json",
|
|
23
|
+
generate(() => blockPackageJsonInitial(getActiveRunContext())),
|
|
24
|
+
() => {
|
|
25
|
+
blockPackageJsonRules();
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Migrations + legacy cleanup live here.
|
|
2
|
+
//
|
|
3
|
+
// Two patterns coexist:
|
|
4
|
+
//
|
|
5
|
+
// 1. One-shot migrations gated on filesystem state. Folder renames,
|
|
6
|
+
// cross-cutting one-time transforms. Use `when(pathExists/pathMissing)`
|
|
7
|
+
// so the migration fires once and is idempotent on subsequent runs.
|
|
8
|
+
//
|
|
9
|
+
// 2. Legacy cleanup primitives. Unconditional `removeScript`,
|
|
10
|
+
// `removeDep`, `remove(path)` calls naming artefacts the canonical
|
|
11
|
+
// layout never produces. Idempotent by definition (no-op on absent
|
|
12
|
+
// keys/paths). Each line is a one-way decision: every block converges.
|
|
13
|
+
|
|
14
|
+
import { scope, remove, managed, generate, removeScript, removeDep } from "../engine/api";
|
|
15
|
+
import { getActiveRunContext } from "../engine/builders";
|
|
16
|
+
import { rootPackageJsonInitial } from "./root-package-json";
|
|
17
|
+
|
|
18
|
+
export function testFrameworkMigration(): void {
|
|
19
|
+
// No active rename-bearing migration yet. Reserved for the test
|
|
20
|
+
// framework rollout.
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Unconditional legacy-cleanup rules. Each is a no-op on an
|
|
24
|
+
// already-canonical block (remove on an absent path, removeScript /
|
|
25
|
+
// removeDep on an absent key) and idempotent, so they sit at top level
|
|
26
|
+
// with no `when` gate. The trailing comment on each line names the real
|
|
27
|
+
// block(s) that carry the artefact being cleaned.
|
|
28
|
+
//
|
|
29
|
+
// Root-scope cleanup is skipped for `--sdk-internal` blocks (no root
|
|
30
|
+
// module in that mode), so it never touches in-monorepo blocks.
|
|
31
|
+
export function legacyCleanup(): void {
|
|
32
|
+
// eslint → oxlint: the canonical layout ships `.oxlintrc.json` /
|
|
33
|
+
// `.oxfmtrc.json`; flat eslint configs are retired.
|
|
34
|
+
scope("model", () => {
|
|
35
|
+
remove("eslint.config.mjs"); // samples-and-data, mixcr-clonotyping, clonotype-clustering, antibody-sequence-liabilities
|
|
36
|
+
remove("vite.config.mts"); // samples-and-data (vite build → ts-builder)
|
|
37
|
+
});
|
|
38
|
+
scope("ui", () => {
|
|
39
|
+
remove("eslint.config.mjs"); // samples-and-data, mixcr-clonotyping, clonotype-clustering, antibody-sequence-liabilities
|
|
40
|
+
remove("vite.config.ts"); // samples-and-data (vite build → ts-builder)
|
|
41
|
+
remove("tsconfig.app.json"); // samples-and-data (vite-era split tsconfig)
|
|
42
|
+
remove("tsconfig.node.json"); // samples-and-data (vite-era split tsconfig)
|
|
43
|
+
});
|
|
44
|
+
scope("test", () => {
|
|
45
|
+
remove("eslint.config.mjs"); // all 5 experiment blocks
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Root-scope cleanup. The second `managed("package.json")` body composes
|
|
49
|
+
// after rootRules.
|
|
50
|
+
scope("root", () => {
|
|
51
|
+
remove(".prettierrc"); // samples-and-data, sequence-properties (prettier → oxfmt)
|
|
52
|
+
managed(
|
|
53
|
+
"package.json",
|
|
54
|
+
generate(() => rootPackageJsonInitial(getActiveRunContext())),
|
|
55
|
+
() => {
|
|
56
|
+
// `pretty` (prettier) is superseded by the canonical `fmt` (oxfmt);
|
|
57
|
+
// the standalone deps-updater was merged into block-tools.
|
|
58
|
+
removeScript("pretty"); // samples-and-data
|
|
59
|
+
removeDep("@platforma-sdk/blocks-deps-updater"); // samples-and-data
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Model `package.json`: the initial generator and the drift-correcting body
|
|
2
|
+
// rules, co-located. Identity (name/version) stays untouched; the body
|
|
3
|
+
// enforces type/main/types/exports + canonical scripts + canonical dep sets,
|
|
4
|
+
// then projects canonical field / dependency order.
|
|
5
|
+
//
|
|
6
|
+
// Authored oxfmt-clean: the model is one of the two scopes `ts-builder
|
|
7
|
+
// check` runs oxfmt on, so neither half enforces field / dependency ordering
|
|
8
|
+
// beyond what oxfmt itself produces — the enforce* calls match oxfmt's order
|
|
9
|
+
// exactly, so refreshing a legacy block yields oxfmt-clean output and the
|
|
10
|
+
// build→check gate (oxfmt --check) passes without a prior `pnpm fmt`.
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
ensureField,
|
|
14
|
+
ensureScript,
|
|
15
|
+
ensureDep,
|
|
16
|
+
ensureDevDep,
|
|
17
|
+
ensureDevDeps,
|
|
18
|
+
ensurePeerDeps,
|
|
19
|
+
enforceAlphabeticalOrder,
|
|
20
|
+
enforceFieldOrder,
|
|
21
|
+
when,
|
|
22
|
+
whenFilesExist,
|
|
23
|
+
type RunContext,
|
|
24
|
+
} from "../engine/api";
|
|
25
|
+
import { canonicalPackageJsonOrder } from "./shared/key-order";
|
|
26
|
+
import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
|
|
27
|
+
|
|
28
|
+
export function modelPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
|
|
29
|
+
const v = ctx.blockVars;
|
|
30
|
+
return {
|
|
31
|
+
name: `${v.facadeName}.model`,
|
|
32
|
+
version: "1.0.0",
|
|
33
|
+
type: "module",
|
|
34
|
+
// The block-model build emits both index.cjs and index.js. `main` is
|
|
35
|
+
// the CommonJS entry (require fallback); `module` the ESM entry.
|
|
36
|
+
main: "dist/index.cjs",
|
|
37
|
+
module: "dist/index.js",
|
|
38
|
+
types: "dist/index.d.ts",
|
|
39
|
+
exports: {
|
|
40
|
+
".": {
|
|
41
|
+
types: "./dist/index.d.ts",
|
|
42
|
+
sources: "./src/index.ts",
|
|
43
|
+
import: "./dist/index.js",
|
|
44
|
+
},
|
|
45
|
+
"./dist/*": "./dist/*",
|
|
46
|
+
},
|
|
47
|
+
scripts: {
|
|
48
|
+
fmt: "ts-builder format",
|
|
49
|
+
watch: "ts-builder build --target block-model --watch",
|
|
50
|
+
build: "ts-builder build --target block-model && block-tools build-model",
|
|
51
|
+
check: "ts-builder check --target block-model",
|
|
52
|
+
// No `test`: the vitest `test` script is wired by the body rule ONLY
|
|
53
|
+
// when co-located test files exist (a freshly-init'd model has none).
|
|
54
|
+
},
|
|
55
|
+
dependencies: {
|
|
56
|
+
"@platforma-sdk/model": "sdk:",
|
|
57
|
+
},
|
|
58
|
+
devDependencies: {
|
|
59
|
+
"@milaboratories/ts-builder": "sdk:",
|
|
60
|
+
"@milaboratories/ts-configs": "sdk:",
|
|
61
|
+
"@platforma-sdk/block-tools": "sdk:",
|
|
62
|
+
vitest: "catalog:",
|
|
63
|
+
},
|
|
64
|
+
peerDependencies: {
|
|
65
|
+
"@types/node": "*",
|
|
66
|
+
typescript: "*",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function modelPackageJsonRules(): void {
|
|
72
|
+
ensureField("type", "module");
|
|
73
|
+
// build emits both; main = CJS entry, module = ESM entry (prod convention).
|
|
74
|
+
ensureField("main", "dist/index.cjs");
|
|
75
|
+
ensureField("module", "dist/index.js");
|
|
76
|
+
ensureField("types", "dist/index.d.ts");
|
|
77
|
+
ensureField("exports", {
|
|
78
|
+
".": {
|
|
79
|
+
types: "./dist/index.d.ts",
|
|
80
|
+
sources: "./src/index.ts",
|
|
81
|
+
import: "./dist/index.js",
|
|
82
|
+
},
|
|
83
|
+
"./dist/*": "./dist/*",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
ensureScript("fmt", "ts-builder format");
|
|
87
|
+
ensureScript("watch", "ts-builder build --target block-model --watch");
|
|
88
|
+
ensureScript("build", "ts-builder build --target block-model && block-tools build-model");
|
|
89
|
+
ensureScript("check", "ts-builder check --target block-model");
|
|
90
|
+
|
|
91
|
+
ensureDep("@platforma-sdk/model", "sdk:");
|
|
92
|
+
|
|
93
|
+
ensureDevDeps({
|
|
94
|
+
"@milaboratories/ts-builder": "sdk:",
|
|
95
|
+
"@milaboratories/ts-configs": "sdk:",
|
|
96
|
+
"@platforma-sdk/block-tools": "sdk:",
|
|
97
|
+
vitest: "catalog:",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
ensurePeerDeps({
|
|
101
|
+
"@types/node": "*",
|
|
102
|
+
typescript: "*",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// The vitest `test` script + node ambient types are wired ONLY when the
|
|
106
|
+
// model carries co-located unit tests (`src/**/*.test.ts`). A test-less
|
|
107
|
+
// model gets neither — turbo simply has no `test` task for it, and the
|
|
108
|
+
// scope stays a refresh fixpoint. When tests ARE present, `@types/node`
|
|
109
|
+
// is promoted from peer to a devDep (the single-section invariant drops
|
|
110
|
+
// the peer) so the tests' `node:*` imports resolve under the
|
|
111
|
+
// `types: ["node"]` that model/tsconfig adds under the same predicate.
|
|
112
|
+
// Runs AFTER ensurePeerDeps so the promotion is not reverted.
|
|
113
|
+
when(whenFilesExist(COLOCATED_TEST_GLOB), () => {
|
|
114
|
+
ensureScript("test", "vitest run --passWithNoTests");
|
|
115
|
+
ensureDevDep("@types/node", "*");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
enforceAlphabeticalOrder("dependencies");
|
|
119
|
+
enforceAlphabeticalOrder("devDependencies");
|
|
120
|
+
enforceAlphabeticalOrder("peerDependencies");
|
|
121
|
+
enforceAlphabeticalOrder("optionalDependencies");
|
|
122
|
+
enforceFieldOrder([...canonicalPackageJsonOrder]);
|
|
123
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Model-scope rules. Static config files + managed package.json; the
|
|
2
|
+
// `src/index.ts` seed is dropped by `init` and never touched again
|
|
3
|
+
// (block author owns it).
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
scope,
|
|
7
|
+
fixed,
|
|
8
|
+
managed,
|
|
9
|
+
seed,
|
|
10
|
+
file,
|
|
11
|
+
generate,
|
|
12
|
+
when,
|
|
13
|
+
whenFilesExist,
|
|
14
|
+
ensureField,
|
|
15
|
+
removeField,
|
|
16
|
+
pruneKeysMatching,
|
|
17
|
+
} from "../engine/api";
|
|
18
|
+
import { getActiveRunContext } from "../engine/builders";
|
|
19
|
+
import { modelPackageJsonInitial, modelPackageJsonRules } from "./model-package-json";
|
|
20
|
+
import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
|
|
21
|
+
|
|
22
|
+
export function modelRules(): void {
|
|
23
|
+
scope("model", () => {
|
|
24
|
+
// tsconfig is managed (not fixed) so node ambient types can be provided
|
|
25
|
+
// CONDITIONALLY. The body still enforces the canonical shape —
|
|
26
|
+
// `{extends, include}` with NO stray keys — and, ONLY when the model
|
|
27
|
+
// carries co-located unit tests (`src/**/*.test.ts`), adds
|
|
28
|
+
// `compilerOptions.types: ["node"]` so those tests type-check: provide
|
|
29
|
+
// node types, do NOT exclude the tests. A test-less model is
|
|
30
|
+
// canonicalised to bare `{extends, include}`
|
|
31
|
+
// and stays a refresh fixpoint. The matching `@types/node` devDep is
|
|
32
|
+
// wired by the package.json rule under the same predicate.
|
|
33
|
+
managed("tsconfig.json", file("model/tsconfig.json"), () => {
|
|
34
|
+
ensureField("extends", "@milaboratories/ts-configs/block/model");
|
|
35
|
+
// Canonical default carries no compilerOptions; drop any legacy block's
|
|
36
|
+
// before (conditionally) re-adding only `types`.
|
|
37
|
+
removeField("compilerOptions");
|
|
38
|
+
when(whenFilesExist(COLOCATED_TEST_GLOB), () => {
|
|
39
|
+
ensureField("compilerOptions.types", ["node"]);
|
|
40
|
+
});
|
|
41
|
+
ensureField("include", ["src/**/*"]);
|
|
42
|
+
// Strip anything else a legacy tsconfig carried (files, references, a
|
|
43
|
+
// different extends layout) — keep only the canonical keys.
|
|
44
|
+
pruneKeysMatching((k) => k !== "extends" && k !== "include" && k !== "compilerOptions");
|
|
45
|
+
});
|
|
46
|
+
fixed(".oxlintrc.json", file("model/.oxlintrc.json"));
|
|
47
|
+
fixed(".oxfmtrc.json", file("model/.oxfmtrc.json"));
|
|
48
|
+
|
|
49
|
+
// A minimal-but-REAL BlockModelV3: `build-model` rejects a non-model
|
|
50
|
+
// export ("Malformed model object"), and a bare `.create(dataModel)`
|
|
51
|
+
// still errors ("Args rendering function not set"). This seed is the
|
|
52
|
+
// smallest chain that produces a valid `dist/model.json` — empty data
|
|
53
|
+
// model, identity args, a single "Main" section. The block author owns
|
|
54
|
+
// and extends it after init.
|
|
55
|
+
seed("src/index.ts", file("model/src/index.ts"));
|
|
56
|
+
|
|
57
|
+
managed(
|
|
58
|
+
"package.json",
|
|
59
|
+
generate(() => modelPackageJsonInitial(getActiveRunContext())),
|
|
60
|
+
() => {
|
|
61
|
+
modelPackageJsonRules();
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
}
|