@platforma-sdk/block-tools 2.10.17 → 2.10.19

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 (36) hide show
  1. package/dist/cli.js +13 -13
  2. package/dist/cli.js.map +1 -1
  3. package/dist/cli.mjs +674 -615
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/structure/engine/api.d.ts +1 -1
  6. package/dist/structure/engine/content-rules.d.ts +17 -0
  7. package/dist/structure/engine/registry-client.d.ts +30 -2
  8. package/dist/structure/engine/runner.d.ts +6 -0
  9. package/dist/structure/rules/root-pnpm-workspace.d.ts +13 -0
  10. package/dist/structure/rules/shared/retired-deps.d.ts +7 -0
  11. package/dist/v2/registry/schema_public.d.ts +6177 -6177
  12. package/package.json +8 -8
  13. package/src/structure/CLAUDE.md +28 -0
  14. package/src/structure/__tests__/model-package-json.snapshot.test.ts +1 -2
  15. package/src/structure/cli/run-structure.ts +12 -2
  16. package/src/structure/engine/__tests__/content-rules/pin-catalog-to-dependency-of.test.ts +65 -0
  17. package/src/structure/engine/__tests__/derived-pin-lookup.test.ts +54 -0
  18. package/src/structure/engine/api.ts +1 -0
  19. package/src/structure/engine/content-rules.ts +29 -0
  20. package/src/structure/engine/registry-client.ts +116 -2
  21. package/src/structure/engine/runner.ts +18 -2
  22. package/src/structure/init-block-constructor.ts +16 -3
  23. package/src/structure/rules/migrations.ts +15 -16
  24. package/src/structure/rules/model-package-json.ts +34 -16
  25. package/src/structure/rules/root-catalog-bump.ts +12 -1
  26. package/src/structure/rules/root-package-json.ts +15 -11
  27. package/src/structure/rules/root-pnpm-workspace.ts +21 -8
  28. package/src/structure/rules/root.ts +0 -1
  29. package/src/structure/rules/shared/retired-deps.ts +42 -0
  30. package/src/structure/rules/test-package-json.ts +12 -9
  31. package/src/structure/rules/test.ts +4 -0
  32. package/src/structure/rules/ui-package-json.ts +33 -25
  33. package/src/structure/rules/workflow-package-json.ts +32 -14
  34. package/src/structure/rules/workflow.ts +32 -8
  35. package/src/structure/templates/static/test/.oxfmtrc.json +4 -0
  36. package/src/structure/templates/static/test/.oxlintrc.json +3 -0
@@ -15,6 +15,9 @@ import {
15
15
  ensureDep,
16
16
  ensureDevDep,
17
17
  ensureDevDeps,
18
+ removeDep,
19
+ removeScript,
20
+ removeField,
18
21
  ensurePeerDeps,
19
22
  enforceAlphabeticalOrder,
20
23
  enforceFieldOrder,
@@ -24,6 +27,7 @@ import {
24
27
  } from "../engine/api";
25
28
  import { canonicalPackageJsonOrder } from "./shared/key-order";
26
29
  import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
30
+ import { removeRetiredToolchainDeps } from "./shared/retired-deps";
27
31
 
28
32
  export function modelPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
29
33
  const v = ctx.blockVars;
@@ -49,8 +53,6 @@ export function modelPackageJsonInitial(ctx: RunContext): Record<string, unknown
49
53
  watch: "ts-builder build --target block-model --watch",
50
54
  build: "ts-builder build --target block-model && block-tools build-model",
51
55
  check: "ts-builder check --target block-model",
52
- // No `test`: the vitest `test` script is wired by the body rule ONLY
53
- // when co-located test files exist (a freshly-init'd model has none).
54
56
  },
55
57
  dependencies: {
56
58
  "@platforma-sdk/model": "sdk:",
@@ -59,7 +61,6 @@ export function modelPackageJsonInitial(ctx: RunContext): Record<string, unknown
59
61
  "@milaboratories/ts-builder": "sdk:",
60
62
  "@milaboratories/ts-configs": "sdk:",
61
63
  "@platforma-sdk/block-tools": "sdk:",
62
- vitest: "catalog:",
63
64
  },
