@ykdz/template 0.0.35 → 0.0.36

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 (80) hide show
  1. package/dist/application.d.ts +2 -1
  2. package/dist/application.js +64 -75
  3. package/dist/main.js +6 -2
  4. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +50 -36
  5. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +968 -364
  6. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.d.ts +24 -0
  7. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.js +154 -0
  8. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.d.ts +2 -2
  9. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.js +1 -1
  10. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.d.ts +31 -2
  11. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +29 -33
  12. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +4 -3
  13. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +9 -5
  14. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +1 -1
  15. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +0 -1
  16. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.d.ts +31 -2
  17. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +79 -58
  18. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.d.ts +31 -2
  19. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +31 -23
  20. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.d.ts +22 -2
  21. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +53 -25
  22. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.d.ts +26 -2
  23. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +19 -7
  24. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.d.ts +22 -2
  25. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +31 -20
  26. package/node_modules/@ykdz/template-builtin-presets/package.json +12 -11
  27. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +1 -1
  28. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +1 -1
  29. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.pnpmfile.mjs +19 -0
  30. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
  31. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/CHANGELOG.md.template +17 -0
  32. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/RELEASING.md +17 -0
  33. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +1271 -0
  34. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/changelog.ts +283 -0
  35. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-artifact.ts +99 -0
  36. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-readiness.ts +66 -0
  37. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/cli-command-identity.ts +4 -0
  38. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +450 -0
  39. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/publish.ts +483 -0
  40. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +1125 -0
  41. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.ts +819 -0
  42. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.yml +119 -0
  43. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/root-tsconfig.json +13 -0
  44. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/tsconfig.json +4 -0
  45. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/turbo.json +6 -0
  46. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/README.md +48 -0
  47. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/CHANGELOG.md.template +17 -0
  48. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-APACHE-2.0.txt +202 -0
  49. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-MIT.txt +21 -0
  50. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/README.md.template +5 -0
  51. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/bridge.ts +3336 -0
  52. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/setup.sh +221 -0
  53. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli-command-identity.ts +133 -0
  54. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +4 -3
  55. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +5 -4
  56. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +14 -13
  57. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/cli-command-identity.test.ts +76 -0
  58. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.build.json +2 -0
  59. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +15 -53
  60. package/node_modules/@ykdz/template-core/dist/module-graph.js +134 -101
  61. package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +13 -1
  62. package/node_modules/@ykdz/template-core/dist/package-contribution.js +92 -0
  63. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +14 -10
  64. package/node_modules/@ykdz/template-core/dist/preset-definition.d.ts +71 -13
  65. package/node_modules/@ykdz/template-core/dist/preset-definition.js +16 -1
  66. package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +56 -0
  67. package/node_modules/@ykdz/template-core/dist/project-blueprint.js +271 -0
  68. package/node_modules/@ykdz/template-core/dist/project-linking-v2.d.ts +2 -2
  69. package/node_modules/@ykdz/template-core/dist/project-linking-v2.js +2 -2
  70. package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +2 -1
  71. package/node_modules/@ykdz/template-core/dist/project-projection.js +107 -20
  72. package/node_modules/@ykdz/template-core/dist/renderer.d.ts +0 -2
  73. package/node_modules/@ykdz/template-core/dist/renderer.js +17 -38
  74. package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +2 -2
  75. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +6 -0
  76. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.d.ts +2 -0
  77. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +9 -3
  78. package/package.json +5 -5
  79. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.d.ts +0 -33
  80. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.js +0 -182
@@ -1,18 +1,65 @@
1
1
  import type { PackageContribution } from "./package-contribution.ts";
2
- import type { ProjectBlueprintV2 } from "./project-blueprint-v2.ts";
2
+ import type { PackageDefinition, PackageRole, ProjectBlueprintDraft } from "./project-blueprint.ts";
3
3
  import type { TemplateSourceHandle } from "./renderer.ts";
4
4
  /** Stable, preset-agnostic input supplied after toolchain resolution. */
5
5
  export type GenerationContext = {
6
6
  readonly targetDir: string;
7
- readonly projectName: string;
8
- readonly scope: string;
7
+ readonly repositoryName: string;
8
+ readonly defaultPackageScope: string;
9
+ readonly foundationPackages: {
10
+ readonly typescriptConfiguration: {
11
+ readonly name: string;
12
+ };
13
+ };
9
14
  readonly toolchain: {
10
15
  readonly nodeLtsMajor: string;
11
16
  readonly packageManagerPin: string;
12
17
  };
13
18
  };
