@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.
Files changed (169) hide show
  1. package/dist/cli.js +23 -2
  2. package/dist/cli.js.map +1 -1
  3. package/dist/cli.mjs +1944 -100
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/cmd/index.d.ts +10 -4
  6. package/dist/cmd/structure/check.d.ts +10 -0
  7. package/dist/cmd/structure/init.d.ts +18 -0
  8. package/dist/cmd/structure/refresh.d.ts +11 -0
  9. package/dist/cmd/update-deps.d.ts +7 -0
  10. package/dist/structure/cli/run-structure.d.ts +19 -0
  11. package/dist/structure/engine/api.d.ts +55 -0
  12. package/dist/structure/engine/builders.d.ts +95 -0
  13. package/dist/structure/engine/content-rules.d.ts +148 -0
  14. package/dist/structure/engine/ctx.d.ts +34 -0
  15. package/dist/structure/engine/discovery-fs.d.ts +22 -0
  16. package/dist/structure/engine/discovery.d.ts +39 -0
  17. package/dist/structure/engine/flatten.d.ts +3 -0
  18. package/dist/structure/engine/fs/api.d.ts +27 -0
  19. package/dist/structure/engine/fs/memory.d.ts +16 -0
  20. package/dist/structure/engine/fs/node.d.ts +14 -0
  21. package/dist/structure/engine/glob.d.ts +4 -0
  22. package/dist/structure/engine/ir.d.ts +124 -0
  23. package/dist/structure/engine/parsers/json.d.ts +37 -0
  24. package/dist/structure/engine/parsers/lines.d.ts +9 -0
  25. package/dist/structure/engine/parsers/yaml.d.ts +18 -0
  26. package/dist/structure/engine/registry-client.d.ts +34 -0
  27. package/dist/structure/engine/runner.d.ts +45 -0
  28. package/dist/structure/engine/templates.d.ts +30 -0
  29. package/dist/structure/engine/testing.d.ts +34 -0
  30. package/dist/structure/engine/version.d.ts +18 -0
  31. package/dist/structure/init-block-constructor.d.ts +32 -0
  32. package/dist/structure/rules/block-package-json.d.ts +9 -0
  33. package/dist/structure/rules/block.d.ts +1 -0
  34. package/dist/structure/rules/migrations.d.ts +2 -0
  35. package/dist/structure/rules/model-package-json.d.ts +3 -0
  36. package/dist/structure/rules/model.d.ts +1 -0
  37. package/dist/structure/rules/root-catalog-bump.d.ts +1 -0
  38. package/dist/structure/rules/root-gitignore.d.ts +2 -0
  39. package/dist/structure/rules/root-package-json.d.ts +3 -0
  40. package/dist/structure/rules/root-pnpm-workspace.d.ts +15 -0
  41. package/dist/structure/rules/root.d.ts +1 -0
  42. package/dist/structure/rules/shared/colocated-tests.d.ts +2 -0
  43. package/dist/structure/rules/shared/key-order.d.ts +1 -0
  44. package/dist/structure/rules/software-package-json.d.ts +3 -0
  45. package/dist/structure/rules/software.d.ts +1 -0
  46. package/dist/structure/rules/test-package-json.d.ts +3 -0
  47. package/dist/structure/rules/test.d.ts +1 -0
  48. package/dist/structure/rules/ui-package-json.d.ts +3 -0
  49. package/dist/structure/rules/ui.d.ts +1 -0
  50. package/dist/structure/rules/workflow-package-json.d.ts +3 -0
  51. package/dist/structure/rules/workflow.d.ts +1 -0
  52. package/dist/structure/structure-definition.d.ts +1 -0
  53. package/package.json +9 -7
  54. package/src/cmd/index.ts +10 -4
  55. package/src/cmd/structure/check.ts +51 -0
  56. package/src/cmd/structure/init.ts +65 -0
  57. package/src/cmd/structure/refresh.ts +44 -0
  58. package/src/cmd/update-deps.ts +15 -3
  59. package/src/structure/__tests__/colocated-test-wiring.test.ts +107 -0
  60. package/src/structure/__tests__/floor-version.test.ts +52 -0
  61. package/src/structure/__tests__/init-catalog-resolution.test.ts +82 -0
  62. package/src/structure/__tests__/init-check-parity.test.ts +59 -0
  63. package/src/structure/__tests__/model-package-json.snapshot.test.ts +60 -0
  64. package/src/structure/__tests__/oxfmt-clean-init.test.ts +64 -0
  65. package/src/structure/__tests__/root-catalog-bump.test.ts +110 -0
  66. package/src/structure/__tests__/seed-disjointness.test.ts +79 -0
  67. package/src/structure/cli/run-structure.ts +89 -0
  68. package/src/structure/engine/__tests__/content-rules/active-state-guards.test.ts +61 -0
  69. package/src/structure/engine/__tests__/content-rules/catalog-pin.test.ts +46 -0
  70. package/src/structure/engine/__tests__/content-rules/enforce-alphabetical-order.test.ts +55 -0
  71. package/src/structure/engine/__tests__/content-rules/enforce-field-order.test.ts +73 -0
  72. package/src/structure/engine/__tests__/content-rules/ensure-catalog-absent.test.ts +29 -0
  73. package/src/structure/engine/__tests__/content-rules/ensure-catalog-latest.test.ts +70 -0
  74. package/src/structure/engine/__tests__/content-rules/ensure-dep.test.ts +63 -0
  75. package/src/structure/engine/__tests__/content-rules/ensure-deps-bulk.test.ts +46 -0
  76. package/src/structure/engine/__tests__/content-rules/ensure-field-entries.test.ts +35 -0
  77. package/src/structure/engine/__tests__/content-rules/ensure-field.test.ts +56 -0
  78. package/src/structure/engine/__tests__/content-rules/ensure-script.test.ts +39 -0
  79. package/src/structure/engine/__tests__/content-rules/ensure-workspace-scope-deps.test.ts +92 -0
  80. package/src/structure/engine/__tests__/content-rules/gitignore.test.ts +50 -0
  81. package/src/structure/engine/__tests__/content-rules/pin-catalog-to.test.ts +64 -0
  82. package/src/structure/engine/__tests__/content-rules/prune-keys.test.ts +59 -0
  83. package/src/structure/engine/__tests__/content-rules/registry-client.test.ts +116 -0
  84. package/src/structure/engine/__tests__/content-rules/remove-field.test.ts +49 -0
  85. package/src/structure/engine/__tests__/content-rules/require-field.test.ts +40 -0
  86. package/src/structure/engine/__tests__/content-rules/transform-at.test.ts +33 -0
  87. package/src/structure/engine/__tests__/content-rules/when-inner.test.ts +244 -0
  88. package/src/structure/engine/__tests__/content-rules/workspace-module-paths.test.ts +53 -0
  89. package/src/structure/engine/__tests__/discovery.test.ts +160 -0
  90. package/src/structure/engine/__tests__/fs-memory.test.ts +59 -0
  91. package/src/structure/engine/__tests__/glob.test.ts +43 -0
  92. package/src/structure/engine/__tests__/parsers-json.test.ts +189 -0
  93. package/src/structure/engine/__tests__/parsers-lines.test.ts +44 -0
  94. package/src/structure/engine/__tests__/parsers-yaml.test.ts +90 -0
  95. package/src/structure/engine/__tests__/recheck.test.ts +99 -0
  96. package/src/structure/engine/__tests__/registry-lookup.test.ts +49 -0
  97. package/src/structure/engine/__tests__/runner-when-inside-managed.test.ts +136 -0
  98. package/src/structure/engine/__tests__/runner.test.ts +136 -0
  99. package/src/structure/engine/__tests__/synthetic.test.ts +153 -0
  100. package/src/structure/engine/__tests__/version.test.ts +44 -0
  101. package/src/structure/engine/__tests__/when-files-exist.test.ts +89 -0
  102. package/src/structure/engine/api.ts +131 -0
  103. package/src/structure/engine/builders.ts +341 -0
  104. package/src/structure/engine/content-rules.ts +616 -0
  105. package/src/structure/engine/ctx.ts +94 -0
  106. package/src/structure/engine/discovery-fs.ts +209 -0
  107. package/src/structure/engine/discovery.ts +168 -0
  108. package/src/structure/engine/flatten.ts +117 -0
  109. package/src/structure/engine/fs/api.ts +40 -0
  110. package/src/structure/engine/fs/memory.ts +132 -0
  111. package/src/structure/engine/fs/node.ts +89 -0
  112. package/src/structure/engine/glob.ts +42 -0
  113. package/src/structure/engine/ir.ts +149 -0
  114. package/src/structure/engine/parsers/json.ts +212 -0
  115. package/src/structure/engine/parsers/lines.ts +42 -0
  116. package/src/structure/engine/parsers/yaml.ts +72 -0
  117. package/src/structure/engine/registry-client.ts +140 -0
  118. package/src/structure/engine/runner.ts +519 -0
  119. package/src/structure/engine/templates.ts +74 -0
  120. package/src/structure/engine/testing.ts +93 -0
  121. package/src/structure/engine/version.ts +60 -0
  122. package/src/structure/init-block-constructor.ts +164 -0
  123. package/src/structure/rules/block-package-json.ts +156 -0
  124. package/src/structure/rules/block.ts +29 -0
  125. package/src/structure/rules/migrations.ts +63 -0
  126. package/src/structure/rules/model-package-json.ts +123 -0
  127. package/src/structure/rules/model.ts +65 -0
  128. package/src/structure/rules/root-catalog-bump.ts +77 -0
  129. package/src/structure/rules/root-gitignore.ts +27 -0
  130. package/src/structure/rules/root-package-json.ts +119 -0
  131. package/src/structure/rules/root-pnpm-workspace.ts +103 -0
  132. package/src/structure/rules/root.ts +43 -0
  133. package/src/structure/rules/shared/colocated-tests.ts +11 -0
  134. package/src/structure/rules/shared/key-order.ts +34 -0
  135. package/src/structure/rules/software-package-json.ts +86 -0
  136. package/src/structure/rules/software.ts +33 -0
  137. package/src/structure/rules/test-package-json.ts +70 -0
  138. package/src/structure/rules/test.ts +40 -0
  139. package/src/structure/rules/ui-package-json.ts +108 -0
  140. package/src/structure/rules/ui.ts +75 -0
  141. package/src/structure/rules/workflow-package-json.ts +92 -0
  142. package/src/structure/rules/workflow.ts +51 -0
  143. package/src/structure/structure-definition.ts +28 -0
  144. package/src/structure/templates/static/block/index.d.ts +6 -0
  145. package/src/structure/templates/static/block/index.js +8 -0
  146. package/src/structure/templates/static/block/logos/block-logo.png +0 -0
  147. package/src/structure/templates/static/block/logos/organization-logo.png +0 -0
  148. package/src/structure/templates/static/model/.oxfmtrc.json +4 -0
  149. package/src/structure/templates/static/model/.oxlintrc.json +3 -0
  150. package/src/structure/templates/static/model/src/index.ts +13 -0
  151. package/src/structure/templates/static/model/tsconfig.json +4 -0
  152. package/src/structure/templates/static/root/.gitignore +13 -0
  153. package/src/structure/templates/static/root/.vscode/settings.json +3 -0
  154. package/src/structure/templates/static/root/turbo.json +35 -0
  155. package/src/structure/templates/static/test/tsconfig.json +5 -0
  156. package/src/structure/templates/static/test/vitest.config.mts +8 -0
  157. package/src/structure/templates/static/ui/.oxfmtrc.json +3 -0
  158. package/src/structure/templates/static/ui/.oxlintrc.json +3 -0
  159. package/src/structure/templates/static/ui/index.html +12 -0
  160. package/src/structure/templates/static/ui/src/main.ts +5 -0
  161. package/src/structure/templates/static/ui/tsconfig.json +4 -0
  162. package/src/structure/templates/static/workflow/format.el +42 -0
  163. package/src/structure/templates/static/workflow/tsconfig.json +16 -0
  164. package/src/structure/templates/static/workflow/vitest.config.mts +9 -0
  165. package/src/structure/templates/text/README.md +5 -0
  166. package/src/structure/templates/text/docs/description.md +3 -0
  167. package/src/structure/templates/text/ui/src/MainPage.tpl.vue +9 -0
  168. package/src/structure/templates/text/ui/src/app.tpl.ts +11 -0
  169. package/src/structure/templates/text/workflow/src/main.tpl.tengo +10 -0
