@platforma-sdk/block-tools 2.10.14 → 2.10.16

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,77 @@
1
+ // Catalog version-resolution + seeding rules. The single `onInitOrUpdate(...)`
2
+ // frame here resolves the SDK catalog families to npm latest AND seeds any
3
+ // missing standard catalog keys (SDK families + the curated infra floor +
4
+ // the python runenv pin). Its leaves fire on `init` AND
5
+ // `refresh --update-deps-only` (mode "init" + "updateDeps") — both mean
6
+ // "write current versions" — and are skipped on a default refresh/check,
7
+ // which leaves catalog versions exactly as the author/lockfile has them.
8
+ //
9
+ // The network value lives in `ensureCatalogLatest`, which reads prefetched
10
+ // "latest" versions from the runner's `registryLookup` (the CLI / init
11
+ // constructor prefetches the SDK catalog membership list before the run;
12
+ // unit tests pass a mock). Seeding the SDK keys from the membership list
13
+ // (not the on-disk catalog) is what fixes the install failures: a
14
+ // migrated block that never carried `@milaboratories/ts-builder` etc. now
15
+ // gets them ADDED on `--update-deps-only`, no manual catalog seed required.
16
+ //
17
+ // Wrapped in the same root-scope `when(!isSdkInternal)` guard as the rest of
18
+ // the root rules — internal blocks get their catalog from the parent
19
+ // monorepo, so catalog resolution never applies there.
20
+
21
+ import {
22
+ onInitOrUpdate,
23
+ scope,
24
+ when,
25
+ managed,
26
+ generate,
27
+ ensureCatalogLatest,
28
+ ensureCatalogVersion,
29
+ } from "../engine/api";
30
+ import { getActiveRunContext } from "../engine/builders";
31
+ import {
32
+ rootPnpmWorkspaceInitial,
33
+ SDK_CATALOG_PACKAGES,
34
+ INFRA_CATALOG_FLOOR,
35
+ RUNENV_PYTHON,
36
+ RUNENV_PYTHON_VERSION,
37
+ } from "./root-pnpm-workspace";
38
+
39
+ export function rootCatalogBumpRules(): void {
40
+ onInitOrUpdate(() => {
41
+ scope("root", () => {
42
+ when(
43
+ ({ ctx }) => !ctx.isSdkInternal,
44
+ () => {
45
+ // On init, the default root rules create pnpm-workspace.yaml from
46
+ // this same generator (the curated SDK placeholders + infra
47
+ // floor); this leaf then overwrites the SDK entries with npm
48
+ // latest (the floor entries are add-if-absent no-ops). On
49
+ // update-deps the file already exists, so `initial` is not
50
+ // exercised — but a ContentForm is required, so reuse the
51
+ // generator.
52
+ managed(
53
+ "pnpm-workspace.yaml",
54
+ generate(() => rootPnpmWorkspaceInitial(getActiveRunContext())),
55
+ () => {
56
+ // SDK families → npm latest, ADD-IF-ABSENT (seeds a migrated
57
+ // block's missing standard keys + overwrites the init
58
+ // placeholder).
59
+ for (const name of SDK_CATALOG_PACKAGES) ensureCatalogLatest(name);
60
+ // Curated infra floor → fixed version, ADD-IF-ABSENT (seeds
61
+ // shx/turbo/vitest/vue/changesets when missing; never touches a
62
+ // version the block already pins).
63
+ for (const [name, version] of Object.entries(INFRA_CATALOG_FLOOR)) {
64
+ ensureCatalogVersion(name, version);
65
+ }
66
+ // The python runenv pin is relevant only to software-bearing
67
+ // blocks; seed it add-if-absent when one is present.
68
+ if (getActiveRunContext().modules.some((m) => m.scope === "software")) {
69
+ ensureCatalogVersion(RUNENV_PYTHON, RUNENV_PYTHON_VERSION);
70
+ }
71
+ },
72
+ );
73
+ },
74
+ );
75
+ });
76
+ });
77
+ }
@@ -0,0 +1,27 @@
1
+ // Root `.gitignore` content rules.
2
+ // The static template under templates/static/root/.gitignore carries the
3
+ // canonical line set; the body re-asserts the same set via
4
+ // `ensureGitignoreEntries` so any future edit that drops a required line
5
+ // is restored.
6
+
7
+ import { ensureGitignoreEntries } from "../engine/api";
8
+
9
+ export const GITIGNORE_REQUIRED_LINES = [
10
+ ".test_auth.json",
11
+ "node_modules/",
12
+ "dist/",
13
+ "block-pack/",
14
+ "dev/",
15
+ "work/",
16
+ "log/",
17
+ ".turbo",
18
+ "vite.config.*.timestamp-*",
19
+ "software/**/*.tgz",
20
+ ".DS_Store",
21
+ ".vscode/sftp.json",
22
+ "test-dry-run.json",
23
+ ];
24
+
25
+ export function rootGitignoreRules(): void {
26
+ ensureGitignoreEntries(GITIGNORE_REQUIRED_LINES);
27
+ }
@@ -0,0 +1,119 @@
1
+ // Root `package.json`: the initial generator and the drift-correcting body
2
+ // rules, co-located. The body re-asserts the canonical scripts, devDeps,
3
+ // peerDeps, and the final field-order projection (enforceFieldOrder is the
4
+ // LAST call).
5
+
6
+ import {
7
+ ensureField,
8
+ ensureScript,
9
+ ensureDevDeps,
10
+ ensurePeerDeps,
11
+ removeField,
12
+ pruneKeysMatching,
13
+ enforceAlphabeticalOrder,
14
+ enforceFieldOrder,
15
+ type RunContext,
16
+ } from "../engine/api";
17
+ import { canonicalPackageJsonOrder } from "./shared/key-order";
18
+
19
+ export function rootPackageJsonInitial(_ctx: RunContext): Record<string, unknown> {
20
+ // No `name`: the root is never published. Body rules re-assert
21
+ // (removeField("name")) as a drift-corrector.
22
+ return {
23
+ version: "1.0.0",
24
+ private: true,
25
+ scripts: {
26
+ fmt: "turbo run fmt",
27
+ check: "turbo run check",
28
+ build: "turbo run build",
29
+ "build:dev": "env PL_PKG_DEV=local turbo run build",
30
+ test: "env PL_PKG_DEV=local turbo run test --concurrency 1",
31
+ "test:dry-run": "env PL_PKG_DEV=local turbo run test --dry-run=json",
32
+ "mark-stable": "turbo run mark-stable",
33
+ "do-pack": "turbo run do-pack",
34
+ watch: "turbo watch build",
35
+ changeset: "changeset",
36
+ "version-packages": "changeset version",
37
+ // Deprecated in favour of `update` (the full refresh → install →
38
+ // refresh → format flow); kept for compatibility.
39
+ "update-sdk": "block-tools structure refresh --update-deps-only",
40
+ update:
41
+ "block-tools structure refresh --update-deps-only && pnpm i && block-tools structure refresh && pnpm fmt",
42
+ },
43
+ peerDependencies: {
44
+ oxlint: "*",
45
+ oxfmt: "*",
46
+ },
47
+ devDependencies: {
48
+ "@changesets/cli": "catalog:",
49
+ "@milaboratories/ts-builder": "sdk:",
50
+ "@platforma-sdk/block-tools": "sdk:",
51
+ shx: "catalog:",
52
+ turbo: "catalog:",
53
+ },
54
+ packageManager: "pnpm@9.12.0",
55
+ };
56
+ }
57
+
58
+ export function rootPackageJsonRules(): void {
59
+ ensureField("packageManager", "pnpm@9.12.0");
60
+
61
+ // Root is never published — it carries no `name`; delete any stray one.
62
+ removeField("name");
63
+
64
+ // Canonical lifecycle script set. `test` / `test:dry-run` carry the
65
+ // PL_PKG_DEV wrapper; the live backend's env reaches the integration
66
+ // tests via the turbo `test` task's passThroughEnv (no --env-mode=loose
67
+ // needed).
68
+ ensureScript("fmt", "turbo run fmt");
69
+ ensureScript("check", "turbo run check");
70
+ ensureScript("build", "turbo run build");
71
+ ensureScript("build:dev", "env PL_PKG_DEV=local turbo run build");
72
+ ensureScript("test", "env PL_PKG_DEV=local turbo run test --concurrency 1");
73
+ ensureScript("test:dry-run", "env PL_PKG_DEV=local turbo run test --dry-run=json");
74
+ ensureScript("mark-stable", "turbo run mark-stable");
75
+ ensureScript("do-pack", "turbo run do-pack");
76
+ ensureScript("watch", "turbo watch build");
77
+ ensureScript("changeset", "changeset");
78
+ ensureScript("version-packages", "changeset version");
79
+ // Deprecated in favour of `update`; kept for compatibility.
80
+ ensureScript("update-sdk", "block-tools structure refresh --update-deps-only");
81
+ // Full SDK-update flow: bump catalog (deps-only) → install → re-apply
82
+ // structure against the freshly-pulled deps → format the result, wrapped
83
+ // as one script. `pnpm fmt` (turbo run fmt) leaves the tree oxfmt-clean
84
+ // after the structural rewrite so a follow-up `pnpm check` passes.
85
+ ensureScript(
86
+ "update",
87
+ "block-tools structure refresh --update-deps-only && pnpm i && block-tools structure refresh && pnpm fmt",
88
+ );
89
+
90
+ ensureDevDeps({
91
+ "@changesets/cli": "catalog:",
92
+ "@milaboratories/ts-builder": "sdk:",
93
+ "@platforma-sdk/block-tools": "sdk:",
94
+ shx: "catalog:",
95
+ turbo: "catalog:",
96
+ });
97
+
98
+ ensurePeerDeps({
99
+ oxlint: "*",
100
+ oxfmt: "*",
101
+ });
102
+
103
+ // Strip committed dev-machine override clutter: keys like "//pnpm",
104
+ // "///pnpm", "--" carrying a pnpm/overrides sub-object (often absolute
105
+ // local paths). Left in place these are unknown top-level keys that oxfmt
106
+ // shoves past the canonical sections, breaking `oxfmt --check`.
107
+ pruneKeysMatching((key, value): boolean => {
108
+ if (!/^(\/\/|--)/.test(key)) return false;
109
+ if (typeof value !== "object" || value === null) return false;
110
+ const v = value as { pnpm?: unknown; overrides?: unknown };
111
+ return v.pnpm !== undefined || v.overrides !== undefined;
112
+ });
113
+
114
+ enforceAlphabeticalOrder("dependencies");
115
+ enforceAlphabeticalOrder("devDependencies");
116
+ enforceAlphabeticalOrder("peerDependencies");
117
+ enforceAlphabeticalOrder("optionalDependencies");
118
+ enforceFieldOrder([...canonicalPackageJsonOrder]);
119
+ }
@@ -0,0 +1,103 @@
1
+ // Root `pnpm-workspace.yaml`: the catalog membership, the initial generator,
2
+ // and the drift-correcting body rules, co-located.
3
+ //
4
+ // The catalog is split in two:
5
+ // - SDK packages: `SDK_CATALOG_PACKAGES` is a list of package NAMES (catalog
6
+ // membership only — no versions). On init the `onInitOrUpdate` frame fetches
7
+ // npm latest for each (init requires network) and writes it; the keys must
8
+ // exist in the catalog first for that in-place bump to land, so the initial
9
+ // generator seeds them with an obviously-invalid placeholder that the bump
10
+ // always overwrites. No version is stored here and there is no fallback.
11
+ // - infra / tooling: a `~`-floored set we control deliberately. No rule
12
+ // ever fetches these — refresh leaves them untouched and update-deps
13
+ // does not match them. Bump the floor here when authoring the scaffold.
14
+ //
15
+ // Default refresh is purely structural: it verifies the workspace
16
+ // `packages:` list matches the discovered modules and leaves every catalog
17
+ // version exactly as the author/lockfile has it. Version resolution (writing
18
+ // npm-latest for the SDK families) lives in the separate `onInitOrUpdate`
19
+ // frame (`rootCatalogBumpRules`), which fires on init + update-deps only.
20
+
21
+ import { ensureWorkspaceModulePaths, type RunContext } from "../engine/api";
22
+ import { matchesBumpPattern } from "../engine/registry-client";
23
+
24
+ /** SDK packages that live in the catalog (membership only — no versions).
25
+ * Init fetches npm latest for each; refresh leaves them as the lockfile has
26
+ * them. */
27
+ export const SDK_CATALOG_PACKAGES: readonly string[] = [
28
+ "@platforma-sdk/model",
29
+ "@platforma-sdk/ui-vue",
30
+ "@platforma-sdk/workflow-tengo",
31
+ "@platforma-sdk/test",
32
+ "@platforma-sdk/block-tools",
33
+ "@platforma-sdk/tengo-builder",
34
+ "@platforma-sdk/package-builder",
35
+ "@milaboratories/ts-builder",
36
+ "@milaboratories/ts-configs",
37
+ ];
38
+
39
+ /** Init-only catalog seed for every SDK package: an obviously-invalid version
40
+ * the `onInitOrUpdate` bump always overwrites with npm latest. It exists only
41
+ * so the catalog key is present for that in-place bump. It is never a
42
+ * fallback — a real init requires network and overwrites it; were it ever to
43
+ * reach a lockfile, `pnpm install` fails loudly on the unresolvable version. */
44
+ const SDK_CATALOG_INIT_PLACEHOLDER = "0.0.0-init-must-fetch";
45
+
46
+ // Invariant: every SDK package must be fetched on init (match a bump pattern),
47
+ // or its placeholder would ship un-resolved. Caught at load time.
48
+ for (const name of SDK_CATALOG_PACKAGES) {
49
+ if (!matchesBumpPattern(name)) {
50
+ throw new Error(
51
+ `SDK_CATALOG_PACKAGES entry '${name}' matches no catalog bump pattern; ` +
52
+ `its placeholder would never be resolved to npm latest on init. ` +
53
+ `Add it to CATALOG_BUMP_PATTERNS or move it to the infra floor.`,
54
+ );
55
+ }
56
+ }
57
+
58
+ /** Infra / tooling — a `~`-floored curated set. Not exact-pinned on refresh
59
+ * and not bumped by update-deps. */
60
+ export const INFRA_CATALOG_FLOOR: Record<string, string> = {
61
+ turbo: "~2.8.11",
62
+ shx: "~0.4.0",
63
+ "@changesets/cli": "~2.29.8",
64
+ vitest: "~4.0.18",
65
+ // The seeded ui depends on `vue` (catalog:). `@platforma-sdk/ui-vue` pins
66
+ // vue as an EXACT regular dependency (not a peer), so the block must pin the
67
+ // SAME exact version — a floated `~` lets the ui resolve a newer 3.5.x than
68
+ // ui-vue's, yielding two vue instances and a SdkPluginV3-vs-Plugin type
69
+ // clash in `main.ts`. Keep this in lockstep with ui-vue's vue pin.
70
+ vue: "3.5.24",
71
+ };
72
+
73
+ /** Catalog name of the python runenv. Added to a block's catalog only when
74
+ * the block carries a software module. Pinned to an EXACT version — updating
75
+ * the python runenv is an explicit author decision, never automatic. */
76
+ export const RUNENV_PYTHON = "@platforma-open/milaboratories.runenv-python-3";
77
+ export const RUNENV_PYTHON_VERSION = "1.8.2";
78
+
79
+ export function rootPnpmWorkspaceInitial(ctx: RunContext): Record<string, unknown> {
80
+ // Non-root module paths only. The block root ("") is discovered
81
+ // implicitly and must never be written as a "." entry — listing it breaks
82
+ // turbo's task graph. Root stays in ctx.modules for rule fan-out.
83
+ const paths = ctx.modules
84
+ .filter((m) => m.path !== "")
85
+ .map((m) => m.path)
86
+ .sort();
87
+ // SDK packages first (placeholder, overwritten by the init bump), then the
88
+ // infra floor — matching the order the catalog is written.
89
+ const catalog: Record<string, string> = {};
90
+ for (const name of SDK_CATALOG_PACKAGES) catalog[name] = SDK_CATALOG_INIT_PLACEHOLDER;
91
+ Object.assign(catalog, INFRA_CATALOG_FLOOR);
92
+ if (ctx.modules.some((m) => m.scope === "software")) {
93
+ catalog[RUNENV_PYTHON] = RUNENV_PYTHON_VERSION;
94
+ }
95
+ return {
96
+ packages: paths,
97
+ catalog,
98
+ };
99
+ }
100
+
101
+ export function rootPnpmWorkspaceRules(): void {
102
+ ensureWorkspaceModulePaths();
103
+ }
@@ -0,0 +1,43 @@
1
+ // Root-scope rules — apply only to the standalone block layout (the
2
+ // pnpm workspace + tooling around the block). `--sdk-internal` blocks
3
+ // skip this whole scope: the parent monorepo owns root files. Hence the
4
+ // `when(({ ctx }) => !ctx.isSdkInternal, ...)` wrapper.
5
+
6
+ import { scope, when, fixed, managed, file, generate } from "../engine/api";
7
+ import { getActiveRunContext } from "../engine/builders";
8
+ import { rootPackageJsonInitial, rootPackageJsonRules } from "./root-package-json";
9
+ import { rootPnpmWorkspaceInitial, rootPnpmWorkspaceRules } from "./root-pnpm-workspace";
10
+ import { rootGitignoreRules } from "./root-gitignore";
11
+
12
+ export function rootRules(): void {
13
+ scope("root", () => {
14
+ when(
15
+ ({ ctx }) => !ctx.isSdkInternal,
16
+ () => {
17
+ // see templates/static/root/turbo.json — verbatim canonical content.
18
+ fixed("turbo.json", file("root/turbo.json"));
19
+ fixed(".vscode/settings.json", file("root/.vscode/settings.json"));
20
+
21
+ managed(
22
+ "package.json",
23
+ generate(() => rootPackageJsonInitial(getActiveRunContext())),
24
+ () => {
25
+ rootPackageJsonRules();
26
+ },
27
+ );
28
+
29
+ managed(
30
+ "pnpm-workspace.yaml",
31
+ generate(() => rootPnpmWorkspaceInitial(getActiveRunContext())),
32
+ () => {
33
+ rootPnpmWorkspaceRules();
34
+ },
35
+ );
36
+
37
+ managed(".gitignore", file("root/.gitignore"), () => {
38
+ rootGitignoreRules();
39
+ });
40
+ },
41
+ );
42
+ });
43
+ }
@@ -0,0 +1,11 @@
1
+ // Shared definition for co-located unit-test handling.
2
+ //
3
+ // A block's model / ui / workflow scope MAY carry co-located vitest unit
4
+ // tests (`src/**/*.test.ts`, including nested `src/test/*.test.ts`). The
5
+ // structurer wires the vitest `test` script and (for ui/model) node ambient
6
+ // types into a scope ONLY when such files are present — so a test-less scope
7
+ // stays free of a `test` script it would never run, and a freshly-init'd
8
+ // (test-less) block remains a refresh fixpoint.
9
+
10
+ /** Glob (module-relative) for co-located unit tests in a scope's `src/`. */
11
+ export const COLOCATED_TEST_GLOB = "src/**/*.test.ts";
@@ -0,0 +1,34 @@
1
+ // Canonical top-level key order for `package.json` files.
2
+ // Used by `enforceFieldOrder(canonicalPackageJsonOrder)` at the end of
3
+ // every `*-package-json.ts` body.
4
+ // Per-block extensions: rules call
5
+ // `enforceFieldOrder([...canonicalPackageJsonOrder, "customKey"])`.
6
+ //
7
+ // This order is derived EMPIRICALLY from oxfmt (the canonical formatter run
8
+ // by `ts-builder check`): a freshly-refreshed package.json must pass
9
+ // `oxfmt --check` so `pnpm check` is green without a prior `pnpm fmt`. oxfmt
10
+ // also alphabetises the dependency sections — the `*-package-json.ts` bodies
11
+ // call `enforceAlphabeticalOrder` on each dep section to match (scripts and
12
+ // `exports` keep source order; oxfmt does not reorder them).
13
+ export const canonicalPackageJsonOrder = [
14
+ "name",
15
+ "version",
16
+ "private",
17
+ "description",
18
+ "keywords",
19
+ "files",
20
+ "type",
21
+ "main",
22
+ "module",
23
+ "types",
24
+ "exports",
25
+ "scripts",
26
+ "dependencies",
27
+ "devDependencies",
28
+ "peerDependencies",
29
+ "optionalDependencies",
30
+ "packageManager",
31
+ "pnpm",
32
+ "block",
33
+ "block-software",
34
+ ] as const;
@@ -0,0 +1,86 @@
1
+ // Software `package.json`: the initial generator and the drift-correcting
2
+ // body rules, co-located. One body, N modules — fan-out is driven by
3
+ // `ctx.modules`.
4
+ //
5
+ // Drift-correctors only: identity (name/version), `description`, and the
6
+ // `block-software` descriptor are author-owned / opaque and left untouched
7
+ // — re-asserting them would clobber real multi-software blocks on refresh.
8
+ // The body enforces the structural essentials: ESM type, the publish
9
+ // `files` glob, the lifecycle scripts, and the canonical devDeps.
10
+
11
+ import {
12
+ ensureField,
13
+ ensureScript,
14
+ ensureDevDeps,
15
+ enforceAlphabeticalOrder,
16
+ enforceFieldOrder,
17
+ type RunContext,
18
+ } from "../engine/api";
19
+ import { canonicalPackageJsonOrder } from "./shared/key-order";
20
+
21
+ /** The `block-software` package-builder descriptor for a python
22
+ * entrypoint. Kept opaque by the engine; rule authors edit it via
23
+ * `transformAt("block-software", ...)`. The `:3.12.10` tag inside
24
+ * `environment` is the runenv artifact tag (python version), distinct
25
+ * from the npm catalog version. */
26
+ function pythonBlockSoftware(): Record<string, unknown> {
27
+ return {
28
+ entrypoints: {
29
+ main: {
30
+ binary: {
31
+ artifact: {
32
+ type: "python",
33
+ registry: "platforma-open",
34
+ environment: "@platforma-open/milaboratories.runenv-python-3:3.12.10",
35
+ dependencies: { toolset: "pip", requirements: "requirements.txt" },
36
+ root: "./src",
37
+ },
38
+ cmd: ["python", "{pkg}/main.py"],
39
+ },
40
+ },
41
+ },
42
+ };
43
+ }
44
+
45
+ export function softwarePackageJsonInitial(ctx: RunContext): Record<string, unknown> {
46
+ const v = ctx.blockVars;
47
+ return {
48
+ name: `${v.facadeName}.software`,
49
+ version: "1.0.0",
50
+ type: "module",
51
+ description: "Block Software",
52
+ files: ["./dist/**/*"],
53
+ scripts: {
54
+ build: "pl-pkg build",
55
+ prepublishOnly: "pl-pkg prepublish",
56
+ changeset: "changeset",
57
+ "version-packages": "changeset version",
58
+ },
59
+ "block-software": pythonBlockSoftware(),
60
+ devDependencies: {
61
+ "@platforma-open/milaboratories.runenv-python-3": "catalog:",
62
+ "@platforma-sdk/package-builder": "sdk:",
63
+ },
64
+ };
65
+ }
66
+
67
+ export function softwarePackageJsonRules(): void {
68
+ ensureField("type", "module");
69
+ ensureField("files", ["./dist/**/*"]);
70
+
71
+ ensureScript("build", "pl-pkg build");
72
+ ensureScript("prepublishOnly", "pl-pkg prepublish");
73
+ ensureScript("changeset", "changeset");
74
+ ensureScript("version-packages", "changeset version");
75
+
76
+ ensureDevDeps({
77
+ "@platforma-open/milaboratories.runenv-python-3": "catalog:",
78
+ "@platforma-sdk/package-builder": "sdk:",
79
+ });
80
+
81
+ enforceAlphabeticalOrder("dependencies");
82
+ enforceAlphabeticalOrder("devDependencies");
83
+ enforceAlphabeticalOrder("peerDependencies");
84
+ enforceAlphabeticalOrder("optionalDependencies");
85
+ enforceFieldOrder([...canonicalPackageJsonOrder]);
86
+ }
@@ -0,0 +1,33 @@
1
+ // Software-scope rules. One scope body, N modules — the engine fans out
2
+ // once per discovered software module. Zero software modules → entire
3
+ // scope body no-ops.
4
+ //
5
+ // Seeds (`software/src/main.py` + `requirements.txt`) are written once by
6
+ // `init` and never touched again — the block author owns them.
7
+
8
+ import { scope, managed, seed, generate, text } from "../engine/api";
9
+ import { getActiveRunContext } from "../engine/builders";
10
+ import { softwarePackageJsonInitial, softwarePackageJsonRules } from "./software-package-json";
11
+
12
+ const MAIN_PY_SEED = `import sys
13
+
14
+ print("Hello from Python, " + sys.argv[1] + "!")
15
+ `;
16
+
17
+ const REQUIREMENTS_SEED = `# No dependencies specified for this script
18
+ `;
19
+
20
+ export function softwareRules(): void {
21
+ scope("software", () => {
22
+ seed("src/main.py", text(MAIN_PY_SEED));
23
+ seed("src/requirements.txt", text(REQUIREMENTS_SEED));
24
+
25
+ managed(
26
+ "package.json",
27
+ generate(() => softwarePackageJsonInitial(getActiveRunContext())),
28
+ () => {
29
+ softwarePackageJsonRules();
30
+ },
31
+ );
32
+ });
33
+ }
@@ -0,0 +1,70 @@
1
+ // Test `package.json`: the initial generator and the drift-correcting body
2
+ // rules, co-located.
3
+
4
+ import {
5
+ ensureField,
6
+ ensureScript,
7
+ ensureDevDeps,
8
+ ensurePeerDeps,
9
+ removeDep,
10
+ enforceAlphabeticalOrder,
11
+ enforceFieldOrder,
12
+ type RunContext,
13
+ } from "../engine/api";
14
+ import { canonicalPackageJsonOrder } from "./shared/key-order";
15
+
16
+ export function testPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
17
+ const v = ctx.blockVars;
18
+ return {
19
+ name: `${v.facadeName}.test`,
20
+ version: "1.0.0",
21
+ private: true,
22
+ type: "module",
23
+ scripts: {
24
+ // Type-only check (no oxlint/oxfmt — the test scope ships no lint/fmt
25
+ // config). Slots into the turbo `check` task.
26
+ check: "ts-builder type-check --target block-test",
27
+ test: "vitest run --passWithNoTests",
28
+ },
29
+ peerDependencies: {
30
+ typescript: "*",
31
+ },
32
+ devDependencies: {
33
+ "@platforma-sdk/test": "sdk:",
34
+ "@milaboratories/ts-builder": "sdk:",
35
+ "@milaboratories/ts-configs": "sdk:",
36
+ vitest: "catalog:",
37
+ },
38
+ };
39
+ }
40
+
41
+ export function testPackageJsonRules(): void {
42
+ ensureField("type", "module");
43
+
44
+ // Type-only check (no oxlint/oxfmt — the test scope ships no lint/fmt
45
+ // config); slots into the turbo `check` task.
46
+ ensureScript("check", "ts-builder type-check --target block-test");
47
+ ensureScript("test", "vitest run --passWithNoTests");
48
+
49
+ // @platforma-sdk/test is a test-time dep → devDependencies (matches prod).
50
+ ensureDevDeps({
51
+ "@platforma-sdk/test": "sdk:",
52
+ "@milaboratories/ts-builder": "sdk:",
53
+ "@milaboratories/ts-configs": "sdk:",
54
+ vitest: "catalog:",
55
+ });
56
+
57
+ // The test tsconfig (@milaboratories/ts-configs/block/test) supplies the
58
+ // ambient types; only the `typescript` peer is needed. Drop any stray
59
+ // `@types/node` peer a legacy block declares.
60
+ ensurePeerDeps({
61
+ typescript: "*",
62
+ });
63
+ removeDep("@types/node");
64
+
65
+ enforceAlphabeticalOrder("dependencies");
66
+ enforceAlphabeticalOrder("devDependencies");
67
+ enforceAlphabeticalOrder("peerDependencies");
68
+ enforceAlphabeticalOrder("optionalDependencies");
69
+ enforceFieldOrder([...canonicalPackageJsonOrder]);
70
+ }
@@ -0,0 +1,40 @@
1
+ // Test-scope rules.
2
+
3
+ import { scope, fixed, managed, scaffold, seed, file, text, generate } from "../engine/api";
4
+ import { getActiveRunContext } from "../engine/builders";
5
+ import { testPackageJsonInitial, testPackageJsonRules } from "./test-package-json";
6
+
7
+ export function testRules(): void {
8
+ scope("test", () => {
9
+ // tsconfig carries `files: []` alongside `include: ["src/**/*"]`: tsc
10
+ // errors TS18003 when an `include` matches nothing, so an empty `files`
11
+ // lets the check pass on a test-less block while `include` still
12
+ // type-checks real tests once present.
13
+ fixed("tsconfig.json", file("test/tsconfig.json"));
14
+ // scaffold (not fixed): integration tests legitimately tune timeout /
15
+ // retry per block. Write the canonical default if absent; never
16
+ // overwrite an author's config.
17
+ scaffold("vitest.config.mts", file("test/vitest.config.mts"));
18
+
19
+ // A trivial, backend-free demo test so a freshly-init'd block ships a
20
+ // runnable suite (`pnpm test` green out of the box) rather than an empty
21
+ // scope. Author-owned seed: written once at init, untouched on refresh.
22
+ seed(
23
+ "src/demo.test.ts",
24
+ text(
25
+ `import { expect, test } from "vitest";\n\n` +
26
+ `test("scaffolded block sanity check", () => {\n` +
27
+ ` expect(true).toBe(true);\n` +
28
+ `});\n`,
29
+ ),
30
+ );
31
+
32
+ managed(
33
+ "package.json",
34
+ generate(() => testPackageJsonInitial(getActiveRunContext())),
35
+ () => {
36
+ testPackageJsonRules();
37
+ },
38
+ );
39
+ });
40
+ }