14
- /** A side-effect-free Built-in Preset planner. */
15
- export type BuiltInPresetDefinition = {
19
+ /** An in-memory plan tagged with the stable adapter that can replay it. */
20
+ export type PlannedPackageContribution = PackageContribution & {
21
+ readonly planningIdentity: string;
22
+ };
23
+ /** Narrow access to initial sibling packages, keyed by stable adapter identity. */
24
+ export type InitialPackageDefinitionLookup = {
25
+ require(identity: string): PackageDefinition;
26
+ };
27
+ export type PackageContributionReplayAdapter = {
28
+ readonly identity: string;
29
+ identify(contribution: PackageContribution): PlannedPackageContribution;
30
+ replay(options: {
31
+ readonly context: GenerationContext;
32
+ readonly planningContribution: "foundationPlan" | "planInitialization" | "planPackageAddition";
33
+ readonly packageDefinition: PackageDefinition;
34
+ readonly packageLeafName: string;
35
+ readonly initialPackages: InitialPackageDefinitionLookup;
36
+ }): PackageContribution;
37
+ };
38
+ /**
39
+ * Declares one stable replay semantic. Persisted provenance selection and
40
+ * diagnostics stay with the Foundation instead of leaking into Presets.
41
+ */
42
+ export declare function definePackageContributionReplayAdapter(options: {
43
+ readonly identity: string;
44
+ readonly replay: PackageContributionReplayAdapter["replay"];
45
+ }): PackageContributionReplayAdapter;
46
+ export type ResolvedPrimaryPackageIdentity = {
47
+ readonly leafName: string;
48
+ readonly definition: PackageDefinition;
49
+ };
50
+ /** Optional behavior owned only by Presets with a configurable initial package. */
51
+ export type InitialPrimaryPackageCapability = {
52
+ readonly defaultLeafName: string;
53
+ readonly role: PackageRole;
54
+ defaultPackagePath(options: {
55
+ readonly packageLeafName: string;
56
+ }): string;
57
+ planInitialContribution(options: {
58
+ readonly context: GenerationContext;
59
+ readonly resolvedPackageIdentity: ResolvedPrimaryPackageIdentity;
60
+ }): PlannedPackageContribution;
61
+ };
62
+ type BuiltInPresetDefinitionBase = {
16
63
  readonly metadata: {
17
64
  readonly name: string;
18
65
  readonly title: string;
@@ -22,13 +69,8 @@ export type BuiltInPresetDefinition = {
22
69
  readonly source: TemplateSourceHandle;
23
70
  /** The owned planner source inspected by the Template Boundary Check. */
24
71
  readonly plannerSourceFile: string;
25
- blueprint(context: GenerationContext): ProjectBlueprintV2;
26
- planInitialization(context: GenerationContext): PackageContribution;
27
- /**
28
- * Multi-package Definitions expose their complete owned topology directly,
29
- * while single-package Definitions keep the compact tracer interface.
30
- */
31
- planInitializationContributions?(context: GenerationContext): readonly PackageContribution[];
72
+ /** Stable semantics used to replay persisted Package Definitions. */
73
+ readonly packageContributionReplayAdapters: readonly PackageContributionReplayAdapter[];
32
74
  /** Package layout is Preset-owned even when callers omit --path. */
33
75
  defaultPackagePath?(options: {
34
76
  readonly context: GenerationContext;
@@ -38,5 +80,21 @@ export type BuiltInPresetDefinition = {
38
80
  readonly context: GenerationContext;
39
81
  readonly packageLeafName: string;
40
82
  readonly packagePath: string;
41
- }): PackageContribution;
83
+ }): PlannedPackageContribution;
84
+ };
85
+ type ConfigurablePrimaryPackagePresetDefinition = BuiltInPresetDefinitionBase & {
86
+ readonly initialPrimaryPackage: InitialPrimaryPackageCapability;
87
+ readonly blueprint?: never;
88
+ readonly planInitialization?: never;
89
+ readonly planInitializationContributions?: never;
42
90
  };
91
+ type FixedTopologyPresetDefinition = BuiltInPresetDefinitionBase & {
92
+ readonly initialPrimaryPackage?: never;
93
+ blueprint(context: GenerationContext): ProjectBlueprintDraft;
94
+ planInitialization(context: GenerationContext): PlannedPackageContribution;
95
+ /** Multi-package Definitions expose their complete owned topology directly. */
96
+ planInitializationContributions?(context: GenerationContext): readonly PlannedPackageContribution[];
97
+ };
98
+ /** A side-effect-free Built-in Preset planner. */
99
+ export type BuiltInPresetDefinition = ConfigurablePrimaryPackagePresetDefinition | FixedTopologyPresetDefinition;
100
+ export {};
@@ -1 +1,16 @@
1
- export {};
1
+ /**
2
+ * Declares one stable replay semantic. Persisted provenance selection and
3
+ * diagnostics stay with the Foundation instead of leaking into Presets.
4
+ */
5
+ export function definePackageContributionReplayAdapter(options) {
6
+ if (options.identity.length === 0) {
7
+ throw new Error("Package Contribution replay adapter identity is required");
8
+ }
9
+ return {
10
+ identity: options.identity,
11
+ identify(contribution) {
12
+ return { ...contribution, planningIdentity: options.identity };
13
+ },
14
+ replay: options.replay,
15
+ };
16
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The durable, preset-agnostic topology used by local template follow-up
3
+ * operations. Preset provenance deliberately belongs in Generation Record.
4
+ */
5
+ declare const packageRoles: readonly ["cli-tool", "runtime-service", "shared-library", "native-package"];
6
+ export type PackageRole = (typeof packageRoles)[number];
7
+ export type PackageDefinition = {
8
+ readonly name: string;
9
+ readonly path: string;
10
+ readonly role: PackageRole;
11
+ };
12
+ /** Opaque durable identity assigned once when a Package Definition is created. */
13
+ export type PackageDefinitionId = `package-${string}`;
14
+ export type PersistedPackageDefinition = PackageDefinition & {
15
+ readonly packageDefinitionId: PackageDefinitionId;
16
+ };
17
+ export type PackageLinkIntent = {
18
+ readonly consumerPackagePath: string;
19
+ readonly providerPackagePath: string;
20
+ };
21
+ export type ProjectBlueprint = {
22
+ readonly schemaVersion: 3;
23
+ readonly packages: readonly PersistedPackageDefinition[];
24
+ readonly packageLinkIntents?: readonly PackageLinkIntent[];
25
+ };
26
+ /** Preset-owned topology before Foundation assigns durable package identities. */
27
+ export type ProjectBlueprintDraft = {
28
+ readonly schemaVersion: 3;
29
+ readonly packages: readonly PackageDefinition[];
30
+ readonly packageLinkIntents?: readonly PackageLinkIntent[];
31
+ };
32
+ export type BlueprintValidationIssue = {
33
+ readonly path: string;
34
+ readonly message: string;
35
+ };
36
+ export type BlueprintValidationResult = {
37
+ readonly ok: true;
38
+ readonly value: ProjectBlueprint;
39
+ } | {
40
+ readonly ok: false;
41
+ readonly issues: readonly BlueprintValidationIssue[];
42
+ };
43
+ /** Canonical npm package-name predicate shared by Blueprint and input preparation. */
44
+ export declare function isValidNewNpmPackageName(value: string): boolean;
45
+ export type NewPackagePathValidation = {
46
+ readonly hasValidShape: boolean;
47
+ readonly reservedWorkspaceCollection?: string;
48
+ };
49
+ /** Canonical Package Path facts shared by Blueprint and input preparation. */
50
+ export declare function validateNewPackagePath(value: string): NewPackagePathValidation;
51
+ /** Validates persisted metadata before it is used to plan or render changes. */
52
+ export declare function validateProjectBlueprint(value: unknown): BlueprintValidationResult;
53
+ /** Validates preset-owned topology before any persisted identity is assigned. */
54
+ export declare function assertProjectBlueprintDraft(value: ProjectBlueprintDraft): ProjectBlueprintDraft;
55
+ export declare function assertProjectBlueprint(value: unknown): ProjectBlueprint;
56
+ export {};
@@ -0,0 +1,271 @@
1
+ import { isBuiltin } from "node:module";
2
+ import * as v from "valibot";
3
+ /**
4
+ * The durable, preset-agnostic topology used by local template follow-up
5
+ * operations. Preset provenance deliberately belongs in Generation Record.
6
+ */
7
+ const packageRoles = [
8
+ "cli-tool",
9
+ "runtime-service",
10
+ "shared-library",
11
+ "native-package",
12
+ ];
13
+ const packagePath = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
14
+ const reservedWorkspaceCollections = new Set([
15
+ ".git",
16
+ ".github",
17
+ ".devcontainer",
18
+ ".template",
19
+ "node_modules",
20
+ "dist",
21
+ "target",
22
+ ]);
23
+ const reservedPackageNames = new Set(["node_modules", "favicon.ico"]);
24
+ const unknownBlueprintField = "Unknown Blueprint v3 field";
25
+ const packageDefinitionEntries = {
26
+ name: v.string("Package name must be a string"),
27
+ path: v.string("Package Path must be a string"),
28
+ role: v.picklist(packageRoles, "Package Role must be cli-tool, runtime-service, shared-library, or native-package"),
29
+ };
30
+ function packageDefinitionMessage(issue) {
31
+ return issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
32
+ ? unknownBlueprintField
33
+ : "Package Definition must be an object";
34
+ }
35
+ const packageDefinitionSchema = v.strictObject({
36
+ packageDefinitionId: v.string("Package Definition ID must use the package-<sha256> format"),
37
+ ...packageDefinitionEntries,
38
+ }, packageDefinitionMessage);
39
+ const packageLinkIntentSchema = v.strictObject({
40
+ consumerPackagePath: v.string("Package Link Intent consumer Package Path must be a string"),
41
+ providerPackagePath: v.string("Package Link Intent provider Package Path must be a string"),
42
+ }, (issue) => issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
43
+ ? unknownBlueprintField
44
+ : "Package Link Intent must be an object");
45
+ const projectBlueprintSchema = v.strictObject({
46
+ schemaVersion: v.literal(3, (issue) => `Unsupported Local Template Metadata schema version ${String(issue.input)}; expected 3`),
47
+ packages: v.array(packageDefinitionSchema, "Package Definitions must be an array"),
48
+ packageLinkIntents: v.optional(v.array(packageLinkIntentSchema, "Package Link Intents must be an array")),
49
+ }, (issue) => issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
50
+ ? unknownBlueprintField
51
+ : "Local Template Metadata must be an object");
52
+ /** Canonical npm package-name predicate shared by Blueprint and input preparation. */
53
+ export function isValidNewNpmPackageName(value) {
54
+ if (value.length === 0 ||
55
+ value.length > 214 ||
56
+ value !== value.trim() ||
57
+ value !== value.toLowerCase() ||
58
+ value === "." ||
59
+ value === ".." ||
60
+ reservedPackageNames.has(value) ||
61
+ isBuiltin(value)) {
62
+ return false;
63
+ }
64
+ const scopedMatch = /^@([^/]+)\/([^/]+)$/.exec(value);
65
+ if (value.startsWith("@") && scopedMatch === null)
66
+ return false;
67
+ const scope = scopedMatch?.[1];
68
+ const leaf = scopedMatch?.[2] ?? value;
69
+ if (scope === undefined && /^[._-]/.test(leaf)) {
70
+ return false;
71
+ }
72
+ if (scope !== undefined && leaf.startsWith(".")) {
73
+ return false;
74
+ }
75
+ if (/[~'!()*]/.test(leaf))
76
+ return false;
77
+ return (encodeURIComponent(leaf) === leaf &&
78
+ (scope === undefined || encodeURIComponent(scope) === scope));
79
+ }
80
+ /** Canonical Package Path facts shared by Blueprint and input preparation. */
81
+ export function validateNewPackagePath(value) {
82
+ const workspaceCollection = value.split("/", 1)[0];
83
+ return {
84
+ hasValidShape: packagePath.test(value),
85
+ ...(reservedWorkspaceCollections.has(workspaceCollection)
86
+ ? { reservedWorkspaceCollection: workspaceCollection }
87
+ : {}),
88
+ };
89
+ }
90
+ function blueprintIssuePath(issue) {
91
+ let result = ".";
92
+ for (const item of issue.path ?? []) {
93
+ if (typeof item.key === "number") {
94
+ result += `[${item.key}]`;
95
+ }
96
+ else if (typeof item.key === "string") {
97
+ result += `${result === "." ? "" : "."}${item.key}`;
98
+ }
99
+ }
100
+ return result;
101
+ }
102
+ function blueprintStructureMessage(issue) {
103
+ if (issue.type !== "strict_object" ||
104
+ issue.path?.at(-1)?.origin !== "key" ||
105
+ issue.input !== undefined) {
106
+ return issue.message;
107
+ }
108
+ const path = blueprintIssuePath(issue);
109
+ if (path === ".schemaVersion") {
110
+ return "Unsupported Local Template Metadata schema version undefined; expected 3";
111
+ }
112
+ if (path === ".packages")
113
+ return "Package Definitions must be an array";
114
+ if (path.endsWith(".name"))
115
+ return "Package name must be a string";
116
+ if (path.endsWith(".packageDefinitionId")) {
117
+ return "Package Definition ID must use the package-<sha256> format";
118
+ }
119
+ if (path.endsWith(".path"))
120
+ return "Package Path must be a string";
121
+ if (path.endsWith(".role")) {
122
+ return "Package Role must be cli-tool, runtime-service, shared-library, or native-package";
123
+ }
124
+ if (path === ".packageLinkIntents") {
125
+ return "Package Link Intents must be an array";
126
+ }
127
+ if (path.endsWith(".consumerPackagePath")) {
128
+ return "Package Link Intent consumer Package Path must be a string";
129
+ }
130
+ if (path.endsWith(".providerPackagePath")) {
131
+ return "Package Link Intent provider Package Path must be a string";
132
+ }
133
+ return issue.message;
134
+ }
135
+ function validateBlueprintTopologySemantics(blueprint) {
136
+ const issues = [];
137
+ for (const [index, definition] of blueprint.packages.entries()) {
138
+ const itemPath = `.packages[${index}]`;
139
+ const pathValidation = validateNewPackagePath(definition.path);
140
+ if (!isValidNewNpmPackageName(definition.name)) {
141
+ issues.push({
142
+ path: `${itemPath}.name`,
143
+ message: "Package name must be a valid npm package name for new packages",
144
+ });
145
+ }
146
+ if (pathValidation.reservedWorkspaceCollection !== undefined) {
147
+ issues.push({
148
+ path: `${itemPath}.path`,
149
+ message: `Package Path ${definition.path} uses reserved workspace collection ${pathValidation.reservedWorkspaceCollection}`,
150
+ });
151
+ }
152
+ else if (!pathValidation.hasValidShape) {
153
+ issues.push({
154
+ path: `${itemPath}.path`,
155
+ message: "Package Path must be exactly two safe path segments",
156
+ });
157
+ }
158
+ }
159
+ for (const [property, label] of [
160
+ ["name", "Package name"],
161
+ ["path", "Package Path"],
162
+ ]) {
163
+ const seen = new Set();
164
+ for (const definition of blueprint.packages) {
165
+ const member = definition[property];
166
+ if (seen.has(member)) {
167
+ issues.push({
168
+ path: ".packages",
169
+ message: `${label} must be unique: ${member}`,
170
+ });
171
+ }
172
+ seen.add(member);
173
+ }
174
+ }
175
+ const paths = new Set(blueprint.packages.map((definition) => definition.path));
176
+ const links = new Set();
177
+ for (const [index, intent] of (blueprint.packageLinkIntents ?? []).entries()) {
178
+ const itemPath = `.packageLinkIntents[${index}]`;
179
+ if (!paths.has(intent.consumerPackagePath)) {
180
+ issues.push({
181
+ path: `${itemPath}.consumerPackagePath`,
182
+ message: "Package Link Intent references an unknown consumer package",
183
+ });
184
+ }
185
+ if (!paths.has(intent.providerPackagePath)) {
186
+ issues.push({
187
+ path: `${itemPath}.providerPackagePath`,
188
+ message: "Package Link Intent references an unknown provider package",
189
+ });
190
+ }
191
+ if (intent.consumerPackagePath === intent.providerPackagePath) {
192
+ issues.push({
193
+ path: itemPath,
194
+ message: "Package Link Intent cannot link a package to itself",
195
+ });
196
+ }
197
+ const key = `${intent.consumerPackagePath}\u0000${intent.providerPackagePath}`;
198
+ if (links.has(key)) {
199
+ issues.push({
200
+ path: itemPath,
201
+ message: "Package Link Intent must be unique",
202
+ });
203
+ }
204
+ links.add(key);
205
+ }
206
+ return issues;
207
+ }
208
+ function validatePersistedPackageIds(blueprint) {
209
+ const issues = [];
210
+ const seen = new Set();
211
+ for (const [index, definition] of blueprint.packages.entries()) {
212
+ if (!/^package-[a-f0-9]{64}$/.test(definition.packageDefinitionId)) {
213
+ issues.push({
214
+ path: `.packages[${index}].packageDefinitionId`,
215
+ message: "Package Definition ID must use the package-<sha256> format",
216
+ });
217
+ }
218
+ if (seen.has(definition.packageDefinitionId)) {
219
+ issues.push({
220
+ path: ".packages",
221
+ message: `Package Definition ID must be unique: ${definition.packageDefinitionId}`,
222
+ });
223
+ }
224
+ seen.add(definition.packageDefinitionId);
225
+ }
226
+ return issues;
227
+ }
228
+ /** Validates persisted metadata before it is used to plan or render changes. */
229
+ export function validateProjectBlueprint(value) {
230
+ const parsed = v.safeParse(projectBlueprintSchema, value);
231
+ if (!parsed.success) {
232
+ return {
233
+ ok: false,
234
+ issues: parsed.issues.map((issue) => ({
235
+ path: blueprintIssuePath(issue),
236
+ message: blueprintStructureMessage(issue),
237
+ })),
238
+ };
239
+ }
240
+ const blueprint = {
241
+ schemaVersion: parsed.output.schemaVersion,
242
+ packages: parsed.output.packages,
243
+ ...(parsed.output.packageLinkIntents === undefined
244
+ ? {}
245
+ : { packageLinkIntents: parsed.output.packageLinkIntents }),
246
+ };
247
+ const issues = [
248
+ ...validateBlueprintTopologySemantics(blueprint),
249
+ ...validatePersistedPackageIds(blueprint),
250
+ ];
251
+ return issues.length === 0
252
+ ? { ok: true, value: blueprint }
253
+ : { ok: false, issues };
254
+ }
255
+ /** Validates preset-owned topology before any persisted identity is assigned. */
256
+ export function assertProjectBlueprintDraft(value) {
257
+ const issues = validateBlueprintTopologySemantics(value);
258
+ if (issues.length > 0) {
259
+ throw new Error(issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
260
+ }
261
+ return value;
262
+ }
263
+ export function assertProjectBlueprint(value) {
264
+ const result = validateProjectBlueprint(value);
265
+ if (!result.ok) {
266
+ throw new Error(result.issues
267
+ .map((issue) => `${issue.path}: ${issue.message}`)
268
+ .join("\n"));
269
+ }
270
+ return result.value;
271
+ }
@@ -1,5 +1,5 @@
1
1
  import type { PackageContribution } from "./package-contribution.ts";
2
- import { type PackageRole, type ProjectBlueprintV2 } from "./project-blueprint-v2.ts";
2
+ import { type PackageRole, type ProjectBlueprint } from "./project-blueprint.ts";
3
3
  type PackageManifest = Readonly<Record<string, unknown>>;
4
4
  export type ExplicitProjectLinkPlan = {
5
5
  readonly manifestPatchesByPackagePath: ReadonlyMap<string, {
@@ -24,7 +24,7 @@ export declare function canProvideSourceConditionPackageNameImport(contribution:
24
24
  * string resource protocol is involved.
25
25
  */
26
26
  export declare function planExplicitProjectLinks(options: {
27
- readonly blueprint: ProjectBlueprintV2;
27
+ readonly blueprint: ProjectBlueprint;
28
28
  readonly contributions: readonly PackageContribution[];
29
29
  readonly manifestTruthByPackagePath?: ReadonlyMap<string, PackageManifest>;
30
30
  }): ExplicitProjectLinkPlan;
@@ -1,4 +1,4 @@
1
- import { assertProjectBlueprintV2, } from "./project-blueprint-v2.js";
1
+ import { assertProjectBlueprint, } from "./project-blueprint.js";
2
2
  export function canConsumeNodePackageNameImport(contribution, manifest = contribution.manifest) {
3
3
  return (contribution.definition.role !== "native-package" &&
4
4
  manifest.type === "module");
@@ -74,7 +74,7 @@ function exposesImportablePackageRoot(manifest) {
74
74
  * string resource protocol is involved.
75
75
  */
76
76
  export function planExplicitProjectLinks(options) {
77
- const blueprint = assertProjectBlueprintV2(options.blueprint);
77
+ const blueprint = assertProjectBlueprint(options.blueprint);
78
78
  const contributionsByPath = new Map(options.contributions.map((contribution) => [
79
79
  contribution.definition.path,
80
80
  contribution,
@@ -87,9 +87,10 @@ export type ProjectProjectionAction = {
87
87
  };
88
88
  export type MaterializeProjectProjectionOptions = {
89
89
  readonly operations: readonly RenderOperation[];
90
- readonly variables?: Readonly<Record<string, string>>;
91
90
  readonly reconciliation?: readonly ProjectProjectionReconciliation[];
92
91
  };
92
+ /** Pure structural preflight for a renderer-facing Project Projection plan. */
93
+ export declare function validateProjectProjectionPlan(options: MaterializeProjectProjectionOptions): readonly string[];
93
94
  export declare function reconcileProjectProjections(options: {
94
95
  readonly before: ProjectProjection;
95
96
  readonly after: ProjectProjection;
@@ -11,30 +11,18 @@ class ProjectProjectionPathPreconditionError extends Error {
11
11
  this.conflicts = conflicts;
12
12
  }
13
13
  }
14
- function expandTemplatePath(templatePath, variables) {
15
- return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/gu, (_placeholder, name) => {
16
- const value = variables[name];
17
- if (!value)
18
- throw new Error(`Missing renderer variable: ${name}`);
19
- if (!/^[A-Za-z0-9._-]+$/u.test(value)) {
20
- throw new Error(`Renderer variable ${name} is not safe for a path segment`);
21
- }
22
- return value;
23
- });
24
- }
25
- function normalizedOutputPath(operation, variables) {
14
+ function normalizedOutputPath(operation) {
26
15
  const operationPath = operation.kind === "setExecutable" || operation.kind === "replaceAnchors"
27
16
  ? operation.path
28
17
  : operation.to;
29
- const expanded = expandTemplatePath(operationPath, variables);
30
- if (path.isAbsolute(expanded)) {
31
- throw new Error(`Project Projection paths must be relative: ${expanded}`);
18
+ if (path.isAbsolute(operationPath)) {
19
+ throw new Error(`Project Projection paths must be relative: ${operationPath}`);
32
20
  }
33
- const normalized = path.posix.normalize(expanded.split(path.sep).join("/"));
21
+ const normalized = path.posix.normalize(operationPath.split(path.sep).join("/"));
34
22
  if (normalized === "." ||
35
23
  normalized === ".." ||
36
24
  normalized.startsWith("../")) {
37
- throw new Error(`Project Projection path escapes its root: ${expanded}`);
25
+ throw new Error(`Project Projection path escapes its root: ${operationPath}`);
38
26
  }
39
27
  return normalized;
40
28
  }
@@ -55,6 +43,103 @@ function operationDriver(operation) {
55
43
  return undefined;
56
44
  }
57
45
  }
46
+ /** Pure structural preflight for a renderer-facing Project Projection plan. */
47
+ export function validateProjectProjectionPlan(options) {
48
+ const diagnostics = [];
49
+ const contentKindByPath = new Map();
50
+ const contentTypeByPath = new Map();
51
+ const transformationKindByPath = new Map();
52
+ const finalPaths = new Set();
53
+ for (const operation of options.operations) {
54
+ let outputPath;
55
+ try {
56
+ outputPath = normalizedOutputPath(operation);
57
+ }
58
+ catch (error) {
59
+ diagnostics.push(error instanceof Error ? error.message : String(error));
60
+ continue;
61
+ }
62
+ finalPaths.add(outputPath);
63
+ const precedingKind = contentKindByPath.get(outputPath);
64
+ if (operation.kind === "setExecutable" ||
65
+ operation.kind === "replaceAnchors") {
66
+ if (precedingKind === undefined) {
67
+ diagnostics.push(`Project Projection ${operation.kind} requires a preceding content-producing operation: ${outputPath}`);
68
+ }
69
+ const hasCompatibleContent = operation.kind !== "replaceAnchors" ||
70
+ contentTypeByPath.get(outputPath) !== "json";
71
+ if (!hasCompatibleContent) {
72
+ diagnostics.push(`Project Projection replaceAnchors requires text-compatible content: ${outputPath} follows ${precedingKind}`);
73
+ }
74
+ if (precedingKind !== undefined && hasCompatibleContent) {
75
+ if (operation.kind === "replaceAnchors") {
76
+ contentTypeByPath.set(outputPath, "text");
77
+ }
78
+ transformationKindByPath.set(outputPath, operation.kind);
79
+ }
80
+ continue;
81
+ }
82
+ if (operation.kind === "mergeJson" ||
83
+ operation.kind === "mergeJsonTemplate") {
84
+ const transformationKind = transformationKindByPath.get(outputPath);
85
+ if (transformationKind !== undefined) {
86
+ diagnostics.push(`Project Projection ${operation.kind} may not overwrite content after ${transformationKind}: ${outputPath}`);
87
+ }
88
+ if (contentTypeByPath.get(outputPath) === "text") {
89
+ diagnostics.push(`Project Projection ${operation.kind} requires JSON-compatible content: ${outputPath} follows ${precedingKind}`);
90
+ }
91
+ if (precedingKind === undefined) {
92
+ contentKindByPath.set(outputPath, operation.kind);
93
+ }
94
+ contentTypeByPath.set(outputPath, "json");
95
+ transformationKindByPath.delete(outputPath);
96
+ continue;
97
+ }
98
+ const overwrite = "overwrite" in operation && operation.overwrite;
99
+ const transformationKind = transformationKindByPath.get(outputPath);
100
+ if (overwrite && transformationKind !== undefined) {
101
+ diagnostics.push(`Project Projection ${operation.kind} may not overwrite content after ${transformationKind}: ${outputPath}`);
102
+ }
103
+ if (precedingKind !== undefined && !overwrite) {
104
+ diagnostics.push(`Project Projection collision at ${outputPath}: ${operation.kind} follows ${precedingKind} without overwrite`);
105
+ }
106
+ contentKindByPath.set(outputPath, operation.kind);
107
+ if (operation.kind === "writeJson") {
108
+ contentTypeByPath.set(outputPath, "json");
109
+ }
110
+ else if (operation.kind === "writeText") {
111
+ try {
112
+ JSON.parse(operation.text);
113
+ contentTypeByPath.set(outputPath, "json");
114
+ }
115
+ catch {
116
+ contentTypeByPath.set(outputPath, "text");
117
+ }
118
+ }
119
+ else {
120
+ contentTypeByPath.set(outputPath, "unknown");
121
+ }
122
+ transformationKindByPath.delete(outputPath);
123
+ }
124
+ const orderedPaths = [...finalPaths].toSorted();
125
+ for (const candidate of orderedPaths) {
126
+ const ancestor = orderedPaths.find((other) => other !== candidate && candidate.startsWith(`${other}/`));
127
+ if (ancestor !== undefined) {
128
+ diagnostics.push(`Project Projection path collision: file ${ancestor} is an ancestor of ${candidate}`);
129
+ }
130
+ }
131
+ const reconciliationPaths = new Set();
132
+ for (const reconciliation of options.reconciliation ?? []) {
133
+ if (reconciliationPaths.has(reconciliation.path)) {
134
+ diagnostics.push(`Project Projection reconciliation paths must be unique: ${reconciliation.path}`);
135
+ }
136
+ else if (!finalPaths.has(reconciliation.path)) {
137
+ diagnostics.push(`Project Projection reconciliation references an unknown path: ${reconciliation.path}`);
138
+ }
139
+ reconciliationPaths.add(reconciliation.path);
140
+ }
141
+ return diagnostics;
142
+ }
58
143
  function bytesEqual(left, right) {
59
144
  return (left.byteLength === right.byteLength &&
60
145
  left.every((value, index) => value === right[index]));
@@ -1031,12 +1116,15 @@ export function createProjectProjectionReconciler(dependencies = {}) {
1031
1116
  }
1032
1117
  export const reconcileAndApplyProjectProjections = createProjectProjectionReconciler();
1033
1118
  export async function materializeProjectProjection(options) {
1034
- const variables = options.variables ?? {};
1119
+ const planDiagnostics = validateProjectProjectionPlan(options);
1120
+ if (planDiagnostics.length > 0) {
1121
+ throw new Error(planDiagnostics.join("\n"));
1122
+ }
1035
1123
  const finalPaths = new Set();
1036
1124
  const contentPaths = new Set();
1037
1125
  const policies = new Map();
1038
1126
  for (const operation of options.operations) {
1039
- const outputPath = normalizedOutputPath(operation, variables);
1127
+ const outputPath = normalizedOutputPath(operation);
1040
1128
  finalPaths.add(outputPath);
1041
1129
  if (operation.kind === "setExecutable") {
1042
1130
  if (!contentPaths.has(outputPath)) {
@@ -1067,7 +1155,6 @@ export async function materializeProjectProjection(options) {
1067
1155
  try {
1068
1156
  await renderProject({
1069
1157
  targetRoot,
1070
- variables: { ...variables },
1071
1158
  operations: [...options.operations],
1072
1159
  });
1073
1160
  const entries = await Promise.all([...finalPaths].toSorted().map(async (relativePath) => {
@@ -1,4 +1,3 @@
1
- type RenderVariables = Record<string, string>;
2
1
  export type RenderOperationProvenance = {
3
2
  readonly definitionName: string;
4
3
  readonly plannerSourceFile: string;
@@ -96,7 +95,6 @@ export type ReplaceAnchorsOperation = {
96
95
  export type RenderOperation = CopyFileOperation | WriteJsonOperation | MergeJsonOperation | MergeJsonTemplateOperation | WriteTextOperation | WriteTextFromFragmentsOperation | WriteTextTemplateOperation | SetExecutableOperation | ReplaceAnchorsOperation;
97
96
  export type RenderProjectOptions = {
98
97
  targetRoot: string;
99
- variables?: RenderVariables | undefined;
100
98
  operations: RenderOperation[];
101
99
  };
102
100
  export declare function renderProject(options: RenderProjectOptions): Promise<void>;