@@ -0,0 +1,124 @@
1
+ import { Scope, RunContext } from './api';
2
+ /** The three leaf-firing modes. `check` and `refresh` share the
3
+ * `"default"` mode — they run the same leaves, the `dryRun` flag (carried
4
+ * separately on `RunContext`) only decides whether writes happen.
5
+ * `"updateDeps"` is `--update-deps-only`; `"init"` is the init
6
+ * constructor. A leaf declares which modes it fires in via its enclosing
7
+ * mode frame (see `ModeFrame`); the runner derives the current mode from
8
+ * the run context and keeps only the leaves whose membership includes it. */
9
+ export type RunMode = "default" | "updateDeps" | "init";
10
+ /** Predicate evaluated against derived trigger context. */
11
+ export type TriggerContext = {
12
+ ctx: RunContext;
13
+ version: number;
14
+ /** Block-relative path queries against the post-structural FS snapshot. */
15
+ pathExists(path: string): boolean;
16
+ pathMissing(path: string): boolean;
17
+ /** True if any file in the snapshot matches `glob`, resolved
18
+ * MODULE-RELATIVE to the leaf's bound module: a co-located-test glob
19
+ * inside `scope("model")` is checked under the `model/` dir. At the
20
+ * block root the glob is block-relative. Unlike `pathExists`/`pathMissing`
21
+ * (block-relative), `filesMatch` is the module-aware predicate used by
22
+ * `whenFilesExist`. */
23
+ filesMatch(glob: string): boolean;
24
+ };
25
+ export type TriggerFn = (tctx: TriggerContext) => boolean;
26
+ /** Content forms — what to write into a file when creating it.
27
+ * `tpl.vars` may be a record or a thunk returning a record; the latter
28
+ * lets rule authors call `blockVars()` lazily at resolve time. */
29
+ export type TplVarsLike = Record<string, string> | (() => Record<string, string>);
30
+ export type ContentForm = {
31
+ kind: "file";
32
+ path: string;
33
+ } | {
34
+ kind: "text";
35
+ value: string;
36
+ } | {
37
+ kind: "tpl";
38
+ path: string;
39
+ vars: TplVarsLike;
40
+ } | {
41
+ kind: "generate";
42
+ fn: () => unknown;
43
+ } | {
44
+ kind: "binary";
45
+ path: string;
46
+ };
47
+ /** Builder-body for a `managed` file (the content-rules lambda). */
48
+ export type ManagedBody = () => void;
49
+ export type FixedItem = {
50
+ kind: "fixed";
51
+ path: string;
52
+ content: ContentForm;
53
+ };
54
+ export type ManagedItem = {
55
+ kind: "managed";
56
+ path: string;
57
+ initial: ContentForm;
58
+ body: ManagedBody;
59
+ };
60
+ export type ScaffoldItem = {
61
+ kind: "scaffold";
62
+ path: string;
63
+ initial: ContentForm;
64
+ };
65
+ export type SeedItem = {
66
+ kind: "seed";
67
+ path: string;
68
+ initial: ContentForm;
69
+ };
70
+ export type RemoveItem = {
71
+ kind: "remove";
72
+ path: string;
73
+ };
74
+ export type RenameItem = {
75
+ kind: "rename";
76
+ from: string;
77
+ to: string;
78
+ };
79
+ export type LeafItem = FixedItem | ManagedItem | ScaffoldItem | SeedItem | RemoveItem | RenameItem;
80
+ export type ScopeFrame = {
81
+ kind: "scope";
82
+ scope: Scope;
83
+ children: TreeNode[];
84
+ };
85
+ export type WhenFrame = {
86
+ kind: "when";
87
+ trigger: TriggerFn;
88
+ children: TreeNode[];
89
+ };
90
+ /** Top-level mode frame: contained leaves fire only in the listed run
91
+ * modes. Leaves outside any mode frame fire in `["default", "init"]`
92
+ * (refresh/check + init). `onUpdateDeps()` produces `["updateDeps"]`;
93
+ * `onInitOrUpdate()` produces `["init", "updateDeps"]`. */
94
+ export type ModeFrame = {
95
+ kind: "mode";
96
+ modes: RunMode[];
97
+ children: TreeNode[];
98
+ };
99
+ export type TreeNode = LeafItem | ScopeFrame | WhenFrame | ModeFrame;
100
+ /** Structure object returned by `defineStructure`. */
101
+ export type Structure = {
102
+ /** Top-level children — may contain leaves, scope frames, when
103
+ * frames, and mode frames. */
104
+ children: TreeNode[];
105
+ };
106
+ /**
107
+ * After fan-out: one entry per (leaf × matching module). `scope` is
108
+ * always set; `modulePath` is the workspace path the leaf is bound to
109
+ * (empty string for the workspace root). `triggers` is the AND of all
110
+ * enclosing `when` predicates. `modes` is the set of run modes the leaf
111
+ * fires in — `["default", "init"]` outside any mode frame, otherwise the
112
+ * enclosing mode frame's `modes`.
113
+ */
114
+ export type FlatItem = {
115
+ leaf: LeafItem;
116
+ scope: Scope;
117
+ modulePath: string;
118
+ /** Block-relative resolved path(s). */
119
+ resolvedPath: string;
120
+ /** For rename, the resolved destination. */
121
+ resolvedTo?: string;
122
+ triggers: TriggerFn[];
123
+ modes: RunMode[];
124
+ };
@@ -0,0 +1,37 @@
1
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
2
+ [k: string]: JsonValue;
3
+ };
4
+ export type JsonObject = {
5
+ [k: string]: unknown;
6
+ };
7
+ /** Parse JSON; throws on invalid input. */
8
+ export declare function parseJson(raw: string): unknown;
9
+ /** Canonical serialiser: 2-space indent, trailing newline.
10
+ * Default — every container expanded (the package.json shape, byte-identical
11
+ * to `JSON.stringify(…, 2)`).
12
+ * `{ collapse: true }` — oxfmt's generic-.json shape: containers that fit
13
+ * `PRINT_WIDTH` collapse onto one line. Used for tsconfig.json and any other
14
+ * non-package.json the engine emits, so `oxfmt --check` passes without a
15
+ * prior format pass. */
16
+ export declare function stringifyJson(value: unknown, opts?: {
17
+ collapse?: boolean;
18
+ }): string;
19
+ /** Get the value at `jsonPath`. Returns `undefined` if any segment is
20
+ * missing or not an object. */
21
+ export declare function getAtPath(obj: JsonObject, jsonPath: string): unknown;
22
+ /** True if `jsonPath` resolves to a defined value (including `null`). */
23
+ export declare function hasAtPath(obj: JsonObject, jsonPath: string): boolean;
24
+ /** Set `value` at `jsonPath`. Auto-creates intermediate objects. Throws
25
+ * if a non-object value is on the path (would be silently overwritten
26
+ * otherwise — caller bug). */
27
+ export declare function setAtPath(obj: JsonObject, jsonPath: string, value: unknown): void;
28
+ /** Delete the value at `jsonPath`. No-op if any segment is missing. */
29
+ export declare function deleteAtPath(obj: JsonObject, jsonPath: string): void;
30
+ /**
31
+ * Reorder top-level keys of `obj`:
32
+ * - Known keys (those listed in `order`) appear in declared order.
33
+ * - Unknown keys (not in `order`) stay adjacent to the preceding known
34
+ * key they followed in the source. Unknown keys appearing before any
35
+ * known key go first, in source order.
36
+ */
37
+ export declare function reorderTopLevel(obj: JsonObject, order: readonly string[]): JsonObject;
@@ -0,0 +1,9 @@
1
+ export declare function parseLines(raw: string): string[];
2
+ export declare function serializeLines(lines: readonly string[]): string;
3
+ /** Strip leading/trailing whitespace and trailing inline `# ...` comments.
4
+ * Returns empty string for blank / comment-only lines. */
5
+ export declare function normaliseLine(line: string): string;
6
+ /** True if `line` (after normalisation) equals `entry` (after trim). */
7
+ export declare function lineEqualsEntry(line: string, entry: string): boolean;
8
+ /** True if any line in `lines` normalises to `entry`. */
9
+ export declare function containsEntry(lines: readonly string[], entry: string): boolean;
@@ -0,0 +1,18 @@
1
+ import { Document, isMap, isPair, isSeq, YAMLMap, YAMLSeq, Scalar, Node } from 'yaml';
2
+ export type YamlDocument = Document.Parsed;
3
+ /** Parse YAML into a CST-preserving Document. */
4
+ export declare function parseYaml(raw: string): YamlDocument;
5
+ /** Serialise back to string. Preserves comments + ordering of unchanged
6
+ * nodes per the `yaml` library's contract. */
7
+ export declare function stringifyYaml(doc: YamlDocument): string;
8
+ /** Get the JS value at the given jsonPath in the document. */
9
+ export declare function yamlGet(doc: YamlDocument, jsonPath: string): unknown;
10
+ export declare function yamlHas(doc: YamlDocument, jsonPath: string): boolean;
11
+ /** Set a value at jsonPath. Auto-creates intermediate maps. Existing
12
+ * comments on parent map entries are preserved by the yaml library. */
13
+ export declare function yamlSet(doc: YamlDocument, jsonPath: string, value: unknown): void;
14
+ /** Delete a value at jsonPath. No-op if missing. */
15
+ export declare function yamlDelete(doc: YamlDocument, jsonPath: string): void;
16
+ /** Re-export selected `yaml` lib types for downstream content-rule builders. */
17
+ export { isMap, isPair, isSeq, YAMLMap, YAMLSeq, Scalar };
18
+ export type { Node };
@@ -0,0 +1,34 @@
1
+ export interface RegistryClient {
2
+ getLatestVersion(packageName: string): Promise<string>;
3
+ }
4
+ /** Parse the HTTP Retry-After header (RFC 9110): either a delay in
5
+ * seconds or an absolute HTTP-date. Returns the delay in ms, or null
6
+ * if the header is missing / unparseable. */
7
+ export declare function parseRetryAfter(header: string | null): number | null;
8
+ /** Live npm registry client. Hits the network on each call. */
9
+ export declare function createRealRegistryClient(): RegistryClient;
10
+ /** In-memory client backed by a fixed `name → version` map. */
11
+ export declare function createMockRegistryClient(versions: Record<string, string>): RegistryClient;
12
+ /** Resolve a batch of `name → latest` entries in parallel. */
13
+ export declare function prefetchLatestVersions(client: RegistryClient, packageNames: readonly string[]): Promise<Map<string, string>>;
14
+ /** Build a sync lookup function (the shape `withManagedYaml` consumes)
15
+ * from a prefetched map. Returns undefined for unknown names. */
16
+ export declare function makeSyncLookup(resolved: Map<string, string>): (packageName: string) => string | undefined;
17
+ /** Catalog package-name patterns the version-resolution rules
18
+ * (`ensureCatalogLatest`) match against — the `@platforma-sdk/*` and
19
+ * `@milaboratories/*` SDK families. Infra/runtime entries (turbo, shx,
20
+ * …) are deliberately NOT here: they stay a curated floor, not auto-latest.
21
+ * Shared so `init` (names from the SDK pin map) and `refresh
22
+ * --update-deps-only` (names from the on-disk catalog) select the same
23
+ * set. */
24
+ export declare const CATALOG_BUMP_PATTERNS: readonly RegExp[];
25
+ /** True if `name` is a version-resolved SDK package (matches any bump
26
+ * pattern). */
27
+ export declare function matchesBumpPattern(name: string): boolean;
28
+ /** Prefetch npm "latest" for `names` and return the sync lookup the
29
+ * runner passes to the catalog ensure-family (`ensureCatalogLatest`).
30
+ * Empty list → a lookup that
31
+ * resolves nothing (no network). A network/registry failure REJECTS
32
+ * (callers that require the network — `init` — let it propagate).
33
+ * `client` defaults to the live npm client; tests inject a mock. */
34
+ export declare function buildRegistryLookupForNames(names: readonly string[], client?: RegistryClient): Promise<(packageName: string) => string | undefined>;
@@ -0,0 +1,45 @@
1
+ import { Structure } from './ir';
2
+ import { RunContext, Scope } from './api';
3
+ import { FileSystem } from './fs/api';
4
+ import { TemplateProvider } from './templates';
5
+ export type ChangeAction = "create" | "update" | "delete" | "rename" | "noop";
6
+ export type Change = {
7
+ scope: Scope;
8
+ /** Resolved (block-relative) path. For rename, the source path. */
9
+ path: string;
10
+ /** "fixed" | "managed" | "scaffold" | "seed" | "remove" | "rename" */
11
+ primitive: string;
12
+ action: ChangeAction;
13
+ };
14
+ export type RunResult = {
15
+ changes: Change[];
16
+ };
17
+ export type RunOptions = {
18
+ /** Called before the post-run recheck to obtain a fresh RunContext
19
+ * reflecting the just-written FS state (re-reads `pnpm-workspace.yaml`).
20
+ * If absent, recheck reuses the original ctx with `dryRun: true`. */
21
+ rediscover?: (fs: FileSystem) => RunContext;
22
+ /** Template provider used by `file(...)` and `tpl(...)` content forms.
23
+ * Required whenever the structure references such forms. */
24
+ templates?: TemplateProvider;
25
+ /** Init mode: write seed leaves (only-if-missing semantics) and skip
26
+ * the post-run recheck. The caller is expected to follow with an
27
+ * explicit dry-run check (Layer-2 invariant). */
28
+ initMode?: boolean;
29
+ /** Sync accessor for prefetched npm "latest" versions, consumed by
30
+ * `ensureCatalogLatest` inside `onUpdateDeps` managed bodies. The CLI
31
+ * prefetches the catalog's package versions before the run (network)
32
+ * and passes the resulting sync map here; unit tests pass a mock.
33
+ * Absent → `ensureCatalogLatest` is a no-op (default refresh). */
34
+ registryLookup?: (packageName: string) => string | undefined;
35
+ };
36
+ export declare class RecheckError extends Error {
37
+ readonly failing: Change;
38
+ constructor(failing: Change);
39
+ }
40
+ /**
41
+ * Run the structurer engine end-to-end. See module header for mode
42
+ * semantics. Throws `RecheckError` if the post-run dry-run finds any
43
+ * leftover diff after a successful refresh.
44
+ */
45
+ export declare function run(structure: Structure, fs: FileSystem, ctx: RunContext, opts?: RunOptions): RunResult;
@@ -0,0 +1,30 @@
1
+ export interface TemplateProvider {
2
+ /** Read a static template file (under `<root>/static/`). */
3
+ staticRead(p: string): string;
4
+ /** Read a static template file as raw bytes (under `<root>/static/`).
5
+ * For binary assets (logos, images) that must not be utf-8 decoded. */
6
+ staticReadBinary(p: string): Uint8Array;
7
+ /** Read a text template file (under `<root>/text/`). */
8
+ textRead(p: string): string;
9
+ }
10
+ /** Provider backed by an on-disk template tree. Reads are synchronous
11
+ * so the runner can stay sync inside content-form resolution. */
12
+ export declare class NodeTemplateProvider implements TemplateProvider {
13
+ private root;
14
+ constructor(root: string);
15
+ staticRead(p: string): string;
16
+ staticReadBinary(p: string): Uint8Array;
17
+ textRead(p: string): string;
18
+ }
19
+ /** In-memory provider for tests. Keyed by the same `static/...` /
20
+ * `text/...` shape as on disk. */
21
+ export declare class MemoryTemplateProvider implements TemplateProvider {
22
+ private files;
23
+ constructor(files: Record<string, string>);
24
+ staticRead(p: string): string;
25
+ staticReadBinary(p: string): Uint8Array;
26
+ textRead(p: string): string;
27
+ }
28
+ /** Shell-style `${name}` substitution. Unknown names left as-is so
29
+ * template authors notice. Backslash escape is not supported. */
30
+ export declare function substituteVars(text: string, vars: Record<string, string>): string;
@@ -0,0 +1,34 @@
1
+ import { BlockVars, Module, RunContext, Structure } from './api';
2
+ import { JsonObject } from './content-rules';
3
+ import { MemoryFileSystem } from './fs/memory';
4
+ import { TemplateProvider } from './templates';
5
+ /** Layer-1 helper. */
6
+ export declare function runRulesAgainst(input: JsonObject, body: () => void): JsonObject;
7
+ export type SimulateInitInput = {
8
+ vars: BlockVars;
9
+ /** Override the discovered module set. Defaults to `defaultModulesFor`. */
10
+ modules?: Module[];
11
+ /** Override the structure under test (defaults to the canonical one). */
12
+ structure?: Structure;
13
+ /** Override the template provider (defaults to NodeTemplateProvider
14
+ * pointing at the bundled `templates/` directory). */
15
+ templates?: TemplateProvider;
16
+ /** SDK-internal flag. Default `false` (standalone block). */
17
+ isSdkInternal?: boolean;
18
+ /** Sync npm-latest lookup for the `onInitOrUpdate` catalog resolution.
19
+ * Absent → the SDK catalog keeps its seed versions (no bump), matching
20
+ * an offline scaffold; tests that assert init writes latest pass a mock. */
21
+ registryLookup?: (packageName: string) => string | undefined;
22
+ };
23
+ export type SimulatedInit = {
24
+ fs: MemoryFileSystem;
25
+ ctx: RunContext;
26
+ };
27
+ /** Module set produced by `init` for a given BlockVars. Delegates to the
28
+ * shared `modulesForInit` so the test helper and the real `init`
29
+ * constructor never drift. */
30
+ export declare function defaultModulesFor(vars: BlockVars): Module[];
31
+ /** Default template provider — reads from the bundled templates tree
32
+ * next to this file. */
33
+ export declare function defaultTemplateProvider(): TemplateProvider;
34
+ export declare function simulateInit(input: SimulateInitInput): SimulatedInit;
@@ -0,0 +1,18 @@
1
+ import { FileSystem } from './fs/api';
2
+ export declare const STRUCTURE_VERSION = 1;
3
+ export declare const STRUCTURE_MIN_SUPPORTED = 0;
4
+ /** Block-relative path of the structurer metadata file. */
5
+ export declare const STRUCTURE_META_FILE = ".structure";
6
+ export type StructureMeta = {
7
+ version: number;
8
+ };
9
+ /** Read `.structure`. Missing file or invalid payload → 0. */
10
+ export declare function readStructureVersion(fs: FileSystem): number;
11
+ /** Throw if version is below floor. */
12
+ export declare function assertVersionAboveFloor(version: number, floor?: number): void;
13
+ export declare class StructureVersionFloorError extends Error {
14
+ readonly version: number;
15
+ readonly floor: number;
16
+ constructor(version: number, floor: number);
17
+ }
18
+ export declare function writeStructureVersion(fs: FileSystem, version?: number): void;
@@ -0,0 +1,32 @@
1
+ import { BlockVars } from './engine/api';
2
+ /** Platform choices `init` can scaffold a software module for. Python is
3
+ * the only platform with a real, buildable scaffold at v1; any other
4
+ * value is rejected rather than scaffolded as a non-buildable placeholder,
5
+ * which would defeat the "init must produce a working block" intent.
6
+ * `softwarePlatform` stays in BlockVars for when another platform gets a
7
+ * real scaffold. (Note: Tengo is the workflow language, not a software
8
+ * platform.) */
9
+ export declare const SUPPORTED_PLATFORMS: readonly ["python"];
10
+ export type InitFlagValues = {
11
+ npmOrg?: string;
12
+ orgScope?: string;
13
+ shortName?: string;
14
+ /** Tri-state: true (--with-software), false (--no-software),
15
+ * undefined (unspecified → prompt or, non-interactive, default off). */
16
+ withSoftware?: boolean;
17
+ platform?: string;
18
+ nonInteractive: boolean;
19
+ };
20
+ export type InitInput = {
21
+ /** Resolved BlockVars (from `resolveBlockVars`). */
22
+ vars: BlockVars;
23
+ /** Resolved absolute target directory. */
24
+ blockPath: string;
25
+ /** `<block-tools>/src/structure/templates`. */
26
+ templatesRoot: string;
27
+ log: (msg: string) => void;
28
+ };
29
+ /** Resolve the full BlockVars (incl. softwarePlatform) from flags +
30
+ * prompts. */
31
+ export declare function resolveBlockVars(flags: InitFlagValues): Promise<BlockVars>;
32
+ export declare function runInit(init: InitInput): Promise<BlockVars>;
@@ -0,0 +1,9 @@
1
+ import { RunContext } from '../engine/api';
2
+ /** The facade's `block.components` map: the dist artifact each module build
3
+ * emits, keyed by role. Fully determined by the discovered modules — the
4
+ * body rule re-asserts this exact value, so it is engine-managed (NOT an
5
+ * author seed). Mirrors the artifact paths produced by each scope's build:
6
+ * workflow → pl-tengo, model → build-model, ui → ts-builder. */
7
+ export declare function blockComponents(ctx: RunContext): Record<string, string>;
8
+ export declare function blockPackageJsonInitial(ctx: RunContext): Record<string, unknown>;
9
+ export declare function blockPackageJsonRules(): void;
@@ -0,0 +1 @@
1
+ export declare function blockRules(): void;
@@ -0,0 +1,2 @@
1
+ export declare function testFrameworkMigration(): void;
2
+ export declare function legacyCleanup(): void;
@@ -0,0 +1,3 @@
1
+ import { RunContext } from '../engine/api';
2
+ export declare function modelPackageJsonInitial(ctx: RunContext): Record<string, unknown>;
3
+ export declare function modelPackageJsonRules(): void;
@@ -0,0 +1 @@
1
+ export declare function modelRules(): void;
@@ -0,0 +1 @@
1
+ export declare function rootCatalogBumpRules(): void;
@@ -0,0 +1,2 @@
1
+ export declare const GITIGNORE_REQUIRED_LINES: string[];
2
+ export declare function rootGitignoreRules(): void;
@@ -0,0 +1,3 @@
1
+ import { RunContext } from '../engine/api';
2
+ export declare function rootPackageJsonInitial(_ctx: RunContext): Record<string, unknown>;
3
+ export declare function rootPackageJsonRules(): void;
@@ -0,0 +1,15 @@
1
+ import { RunContext } from '../engine/api';
2
+ /** SDK packages that live in the catalog (membership only — no versions).
3
+ * Init fetches npm latest for each; refresh leaves them as the lockfile has
4
+ * them. */
5
+ export declare const SDK_CATALOG_PACKAGES: readonly string[];
6
+ /** Infra / tooling — a `~`-floored curated set. Not exact-pinned on refresh
7
+ * and not bumped by update-deps. */
8
+ export declare const INFRA_CATALOG_FLOOR: Record<string, string>;
9
+ /** Catalog name of the python runenv. Added to a block's catalog only when
10
+ * the block carries a software module. Pinned to an EXACT version — updating
11
+ * the python runenv is an explicit author decision, never automatic. */
12
+ export declare const RUNENV_PYTHON = "@platforma-open/milaboratories.runenv-python-3";
13
+ export declare const RUNENV_PYTHON_VERSION = "1.8.2";
14
+ export declare function rootPnpmWorkspaceInitial(ctx: RunContext): Record<string, unknown>;
15
+ export declare function rootPnpmWorkspaceRules(): void;
@@ -0,0 +1 @@
1
+ export declare function rootRules(): void;
@@ -0,0 +1,2 @@
1
+ /** Glob (module-relative) for co-located unit tests in a scope's `src/`. */
2
+ export declare const COLOCATED_TEST_GLOB = "src/**/*.test.ts";
@@ -0,0 +1 @@
1
+ export declare const canonicalPackageJsonOrder: readonly ["name", "version", "private", "description", "keywords", "files", "type", "main", "module", "types", "exports", "scripts", "dependencies", "devDependencies", "peerDependencies", "optionalDependencies", "packageManager", "pnpm", "block", "block-software"];
@@ -0,0 +1,3 @@
1
+ import { RunContext } from '../engine/api';
2
+ export declare function softwarePackageJsonInitial(ctx: RunContext): Record<string, unknown>;
3
+ export declare function softwarePackageJsonRules(): void;
@@ -0,0 +1 @@
1
+ export declare function softwareRules(): void;
@@ -0,0 +1,3 @@
1
+ import { RunContext } from '../engine/api';
2
+ export declare function testPackageJsonInitial(ctx: RunContext): Record<string, unknown>;
3
+ export declare function testPackageJsonRules(): void;
@@ -0,0 +1 @@
1
+ export declare function testRules(): void;
@@ -0,0 +1,3 @@
1
+ import { RunContext } from '../engine/api';
2
+ export declare function uiPackageJsonInitial(ctx: RunContext): Record<string, unknown>;
3
+ export declare function uiPackageJsonRules(): void;
@@ -0,0 +1 @@
1
+ export declare function uiRules(): void;
@@ -0,0 +1,3 @@
1
+ import { RunContext } from '../engine/api';
2
+ export declare function workflowPackageJsonInitial(ctx: RunContext): Record<string, unknown>;
3
+ export declare function workflowPackageJsonRules(): void;
@@ -0,0 +1 @@
1
+ export declare function workflowRules(): void;
@@ -0,0 +1 @@
1
+ export declare const STRUCTURE: import('./engine/ir').Structure;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/block-tools",
3
- "version": "2.10.14",
3
+ "version": "2.10.15",
4
4
  "description": "Utility to manipulate Platforma Blocks and Block Registry",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@aws-sdk/client-s3": "3.859.0",
