@platforma-sdk/block-tools 2.11.2 → 2.11.4
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/bin/run.js +9 -5
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +13 -13
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +845 -981
- package/dist/cli.mjs.map +1 -1
- package/dist/cmd/build-meta.d.ts +2 -9
- package/dist/cmd/build-model.d.ts +2 -10
- package/dist/cmd/list-overview-snapshots.d.ts +2 -9
- package/dist/cmd/mark-stable.d.ts +2 -13
- package/dist/cmd/pack.d.ts +2 -9
- package/dist/cmd/publish.d.ts +2 -12
- package/dist/cmd/refresh-registry.d.ts +2 -9
- package/dist/cmd/restore-overview-from-snapshot.d.ts +2 -10
- package/dist/cmd/structure/check.d.ts +2 -10
- package/dist/cmd/structure/init.d.ts +2 -18
- package/dist/cmd/structure/refresh.d.ts +10 -11
- package/dist/cmd/update-deps.d.ts +2 -6
- package/dist/cmd/upload-package-v1.d.ts +2 -14
- package/dist/config-BmJWc8sR.js +3 -0
- package/dist/config-BmJWc8sR.js.map +1 -0
- package/dist/{config-DpfTzcgG.mjs → config-CiC347sB.mjs} +453 -398
- package/dist/config-CiC347sB.mjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +15 -15
- package/dist/registry_v1/config_schema.d.ts +7 -7
- package/dist/registry_v1/flags.d.ts +2 -4
- package/dist/structure/cli/run-structure.d.ts +1 -1
- package/dist/structure/engine/discovery.d.ts +2 -3
- package/dist/structure/rules/block-package-json.d.ts +3 -3
- package/dist/structure/rules/shared/pascal-case.d.ts +1 -0
- package/dist/v2/index.d.ts +1 -0
- package/dist/v2/model/block_components.d.ts +11 -0
- package/dist/v2/model/block_meta.d.ts +7 -0
- package/dist/v2/resolve_to_registry.d.ts +37 -0
- package/dist/v2/source_package.d.ts +12 -0
- package/package.json +9 -24
- package/src/cli.test.ts +56 -0
- package/src/cli.ts +50 -0
- package/src/cmd/build-meta.ts +11 -22
- package/src/cmd/build-model.ts +16 -33
- package/src/cmd/list-overview-snapshots.ts +24 -28
- package/src/cmd/mark-stable.ts +35 -55
- package/src/cmd/pack.ts +11 -22
- package/src/cmd/publish.test.ts +35 -0
- package/src/cmd/publish.ts +55 -49
- package/src/cmd/refresh-registry.ts +22 -26
- package/src/cmd/restore-overview-from-snapshot.ts +24 -34
- package/src/cmd/structure/check.ts +19 -28
- package/src/cmd/structure/init.ts +40 -47
- package/src/cmd/structure/refresh.ts +45 -36
- package/src/cmd/update-deps.ts +22 -12
- package/src/cmd/upload-package-v1.ts +42 -60
- package/src/registry_v1/flags.ts +7 -7
- package/src/structure/__tests__/discovery-shortname.test.ts +3 -0
- package/src/structure/__tests__/facade-end-state.test.ts +195 -0
- package/src/structure/__tests__/model-package-json.snapshot.test.ts +1 -1
- package/src/structure/cli/run-structure.ts +2 -2
- package/src/structure/engine/__tests__/discovery.test.ts +22 -26
- package/src/structure/engine/discovery.ts +12 -30
- package/src/structure/rules/block-package-json.ts +146 -64
- package/src/structure/rules/block.ts +40 -5
- package/src/structure/rules/model-package-json.ts +6 -1
- package/src/structure/rules/shared/pascal-case.ts +16 -0
- package/src/structure/rules/software-package-json.ts +6 -1
- package/src/structure/rules/test-package-json.ts +5 -1
- package/src/structure/rules/ui-package-json.ts +5 -1
- package/src/structure/rules/workflow-package-json.ts +5 -1
- package/src/structure/templates/static/block/src/AGENTS.ts +5 -0
- package/src/structure/templates/static/block/src/agents-extra.ts +8 -0
- package/src/structure/templates/static/block/src/block-extra.ts +9 -0
- package/src/structure/templates/static/block/tsconfig.json +4 -0
- package/src/structure/templates/text/block/src/index.tpl.ts +55 -0
- package/src/v2/from_pack_v2.test.ts +142 -0
- package/src/v2/index.ts +1 -0
- package/src/v2/model/block_components.ts +33 -0
- package/src/v2/model/block_meta.ts +18 -0
- package/src/v2/model/content_conversion.ts +0 -1
- package/src/v2/resolve_to_registry.ts +84 -0
- package/src/v2/source_package.ts +34 -1
- package/bin/dev.cmd +0 -3
- package/bin/dev.js +0 -6
- package/dist/cmd/index.d.ts +0 -28
- package/dist/config-DpfTzcgG.mjs.map +0 -1
- package/dist/config-xxVy3IQR.js +0 -3
- package/dist/config-xxVy3IQR.js.map +0 -1
- package/src/cmd/index.ts +0 -31
- package/src/structure/templates/static/block/index.d.ts +0 -6
- package/src/structure/templates/static/block/index.js +0 -8
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Command, Option } from "commander";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { ConsoleLoggerAdapter } from "@milaboratories/ts-helpers";
|
|
3
4
|
import {
|
|
4
5
|
resolveBlockVars,
|
|
5
6
|
runInit,
|
|
@@ -7,59 +8,51 @@ import {
|
|
|
7
8
|
type InitFlagValues,
|
|
8
9
|
} from "../../structure/init-block-constructor";
|
|
9
10
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
"Scaffold a fresh Platforma block from the canonical structure. Missing flags are prompted unless --non-interactive."
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}),
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
public async run(): Promise<void> {
|
|
41
|
-
const { args, flags } = await this.parse(StructureInit);
|
|
42
|
-
|
|
43
|
-
if (flags["with-software"] && flags["no-software"]) {
|
|
44
|
-
this.error("--with-software and --no-software are mutually exclusive.");
|
|
11
|
+
export function structureInitCommand(packageRoot: string): Command {
|
|
12
|
+
const cmd = new Command("init").description(
|
|
13
|
+
"Scaffold a fresh Platforma block from the canonical structure. Missing flags are prompted unless --non-interactive.",
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
cmd.argument("[path]", "target directory (default: <short-name> under the current directory)");
|
|
17
|
+
cmd.option("--npm-org <org>", "npm org, e.g. @platforma-open");
|
|
18
|
+
cmd.option("--org-scope <scope>", "org scope, e.g. my-org");
|
|
19
|
+
cmd.option("--short-name <name>", "block short name, e.g. mixcr-clonotyping");
|
|
20
|
+
cmd.option("--with-software", "include a software module");
|
|
21
|
+
cmd.option("--no-software", "omit the software module");
|
|
22
|
+
cmd.addOption(
|
|
23
|
+
new Option("--platform <platform>", "software platform (single-valued)").choices([
|
|
24
|
+
...SUPPORTED_PLATFORMS,
|
|
25
|
+
]),
|
|
26
|
+
);
|
|
27
|
+
cmd.option("--non-interactive", "error on any missing flag instead of prompting");
|
|
28
|
+
|
|
29
|
+
cmd.action(async (argPath: string | undefined, flags) => {
|
|
30
|
+
const logger = new ConsoleLoggerAdapter();
|
|
31
|
+
|
|
32
|
+
// commander maps `--with-software` -> flags.withSoftware===true and
|
|
33
|
+
// `--no-software` -> flags.software===false
|
|
34
|
+
const withSoftwareFlag = flags.withSoftware === true;
|
|
35
|
+
const noSoftwareFlag = flags.software === false;
|
|
36
|
+
if (withSoftwareFlag && noSoftwareFlag) {
|
|
37
|
+
throw new Error("--with-software and --no-software are mutually exclusive.");
|
|
45
38
|
}
|
|
46
|
-
const withSoftware =
|
|
39
|
+
const withSoftware = withSoftwareFlag ? true : noSoftwareFlag ? false : undefined;
|
|
47
40
|
|
|
48
41
|
const flagValues: InitFlagValues = {
|
|
49
|
-
npmOrg: flags
|
|
50
|
-
orgScope: flags
|
|
51
|
-
shortName: flags
|
|
42
|
+
npmOrg: flags.npmOrg,
|
|
43
|
+
orgScope: flags.orgScope,
|
|
44
|
+
shortName: flags.shortName,
|
|
52
45
|
withSoftware,
|
|
53
46
|
platform: flags.platform,
|
|
54
|
-
nonInteractive: flags
|
|
47
|
+
nonInteractive: flags.nonInteractive,
|
|
55
48
|
};
|
|
56
49
|
|
|
57
50
|
const vars = await resolveBlockVars(flagValues);
|
|
58
|
-
const blockPath =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
const blockPath = argPath ? path.resolve(argPath) : path.resolve(process.cwd(), vars.shortName);
|
|
52
|
+
const templatesRoot = path.join(packageRoot, "src", "structure", "templates");
|
|
53
|
+
|
|
54
|
+
await runInit({ vars, blockPath, templatesRoot, log: (m) => logger.info(m) });
|
|
55
|
+
});
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
}
|
|
57
|
+
return cmd;
|
|
65
58
|
}
|
|
@@ -1,44 +1,53 @@
|
|
|
1
|
-
import { Command
|
|
1
|
+
import { Command } from "commander";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import type { MiLogger } from "@milaboratories/ts-helpers";
|
|
4
|
+
import { ConsoleLoggerAdapter } from "@milaboratories/ts-helpers";
|
|
3
5
|
import { runStructureForPath, formatChanges } from "../../structure/cli/run-structure";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
// Shared by `structure refresh` and the deprecated `update-deps` alias.
|
|
8
|
+
export async function runRefresh(opts: {
|
|
9
|
+
paths: string[];
|
|
10
|
+
isSdkInternal: boolean;
|
|
11
|
+
updateDepsOnly: boolean;
|
|
12
|
+
templatesRoot: string;
|
|
13
|
+
logger: MiLogger;
|
|
14
|
+
}): Promise<void> {
|
|
15
|
+
for (const p of opts.paths) {
|
|
16
|
+
const res = await runStructureForPath({
|
|
17
|
+
blockPath: p,
|
|
18
|
+
isSdkInternal: opts.isSdkInternal,
|
|
19
|
+
updateDepsOnly: opts.updateDepsOnly,
|
|
20
|
+
mode: "refresh",
|
|
21
|
+
templatesRoot: opts.templatesRoot,
|
|
22
|
+
log: (m) => opts.logger.info(m),
|
|
23
|
+
});
|
|
24
|
+
opts.logger.info(formatChanges(p, res.changes));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
export function structureRefreshCommand(packageRoot: string): Command {
|
|
29
|
+
const cmd = new Command("refresh").description(
|
|
30
|
+
"Bring block(s) up to date with the canonical structure. Applies rules, writes files, bumps .structure version. Default mode is offline; --update-deps-only fires only catalog-bump rules (network).",
|
|
31
|
+
);
|
|
16
32
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
"fire ONLY catalog-bump rules (npm network). Follow with `pnpm install` then a plain refresh.",
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
33
|
+
cmd.argument("[paths...]", "block path(s); defaults to the current directory");
|
|
34
|
+
cmd.option("--sdk-internal", "block(s) live inside the SDK monorepo; skip root-scope rules");
|
|
35
|
+
cmd.option(
|
|
36
|
+
"--update-deps-only",
|
|
37
|
+
"fire ONLY catalog-bump rules (npm network). Follow with `pnpm install` then a plain refresh.",
|
|
38
|
+
);
|
|
26
39
|
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
40
|
+
cmd.action(async (argv: string[], flags) => {
|
|
41
|
+
const paths = argv.length > 0 ? argv : ["."];
|
|
42
|
+
const templatesRoot = path.join(packageRoot, "src", "structure", "templates");
|
|
43
|
+
await runRefresh({
|
|
44
|
+
paths,
|
|
45
|
+
isSdkInternal: Boolean(flags.sdkInternal),
|
|
46
|
+
updateDepsOnly: Boolean(flags.updateDepsOnly),
|
|
47
|
+
templatesRoot,
|
|
48
|
+
logger: new ConsoleLoggerAdapter(),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
const res = await runStructureForPath({
|
|
34
|
-
blockPath: p,
|
|
35
|
-
isSdkInternal: flags["sdk-internal"],
|
|
36
|
-
updateDepsOnly: flags["update-deps-only"],
|
|
37
|
-
mode: "refresh",
|
|
38
|
-
templatesRoot,
|
|
39
|
-
log: (m) => this.log(m),
|
|
40
|
-
});
|
|
41
|
-
this.log(formatChanges(p, res.changes));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
52
|
+
return cmd;
|
|
44
53
|
}
|
package/src/cmd/update-deps.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { Command } from "
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ConsoleLoggerAdapter } from "@milaboratories/ts-helpers";
|
|
4
|
+
import { runRefresh } from "./structure/refresh";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Deprecated. Phase-2 forwarding alias (spec.md § "Coexistence And
|
|
@@ -7,19 +10,26 @@ import { Command } from "@oclif/core";
|
|
|
7
10
|
* semantics. Prints a deprecation warning; will be removed once the
|
|
8
11
|
* structurer path has been live without surprises.
|
|
9
12
|
*/
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
"[DEPRECATED] Use `block-tools structure refresh --update-deps-only`. This alias delegates to it."
|
|
13
|
+
export function updateDepsCommand(packageRoot: string): Command {
|
|
14
|
+
const cmd = new Command("update-deps").description(
|
|
15
|
+
"[DEPRECATED] Use `block-tools structure refresh --update-deps-only`. This alias delegates to it.",
|
|
16
|
+
);
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
this.warn(
|
|
18
|
+
cmd.action(async () => {
|
|
19
|
+
const logger = new ConsoleLoggerAdapter();
|
|
20
|
+
logger.warn(
|
|
18
21
|
"`block-tools update-deps` is deprecated and will be removed in a future release. " +
|
|
19
22
|
"Use `block-tools structure refresh --update-deps-only` instead. Delegating now.",
|
|
20
23
|
);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const templatesRoot = path.join(packageRoot, "src", "structure", "templates");
|
|
25
|
+
await runRefresh({
|
|
26
|
+
paths: ["."],
|
|
27
|
+
isSdkInternal: false,
|
|
28
|
+
updateDepsOnly: true,
|
|
29
|
+
templatesRoot,
|
|
30
|
+
logger,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return cmd;
|
|
25
35
|
}
|
|
@@ -1,67 +1,47 @@
|
|
|
1
|
-
import { Command,
|
|
1
|
+
import { Command, Option } from "commander";
|
|
2
2
|
import { getConfig } from "../registry_v1/config";
|
|
3
|
-
import {
|
|
3
|
+
import { targetFileOption } from "../registry_v1/flags";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import YAML from "yaml";
|
|
6
6
|
import { PlRegPackageConfigDataShard } from "../registry_v1/config_schema";
|
|
7
|
-
import {
|
|
7
|
+
import { ConsoleLoggerAdapter } from "@milaboratories/ts-helpers";
|
|
8
8
|
|
|
9
9
|
type BasicConfigField = keyof PlRegPackageConfigDataShard &
|
|
10
10
|
("registry" | "organization" | "package" | "version");
|
|
11
11
|
const BasicConfigFields: BasicConfigField[] = ["registry", "organization", "package", "version"];
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
env
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
exists: true,
|
|
46
|
-
}),
|
|
47
|
-
|
|
48
|
-
file: targetFile({
|
|
49
|
-
char: "f",
|
|
50
|
-
summary: "package files",
|
|
51
|
-
multiple: true,
|
|
52
|
-
default: [],
|
|
53
|
-
}),
|
|
54
|
-
|
|
55
|
-
refresh: Flags.boolean({
|
|
56
|
-
summary: "refresh repository after adding the package",
|
|
57
|
-
default: true,
|
|
58
|
-
allowNo: true,
|
|
59
|
-
env: "PL_REGISTRY_REFRESH",
|
|
60
|
-
}),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
public async run(): Promise<void> {
|
|
64
|
-
const { flags } = await this.parse(UploadPackageV1);
|
|
13
|
+
export function uploadPackageV1Command(): Command {
|
|
14
|
+
const cmd = new Command("upload-package-v1").description(
|
|
15
|
+
"Uploads V1 package and refreshes the registry",
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
cmd.addOption(
|
|
19
|
+
new Option(
|
|
20
|
+
"-r, --registry <address|alias>",
|
|
21
|
+
"full address of the registry or alias from .pl.reg",
|
|
22
|
+
).env("PL_REGISTRY"),
|
|
23
|
+
);
|
|
24
|
+
cmd.addOption(
|
|
25
|
+
new Option("-o, --organization <organization>", "target organisation").env(
|
|
26
|
+
"PL_PACKAGE_ORGANIZATION",
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
cmd.addOption(new Option("-p, --package <package>", "target package").env("PL_PACKAGE_NAME"));
|
|
30
|
+
cmd.addOption(new Option("-v, --version <version>", "target version").env("PL_PACKAGE_VERSION"));
|
|
31
|
+
cmd.option(
|
|
32
|
+
"-m, --meta <path>",
|
|
33
|
+
"json file containing meta information to associate with tha package",
|
|
34
|
+
);
|
|
35
|
+
cmd.addOption(targetFileOption("-f, --file <file>", "package files"));
|
|
36
|
+
cmd.addOption(
|
|
37
|
+
new Option("--refresh", "refresh repository after adding the package")
|
|
38
|
+
.default(true)
|
|
39
|
+
.env("PL_REGISTRY_REFRESH"),
|
|
40
|
+
);
|
|
41
|
+
cmd.option("--no-refresh", "do not refresh repository after adding the package");
|
|
42
|
+
|
|
43
|
+
cmd.action(async (flags) => {
|
|
44
|
+
const logger = new ConsoleLoggerAdapter();
|
|
65
45
|
const configFromFlags: PlRegPackageConfigDataShard = PlRegPackageConfigDataShard.parse({});
|
|
66
46
|
|
|
67
47
|
for (const field of BasicConfigFields) if (flags[field]) configFromFlags[field] = flags[field];
|
|
@@ -83,23 +63,25 @@ export default class UploadPackageV1 extends Command {
|
|
|
83
63
|
|
|
84
64
|
const conf = await getConfig(configFromFlags);
|
|
85
65
|
|
|
86
|
-
|
|
66
|
+
logger.info(YAML.stringify(conf.conf));
|
|
87
67
|
|
|
88
|
-
const registry = conf.createRegistry(
|
|
68
|
+
const registry = conf.createRegistry(logger);
|
|
89
69
|
const name = conf.fullPackageName;
|
|
90
70
|
|
|
91
71
|
const builder = registry.constructNewPackage(name);
|
|
92
72
|
|
|
93
73
|
for (const [dst, src] of Object.entries(conf.conf.files)) {
|
|
94
|
-
|
|
74
|
+
logger.info(`Uploading ${src} -> ${dst} ...`);
|
|
95
75
|
const content = await fs.promises.readFile(src);
|
|
96
76
|
await builder.addFile(dst, content);
|
|
97
77
|
}
|
|
98
78
|
|
|
99
|
-
|
|
79
|
+
logger.info(`Uploading meta information...`);
|
|
100
80
|
await builder.writeMeta(conf.conf.meta);
|
|
101
81
|
await builder.finish();
|
|
102
82
|
|
|
103
83
|
if (flags.refresh) await registry.updateIfNeeded();
|
|
104
|
-
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return cmd;
|
|
105
87
|
}
|
package/src/registry_v1/flags.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Option } from "commander";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
export interface TargetFile {
|
|
@@ -16,9 +16,9 @@ function parseTargetFile(arg: string): TargetFile {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
19
|
+
// Repeatable `file_path | package_name=file_path` option.
|
|
20
|
+
export function targetFileOption(flags: string, summary: string): Option {
|
|
21
|
+
return new Option(flags, summary)
|
|
22
|
+
.argParser((arg: string, prev?: TargetFile[]) => [...(prev ?? []), parseTargetFile(arg)])
|
|
23
|
+
.default([] as TargetFile[]);
|
|
24
|
+
}
|
|
@@ -36,10 +36,13 @@ describe("DISCOVERY: shortName from the block package name", () => {
|
|
|
36
36
|
// Nameless root — the standalone shape; classified `root` by path.
|
|
37
37
|
"package.json": "{}",
|
|
38
38
|
// Legacy block package: name IS the bare facade, no `.block` suffix.
|
|
39
|
+
// Classified `block` via its top-level `block` field (every real block —
|
|
40
|
+
// legacy shim or migrated facade — carries one).
|
|
39
41
|
"block/package.json": JSON.stringify({
|
|
40
42
|
name: "@platforma-open/test-org.legacy",
|
|
41
43
|
files: ["index.d.ts", "index.js"],
|
|
42
44
|
dependencies: { "@platforma-sdk/block-tools": "*" },
|
|
45
|
+
block: { components: {}, meta: {} },
|
|
43
46
|
}),
|
|
44
47
|
});
|
|
45
48
|
const ctx = discoverRunContext({ fs, isSdkInternal: false });
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Facade end-state coverage — the slim `block/` the structurer emits and
|
|
2
|
+
// enforces: block-scope detection by the top-level `block` field, the four-file
|
|
3
|
+
// `src/` surface + facade `tsconfig.json`, the slim publishable `package.json`,
|
|
4
|
+
// sibling privacy, and legacy-shim cleanup.
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect } from "vitest";
|
|
7
|
+
import type { BlockVars, RunContext } from "../engine/api";
|
|
8
|
+
import { classifyOne, type WorkspacePackage } from "../engine/discovery";
|
|
9
|
+
import { STRUCTURE } from "../structure-definition";
|
|
10
|
+
import { run as engineRun } from "../engine/runner";
|
|
11
|
+
import { defaultTemplateProvider, simulateInit } from "../engine/testing";
|
|
12
|
+
|
|
13
|
+
const TEMPLATES = defaultTemplateProvider();
|
|
14
|
+
|
|
15
|
+
const VARS: BlockVars = {
|
|
16
|
+
facadeName: "@platforma-open/test-org.demo",
|
|
17
|
+
baseName: "test-org.demo",
|
|
18
|
+
npmOrg: "@platforma-open",
|
|
19
|
+
orgScope: "test-org",
|
|
20
|
+
shortName: "demo",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function pkg(path: string, pkgJson: Record<string, unknown>): WorkspacePackage {
|
|
24
|
+
return { path, pkg: pkgJson };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("facade: block-scope detection by the top-level `block` field", () => {
|
|
28
|
+
test("a package with a top-level `block` field is classified `block`", () => {
|
|
29
|
+
expect(classifyOne(pkg("block", { name: "x", block: { components: {} } }))).toBe("block");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("the `block` field wins over the model shape (the collision the order guards)", () => {
|
|
33
|
+
// A migrated facade carries `@platforma-sdk/model` (devDep) + `main`, which
|
|
34
|
+
// matches the model rule — the `block` field must take precedence.
|
|
35
|
+
expect(
|
|
36
|
+
classifyOne(
|
|
37
|
+
pkg("block", {
|
|
38
|
+
name: "x",
|
|
39
|
+
main: "./dist/index.js",
|
|
40
|
+
devDependencies: { "@platforma-sdk/model": "catalog:" },
|
|
41
|
+
block: { components: {} },
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
).toBe("block");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("a model-shaped package WITHOUT a `block` field is still `model`, not `block`", () => {
|
|
48
|
+
expect(
|
|
49
|
+
classifyOne(
|
|
50
|
+
pkg("model", {
|
|
51
|
+
name: "x.model",
|
|
52
|
+
main: "dist/index.cjs",
|
|
53
|
+
dependencies: { "@platforma-sdk/model": "catalog:" },
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
).toBe("model");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("facade: four-file source surface + tsconfig", () => {
|
|
61
|
+
test("init emits the 4 src files + tsconfig, and no legacy shim", () => {
|
|
62
|
+
const { fs } = simulateInit({ vars: VARS });
|
|
63
|
+
for (const f of [
|
|
64
|
+
"block/src/index.ts",
|
|
65
|
+
"block/src/AGENTS.ts",
|
|
66
|
+
"block/src/block-extra.ts",
|
|
67
|
+
"block/src/agents-extra.ts",
|
|
68
|
+
"block/tsconfig.json",
|
|
69
|
+
]) {
|
|
70
|
+
expect(fs.exists(f), `expected ${f} to be emitted`).toBe(true);
|
|
71
|
+
}
|
|
72
|
+
expect(fs.exists("block/index.js")).toBe(false);
|
|
73
|
+
expect(fs.exists("block/index.d.ts")).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("index.ts carries the per-block model import path and PascalName aliases", () => {
|
|
77
|
+
const { fs } = simulateInit({ vars: VARS });
|
|
78
|
+
const index = fs.read("block/src/index.ts");
|
|
79
|
+
expect(index).toContain('import { platforma } from "@platforma-open/test-org.demo.model"');
|
|
80
|
+
expect(index).toContain("export type DemoBlockContract = BlockContract;");
|
|
81
|
+
expect(index).toContain("export const DemoBlockPointer = BlockPointer;");
|
|
82
|
+
expect(index).toContain('export * from "./block-extra"');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("tsconfig extends the facade preset (singular `block/facade`)", () => {
|
|
86
|
+
const { fs } = simulateInit({ vars: VARS });
|
|
87
|
+
const tsconfig = JSON.parse(fs.read("block/tsconfig.json"));
|
|
88
|
+
expect(tsconfig.extends).toBe("@milaboratories/ts-configs/block/facade");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("facade: slim `package.json` end-state", () => {
|
|
93
|
+
test("is slim and carries the facade fields, scripts, and sibling devDeps", () => {
|
|
94
|
+
const { fs } = simulateInit({ vars: VARS });
|
|
95
|
+
const p = JSON.parse(fs.read("block/package.json"));
|
|
96
|
+
|
|
97
|
+
// Slim invariant: zero runtime dependencies.
|
|
98
|
+
expect(p.dependencies).toEqual({});
|
|
99
|
+
|
|
100
|
+
expect(p.type).toBe("module");
|
|
101
|
+
expect(p.files).toEqual(["dist", "block-pack"]);
|
|
102
|
+
expect(p.main).toBe("./dist/index.js");
|
|
103
|
+
expect(p.module).toBe("./dist/index.js");
|
|
104
|
+
expect(p.types).toBe("./dist/index.d.ts");
|
|
105
|
+
expect(p.exports).toEqual({
|
|
106
|
+
".": {
|
|
107
|
+
sources: "./src/index.ts",
|
|
108
|
+
types: "./dist/index.d.ts",
|
|
109
|
+
default: "./dist/index.js",
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(Object.keys(p.scripts).sort()).toEqual(["build", "check", "do-pack", "prepublishOnly"]);
|
|
114
|
+
expect(p.scripts.build).toBe("ts-builder build --target block-facade && block-tools pack");
|
|
115
|
+
expect(p.scripts.check).toBe("ts-builder type-check --target block-facade");
|
|
116
|
+
|
|
117
|
+
// Siblings are build-time devDeps (workspace links), never runtime deps.
|
|
118
|
+
expect(p.devDependencies["@platforma-open/test-org.demo.model"]).toBe("workspace:*");
|
|
119
|
+
expect(p.devDependencies["@platforma-open/test-org.demo.ui"]).toBe("workspace:*");
|
|
120
|
+
expect(p.devDependencies["@platforma-open/test-org.demo.workflow"]).toBe("workspace:*");
|
|
121
|
+
|
|
122
|
+
// Author-owned seed + engine-managed components both present.
|
|
123
|
+
expect(p.block.components).toBeTruthy();
|
|
124
|
+
expect(p.block.meta).toBeTruthy();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("facade: `prepublishOnly` publish target branches on isSdkInternal", () => {
|
|
129
|
+
const AUX_DEV =
|
|
130
|
+
"block-tools publish -r s3://milab-euce1-prod-pkgs-s3-block-registry/aux/dev/?region=eu-central-1 --registry-serve-url https://aux-blocks.pl-open.science";
|
|
131
|
+
|
|
132
|
+
test("in-SDK blocks publish to the aux/dev registry, regardless of org", () => {
|
|
133
|
+
// `@milaboratories` would route to production via ORG_PUBLISH_TARGETS — the
|
|
134
|
+
// isSdkInternal branch must override that and send it to aux/dev.
|
|
135
|
+
const vars: BlockVars = {
|
|
136
|
+
facadeName: "@milaboratories/test-org.demo",
|
|
137
|
+
baseName: "test-org.demo",
|
|
138
|
+
npmOrg: "@milaboratories",
|
|
139
|
+
orgScope: "test-org",
|
|
140
|
+
shortName: "demo",
|
|
141
|
+
};
|
|
142
|
+
const { fs } = simulateInit({ vars, isSdkInternal: true });
|
|
143
|
+
const p = JSON.parse(fs.read("block/package.json"));
|
|
144
|
+
expect(p.scripts.prepublishOnly).toBe(AUX_DEV);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("standalone blocks publish to their per-org production target", () => {
|
|
148
|
+
const open = simulateInit({ vars: VARS });
|
|
149
|
+
expect(JSON.parse(open.fs.read("block/package.json")).scripts.prepublishOnly).toBe(
|
|
150
|
+
"block-tools publish -r s3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1 --registry-serve-url https://blocks.pl-open.science",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const milab = simulateInit({
|
|
154
|
+
vars: {
|
|
155
|
+
facadeName: "@milaboratories/test-org.demo",
|
|
156
|
+
baseName: "test-org.demo",
|
|
157
|
+
npmOrg: "@milaboratories",
|
|
158
|
+
orgScope: "test-org",
|
|
159
|
+
shortName: "demo",
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
expect(JSON.parse(milab.fs.read("block/package.json")).scripts.prepublishOnly).toBe(
|
|
163
|
+
"block-tools publish -r s3://milab-euce1-prod-pkgs-s3-block-registry/pub/releases/?region=eu-central-1 --registry-serve-url https://block.registry.platforma.bio/releases",
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("facade: sibling package privacy", () => {
|
|
169
|
+
test("model / ui / workflow / test are `private` with no `version`", () => {
|
|
170
|
+
const { fs } = simulateInit({ vars: VARS });
|
|
171
|
+
for (const scope of ["model", "ui", "workflow", "test"]) {
|
|
172
|
+
const p = JSON.parse(fs.read(`${scope}/package.json`));
|
|
173
|
+
expect(p.private, `${scope} must be private`).toBe(true);
|
|
174
|
+
expect("version" in p, `${scope} must not carry a version`).toBe(false);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("facade: legacy shim cleanup on refresh", () => {
|
|
180
|
+
test("a refresh removes a pre-existing block/index.js + index.d.ts, then is idempotent", () => {
|
|
181
|
+
const { fs, ctx } = simulateInit({ vars: VARS });
|
|
182
|
+
// Inject the legacy boilerplate shim a pre-facade block would carry.
|
|
183
|
+
fs.write("block/index.js", "module.exports = { blockSpec: {} };\n");
|
|
184
|
+
fs.write("block/index.d.ts", "export declare const blockSpec: unknown;\n");
|
|
185
|
+
|
|
186
|
+
// Default-mode refresh (not dry-run): the engine's post-run recheck throws
|
|
187
|
+
// RecheckError if a second pass would change anything — so this asserts
|
|
188
|
+
// convergence too.
|
|
189
|
+
const refreshCtx: RunContext = { ...ctx, dryRun: false };
|
|
190
|
+
expect(() => engineRun(STRUCTURE, fs, refreshCtx, { templates: TEMPLATES })).not.toThrow();
|
|
191
|
+
|
|
192
|
+
expect(fs.exists("block/index.js")).toBe(false);
|
|
193
|
+
expect(fs.exists("block/index.d.ts")).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -9,7 +9,7 @@ import type { BlockVars } from "../engine/api";
|
|
|
9
9
|
|
|
10
10
|
const EXPECTED_MODEL_PACKAGE_JSON = `{
|
|
11
11
|
"name": "@platforma-open/test-org.demo.model",
|
|
12
|
-
"
|
|
12
|
+
"private": true,
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "dist/index.cjs",
|
|
15
15
|
"module": "dist/index.js",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Shared driver for the `structure check` and `structure refresh`
|
|
2
|
-
//
|
|
2
|
+
// commands. Wraps DISCOVERY (discover-fs) + the engine runner for
|
|
3
3
|
// one block path, handling the standalone vs `--sdk-internal` split, the
|
|
4
4
|
// `--update-deps-only` registry prefetch, and the post-run recheck's
|
|
5
5
|
// fresh re-discovery. Each block path is processed independently (no
|
|
@@ -26,7 +26,7 @@ export type RunStructureInput = {
|
|
|
26
26
|
isSdkInternal: boolean;
|
|
27
27
|
updateDepsOnly: boolean;
|
|
28
28
|
mode: StructureMode;
|
|
29
|
-
/** `<block-tools>/src/structure/templates
|
|
29
|
+
/** `<block-tools>/src/structure/templates`. */
|
|
30
30
|
templatesRoot: string;
|
|
31
31
|
log: (msg: string) => void;
|
|
32
32
|
};
|