64
65
  peerDependencies: {
65
66
  "@types/node": "*",
@@ -94,7 +95,6 @@ export function modelPackageJsonRules(): void {
94
95
  "@milaboratories/ts-builder": "sdk:",
95
96
  "@milaboratories/ts-configs": "sdk:",
96
97
  "@platforma-sdk/block-tools": "sdk:",
97
- vitest: "catalog:",
98
98
  });
99
99
 
100
100
  ensurePeerDeps({
@@ -102,18 +102,36 @@ export function modelPackageJsonRules(): void {
102
102
  typescript: "*",
103
103
  });
104
104
 
105
- // The vitest `test` script + node ambient types are wired ONLY when the
106
- // model carries co-located unit tests (`src/**/*.test.ts`). A test-less
107
- // model gets neither — turbo simply has no `test` task for it, and the
108
- // scope stays a refresh fixpoint. When tests ARE present, `@types/node`
109
- // is promoted from peer to a devDep (the single-section invariant drops
110
- // the peer) so the tests' `node:*` imports resolve under the
111
- // `types: ["node"]` that model/tsconfig adds under the same predicate.
112
- // Runs AFTER ensurePeerDeps so the promotion is not reverted.
113
- when(whenFilesExist(COLOCATED_TEST_GLOB), () => {
114
- ensureScript("test", "vitest run --passWithNoTests");
115
- ensureDevDep("@types/node", "*");
116
- });
105
+ // The vitest `test` script, the `vitest` devDep, and node ambient types are
106
+ // wired ONLY when the model carries co-located unit tests (`src/**/*.test.ts`).
107
+ // A test-less model gets none — turbo simply has no `test` task for it, and
108
+ // the scope stays a refresh fixpoint. When tests ARE present, `@types/node`
109
+ // is promoted from peer to a devDep (the single-section invariant drops the
110
+ // peer) so the tests' `node:*` imports resolve under the `types: ["node"]`
111
+ // that model/tsconfig adds under the same predicate. Runs AFTER ensurePeerDeps
112
+ // so the promotion is not reverted.
113
+ //
114
+ // Skipped entirely for sdk-internal (in-monorepo) blocks: those live inside
115
+ // the platforma monorepo's own test infrastructure (build-configs, shared
116
+ // catalog, coverage providers) and own their test wiring — the structurer
117
+ // must not impose or strip standalone-block test conventions on them.
118
+ when(
119
+ ({ ctx }) => !ctx.isSdkInternal,
120
+ () =>
121
+ when(
122
+ whenFilesExist(COLOCATED_TEST_GLOB),
123
+ () => {
124
+ ensureScript("test", "vitest run --passWithNoTests");
125
+ ensureDevDep("vitest", "catalog:");
126
+ ensureDevDep("@types/node", "*");
127
+ },
128
+ () => removeDep("vitest"),
129
+ ),
130
+ );
131
+
132
+ removeRetiredToolchainDeps();
133
+ removeField("tsup");
134
+ removeScript("lint");
117
135
 
118
136
  enforceAlphabeticalOrder("dependencies");
119
137
  enforceAlphabeticalOrder("devDependencies");
@@ -26,12 +26,14 @@ import {
26
26
  generate,
27
27
  ensureCatalogLatest,
28
28
  ensureCatalogVersion,
29
+ pinCatalogToDependencyOf,
29
30
  } from "../engine/api";
30
31
  import { getActiveRunContext } from "../engine/builders";
31
32
  import {
32
33
  rootPnpmWorkspaceInitial,
33
34
  SDK_CATALOG_PACKAGES,
34
35
  INFRA_CATALOG_FLOOR,
36
+ DERIVED_CATALOG_PINS,
35
37
  RUNENV_PYTHON,
36
38
  RUNENV_PYTHON_VERSION,
37
39
  } from "./root-pnpm-workspace";
@@ -57,8 +59,17 @@ export function rootCatalogBumpRules(): void {
57
59
  // block's missing standard keys + overwrites the init
58
60
  // placeholder).
59
61
  for (const name of SDK_CATALOG_PACKAGES) ensureCatalogLatest(name);
62
+ // Derived catalog pins → the EXACT version their source package
63
+ // declares (e.g. `vue` from `@platforma-sdk/ui-vue`). OVERWRITES
64
+ // (seeds when absent, tightens a loose on-disk entry). Resolved
65
+ // by the prefetched `derivedPinLookup`; a no-op when absent
66
+ // (default refresh passes none). Must run AFTER ensureCatalogLatest
67
+ // — the source package's own version is bumped there first.
68
+ for (const pin of DERIVED_CATALOG_PINS) {
69
+ pinCatalogToDependencyOf(pin.entry, { of: pin.of, ofVersion: pin.ofVersion });
70
+ }
60
71
  // Curated infra floor → fixed version, ADD-IF-ABSENT (seeds
61
- // shx/turbo/vitest/vue/changesets when missing; never touches a
72
+ // shx/turbo/vitest/changesets when missing; never touches a
62
73
  // version the block already pins).
63
74
  for (const [name, version] of Object.entries(INFRA_CATALOG_FLOOR)) {
64
75
  ensureCatalogVersion(name, version);
@@ -8,6 +8,7 @@ import {
8
8
  ensureScript,
9
9
  ensureDevDeps,
10
10
  ensurePeerDeps,
11
+ removeScript,
11
12
  removeField,
12
13
  pruneKeysMatching,
13
14
  enforceAlphabeticalOrder,
@@ -34,11 +35,10 @@ export function rootPackageJsonInitial(_ctx: RunContext): Record<string, unknown
34
35
  watch: "turbo watch build",
35
36
  changeset: "changeset",
36
37
  "version-packages": "changeset version",
37
- // Deprecated in favour of `update` (the full refresh → install →
38
- // refresh → format flow); kept for compatibility.
38
+ // Deprecated alias of `upgrade-sdk`; kept for compatibility.
39
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",
40
+ "upgrade-sdk":
41
+ "block-tools structure refresh --update-deps-only && pnpm i && block-tools structure refresh && pnpm i && pnpm fmt",
42
42
  },
43
43
  peerDependencies: {
44
44
  oxlint: "*",
@@ -76,15 +76,19 @@ export function rootPackageJsonRules(): void {
76
76
  ensureScript("watch", "turbo watch build");
77
77
  ensureScript("changeset", "changeset");
78
78
  ensureScript("version-packages", "changeset version");
79
- // Deprecated in favour of `update`; kept for compatibility.
79
+ // `update-sdk` is a deprecated alias of `upgrade-sdk`, kept for compatibility;
80
+ // `update` is the pre-rename name — drop it so refreshed blocks converge.
80
81
  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.
82
+ removeScript("update");
83
+ // Full SDK-upgrade flow: bump catalog (deps-only) → install → re-apply
84
+ // structure against the freshly-pulled deps → install AGAIN (the structural
85
+ // pass can add new devDeps — e.g. ts-builder/oxlint/oxfmt on a first
86
+ // migration — that `pnpm fmt` then needs on PATH) → format. Wrapped as one
87
+ // script. `pnpm fmt` (turbo run fmt) leaves the tree oxfmt-clean after the
88
+ // structural rewrite so a follow-up `pnpm check` passes.
85
89
  ensureScript(
86
- "update",
87
- "block-tools structure refresh --update-deps-only && pnpm i && block-tools structure refresh && pnpm fmt",
90
+ "upgrade-sdk",
91
+ "block-tools structure refresh --update-deps-only && pnpm i && block-tools structure refresh && pnpm i && pnpm fmt",
88
92
  );
89
93
 
90
94
  ensureDevDeps({
@@ -18,8 +18,9 @@
18
18
  // npm-latest for the SDK families) lives in the separate `onInitOrUpdate`
19
19
  // frame (`rootCatalogBumpRules`), which fires on init + update-deps only.
20
20
 
21
- import { ensureWorkspaceModulePaths, type RunContext } from "../engine/api";
22
- import { matchesBumpPattern } from "../engine/registry-client";
21
+ import { ensureWorkspaceModulePaths, ensureCatalogAbsent, type RunContext } from "../engine/api";
22
+ import { matchesBumpPattern, type DerivedCatalogPin } from "../engine/registry-client";
23
+ import { RETIRED_TOOLCHAIN_DEPS } from "./shared/retired-deps";
23
24
 
24
25
  /** SDK packages that live in the catalog (membership only — no versions).
25
26
  * Init fetches npm latest for each; refresh leaves them as the lockfile has
@@ -62,14 +63,23 @@ export const INFRA_CATALOG_FLOOR: Record<string, string> = {
62
63
  shx: "~0.4.0",
63
64
  "@changesets/cli": "~2.29.8",
64
65
  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
66
  };
72
67
 
68
+ /** Catalog entries whose exact version is DERIVED from the version another
69
+ * package declares for them (see `pinCatalogToDependencyOf`). Resolved on
70
+ * init + update-deps and OVERWRITES the on-disk entry.
71
+ *
72
+ * `vue`: the seeded ui depends on `vue` (catalog:). `@platforma-sdk/ui-vue`
73
+ * pins vue as an EXACT regular dependency (not a peer), so the block must
74
+ * pin the SAME exact version — a floated `~` lets the ui resolve a newer
75
+ * 3.5.x than ui-vue's, yielding two vue instances and a SdkPluginV3-vs-Plugin
76
+ * type clash in `main.ts`. Deriving the pin from ui-vue's declared dep keeps
77
+ * the block in lockstep with whatever vue ui-vue's npm-latest pins, with no
78
+ * hand-maintained version here. */
79
+ export const DERIVED_CATALOG_PINS: readonly DerivedCatalogPin[] = [
80
+ { entry: "vue", of: "@platforma-sdk/ui-vue" },
81
+ ];
82
+
73
83
  /** Catalog name of the python runenv. Added to a block's catalog only when
74
84
  * the block carries a software module. Pinned to an EXACT version — updating
75
85
  * the python runenv is an explicit author decision, never automatic. */
@@ -100,4 +110,7 @@ export function rootPnpmWorkspaceInitial(ctx: RunContext): Record<string, unknow
100
110
 
101
111
  export function rootPnpmWorkspaceRules(): void {
102
112
  ensureWorkspaceModulePaths();
113
+ // Catalog side of the retired-dep cleanup — the per-package side sheds the
114
+ // matching `catalog:` references.
115
+ for (const name of RETIRED_TOOLCHAIN_DEPS) ensureCatalogAbsent(name);
103
116
  }
@@ -14,7 +14,6 @@ export function rootRules(): void {
14
14
  when(
15
15
  ({ ctx }) => !ctx.isSdkInternal,
16
16
  () => {
17
- // see templates/static/root/turbo.json — verbatim canonical content.
18
17
  fixed("turbo.json", file("root/turbo.json"));
19
18
  fixed(".vscode/settings.json", file("root/.vscode/settings.json"));
20
19
 
@@ -0,0 +1,42 @@
1
+ // Retired toolchain dependencies — a SINGLE source of truth.
2
+ //
3
+ // These packages are superseded by the canonical toolchain (ts-builder owns
4
+ // the build, vue-tsc, and the vite plugins; oxlint/oxfmt replace eslint) and
5
+ // must be shed from a migrated block in BOTH places at once:
6
+ // - the root catalog → `ensureCatalogAbsent` (root-pnpm-workspace)
7
+ // - every package.json scope → `removeDep` (via removeRetiredToolchainDeps)
8
+ //
9
+ // Removing from only one side is a latent bug: a catalog entry dropped while a
10
+ // package still references `<dep>: "catalog:"` leaves a dangling reference that
11
+ // breaks `pnpm install`. Driving both from this one list makes that impossible
12
+ // — add (or un-retire) a dependency HERE, once.
13
+ //
14
+ // Per-entry rationale:
15
+ // vite / tsup — bundling now goes through ts-builder + rolldown.
16
+ // vue-tsc — ts-builder runs vue-tsc internally for `block-ui`; a
17
+ // direct dep resolves a different version than the
18
+ // toolchain pins, causing type clashes in main.ts.
19
+ // @vitejs/plugin-vue — bundled by ts-builder's internal vite config.
20
+ // vite-plugin-dts — .d.ts emit is handled by ts-builder.
21
+ // @platforma-sdk/eslint-config — eslint is retired; linting runs via oxlint
22
+ // inside `ts-builder check`.
23
+
24
+ import { removeDep } from "../../engine/api";
25
+
26
+ export const RETIRED_TOOLCHAIN_DEPS: readonly string[] = [
27
+ "vite",
28
+ "tsup",
29
+ "vue-tsc",
30
+ "@vitejs/plugin-vue",
31
+ "vite-plugin-dts",
32
+ "@platforma-sdk/eslint-config",
33
+ ];
34
+
35
+ /** Drop every retired toolchain dep from the current package.json scope.
36
+ * Idempotent (a `removeDep` on an absent key is a no-op), so it is safe to
37
+ * call uniformly from every package.json rules body regardless of which deps
38
+ * a given scope ever carried. Pairs with the catalog-side removal in
39
+ * `rootPnpmWorkspaceRules`. */
40
+ export function removeRetiredToolchainDeps(): void {
41
+ for (const name of RETIRED_TOOLCHAIN_DEPS) removeDep(name);
42
+ }
@@ -7,11 +7,13 @@ import {
7
7
  ensureDevDeps,
8
8
  ensurePeerDeps,
9
9
  removeDep,
10
+ removeScript,
10
11
  enforceAlphabeticalOrder,
11
12
  enforceFieldOrder,
12
13
  type RunContext,
13
14
  } from "../engine/api";
14
15
  import { canonicalPackageJsonOrder } from "./shared/key-order";
16
+ import { removeRetiredToolchainDeps } from "./shared/retired-deps";
15
17
 
16
18
  export function testPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
17
19
  const v = ctx.blockVars;
@@ -21,9 +23,9 @@ export function testPackageJsonInitial(ctx: RunContext): Record<string, unknown>
21
23
  private: true,
22
24
  type: "module",
23
25
  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",
26
+ fmt: "ts-builder format",
27
+ // `ts-builder check` runs type-check + lint (oxlint) + fmt-check (oxfmt).
28
+ check: "ts-builder check --target block-test",
27
29
  test: "vitest run --passWithNoTests",
28
30
  },
29
31
  peerDependencies: {
@@ -41,12 +43,13 @@ export function testPackageJsonInitial(ctx: RunContext): Record<string, unknown>
41
43
  export function testPackageJsonRules(): void {
42
44
  ensureField("type", "module");
43
45
 
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");
46
+ ensureScript("fmt", "ts-builder format");
47
+ // `ts-builder check` runs type-check + lint (oxlint) + fmt-check (oxfmt).
48
+ ensureScript("check", "ts-builder check --target block-test");
47
49
  ensureScript("test", "vitest run --passWithNoTests");
50
+ // Linting runs inside `ts-builder check` — no separate `lint` script.
51
+ removeScript("lint");
48
52
 
49
- // @platforma-sdk/test is a test-time dep → devDependencies (matches prod).
50
53
  ensureDevDeps({
51
54
  "@platforma-sdk/test": "sdk:",
52
55
  "@milaboratories/ts-builder": "sdk:",
@@ -55,12 +58,12 @@ export function testPackageJsonRules(): void {
55
58
  });
56
59
 
57
60
  // 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.
61
+ // ambient types, so only the `typescript` peer is needed — no `@types/node`.
60
62
  ensurePeerDeps({
61
63
  typescript: "*",
62
64
  });
63
65
  removeDep("@types/node");
66
+ removeRetiredToolchainDeps();
64
67
 
65
68
  enforceAlphabeticalOrder("dependencies");
66
69
  enforceAlphabeticalOrder("devDependencies");
@@ -11,6 +11,10 @@ export function testRules(): void {
11
11
  // lets the check pass on a test-less block while `include` still
12
12
  // type-checks real tests once present.
13
13
  fixed("tsconfig.json", file("test/tsconfig.json"));
14
+ // Block-local oxlint/oxfmt config — test sources are lint + fmt-checked
15
+ // (the `check` script runs `ts-builder check --target block-test`).
16
+ fixed(".oxlintrc.json", file("test/.oxlintrc.json"));
17
+ fixed(".oxfmtrc.json", file("test/.oxfmtrc.json"));
14
18
  // scaffold (not fixed): integration tests legitimately tune timeout /
15
19
  // retry per block. Write the canonical default if absent; never
16
20
  // overwrite an author's config.
@@ -16,6 +16,7 @@ import {
16
16
  ensurePeerDeps,
17
17
  ensureWorkspaceScopeDeps,
18
18
  removeDep,
19
+ removeScript,
19
20
  enforceAlphabeticalOrder,
20
21
  enforceFieldOrder,
21
22
  when,
@@ -25,6 +26,7 @@ import {
25
26
  import { scopeDepMap } from "../engine/ctx";
26
27
  import { canonicalPackageJsonOrder } from "./shared/key-order";
27
28
  import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
29
+ import { removeRetiredToolchainDeps } from "./shared/retired-deps";
28
30
 
29
31
  export function uiPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
30
32
  const v = ctx.blockVars;
@@ -38,8 +40,6 @@ export function uiPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
38
40
  watch: "ts-builder build --target block-ui --watch",
39
41
  build: "ts-builder build --target block-ui",
40
42
  check: "ts-builder check --target block-ui",
41
- // No `test`: the vitest `test` script is wired by the body rule ONLY
42
- // when co-located test files exist (a freshly-init'd ui has none).
43
43
  },
44
44
  dependencies: {
45
45
  "@platforma-sdk/ui-vue": "sdk:",
@@ -51,9 +51,9 @@ export function uiPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
51
51
  devDependencies: {
52
52
  "@milaboratories/ts-builder": "sdk:",
53
53
  "@milaboratories/ts-configs": "sdk:",
54
- vitest: "catalog:",
55
54
  },
56
55
  peerDependencies: {
56
+ "@types/node": "*",
57
57
  typescript: "*",
58
58
  },
59
59
  };
@@ -77,35 +77,43 @@ export function uiPackageJsonRules(): void {
77
77
  ensureDevDeps({
78
78
  "@milaboratories/ts-builder": "sdk:",
79
79
  "@milaboratories/ts-configs": "sdk:",
80
- vitest: "catalog:",
81
80
  });
82
81
 
83
- // `vue-tsc` is owned by `ts-builder` (which runs it internally for
84
- // `--target block-ui`), so the ui must NOT declare its own. A direct
85
- // `vue-tsc` dep is the vestigial vite/vue-tsc-era artefact: it resolves
86
- // independently of the version ts-builder pins, which is how a block ended
87
- // up type-checking under a different vue-tsc than the toolchain intends.
88
- removeDep("vue-tsc");
82
+ removeRetiredToolchainDeps();
83
+ removeScript("preview");
84
+ removeScript("lint");
89
85
 
90
- // The ui builds with `--target block-ui` (types: []), so by default it
91
- // carries no browser/vitest/node ambient types — only the `typescript`
92
- // peer for IDE type resolution. Drop any stray `@types/node` a legacy
93
- // block declares...
86
+ // `@types/node` is declared as a peer by default — mirrors the model scope
87
+ // (the block author resolves it from the workspace root). The ui builds with
88
+ // `--target block-ui` (types: []), so it pulls no node ambient types into the
89
+ // build; the peer only satisfies IDE / type resolution, alongside `typescript`.
94
90
  ensurePeerDeps({
91
+ "@types/node": "*",
95
92
  typescript: "*",
96
93
  });
97
- removeDep("@types/node");
98
94
 
99
- // ...but a ui WITH co-located unit tests (`src/**/*.test.ts`) needs the
100
- // vitest `test` script AND node ambient types: re-add `@types/node` as a
101
- // devDep so `types: ["node"]` in ui/tsconfig (wired under the same
102
- // predicate) resolves the tests' `node:*` imports. A test-less ui keeps
103
- // the lean default (no test script, no @types/node) and stays a fixpoint.
104
- // Runs AFTER removeDep so the dev dep is not stripped.
105
- when(whenFilesExist(COLOCATED_TEST_GLOB), () => {
106
- ensureScript("test", "vitest run --passWithNoTests");
107
- ensureDevDep("@types/node", "*");
108
- });
95
+ // A ui WITH co-located unit tests (`src/**/*.test.ts`) needs the vitest
96
+ // `test` script, the `vitest` devDep, AND node ambient types promoted from
97
+ // peer to a devDep (the single-section invariant drops the peer) so that
98
+ // `types: ["node"]` in ui/tsconfig — wired under the same predicate —
99
+ // resolves the tests' `node:*` imports. A test-less ui keeps `@types/node`
100
+ // as a peer and carries no vitest, staying a refresh fixpoint.
101
+ //
102
+ // Skipped entirely for sdk-internal (in-monorepo) blocks: they own their test
103
+ // wiring under the monorepo's shared infrastructure — see model-package-json.
104
+ when(
105
+ ({ ctx }) => !ctx.isSdkInternal,
106
+ () =>
107
+ when(
108
+ whenFilesExist(COLOCATED_TEST_GLOB),
109
+ () => {
110
+ ensureScript("test", "vitest run --passWithNoTests");
111
+ ensureDevDep("vitest", "catalog:");
112
+ ensureDevDep("@types/node", "*");
113
+ },
114
+ () => removeDep("vitest"),
115
+ ),
116
+ );
109
117
 
110
118
  enforceAlphabeticalOrder("dependencies");
111
119
  enforceAlphabeticalOrder("devDependencies");
@@ -8,6 +8,7 @@ import {
8
8
  ensureScript,
9
9
  ensureDep,
10
10
  ensureDevDeps,
11
+ removeDep,
11
12
  ensureWorkspaceScopeDeps,
12
13
  enforceAlphabeticalOrder,
13
14
  enforceFieldOrder,
@@ -18,6 +19,7 @@ import {
18
19
  import { scopeDepMaps } from "../engine/ctx";
19
20
  import { canonicalPackageJsonOrder } from "./shared/key-order";
20
21
  import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
22
+ import { removeRetiredToolchainDeps } from "./shared/retired-deps";
21
23
 
22
24
  export function workflowPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
23
25
  const v = ctx.blockVars;
@@ -44,7 +46,8 @@ export function workflowPackageJsonInitial(ctx: RunContext): Record<string, unkn
44
46
  devDependencies: {
45
47
  "@platforma-sdk/tengo-builder": "sdk:",
46
48
  "@platforma-sdk/test": "sdk:",
47
- vitest: "catalog:",
49
+ // `vitest` is NOT seeded here — it's wired by the body rule only when the
50
+ // workflow carries co-located tests (a freshly-init'd workflow has none).
48
51
  shx: "catalog:",
49
52
  },
50
53
  };
@@ -53,21 +56,33 @@ export function workflowPackageJsonInitial(ctx: RunContext): Record<string, unkn
53
56
  export function workflowPackageJsonRules(): void {
54
57
  ensureField("type", "module");
55
58
 
56
- // No `fmt`: the workflow is Tengo, not TS. Build + check go through
57
- // pl-tengo. shx powers the cross-platform build clean-up.
59
+ // No `fmt` script: the workflow is Tengo, not TS — build + check run via
60
+ // pl-tengo; `format` runs the emacs-batch Tengo formatter (no-op when absent).
58
61
  ensureScript("build", "shx rm -rf dist && pl-tengo build");
59
62
  ensureScript("check", "pl-tengo check");
60
- // Tengo source formatter (emacs batch); no-op notice when emacs is absent.
61
63
  ensureScript("format", "/usr/bin/env emacs --script ./format.el || echo 'No emacs.'");
62
- // The vitest `test` script is wired ONLY when the workflow carries
63
- // co-located integration tests (`src/**/*.test.ts`, incl. `src/test/`).
64
- // A test-less workflow gets no `test` task and stays a refresh fixpoint.
65
- // (Workflow tests type-check via the standalone workflow/tsconfig with
66
- // `types: []` — they pull their types from `@platforma-sdk/test` — so no
67
- // node-types wiring is needed here, unlike model/ui.)
68
- when(whenFilesExist(COLOCATED_TEST_GLOB), () => {
69
- ensureScript("test", "vitest run --passWithNoTests");
70
- });
64
+ // The vitest `test` script AND the `vitest` devDep are wired ONLY when the
65
+ // workflow carries co-located integration tests (`src/**/*.test.ts`, incl.
66
+ // `src/test/`). A test-less workflow gets neither — no `test` task, no
67
+ // vitest dep — and stays a refresh fixpoint. (Workflow tests type-check via
68
+ // the standalone workflow/tsconfig with `types: []` — they pull their types
69
+ // from `@platforma-sdk/test` — so no node-types wiring is needed here,
70
+ // unlike model/ui.)
71
+ //
72
+ // Skipped entirely for sdk-internal (in-monorepo) blocks: they own their test
73
+ // wiring under the monorepo's shared infrastructure — see model-package-json.
74
+ when(
75
+ ({ ctx }) => !ctx.isSdkInternal,
76
+ () =>
77
+ when(
78
+ whenFilesExist(COLOCATED_TEST_GLOB),
79
+ () => {
80
+ ensureScript("test", "vitest run --passWithNoTests");
81
+ ensureDevDeps({ vitest: "catalog:" });
82
+ },
83
+ () => removeDep("vitest"),
84
+ ),
85
+ );
71
86
 
72
87
  ensureDep("@platforma-sdk/workflow-tengo", "sdk:");
73
88
 
@@ -80,10 +95,13 @@ export function workflowPackageJsonRules(): void {
80
95
  ensureDevDeps({
81
96
  "@platforma-sdk/tengo-builder": "sdk:",
82
97
  "@platforma-sdk/test": "sdk:",
83
- vitest: "catalog:",
84
98
  shx: "catalog:",
85
99
  });
86
100
 
101
+ // No-op for a canonical Tengo workflow; called uniformly across every scope
102
+ // for safety.
103
+ removeRetiredToolchainDeps();
104
+
87
105
  enforceAlphabeticalOrder("dependencies");
88
106
  enforceAlphabeticalOrder("devDependencies");
89
107
  enforceAlphabeticalOrder("peerDependencies");
@@ -11,25 +11,49 @@ import {
11
11
  tpl,
12
12
  generate,
13
13
  remove,
14
+ when,
15
+ whenFilesExist,
14
16
  blockVars,
15
17
  } from "../engine/api";
16
18
  import { getActiveRunContext } from "../engine/builders";
17
19
  import { workflowPackageJsonInitial, workflowPackageJsonRules } from "./workflow-package-json";
20
+ import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
18
21
 
19
22
  export function workflowRules(): void {
20
23
  scope("workflow", () => {
21
- // tsconfig for the TypeScript vitest integration tests under src/test
22
- // (e.g. mixcr's workflow/src/test/columns.test.ts). types:[] keeps the
23
- // isomorphic workflow free of ambient Node/DOM types.
24
- fixed("tsconfig.json", file("workflow/tsconfig.json"));
25
24
  // Tengo source formatter (emacs batch); the `format` script invokes it.
26
25
  fixed("format.el", file("workflow/format.el"));
27
- // scaffold (not fixed): tengo test config is author-tunable.
28
- scaffold("vitest.config.mts", file("workflow/vitest.config.mts"));
26
+ // The TS-test tsconfig + vitest config exist ONLY when the workflow carries
27
+ // co-located tests (`src/**/*.test.ts`, incl. `src/test/` — e.g. mixcr's
28
+ // workflow/src/test/columns.test.ts). They pair with the conditional
29
+ // `vitest` devDep + `test` script in workflow-package-json. tsconfig is
30
+ // `fixed` (structural: types:[] keeps the isomorphic workflow free of
31
+ // ambient Node/DOM types); vitest.config is `scaffold` (author-tunable once
32
+ // present). A test-less workflow gets neither and stays a refresh fixpoint.
33
+ //
34
+ // Skipped entirely for sdk-internal (in-monorepo) blocks: they own their
35
+ // test config under the monorepo's shared infrastructure (e.g. a bespoke
36
+ // `vitest run --coverage` + `createVitestConfig`), which the structurer
37
+ // must neither scaffold nor remove. See workflow-package-json.
38
+ when(
39
+ ({ ctx }) => !ctx.isSdkInternal,
40
+ () =>
41
+ when(
42
+ whenFilesExist(COLOCATED_TEST_GLOB),
43
+ () => {
44
+ fixed("tsconfig.json", file("workflow/tsconfig.json"));
45
+ scaffold("vitest.config.mts", file("workflow/vitest.config.mts"));
46
+ },
47
+ () => {
48
+ remove("tsconfig.json");
49
+ remove("vitest.config.mts");
50
+ },
51
+ ),
52
+ );
29
53
  // No workflow facade (index.js / index.d.ts): the block loader resolves the
30
54
  // workflow via the `block:` components dist path (workflow/dist/.../main.plj.gz),
31
- // never the JS facade. It was orphaned in production — dropped. Actively
32
- // removed so existing blocks that carry it get cleaned (idempotent on absence).
55
+ // never the JS facade. Removed so blocks that carry one get cleaned
56
+ // (idempotent on absence).
33
57
  remove("index.js");
34
58
  remove("index.d.ts");
35
59
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": ["node_modules/@milaboratories/ts-builder/configs/oxfmt.json"],
3
+ "ignorePatterns": ["dist", "coverage", "CHANGELOG.md"]
4
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["node_modules/@milaboratories/ts-builder/dist/configs/oxlint-test.json"]
3
+ }