@platforma-sdk/block-tools 2.10.14 → 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 +9 -7
- 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,341 @@
|
|
|
1
|
+
// `defineStructure` + the module-global builder set.
|
|
2
|
+
//
|
|
3
|
+
// Active context is a module-level mutable: `defineStructure` pushes a
|
|
4
|
+
// fresh tree state, invokes the zero-arg lambda, pops, returns. Each
|
|
5
|
+
// builder reads the active state and appends to the current children
|
|
6
|
+
// list. Calling a builder outside `defineStructure` throws with a
|
|
7
|
+
// clear message. Re-entering `defineStructure` from inside another
|
|
8
|
+
// `defineStructure` lambda is rejected.
|
|
9
|
+
|
|
10
|
+
import type { Scope, RunContext, BlockVars, ContentForm } from "./api";
|
|
11
|
+
import type {
|
|
12
|
+
ManagedBody,
|
|
13
|
+
Structure,
|
|
14
|
+
TriggerFn,
|
|
15
|
+
TreeNode,
|
|
16
|
+
ScopeFrame,
|
|
17
|
+
WhenFrame,
|
|
18
|
+
ModeFrame,
|
|
19
|
+
RunMode,
|
|
20
|
+
} from "./ir";
|
|
21
|
+
|
|
22
|
+
type TreeState = {
|
|
23
|
+
root: TreeNode[];
|
|
24
|
+
/** Stack of children-lists; top is where new nodes append. */
|
|
25
|
+
stack: TreeNode[][];
|
|
26
|
+
inScope: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let activeTree: TreeState | undefined;
|
|
30
|
+
let activeRunContext: RunContext | undefined;
|
|
31
|
+
|
|
32
|
+
function requireActiveTree(builder: string): TreeState {
|
|
33
|
+
if (!activeTree) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`${builder}() called outside defineStructure(). ` +
|
|
36
|
+
`Builders must run inside the lambda passed to defineStructure(...).`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return activeTree;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function appendNode(builder: string, node: TreeNode): void {
|
|
43
|
+
const t = requireActiveTree(builder);
|
|
44
|
+
t.stack[t.stack.length - 1]!.push(node);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Engine-internal: scope `blockVars()` lookups during a run. The body is
|
|
48
|
+
* synchronous, so the module-global `activeRunContext` cannot be observed
|
|
49
|
+
* by an interleaved run — a sync call stack runs to completion before any
|
|
50
|
+
* other `run()` can start. The try/finally restores the previous context
|
|
51
|
+
* (supporting the nested-run case the post-run recheck used to imply). */
|
|
52
|
+
export function withRunContext<T>(ctx: RunContext, fn: () => T): T {
|
|
53
|
+
const prev = activeRunContext;
|
|
54
|
+
activeRunContext = ctx;
|
|
55
|
+
try {
|
|
56
|
+
return fn();
|
|
57
|
+
} finally {
|
|
58
|
+
activeRunContext = prev;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Engine-internal: content-rule builders that need `ctx.modules` look
|
|
63
|
+
* up the active context through this accessor. Returns `undefined`
|
|
64
|
+
* outside a run. */
|
|
65
|
+
export function tryGetActiveRunContext(): RunContext | undefined {
|
|
66
|
+
return activeRunContext;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Get-or-die counterpart of `tryGetActiveRunContext`. Generators run
|
|
70
|
+
* only inside `engine.run()`; an absent context is framework misuse, not
|
|
71
|
+
* a valid state, so throw rather than emit degenerate (empty) output.
|
|
72
|
+
* Mirrors `blockVars()`. */
|
|
73
|
+
export function getActiveRunContext(): RunContext {
|
|
74
|
+
if (!activeRunContext) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"getActiveRunContext() called outside engine.run(). " +
|
|
77
|
+
"Generators only see the run context during a run.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return activeRunContext;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The single entry point: build a {@link Structure} from `fn`, which calls
|
|
84
|
+
* the scaffold-DSL builders (`scope`, `fixed`, `managed`, `seed`, …). Throws
|
|
85
|
+
* if called nested, or if `fn` leaves an unclosed group frame. */
|
|
86
|
+
export function defineStructure(fn: () => void): Structure {
|
|
87
|
+
if (activeTree) {
|
|
88
|
+
throw new Error("defineStructure() cannot be called from inside another defineStructure().");
|
|
89
|
+
}
|
|
90
|
+
const root: TreeNode[] = [];
|
|
91
|
+
const state: TreeState = { root, stack: [root], inScope: false };
|
|
92
|
+
activeTree = state;
|
|
93
|
+
try {
|
|
94
|
+
fn();
|
|
95
|
+
if (state.stack.length !== 1) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`defineStructure: builder body left ${state.stack.length - 1} unclosed group frame(s)`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const structure: Structure = { children: root };
|
|
101
|
+
// Validation: a path may not appear as both `seed` and any
|
|
102
|
+
// engine-managed primitive (`fixed` / `managed` / `scaffold`) within
|
|
103
|
+
// the same scope. The two contracts (write-once-and-ignore vs
|
|
104
|
+
// rewrite-on-every-refresh) are mutually exclusive. Detect at
|
|
105
|
+
// tree-build time.
|
|
106
|
+
validateSeedDisjointness(structure);
|
|
107
|
+
return structure;
|
|
108
|
+
} finally {
|
|
109
|
+
activeTree = undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function validateSeedDisjointness(structure: Structure): void {
|
|
114
|
+
// (scope) → (path) → set of leaf kinds present at that path.
|
|
115
|
+
const perScope = new Map<string, Map<string, Set<string>>>();
|
|
116
|
+
|
|
117
|
+
function visit(node: TreeNode, scopeName: string): void {
|
|
118
|
+
if (node.kind === "scope") {
|
|
119
|
+
for (const c of node.children) visit(c, node.scope);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (node.kind === "when" || node.kind === "mode") {
|
|
123
|
+
for (const c of node.children) visit(c, scopeName);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (node.kind === "rename") return; // rename works on paths; not a content leaf
|
|
127
|
+
let scopeMap = perScope.get(scopeName);
|
|
128
|
+
if (!scopeMap) {
|
|
129
|
+
scopeMap = new Map();
|
|
130
|
+
perScope.set(scopeName, scopeMap);
|
|
131
|
+
}
|
|
132
|
+
let kinds = scopeMap.get(node.path);
|
|
133
|
+
if (!kinds) {
|
|
134
|
+
kinds = new Set();
|
|
135
|
+
scopeMap.set(node.path, kinds);
|
|
136
|
+
}
|
|
137
|
+
kinds.add(node.kind);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const n of structure.children) visit(n, "(top-level)");
|
|
141
|
+
|
|
142
|
+
for (const [scopeName, paths] of perScope) {
|
|
143
|
+
for (const [path, kinds] of paths) {
|
|
144
|
+
if (
|
|
145
|
+
kinds.has("seed") &&
|
|
146
|
+
(kinds.has("managed") || kinds.has("fixed") || kinds.has("scaffold"))
|
|
147
|
+
) {
|
|
148
|
+
const others = [...kinds]
|
|
149
|
+
.filter((k) => k !== "seed")
|
|
150
|
+
.sort()
|
|
151
|
+
.join(", ");
|
|
152
|
+
throw new Error(
|
|
153
|
+
`defineStructure validation: path '${path}' under scope '${scopeName}' ` +
|
|
154
|
+
`is declared as both seed and ${others}. Seeds are block-author territory ` +
|
|
155
|
+
`(written once at init, untouched thereafter); engine-managed primitives ` +
|
|
156
|
+
`(fixed/managed/scaffold) rewrite on every refresh. The two contracts cannot ` +
|
|
157
|
+
`coexist on the same path.`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Open a scope (`root` / `block` / `model` / `ui` / `workflow` / `test` /
|
|
165
|
+
* `software`); builders called inside `body` attach to it. Cannot nest. */
|
|
166
|
+
export function scope(name: Scope, body: () => void): void {
|
|
167
|
+
const t = requireActiveTree("scope");
|
|
168
|
+
if (t.inScope) {
|
|
169
|
+
throw new Error("scope() cannot nest. Already inside a scope.");
|
|
170
|
+
}
|
|
171
|
+
const frame: ScopeFrame = { kind: "scope", scope: name, children: [] };
|
|
172
|
+
t.stack[t.stack.length - 1]!.push(frame);
|
|
173
|
+
t.stack.push(frame.children);
|
|
174
|
+
t.inScope = true;
|
|
175
|
+
try {
|
|
176
|
+
body();
|
|
177
|
+
} finally {
|
|
178
|
+
t.stack.pop();
|
|
179
|
+
t.inScope = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Inner-mode `when` handler. Registered by `content-rules.ts` at
|
|
184
|
+
* load time. Returns true if the call was dispatched (an inner active
|
|
185
|
+
* state was present); false otherwise. Avoids a circular import. */
|
|
186
|
+
type InnerWhenHandler = (trigger: TriggerFn, body: () => void) => boolean;
|
|
187
|
+
let innerWhenHandler: InnerWhenHandler | undefined;
|
|
188
|
+
|
|
189
|
+
/** Engine-internal: register the inner-mode `when` handler (set by
|
|
190
|
+
* `content-rules.ts` at load time to avoid a circular import). */
|
|
191
|
+
export function registerInnerWhenHandler(h: InnerWhenHandler): void {
|
|
192
|
+
innerWhenHandler = h;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Conditionally run `body`. Two dispatch modes by active state:
|
|
197
|
+
* - inside `defineStructure(...)` → push a `WhenFrame` into the tree;
|
|
198
|
+
* trigger fires later at runner time per `FlatItem`.
|
|
199
|
+
* - inside a `managed(...)` body → evaluate `trigger` immediately
|
|
200
|
+
* against the runner-supplied `TriggerContext`; run/skip `body`
|
|
201
|
+
* synchronously.
|
|
202
|
+
*
|
|
203
|
+
* One `when` symbol for both layers — same user contract, dispatch
|
|
204
|
+
* picks the matching execution timing.
|
|
205
|
+
*/
|
|
206
|
+
export function when(trigger: TriggerFn, body: () => void): void {
|
|
207
|
+
if (activeTree) {
|
|
208
|
+
const t = activeTree;
|
|
209
|
+
const frame: WhenFrame = { kind: "when", trigger, children: [] };
|
|
210
|
+
t.stack[t.stack.length - 1]!.push(frame);
|
|
211
|
+
t.stack.push(frame.children);
|
|
212
|
+
try {
|
|
213
|
+
body();
|
|
214
|
+
} finally {
|
|
215
|
+
t.stack.pop();
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (innerWhenHandler && innerWhenHandler(trigger, body)) return;
|
|
220
|
+
throw new Error(
|
|
221
|
+
"when() called outside defineStructure() and outside any managed(...) body. " +
|
|
222
|
+
"Place this call inside one of the two.",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Trigger factory: true when at least one file in the leaf's module
|
|
227
|
+
* matches `glob` (module-relative — see `TriggerContext.filesMatch`). Use
|
|
228
|
+
* to gate a rule on the presence of co-located files, e.g. add a vitest
|
|
229
|
+
* `test` script only when `src/**\/*.test.ts` exists. Pairs with `when`:
|
|
230
|
+
* `when(whenFilesExist("src/**\/*.test.ts"), () => { ... })`. */
|
|
231
|
+
export function whenFilesExist(glob: string): TriggerFn {
|
|
232
|
+
return (tctx) => tctx.filesMatch(glob);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function pushModeFrame(builder: string, modes: RunMode[], body: () => void): void {
|
|
236
|
+
const t = requireActiveTree(builder);
|
|
237
|
+
const frame: ModeFrame = { kind: "mode", modes, children: [] };
|
|
238
|
+
t.stack[t.stack.length - 1]!.push(frame);
|
|
239
|
+
t.stack.push(frame.children);
|
|
240
|
+
try {
|
|
241
|
+
body();
|
|
242
|
+
} finally {
|
|
243
|
+
t.stack.pop();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Leaves inside fire ONLY under `--update-deps-only` (mode
|
|
248
|
+
* `"updateDeps"`); they are skipped on default refresh/check and init. */
|
|
249
|
+
export function onUpdateDeps(body: () => void): void {
|
|
250
|
+
pushModeFrame("onUpdateDeps", ["updateDeps"], body);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Leaves inside fire on `init` AND `--update-deps-only`, but NOT on
|
|
254
|
+
* default refresh/check. The frame for rules that resolve/write current
|
|
255
|
+
* versions: both init and update-deps mean "fetch and write latest",
|
|
256
|
+
* while a plain refresh leaves them as the author/lockfile has them. */
|
|
257
|
+
export function onInitOrUpdate(body: () => void): void {
|
|
258
|
+
pushModeFrame("onInitOrUpdate", ["init", "updateDeps"], body);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Engine-OWNED file: (re)written verbatim from `content` on every refresh —
|
|
262
|
+
* author edits are overwritten. */
|
|
263
|
+
export function fixed(path: string, content: ContentForm): void {
|
|
264
|
+
appendNode("fixed", { kind: "fixed", path, content });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Engine-RECONCILED file: parsed from disk (or `initial` when absent),
|
|
268
|
+
* mutated by `body` (the JSON/YAML/lines builders), then re-serialised.
|
|
269
|
+
* Content the body does not touch is preserved. */
|
|
270
|
+
export function managed(path: string, initial: ContentForm, body: ManagedBody): void {
|
|
271
|
+
appendNode("managed", { kind: "managed", path, initial, body });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Write-once file: created from `initial` if absent (on init AND refresh),
|
|
275
|
+
* never overwritten once present. For files the author subsequently owns. */
|
|
276
|
+
export function scaffold(path: string, initial: ContentForm): void {
|
|
277
|
+
appendNode("scaffold", { kind: "scaffold", path, initial });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Author-owned starter file: written from `initial` only on `init`, and only
|
|
281
|
+
* if absent; `refresh` / `check` never touch it. */
|
|
282
|
+
export function seed(path: string, initial: ContentForm): void {
|
|
283
|
+
appendNode("seed", { kind: "seed", path, initial });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Delete `path` if present, on every refresh. */
|
|
287
|
+
export function remove(path: string): void {
|
|
288
|
+
appendNode("remove", { kind: "remove", path });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Rename file `from` → `to` on refresh (migrating a renamed scaffold file). */
|
|
292
|
+
export function rename(from: string, to: string): void {
|
|
293
|
+
appendNode("rename", { kind: "rename", from, to });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Content form: a UTF-8 static template read from the structurer's static
|
|
297
|
+
* templates. Use inside `fixed` / `managed` / `scaffold` / `seed`. */
|
|
298
|
+
export function file(path: string): ContentForm {
|
|
299
|
+
return { kind: "file", path };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** A binary static asset (e.g. a logo) read verbatim from `<root>/static/`.
|
|
303
|
+
* Unlike `file()`, the bytes are never decoded to a string — use for
|
|
304
|
+
* images / archives. Only valid inside `seed()` / `scaffold()`. */
|
|
305
|
+
export function binaryFile(path: string): ContentForm {
|
|
306
|
+
return { kind: "binary", path };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Content form: an inline literal string. */
|
|
310
|
+
export function text(value: string): ContentForm {
|
|
311
|
+
return { kind: "text", value };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Content form: a text template (the structurer's `text/` templates) with
|
|
315
|
+
* `${var}` placeholders substituted from `vars`. */
|
|
316
|
+
export function tpl(
|
|
317
|
+
path: string,
|
|
318
|
+
vars: Record<string, string> | (() => Record<string, string>),
|
|
319
|
+
): ContentForm {
|
|
320
|
+
return { kind: "tpl", path, vars };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Content form: content computed at run time. The return value is serialised
|
|
324
|
+
* as JSON / YAML by the target file's extension, or used verbatim if it is
|
|
325
|
+
* already a string. */
|
|
326
|
+
export function generate(fn: () => unknown): ContentForm {
|
|
327
|
+
return { kind: "generate", fn };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** The active run's {@link BlockVars} (block name parts + software platform).
|
|
331
|
+
* Valid only during a run (inside a generator or managed body); throws
|
|
332
|
+
* otherwise. */
|
|
333
|
+
export function blockVars(): BlockVars {
|
|
334
|
+
if (!activeRunContext) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"blockVars() called outside engine.run(). " +
|
|
337
|
+
"Generators and managed bodies only see BlockVars during a run.",
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return activeRunContext.blockVars;
|
|
341
|
+
}
|