27
+ "@inquirer/prompts": "^7.0.1",
27
28
  "@oclif/core": "^4.0.37",
28
29
  "canonicalize": "~2.1.0",
29
30
  "lru-cache": "^11.2.2",
@@ -33,13 +34,13 @@
33
34
  "yaml": "^2.8.0",
34
35
  "zod": "~3.25.76",
35
36
  "@milaboratories/pl-http": "1.2.4",
36
- "@milaboratories/pl-model-backend": "1.4.7",
37
37
  "@milaboratories/pl-model-common": "1.46.1",
38
+ "@milaboratories/pl-model-backend": "1.4.7",
38
39
  "@milaboratories/pl-model-middle-layer": "1.30.5",
39
40
  "@milaboratories/ts-helpers": "1.8.3",
40
- "@platforma-sdk/blocks-deps-updater": "2.2.0",
41
+ "@milaboratories/resolve-helper": "1.1.3",
41
42
  "@milaboratories/ts-helpers-oclif": "1.1.42",
42
- "@milaboratories/resolve-helper": "1.1.3"
43
+ "@platforma-sdk/blocks-deps-updater": "2.2.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@rollup/plugin-node-resolve": "^16.0.1",
@@ -48,16 +49,17 @@
48
49
  "@types/semver": "^7.7.0",
