@platforma-sdk/block-tools 2.13.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +12 -12
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +727 -648
- package/dist/cli.mjs.map +1 -1
- package/dist/cmd/build-kind-manifest.d.ts +7 -0
- package/dist/config-CtXMWNgm.js +3 -0
- package/dist/config-CtXMWNgm.js.map +1 -0
- package/dist/{config-CiC347sB.mjs → config-Dx45aFJ_.mjs} +792 -456
- package/dist/config-Dx45aFJ_.mjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +15 -15
- package/dist/structure/engine/api.d.ts +1 -1
- package/dist/structure/rules/kind-package-json.d.ts +3 -0
- package/dist/structure/rules/kind.d.ts +1 -0
- package/dist/v2/build_kind_dist.d.ts +25 -0
- package/dist/v2/index.d.ts +4 -0
- package/dist/v2/kind/resolve-refs.d.ts +63 -0
- package/dist/v2/kind/version-match.d.ts +23 -0
- package/dist/v2/model/block_description.d.ts +8 -1
- package/dist/v2/publish-block.d.ts +23 -0
- package/dist/v2/registry/index.d.ts +2 -0
- package/dist/v2/registry/kind_resolver.d.ts +121 -0
- package/dist/v2/registry/registry.d.ts +23 -0
- package/dist/v2/registry/registry_reader.d.ts +34 -1
- package/dist/v2/registry/schema_kinds.d.ts +632 -0
- package/dist/v2/registry/schema_public.d.ts +72 -0
- package/package.json +10 -10
- package/src/cli.test.ts +2 -1
- package/src/cli.ts +2 -0
- package/src/cmd/build-kind-manifest.ts +41 -0
- package/src/cmd/publish.ts +10 -1
- package/src/structure/__tests__/model-package-json.snapshot.test.ts +1 -0
- package/src/structure/engine/api.ts +1 -1
- package/src/structure/engine/ctx.ts +1 -0
- package/src/structure/engine/discovery.ts +16 -2
- package/src/structure/rules/block-package-json.ts +18 -0
- package/src/structure/rules/kind-package-json.ts +117 -0
- package/src/structure/rules/kind.ts +29 -0
- package/src/structure/rules/model-package-json.ts +8 -0
- package/src/structure/rules/model.ts +15 -13
- package/src/structure/rules/shared/pascal-case.ts +1 -1
- package/src/structure/structure-definition.ts +2 -0
- package/src/structure/templates/static/kind/.oxfmtrc.json +4 -0
- package/src/structure/templates/static/kind/.oxlintrc.json +3 -0
- package/src/structure/templates/static/kind/src/index.ts +49 -0
- package/src/structure/templates/static/kind/tsconfig.json +10 -0
- package/src/structure/templates/text/model/src/index.tpl.ts +18 -0
- package/src/v2/build_dist.ts +35 -0
- package/src/v2/build_kind_dist.ts +104 -0
- package/src/v2/from_pack_v2.test.ts +2 -2
- package/src/v2/index.ts +4 -0
- package/src/v2/kind/resolve-refs.ts +150 -0
- package/src/v2/kind/version-match.test.ts +60 -0
- package/src/v2/kind/version-match.ts +55 -0
- package/src/v2/model/block_description.ts +15 -4
- package/src/v2/publish-block.ts +60 -0
- package/src/v2/registry/index.ts +2 -0
- package/src/v2/registry/kind_resolver.test.ts +123 -0
- package/src/v2/registry/kind_resolver.ts +190 -0
- package/src/v2/registry/registry.test.ts +247 -1
- package/src/v2/registry/registry.ts +227 -4
- package/src/v2/registry/registry_reader.ts +68 -0
- package/src/v2/registry/schema_kinds.test.ts +65 -0
- package/src/v2/registry/schema_kinds.ts +169 -0
- package/dist/config-BmJWc8sR.js +0 -3
- package/dist/config-BmJWc8sR.js.map +0 -1
- package/dist/config-CiC347sB.mjs.map +0 -1
- package/src/structure/templates/static/model/src/index.ts +0 -13
package/src/cli.test.ts
CHANGED
|
@@ -15,8 +15,9 @@ function longNames(cmd: Command): string[] {
|
|
|
15
15
|
describe("block-tools CLI structure", () => {
|
|
16
16
|
const program = buildProgram();
|
|
17
17
|
|
|
18
|
-
it("exposes the full command surface (
|
|
18
|
+
it("exposes the full command surface (12 commands + structure topic)", () => {
|
|
19
19
|
expect(program.commands.map((c) => c.name()).sort()).toEqual([
|
|
20
|
+
"build-kind-manifest",
|
|
20
21
|
"build-meta",
|
|
21
22
|
"build-model",
|
|
22
23
|
"list-overview-snapshots",
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { buildMetaCommand } from "./cmd/build-meta";
|
|
3
3
|
import { buildModelCommand } from "./cmd/build-model";
|
|
4
|
+
import { buildKindManifestCommand } from "./cmd/build-kind-manifest";
|
|
4
5
|
import { packCommand } from "./cmd/pack";
|
|
5
6
|
import { publishCommand } from "./cmd/publish";
|
|
6
7
|
import { refreshRegistryCommand } from "./cmd/refresh-registry";
|
|
@@ -25,6 +26,7 @@ export function buildProgram(packageRoot: string = process.cwd()): Command {
|
|
|
25
26
|
|
|
26
27
|
program.addCommand(buildMetaCommand());
|
|
27
28
|
program.addCommand(buildModelCommand());
|
|
29
|
+
program.addCommand(buildKindManifestCommand());
|
|
28
30
|
program.addCommand(packCommand());
|
|
29
31
|
program.addCommand(publishCommand());
|
|
30
32
|
program.addCommand(refreshRegistryCommand());
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { buildKindDist } from "../v2/build_kind_dist";
|
|
3
|
+
|
|
4
|
+
// @todo: disscuss why we need manifest for kind
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thin commander wrapper (build-model.ts shape) delegating to the commander-free
|
|
8
|
+
* `buildKindDist` core. Bundles the compiled kind's identity + src source-hash
|
|
9
|
+
* into `dist/manifest.json`.
|
|
10
|
+
*/
|
|
11
|
+
export function buildKindManifestCommand(): Command {
|
|
12
|
+
const cmd = new Command("build-kind-manifest").description(
|
|
13
|
+
"Writes the block-kind dist manifest (identity + src source-hash) from a pre-built kind bundle",
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
cmd.option(
|
|
17
|
+
"-i, --modulePath <path>",
|
|
18
|
+
"kind package dir (contains package.json, src/, dist/)",
|
|
19
|
+
".",
|
|
20
|
+
);
|
|
21
|
+
cmd.option(
|
|
22
|
+
"-s, --srcDir <path>",
|
|
23
|
+
"source dir hashed into sourceHash, relative to modulePath",
|
|
24
|
+
"src",
|
|
25
|
+
);
|
|
26
|
+
cmd.option(
|
|
27
|
+
"-o, --dst <path>",
|
|
28
|
+
"output dir with the compiled bundle, relative to modulePath",
|
|
29
|
+
"dist",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
cmd.action(async (flags) => {
|
|
33
|
+
await buildKindDist({
|
|
34
|
+
modulePath: flags.modulePath,
|
|
35
|
+
srcDir: flags.srcDir,
|
|
36
|
+
dst: flags.dst,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return cmd;
|
|
41
|
+
}
|
package/src/cmd/publish.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "@milaboratories/pl-model-middle-layer";
|
|
10
10
|
import { storageByUrl } from "../io/storage";
|
|
11
11
|
import { BlockRegistryV2 } from "../v2/registry/registry";
|
|
12
|
+
import { publishBlock } from "../v2/publish-block";
|
|
12
13
|
import { buildPublishedCoords, writePublishedCoords } from "../v2/resolve_to_registry";
|
|
13
14
|
import path from "node:path";
|
|
14
15
|
|
|
@@ -82,15 +83,23 @@ export function publishCommand(): Command {
|
|
|
82
83
|
manifest as BlockPackManifest & Record<string, unknown>,
|
|
83
84
|
);
|
|
84
85
|
const manifestRoot = path.dirname(manifestPath);
|
|
86
|
+
// The facade package root holds `package.json` (with the direct kind
|
|
87
|
+
// dependency the kind-first publish reads); the manifest sits one level down
|
|
88
|
+
// in `block-pack/`. `readFacadeKindDependency`/`resolveFacadeKind` need the
|
|
89
|
+
// package root, not the block-pack dir.
|
|
90
|
+
const facadeDir = path.dirname(manifestRoot);
|
|
85
91
|
|
|
86
92
|
logger.info(`Manifest root = ${manifestRoot}`);
|
|
93
|
+
logger.info(`Facade root = ${facadeDir}`);
|
|
87
94
|
|
|
88
95
|
if (flags.versionOverride) manifest = overrideManifestVersion(manifest, flags.versionOverride);
|
|
89
96
|
|
|
90
97
|
const storage = storageByUrl(flags.registry);
|
|
91
98
|
const registry = new BlockRegistryV2(storage, logger);
|
|
92
99
|
|
|
93
|
-
|
|
100
|
+
// Kind-first publish: resolve refs -> version-match gate (before any S3
|
|
101
|
+
// write) -> publishKind (if the block declares a kind) -> publishPackage.
|
|
102
|
+
await publishBlock(registry, manifest, facadeDir, async (file) =>
|
|
94
103
|
Buffer.from(await fs.promises.readFile(path.resolve(manifestRoot, file))),
|
|
95
104
|
);
|
|
96
105
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ContentForm, ManagedBody, RunMode, Structure, TriggerFn } from "./ir";
|
|
12
12
|
|
|
13
|
-
export type Scope = "root" | "block" | "model" | "ui" | "workflow" | "test" | "software";
|
|
13
|
+
export type Scope = "root" | "block" | "model" | "ui" | "workflow" | "test" | "software" | "kind";
|
|
14
14
|
|
|
15
15
|
/** Variables describing a block — populated by `init` or parsed by
|
|
16
16
|
* `check`/`refresh` from the block's package.json `name`. */
|
|
@@ -55,6 +55,7 @@ export function modulesForInit(vars: BlockVars): Module[] {
|
|
|
55
55
|
{ scope: "root", name: facade, path: "" },
|
|
56
56
|
{ scope: "block", name: `${facade}.block`, path: "block" },
|
|
57
57
|
{ scope: "model", name: `${facade}.model`, path: "model" },
|
|
58
|
+
{ scope: "kind", name: `${facade}.kind`, path: "kind" },
|
|
58
59
|
{ scope: "ui", name: `${facade}.ui`, path: "ui" },
|
|
59
60
|
{ scope: "workflow", name: `${facade}.workflow`, path: "workflow" },
|
|
60
61
|
{ scope: "test", name: `${facade}.test`, path: "test" },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Workspace-package classification — the
|
|
1
|
+
// Workspace-package classification — the eight scope detection rules.
|
|
2
2
|
//
|
|
3
3
|
// Input: the parsed package.json of every workspace member, plus its
|
|
4
4
|
// block-relative path. Output: `Module[]` (scope + name + path).
|
|
@@ -7,7 +7,16 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Module, Scope } from "./api";
|
|
9
9
|
|
|
10
|
-
export const ALL_SCOPES: Scope[] = [
|
|
10
|
+
export const ALL_SCOPES: Scope[] = [
|
|
11
|
+
"root",
|
|
12
|
+
"block",
|
|
13
|
+
"model",
|
|
14
|
+
"ui",
|
|
15
|
+
"workflow",
|
|
16
|
+
"test",
|
|
17
|
+
"software",
|
|
18
|
+
"kind",
|
|
19
|
+
];
|
|
11
20
|
|
|
12
21
|
export type WorkspacePackage = {
|
|
13
22
|
/** Block-relative path of the package — "" or "." means the
|
|
@@ -96,6 +105,11 @@ export function classifyOne(wp: WorkspacePackage): Scope | undefined {
|
|
|
96
105
|
|
|
97
106
|
const deps = depKeys(wp.pkg);
|
|
98
107
|
|
|
108
|
+
// kind — @platforma-sdk/block-kind is unique to kind packages (no other scope
|
|
109
|
+
// depends on it). MUST precede the model rule: a kind sets `main`, and were it
|
|
110
|
+
// to carry any model-ish signal it would otherwise misclassify.
|
|
111
|
+
if (deps.has("@platforma-sdk/block-kind")) return "kind";
|
|
112
|
+
|
|
99
113
|
// 1. software — top-level `block-software` field OR
|
|
100
114
|
// (package-builder AND any @platforma-open/*runenv* dep).
|
|
101
115
|
if (wp.pkg["block-software"] !== undefined) return "software";
|
|
@@ -153,6 +153,11 @@ export function blockPackageJsonInitial(ctx: RunContext): Record<string, unknown
|
|
|
153
153
|
...scopeDepMaps(ctx, "model"),
|
|
154
154
|
...scopeDepMaps(ctx, "ui"),
|
|
155
155
|
...scopeDepMaps(ctx, "workflow"),
|
|
156
|
+
// The kind is a fourth block component the facade depends on directly (the
|
|
157
|
+
// tetrad); like its siblings it is a build-time workspace devDep, never a
|
|
158
|
+
// runtime dependency, and it is NOT a bundled `block.component` (its content
|
|
159
|
+
// publishes to the `kinds/` registry tree, not into block-pack).
|
|
160
|
+
...scopeDepMaps(ctx, "kind"),
|
|
156
161
|
};
|
|
157
162
|
// `block-tools pack` requires both `block.components` and `block.meta`. The
|
|
158
163
|
// author edits the placeholder meta before publishing.
|
|
@@ -194,6 +199,18 @@ export function blockPackageJsonInitial(ctx: RunContext): Record<string, unknown
|
|
|
194
199
|
export function blockPackageJsonRules(ctx: RunContext): void {
|
|
195
200
|
const v = ctx.blockVars;
|
|
196
201
|
|
|
202
|
+
// The kind is a MANDATORY block component: every block must declare exactly
|
|
203
|
+
// one sibling `kind/` package. Fail loudly otherwise — a kind-less block is
|
|
204
|
+
// invalid, and this is what surfaces the not-yet-migrated blocks.
|
|
205
|
+
const kindModules = findModules(ctx, "kind");
|
|
206
|
+
if (kindModules.length !== 1) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
kindModules.length === 0
|
|
209
|
+
? `block '${v.facadeName}' declares no kind — every block must have a sibling kind/ package`
|
|
210
|
+
: `block '${v.facadeName}' declares ${kindModules.length} kinds; exactly one is required`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
197
214
|
ensureField("type", "module");
|
|
198
215
|
ensureField("files", ["dist", "block-pack"]);
|
|
199
216
|
ensureField("main", "./dist/index.js");
|
|
@@ -212,6 +229,7 @@ export function blockPackageJsonRules(ctx: RunContext): void {
|
|
|
212
229
|
ensureWorkspaceScopeDevDeps("model");
|
|
213
230
|
ensureWorkspaceScopeDevDeps("ui");
|
|
214
231
|
ensureWorkspaceScopeDevDeps("workflow");
|
|
232
|
+
ensureWorkspaceScopeDevDeps("kind");
|
|
215
233
|
ensureDevDep("@milaboratories/ts-builder", "sdk:");
|
|
216
234
|
ensureDevDep("@milaboratories/ts-configs", "sdk:");
|
|
217
235
|
ensureDevDep("@platforma-sdk/block-tools", "sdk:");
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Kind `package.json`: the initial generator and the drift-correcting body,
|
|
2
|
+
// co-located. A kind is a private workspace sibling (like the model): its
|
|
3
|
+
// content ships to the `kinds/` registry tree via `build-kind-manifest`, not to
|
|
4
|
+
// npm. Identity (name/version) stays untouched; the body enforces
|
|
5
|
+
// type/main/exports + canonical scripts + canonical dep sets, then projects
|
|
6
|
+
// canonical field / dependency order (oxfmt-clean, so build->check passes with
|
|
7
|
+
// no prior `pnpm fmt`).
|
|
8
|
+
//
|
|
9
|
+
// A kind builds TWICE, like the model: `dist/index.{js,cjs}` with dependencies
|
|
10
|
+
// external — what a block imports — and the self-contained `dist/kind.js` that
|
|
11
|
+
// the registry publishes and `build-kind-manifest` hashes. The `exports` map
|
|
12
|
+
// points at the externalized pair only, so a kind and the block implementing it
|
|
13
|
+
// share one copy of any dependency they both use; the self-contained bundle is
|
|
14
|
+
// read off disk by the manifest builder and needs no export.
|
|
15
|
+
//
|
|
16
|
+
// All three of `import` / `require` / `default` are spelled out: `block-tools
|
|
17
|
+
// build-model` loads the model via `require()`, and the model `require`s its
|
|
18
|
+
// kind, so the CJS arm has to resolve to real CJS.
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
ensureField,
|
|
22
|
+
ensureScript,
|
|
23
|
+
ensureDep,
|
|
24
|
+
ensureDevDeps,
|
|
25
|
+
ensurePeerDeps,
|
|
26
|
+
removeScript,
|
|
27
|
+
enforceAlphabeticalOrder,
|
|
28
|
+
enforceFieldOrder,
|
|
29
|
+
type RunContext,
|
|
30
|
+
} from "../engine/api";
|
|
31
|
+
import { canonicalPackageJsonOrder } from "./shared/key-order";
|
|
32
|
+
|
|
33
|
+
const KIND_EXPORTS = {
|
|
34
|
+
".": {
|
|
35
|
+
types: "./dist/index.d.ts",
|
|
36
|
+
sources: "./src/index.ts",
|
|
37
|
+
import: "./dist/index.js",
|
|
38
|
+
require: "./dist/index.cjs",
|
|
39
|
+
default: "./dist/index.js",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function kindPackageJsonInitial(ctx: RunContext): Record<string, unknown> {
|
|
44
|
+
const v = ctx.blockVars;
|
|
45
|
+
return {
|
|
46
|
+
name: `${v.facadeName}.kind`,
|
|
47
|
+
private: true,
|
|
48
|
+
type: "module",
|
|
49
|
+
// `main` is the CommonJS entry (require fallback), `module` the ESM one —
|
|
50
|
+
// the model's convention. `dist/kind.js`, the self-contained bundle, is
|
|
51
|
+
// deliberately not an entry: it exists for the registry, not for importers.
|
|
52
|
+
main: "./dist/index.cjs",
|
|
53
|
+
module: "./dist/index.js",
|
|
54
|
+
types: "./dist/index.d.ts",
|
|
55
|
+
exports: KIND_EXPORTS,
|
|
56
|
+
scripts: {
|
|
57
|
+
fmt: "ts-builder format",
|
|
58
|
+
watch: "ts-builder build --target block-kind --watch",
|
|
59
|
+
build: "ts-builder build --target block-kind && block-tools build-kind-manifest",
|
|
60
|
+
check: "ts-builder check --target block-kind",
|
|
61
|
+
},
|
|
62
|
+
// A kind ships a runtime params check, so it is not a types-only package — but the
|
|
63
|
+
// check is plain TypeScript, held to the contract by its return type, so nothing but
|
|
64
|
+
// the SDK is needed to write one. An author whose params shape earns a schema library
|
|
65
|
+
// adds it here themselves; seeding one would make every kind carry it to be deleted.
|
|
66
|
+
dependencies: {
|
|
67
|
+
"@platforma-sdk/block-kind": "sdk:",
|
|
68
|
+
},
|
|
69
|
+
devDependencies: {
|
|
70
|
+
"@milaboratories/ts-builder": "sdk:",
|
|
71
|
+
"@milaboratories/ts-configs": "sdk:",
|
|
72
|
+
"@platforma-sdk/block-tools": "sdk:",
|
|
73
|
+
},
|
|
74
|
+
peerDependencies: {
|
|
75
|
+
"@types/node": "*",
|
|
76
|
+
typescript: "*",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function kindPackageJsonRules(): void {
|
|
82
|
+
// Controlled sibling — workspace-only, never published to npm. `private: true`
|
|
83
|
+
// makes npm refuse to publish it; the `version` is kept (changesets-owned) and
|
|
84
|
+
// is what the kind's on-wire `{name}@{version}` reference is baked from.
|
|
85
|
+
ensureField("private", true);
|
|
86
|
+
|
|
87
|
+
ensureField("type", "module");
|
|
88
|
+
ensureField("main", "./dist/index.cjs");
|
|
89
|
+
ensureField("module", "./dist/index.js");
|
|
90
|
+
ensureField("types", "./dist/index.d.ts");
|
|
91
|
+
ensureField("exports", KIND_EXPORTS);
|
|
92
|
+
|
|
93
|
+
ensureScript("fmt", "ts-builder format");
|
|
94
|
+
ensureScript("watch", "ts-builder build --target block-kind --watch");
|
|
95
|
+
ensureScript("build", "ts-builder build --target block-kind && block-tools build-kind-manifest");
|
|
96
|
+
ensureScript("check", "ts-builder check --target block-kind");
|
|
97
|
+
removeScript("lint");
|
|
98
|
+
|
|
99
|
+
ensureDep("@platforma-sdk/block-kind", "sdk:");
|
|
100
|
+
|
|
101
|
+
ensureDevDeps({
|
|
102
|
+
"@milaboratories/ts-builder": "sdk:",
|
|
103
|
+
"@milaboratories/ts-configs": "sdk:",
|
|
104
|
+
"@platforma-sdk/block-tools": "sdk:",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
ensurePeerDeps({
|
|
108
|
+
"@types/node": "*",
|
|
109
|
+
typescript: "*",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
enforceAlphabeticalOrder("dependencies");
|
|
113
|
+
enforceAlphabeticalOrder("devDependencies");
|
|
114
|
+
enforceAlphabeticalOrder("peerDependencies");
|
|
115
|
+
enforceAlphabeticalOrder("optionalDependencies");
|
|
116
|
+
enforceFieldOrder([...canonicalPackageJsonOrder]);
|
|
117
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Kind-scope rules. Static config files + managed package.json; the
|
|
2
|
+
// `src/index.ts` seed is dropped by `init` and never touched again (the block
|
|
3
|
+
// author owns the BlockParams contract). The seed is a deliberate non-building
|
|
4
|
+
// sentinel — its BlockParams is undefined, so a scaffolded-but-unmigrated block
|
|
5
|
+
// fails to typecheck until the params contract is chosen on purpose.
|
|
6
|
+
|
|
7
|
+
import { scope, fixed, managed, seed, file, generate } from "../engine/api";
|
|
8
|
+
import { kindPackageJsonInitial, kindPackageJsonRules } from "./kind-package-json";
|
|
9
|
+
|
|
10
|
+
export function kindRules(): void {
|
|
11
|
+
scope("kind", () => {
|
|
12
|
+
fixed("tsconfig.json", file("kind/tsconfig.json"));
|
|
13
|
+
fixed(".oxlintrc.json", file("kind/.oxlintrc.json"));
|
|
14
|
+
fixed(".oxfmtrc.json", file("kind/.oxfmtrc.json"));
|
|
15
|
+
|
|
16
|
+
// Minimal-but-real kind: identity imported from the package's own
|
|
17
|
+
// package.json, plus a non-building BlockParams sentinel the block author
|
|
18
|
+
// must resolve (via the migration recipe or by hand) before the block builds.
|
|
19
|
+
seed("src/index.ts", file("kind/src/index.ts"));
|
|
20
|
+
|
|
21
|
+
managed(
|
|
22
|
+
"package.json",
|
|
23
|
+
generate((ctx) => kindPackageJsonInitial(ctx)),
|
|
24
|
+
() => {
|
|
25
|
+
kindPackageJsonRules();
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ensureDep,
|
|
16
16
|
ensureDevDep,
|
|
17
17
|
ensureDevDeps,
|
|
18
|
+
ensureWorkspaceScopeDeps,
|
|
18
19
|
removeDep,
|
|
19
20
|
removeScript,
|
|
20
21
|
removeField,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
whenFilesExist,
|
|
26
27
|
type RunContext,
|
|
27
28
|
} from "../engine/api";
|
|
29
|
+
import { scopeDepMaps } from "../engine/ctx";
|
|
28
30
|
import { canonicalPackageJsonOrder } from "./shared/key-order";
|
|
29
31
|
import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
|
|
30
32
|
import { removeRetiredToolchainDeps } from "./shared/retired-deps";
|
|
@@ -56,6 +58,9 @@ export function modelPackageJsonInitial(ctx: RunContext): Record<string, unknown
|
|
|
56
58
|
},
|
|
57
59
|
dependencies: {
|
|
58
60
|
"@platforma-sdk/model": "sdk:",
|
|
61
|
+
// The model imports its block's kind (to bake the kind reference into
|
|
62
|
+
// model.json), so it depends on the sibling kind package directly.
|
|
63
|
+
...scopeDepMaps(ctx, "kind"),
|
|
59
64
|
},
|
|
60
65
|
devDependencies: {
|
|
61
66
|
"@milaboratories/ts-builder": "sdk:",
|
|
@@ -95,6 +100,9 @@ export function modelPackageJsonRules(): void {
|
|
|
95
100
|
ensureScript("check", "ts-builder check --target block-model");
|
|
96
101
|
|
|
97
102
|
ensureDep("@platforma-sdk/model", "sdk:");
|
|
103
|
+
// The model imports its block's kind to bake the kind reference — direct
|
|
104
|
+
// dependency on the sibling kind package.
|
|
105
|
+
ensureWorkspaceScopeDeps("kind");
|
|
98
106
|
|
|
99
107
|
ensureDevDeps({
|
|
100
108
|
"@milaboratories/ts-builder": "sdk:",
|
|
@@ -2,17 +2,22 @@
|
|
|
2
2
|
// `src/index.ts` seed is dropped by `init` and never touched again
|
|
3
3
|
// (block author owns it).
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
scope,
|
|
7
|
+
fixed,
|
|
8
|
+
managed,
|
|
9
|
+
seed,
|
|
10
|
+
file,
|
|
11
|
+
tpl,
|
|
12
|
+
generate,
|
|
13
|
+
when,
|
|
14
|
+
whenFilesExist,
|
|
15
|
+
} from "../engine/api";
|
|
6
16
|
import { modelPackageJsonInitial, modelPackageJsonRules } from "./model-package-json";
|
|
7
17
|
import { COLOCATED_TEST_GLOB } from "./shared/colocated-tests";
|
|
8
18
|
|
|
9
19
|
export function modelRules(): void {
|
|
10
20
|
scope("model", () => {
|
|
11
|
-
// Two static end states by co-located-test presence: a test-bearing model
|
|
12
|
-
// gets node ambient types (`.node.json`) so the tests type-check —
|
|
13
|
-
// `@types/node` is wired alongside by the package.json rule; a test-less
|
|
14
|
-
// model stays bare. `fixed` (engine-owned, whole-file overwrite) not
|
|
15
|
-
// `managed`, so refresh is idempotent by construction — no key-order drift.
|
|
16
21
|
when(
|
|
17
22
|
whenFilesExist(COLOCATED_TEST_GLOB),
|
|
18
23
|
() => fixed("tsconfig.json", file("model/tsconfig.node.json")),
|
|
@@ -21,13 +26,10 @@ export function modelRules(): void {
|
|
|
21
26
|
fixed(".oxlintrc.json", file("model/.oxlintrc.json"));
|
|
22
27
|
fixed(".oxfmtrc.json", file("model/.oxfmtrc.json"));
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// model, identity args, a single "Main" section. The block author owns
|
|
29
|
-
// and extends it after init.
|
|
30
|
-
seed("src/index.ts", file("model/src/index.ts"));
|
|
29
|
+
seed(
|
|
30
|
+
"src/index.ts",
|
|
31
|
+
tpl("model/src/index.tpl.ts", (ctx) => ({ kindPkg: `${ctx.blockVars.facadeName}.kind` })),
|
|
32
|
+
);
|
|
31
33
|
|
|
32
34
|
managed(
|
|
33
35
|
"package.json",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `pascalCase(shortName)` → the block-named alias prefix the facade entry
|
|
2
2
|
// template emits (`<PascalName>BlockContract`, …). Splits on any run of
|
|
3
3
|
// non-alphanumeric characters and capitalises each segment's first char:
|
|
4
|
-
// test-sum-numbers
|
|
4
|
+
// test-sum-numbers → TestSumNumbersV3
|
|
5
5
|
// mixcr-clonotyping-2 → MixcrClonotyping2
|
|
6
6
|
// samples-and-data → SamplesAndData
|
|
7
7
|
export function pascalCase(s: string): string {
|
|
@@ -8,6 +8,7 @@ import { rootRules } from "./rules/root";
|
|
|
8
8
|
import { rootCiRules } from "./rules/root-ci";
|
|
9
9
|
import { blockRules } from "./rules/block";
|
|
10
10
|
import { modelRules } from "./rules/model";
|
|
11
|
+
import { kindRules } from "./rules/kind";
|
|
11
12
|
import { uiRules } from "./rules/ui";
|
|
12
13
|
import { workflowRules } from "./rules/workflow";
|
|
13
14
|
import { testRules } from "./rules/test";
|
|
@@ -20,6 +21,7 @@ export const STRUCTURE = defineStructure(() => {
|
|
|
20
21
|
rootCiRules();
|
|
21
22
|
blockRules();
|
|
22
23
|
modelRules();
|
|
24
|
+
kindRules();
|
|
23
25
|
uiRules();
|
|
24
26
|
workflowRules();
|
|
25
27
|
testRules();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { assertParamsObject, defineBlockKind } from "@platforma-sdk/block-kind";
|
|
2
|
+
import { name, version } from "../package.json" with { type: "json" };
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This block's init-params contract — the shape a block of this kind receives
|
|
6
|
+
* at creation, and exactly what a project template serializes for it.
|
|
7
|
+
*
|
|
8
|
+
* TODO(block-kind): replace `NEEDS_BLOCK_PARAMS` with the real params shape, then
|
|
9
|
+
* wire the model's `init(({ params }) => …)` to consume them. If this block takes
|
|
10
|
+
* no author-supplied params, set it to `Record<string, never>` deliberately.
|
|
11
|
+
*
|
|
12
|
+
* This is an intentional sentinel: `NEEDS_BLOCK_PARAMS` is an undefined type, so
|
|
13
|
+
* the block fails to typecheck (TS2304) until the contract is chosen on purpose.
|
|
14
|
+
* A scaffolded-but-unmigrated block must never compile with an empty contract by
|
|
15
|
+
* default — see the block-kind migration recipe in the `block-dev` skill.
|
|
16
|
+
*/
|
|
17
|
+
export type BlockParams = NEEDS_BLOCK_PARAMS;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The same contract at runtime, for params that arrive from a template file rather than
|
|
21
|
+
* from typed code — the only point that can catch a hand-written entry being wrong.
|
|
22
|
+
*
|
|
23
|
+
* TODO(block-kind): read each key `BlockParams` declares and say what it must be, then
|
|
24
|
+
* return them. Plain TypeScript is the default here: a kind owes no schema library, and a
|
|
25
|
+
* check written by hand is held to the contract by the return type. Reach for a validation
|
|
26
|
+
* library only where the shape earns it, and add it to this package's dependencies yourself.
|
|
27
|
+
*
|
|
28
|
+
* Check the fields the contract requires, and stop there. A key the contract does not name
|
|
29
|
+
* needs no rejection: it is dropped by not being read.
|
|
30
|
+
*
|
|
31
|
+
* This is a second intentional sentinel. The function has to return `BlockParams`, so
|
|
32
|
+
* `return {}` stops compiling the moment the contract declares a required field — the check
|
|
33
|
+
* cannot drift from the contract by being left behind. Never satisfy it with a cast: `value
|
|
34
|
+
* as BlockParams` compiles today and checks nothing forever.
|
|
35
|
+
*/
|
|
36
|
+
function parseInitializationParams(value: unknown): BlockParams {
|
|
37
|
+
assertParamsObject(value);
|
|
38
|
+
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Identity (`name`/`version`) comes from this package's own `package.json`, so
|
|
43
|
+
// the on-wire `{name}@{version}` reference can never drift from what npm
|
|
44
|
+
// publishes; the bundler inlines the JSON import.
|
|
45
|
+
export const kind = defineBlockKind<BlockParams>({
|
|
46
|
+
name,
|
|
47
|
+
version,
|
|
48
|
+
parseInitializationParams,
|
|
49
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InferOutputsType } from "@platforma-sdk/model";
|
|
2
|
+
import { BlockModelV3, DataModelBuilder } from "@platforma-sdk/model";
|
|
3
|
+
import type { BlockParams } from "${kindPkg}";
|
|
4
|
+
import { kind } from "${kindPkg}";
|
|
5
|
+
|
|
6
|
+
export type BlockData = BlockParams;
|
|
7
|
+
|
|
8
|
+
const dataModel = new DataModelBuilder({ kind })
|
|
9
|
+
.from<BlockData>("v1")
|
|
10
|
+
.init(({ params }) => params ?? {});
|
|
11
|
+
|
|
12
|
+
export const platforma = BlockModelV3.create({ dataModel, kind })
|
|
13
|
+
.args(() => ({}))
|
|
14
|
+
.templateParams((data) => ({}))
|
|
15
|
+
.sections(() => [{ type: "link", href: "/", label: "Main" }])
|
|
16
|
+
.done();
|
|
17
|
+
|
|
18
|
+
export type BlockOutputs = InferOutputsType<typeof platforma>;
|
package/src/v2/build_dist.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
ManifestFileInfo,
|
|
4
4
|
} from "@milaboratories/pl-model-middle-layer";
|
|
5
5
|
import { BlockPackManifest, BlockPackManifestFile } from "@milaboratories/pl-model-middle-layer";
|
|
6
|
+
import type { BlockKindReference } from "@milaboratories/pl-model-common";
|
|
6
7
|
import type { CompiledTemplateV3 } from "@milaboratories/pl-model-backend";
|
|
7
8
|
import type { BlockPackDescriptionAbsolute } from "./model";
|
|
8
9
|
import { consolidateBlockPackDescription } from "./model";
|
|
@@ -41,6 +42,33 @@ async function workflowRequiredCapabilities(
|
|
|
41
42
|
return pack.template.requiredCapabilities;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Returns the block-kind reference the model config declares (baked at the
|
|
47
|
+
* container level by `BlockModelV3.done()`), or `undefined` for kind-less
|
|
48
|
+
* blocks or malformed model files — fail-safe, mirroring
|
|
49
|
+
* {@link workflowRequiredCapabilities}.
|
|
50
|
+
*
|
|
51
|
+
* Note: `build-model` writes `JSON.stringify(config)` where `config` is the
|
|
52
|
+
* `BlockConfigContainer` itself, so `kind` sits at the top level of
|
|
53
|
+
* `model.json` (not nested under a `config` key).
|
|
54
|
+
*/
|
|
55
|
+
async function modelKindReference(
|
|
56
|
+
descriptionRelative: BlockPackDescriptionManifest,
|
|
57
|
+
dst: string,
|
|
58
|
+
): Promise<BlockKindReference | undefined> {
|
|
59
|
+
// After consolidateBlockPackDescription runs, components.model is always a
|
|
60
|
+
// `{type: "relative", path: ...}` reference into `dst`.
|
|
61
|
+
const model = descriptionRelative.components.model;
|
|
62
|
+
try {
|
|
63
|
+
const cfg = JSON.parse(await fsp.readFile(path.resolve(dst, model.path), "utf-8")) as {
|
|
64
|
+
kind?: BlockKindReference;
|
|
65
|
+
};
|
|
66
|
+
return cfg.kind ?? undefined;
|
|
67
|
+
} catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
44
72
|
export async function buildBlockPackDist(
|
|
45
73
|
description: BlockPackDescriptionAbsolute,
|
|
46
74
|
dst: string,
|
|
@@ -71,6 +99,13 @@ export async function buildBlockPackDist(
|
|
|
71
99
|
};
|
|
72
100
|
}
|
|
73
101
|
|
|
102
|
+
// Lift the model's container-level block-kind reference onto the manifest
|
|
103
|
+
// description (top-level, like featureFlags), so the published manifest
|
|
104
|
+
// advertises which kind the block implements. Fail-safe undefined leaves
|
|
105
|
+
// kind-less blocks unprojected.
|
|
106
|
+
const kindRef = await modelKindReference(descriptionRelative, dst);
|
|
107
|
+
if (kindRef) descriptionRelative.kind = kindRef;
|
|
108
|
+
|
|
74
109
|
const filesForManifest = await Promise.all(
|
|
75
110
|
files.map(async (f): Promise<ManifestFileInfo> => {
|
|
76
111
|
const bytes = await fsp.readFile(path.resolve(dst, f));
|