49
50
  "@vitest/coverage-istanbul": "^4.1.3",
50
51
  "oclif": "^4.16.2",
52
+ "oxfmt": "0.35.0",
51
53
  "rollup-plugin-node-externals": "^8.0.0",
52
54
  "semver": "^7.7.2",
53
55
  "typescript": "~5.9.3",
54
56
  "vite": "^8.0.6",
55
57
  "vite-plugin-dts": "^4.5.3",
56
58
  "vitest": "^4.1.3",
57
- "@milaboratories/build-configs": "2.0.0",
58
59
  "@milaboratories/oclif-index": "1.1.1",
59
- "@milaboratories/ts-builder": "1.5.0",
60
- "@milaboratories/ts-configs": "1.2.3"
60
+ "@milaboratories/ts-configs": "1.2.3",
61
+ "@milaboratories/build-configs": "2.0.0",
62
+ "@milaboratories/ts-builder": "1.5.0"
61
63
  },
62
64
  "oclif": {
63
65
  "bin": "block-tools",
package/src/cmd/index.ts CHANGED
@@ -8,8 +8,11 @@ import Cmd5 from "./pack";
8
8
  import Cmd6 from "./publish";
9
9
  import Cmd7 from "./refresh-registry";
10
10
  import Cmd8 from "./restore-overview-from-snapshot";
11
- import Cmd9 from "./upload-package-v1";
12
- import Cmd10 from "./update-deps";
11
+ import Cmd9 from "./update-deps";
12
+ import Cmd10 from "./upload-package-v1";
13
+ import Cmd11 from "./structure/check";
14
+ import Cmd12 from "./structure/init";
15
+ import Cmd13 from "./structure/refresh";
13
16
 
14
17
  export const COMMANDS = {
15
18
  "build-meta": Cmd0,
@@ -20,6 +23,9 @@ export const COMMANDS = {
20
23
  publish: Cmd6,
21
24
  "refresh-registry": Cmd7,
22
25
  "restore-overview-from-snapshot": Cmd8,
23
- "upload-package-v1": Cmd9,
24
- "update-deps": Cmd10,
26
+ "update-deps": Cmd9,
27
+ "upload-package-v1": Cmd10,
28
+ "structure:check": Cmd11,
29
+ "structure:init": Cmd12,
30
+ "structure:refresh": Cmd13,
25
31
  };
@@ -0,0 +1,51 @@
1
+ import { Command, Flags } from "@oclif/core";
2
+ import path from "node:path";
3
+ import { runStructureForPath, formatChanges } from "../../structure/cli/run-structure";
4
+
5
+ export default class StructureCheck extends Command {
6
+ static override description =
7
+ "Check block(s) against the canonical structure (read-only). Exits non-zero if any diff would land — used as the CI gate.";
8
+
9
+ static override examples = [
10
+ "<%= config.bin %> <%= command.id %> ./my-block",
11
+ "<%= config.bin %> <%= command.id %> --sdk-internal etc/blocks/*",
12
+ ];
13
+
14
+ // Variadic positional block paths (shell-expanded globs).
15
+ static override strict = false;
16
+
17
+ static override flags = {
18
+ "sdk-internal": Flags.boolean({
19
+ description: "block(s) live inside the SDK monorepo; skip root-scope rules",
20
+ }),
21
+ };
22
+
23
+ public async run(): Promise<void> {
24
+ const { argv, flags } = await this.parse(StructureCheck);
25
+ const paths = (argv as string[]).length > 0 ? (argv as string[]) : ["."];
26
+ const templatesRoot = path.join(this.config.root, "src", "structure", "templates");
27
+
28
+ let anyChanged = false;
29
+ for (const p of paths) {
30
+ const res = await runStructureForPath({
31
+ blockPath: p,
32
+ isSdkInternal: flags["sdk-internal"],
33
+ updateDepsOnly: false,
34
+ mode: "check",
35
+ templatesRoot,
36
+ log: (m) => this.log(m),
37
+ });
38
+ this.log(formatChanges(p, res.changes));
39
+ if (res.changes.length > 0) anyChanged = true;
40
+ }
41
+
42
+ if (anyChanged) {
43
+ this.error(
44
+ "Structure check failed: one or more blocks are out of date. Run `structure refresh`.",
45
+ {
46
+ exit: 1,
47
+ },
48
+ );
49
+ }
50
+ }
51
+ }