@ykdz/template 0.0.3 → 0.0.5

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 (95) hide show
  1. package/README.md +1 -1
  2. package/dist/src/cli.d.ts +0 -1
  3. package/dist/src/cli.js +26 -2
  4. package/dist/src/declarations.d.ts +38 -3
  5. package/dist/src/declarations.js +75 -36
  6. package/dist/src/dependency-catalog.d.ts +9 -1
  7. package/dist/src/dependency-catalog.js +69 -0
  8. package/dist/src/devcontainer.d.ts +49 -9
  9. package/dist/src/devcontainer.js +192 -14
  10. package/dist/src/editor-customization.d.ts +0 -1
  11. package/dist/src/editor-customization.js +54 -47
  12. package/dist/src/generation-context.d.ts +0 -1
  13. package/dist/src/generation-context.js +4 -1
  14. package/dist/src/module-graph.d.ts +8 -5
  15. package/dist/src/module-graph.js +20 -2
  16. package/dist/src/next-step-instructions.d.ts +0 -1
  17. package/dist/src/next-step-instructions.js +35 -0
  18. package/dist/src/package-addition-support.d.ts +5 -0
  19. package/dist/src/package-addition-support.js +4 -0
  20. package/dist/src/package-addition.d.ts +2 -1
  21. package/dist/src/package-addition.js +557 -55
  22. package/dist/src/package-linking.d.ts +55 -0
  23. package/dist/src/package-linking.js +116 -0
  24. package/dist/src/preset-projection.d.ts +4 -2
  25. package/dist/src/project-github.d.ts +3 -2
  26. package/dist/src/project-github.js +24 -6
  27. package/dist/src/renderer.d.ts +16 -2
  28. package/dist/src/renderer.js +96 -12
  29. package/dist/src/runtime-paths.d.ts +0 -1
  30. package/dist/src/template-boundary-check.d.ts +34 -0
  31. package/dist/src/template-boundary-check.js +448 -0
  32. package/dist/src/toolchain-resolution.d.ts +0 -1
  33. package/dist/templates/hono-api/projection.d.ts +2 -3
  34. package/dist/templates/hono-api/projection.js +258 -76
  35. package/dist/templates/projection-plans.d.ts +0 -1
  36. package/dist/templates/registry.d.ts +0 -1
  37. package/dist/templates/registry.js +5 -2
  38. package/dist/templates/rust-bin/projection.d.ts +2 -3
  39. package/dist/templates/rust-bin/projection.js +171 -53
  40. package/dist/templates/ts-lib/projection.d.ts +2 -3
  41. package/dist/templates/ts-lib/projection.js +251 -87
  42. package/dist/templates/vue-app/projection.d.ts +2 -3
  43. package/dist/templates/vue-app/projection.js +266 -94
  44. package/dist/templates/vue-hono-app/projection.d.ts +0 -1
  45. package/dist/templates/vue-hono-app/projection.js +232 -108
  46. package/package.json +10 -5
  47. package/pnpm-workspace.yaml +1 -0
  48. package/templates/hono-api/.github/dependabot.yml +13 -0
  49. package/templates/hono-api/src/server.ts +1 -1
  50. package/templates/hono-api/test/app.test.ts +1 -1
  51. package/templates/rust-bin/.github/dependabot.yml +18 -1
  52. package/templates/rust-bin/rust-toolchain.toml +3 -0
  53. package/templates/rust-bin/rustfmt.toml +2 -0
  54. package/templates/shared/devcontainer/browser-test.Dockerfile +2 -0
  55. package/templates/shared/devcontainer/node-pnpm.Dockerfile +6 -0
  56. package/templates/shared/devcontainer/rust.Dockerfile +16 -0
  57. package/templates/shared/editor-customization/capabilities.json +56 -0
  58. package/templates/ts-lib/.github/dependabot.yml +13 -0
  59. package/templates/ts-lib/src/index.ts +5 -1
  60. package/templates/ts-lib/src/name-schema.ts +3 -0
  61. package/templates/vue-app/.github/dependabot.yml +13 -0
  62. package/templates/vue-app/.github/workflows/check.yml +1 -1
  63. package/templates/vue-app/src/App.vue +1 -1
  64. package/templates/vue-app/test/app.test.ts +1 -1
  65. package/templates/vue-hono-app/.github/dependabot.yml +13 -0
  66. package/templates/vue-hono-app/web/src/App.vue +2 -2
  67. package/templates/vue-hono-app/web/test/app.test.ts +1 -1
  68. package/dist/src/cli.d.ts.map +0 -1
  69. package/dist/src/declarations.d.ts.map +0 -1
  70. package/dist/src/dependency-catalog.d.ts.map +0 -1
  71. package/dist/src/devcontainer.d.ts.map +0 -1
  72. package/dist/src/editor-customization.d.ts.map +0 -1
  73. package/dist/src/generation-context.d.ts.map +0 -1
  74. package/dist/src/module-graph.d.ts.map +0 -1
  75. package/dist/src/next-step-instructions.d.ts.map +0 -1
  76. package/dist/src/package-addition.d.ts.map +0 -1
  77. package/dist/src/preset-projection.d.ts.map +0 -1
  78. package/dist/src/project-github.d.ts.map +0 -1
  79. package/dist/src/renderer.d.ts.map +0 -1
  80. package/dist/src/runtime-paths.d.ts.map +0 -1
  81. package/dist/src/toolchain-resolution.d.ts.map +0 -1
  82. package/dist/templates/hono-api/projection.d.ts.map +0 -1
  83. package/dist/templates/projection-plans.d.ts.map +0 -1
  84. package/dist/templates/registry.d.ts.map +0 -1
  85. package/dist/templates/rust-bin/projection.d.ts.map +0 -1
  86. package/dist/templates/ts-lib/projection.d.ts.map +0 -1
  87. package/dist/templates/vue-app/projection.d.ts.map +0 -1
  88. package/dist/templates/vue-hono-app/projection.d.ts.map +0 -1
  89. package/templates/hono-api/projection.ts +0 -453
  90. package/templates/projection-plans.ts +0 -135
  91. package/templates/registry.ts +0 -59
  92. package/templates/rust-bin/projection.ts +0 -346
  93. package/templates/ts-lib/projection.ts +0 -454
  94. package/templates/vue-app/projection.ts +0 -687
  95. package/templates/vue-hono-app/projection.ts +0 -669
@@ -0,0 +1,55 @@
1
+ export type PackageRole = "runtime-service" | "shared-library";
2
+ export type PackageSourcePreset = "hono-api" | "ts-lib" | "vue-app";
3
+ export type PackageDefinition = {
4
+ readonly name: string;
5
+ readonly path: string;
6
+ readonly role?: PackageRole;
7
+ readonly sourcePreset?: PackageSourcePreset;
8
+ };
9
+ export type PackageLinkIntent = {
10
+ readonly consumerPackagePath: string;
11
+ readonly providerPackagePath: string;
12
+ };
13
+ export type JitSourcePackageExposure = {
14
+ readonly kind: "jit-source";
15
+ readonly entrypoint: string;
16
+ readonly packageLocalImportPattern: string;
17
+ readonly packageLocalImportTarget: string;
18
+ };
19
+ export type CompiledPackageExposure = {
20
+ readonly kind: "compiled";
21
+ readonly entrypoint: string;
22
+ readonly sourceTypes: string;
23
+ readonly packageLocalImportPattern: string;
24
+ readonly packageLocalImportRuntimeTarget: string;
25
+ readonly packageLocalImportTypesTarget: string;
26
+ };
27
+ export type PackageExposure = CompiledPackageExposure | JitSourcePackageExposure;
28
+ export type PackageLinkPlan = {
29
+ readonly exposuresByPackagePath: ReadonlyMap<string, PackageExposure>;
30
+ readonly manifestDependenciesByPackagePath: ReadonlyMap<string, Readonly<Record<string, "workspace:*">>>;
31
+ readonly turboTasks: TurboTaskGraph;
32
+ };
33
+ export type PackageManifestExposureFields = {
34
+ readonly exports: Record<string, unknown>;
35
+ readonly imports: Record<string, unknown>;
36
+ readonly types?: string;
37
+ };
38
+ export type PackageLinkBoundaryDirection = "consumer" | "provider";
39
+ export type TurboTaskDefinition = {
40
+ readonly dependsOn?: readonly string[];
41
+ readonly outputs?: readonly string[];
42
+ readonly cache?: boolean;
43
+ };
44
+ export type TurboTaskGraph = Readonly<Record<string, TurboTaskDefinition>>;
45
+ export type PackageTurboTaskOptions = {
46
+ readonly dependencyBuildsRequired: boolean;
47
+ };
48
+ export declare function derivePackageExposure(definition: PackageDefinition): PackageExposure;
49
+ export declare function assertTypeScriptPackageBoundaryForLinkIntent(definition: PackageDefinition, direction: PackageLinkBoundaryDirection): asserts definition is PackageDefinition & {
50
+ readonly role: PackageRole;
51
+ readonly sourcePreset: PackageSourcePreset;
52
+ };
53
+ export declare function planPackageLinks(definitions: readonly PackageDefinition[], intents?: readonly PackageLinkIntent[]): PackageLinkPlan;
54
+ export declare function packageTurboTasks({ dependencyBuildsRequired, }: PackageTurboTaskOptions): TurboTaskGraph;
55
+ export declare function packageManifestExposureFields(exposure: PackageExposure): PackageManifestExposureFields;
@@ -0,0 +1,116 @@
1
+ export function derivePackageExposure(definition) {
2
+ if (definition.role === "shared-library" &&
3
+ definition.sourcePreset === "ts-lib") {
4
+ return {
5
+ kind: "jit-source",
6
+ entrypoint: "./src/index.ts",
7
+ packageLocalImportPattern: "#/*",
8
+ packageLocalImportTarget: "./src/*.ts",
9
+ };
10
+ }
11
+ if (definition.role === "runtime-service" &&
12
+ definition.sourcePreset === "hono-api") {
13
+ return {
14
+ kind: "compiled",
15
+ entrypoint: "./dist/index.js",
16
+ sourceTypes: "./src/index.ts",
17
+ packageLocalImportPattern: "#/*",
18
+ packageLocalImportRuntimeTarget: "./dist/*.js",
19
+ packageLocalImportTypesTarget: "./src/*.ts",
20
+ };
21
+ }
22
+ throw new Error(`Unsupported Package Exposure for ${definition.name} at ${definition.path}`);
23
+ }
24
+ export function assertTypeScriptPackageBoundaryForLinkIntent(definition, direction) {
25
+ if (definition.role !== undefined && definition.sourcePreset !== undefined) {
26
+ return;
27
+ }
28
+ const relationship = direction === "consumer" ? "from native package" : "to native package";
29
+ throw new Error(`Package Link Intent ${relationship} ${definition.path} is unsupported in V1 TypeScript-only Project Linking`);
30
+ }
31
+ export function planPackageLinks(definitions, intents = []) {
32
+ const definitionsByPath = new Map(definitions.map((definition) => [definition.path, definition]));
33
+ const manifestDependenciesByPackagePath = new Map();
34
+ for (const intent of intents) {
35
+ const consumer = definitionsByPath.get(intent.consumerPackagePath);
36
+ const provider = definitionsByPath.get(intent.providerPackagePath);
37
+ if (consumer !== undefined) {
38
+ assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
39
+ }
40
+ if (provider === undefined) {
41
+ throw new Error(`Package Link Intent references unknown provider package at ${intent.providerPackagePath}`);
42
+ }
43
+ assertTypeScriptPackageBoundaryForLinkIntent(provider, "provider");
44
+ }
45
+ const exposuresByPackagePath = new Map(definitions.map((definition) => [
46
+ definition.path,
47
+ derivePackageExposure(definition),
48
+ ]));
49
+ let dependencyBuildsRequired = false;
50
+ for (const intent of intents) {
51
+ const provider = definitionsByPath.get(intent.providerPackagePath);
52
+ if (provider === undefined) {
53
+ throw new Error(`Package Link Intent references unknown provider package at ${intent.providerPackagePath}`);
54
+ }
55
+ if (exposuresByPackagePath.get(provider.path)?.kind === "compiled") {
56
+ dependencyBuildsRequired = true;
57
+ }
58
+ const dependencies = manifestDependenciesByPackagePath.get(intent.consumerPackagePath) ?? {};
59
+ dependencies[provider.name] = "workspace:*";
60
+ manifestDependenciesByPackagePath.set(intent.consumerPackagePath, dependencies);
61
+ }
62
+ return {
63
+ exposuresByPackagePath,
64
+ manifestDependenciesByPackagePath,
65
+ turboTasks: packageTurboTasks({ dependencyBuildsRequired }),
66
+ };
67
+ }
68
+ export function packageTurboTasks({ dependencyBuildsRequired, }) {
69
+ return {
70
+ typecheck: { dependsOn: ["^typecheck"] },
71
+ build: dependencyBuildsRequired
72
+ ? { dependsOn: ["^build"], outputs: ["dist/**"] }
73
+ : { outputs: ["dist/**"] },
74
+ test: { dependsOn: ["^typecheck"] },
75
+ "test:e2e": {
76
+ dependsOn: dependencyBuildsRequired ? ["build", "^build"] : ["build"],
77
+ },
78
+ check: { dependsOn: ["typecheck", "build", "test", "test:e2e"] },
79
+ fix: { cache: false },
80
+ };
81
+ }
82
+ export function packageManifestExposureFields(exposure) {
83
+ switch (exposure.kind) {
84
+ case "jit-source":
85
+ return {
86
+ imports: {
87
+ [exposure.packageLocalImportPattern]: {
88
+ default: exposure.packageLocalImportTarget,
89
+ types: exposure.packageLocalImportTarget,
90
+ },
91
+ },
92
+ exports: {
93
+ ".": {
94
+ default: exposure.entrypoint,
95
+ types: exposure.entrypoint,
96
+ },
97
+ },
98
+ };
99
+ case "compiled":
100
+ return {
101
+ types: exposure.sourceTypes,
102
+ imports: {
103
+ [exposure.packageLocalImportPattern]: {
104
+ default: exposure.packageLocalImportRuntimeTarget,
105
+ types: exposure.packageLocalImportTypesTarget,
106
+ },
107
+ },
108
+ exports: {
109
+ ".": {
110
+ default: exposure.entrypoint,
111
+ types: exposure.sourceTypes,
112
+ },
113
+ },
114
+ };
115
+ }
116
+ }
@@ -2,6 +2,7 @@ import type { BuiltInPreset, ProjectBlueprint } from "./declarations.js";
2
2
  import type { GenerationContext } from "./generation-context.js";
3
3
  import { type CheckPlan, type FixPlan } from "./module-graph.js";
4
4
  import { type NextStepInstruction } from "./next-step-instructions.js";
5
+ import type { PackageRole, PackageSourcePreset } from "./package-linking.js";
5
6
  import type { DependencyMaintenancePolicy } from "./project-github.js";
6
7
  import type { RenderOperation } from "./renderer.js";
7
8
  export type PresetPackageAdditionOptions = {
@@ -9,12 +10,14 @@ export type PresetPackageAdditionOptions = {
9
10
  readonly blueprint: ProjectBlueprint;
10
11
  readonly packageLeafName: string;
11
12
  readonly packageName: string;
13
+ readonly packagePath: string;
12
14
  readonly nodeVersion: string;
13
15
  };
14
16
  export type PresetPackageAdditionPlan = {
15
17
  readonly packagePath: string;
16
18
  readonly workspacePackageGlob: string;
17
- readonly rootTsconfigReferences: readonly string[];
19
+ readonly packageRole: PackageRole;
20
+ readonly packageSourcePreset: PackageSourcePreset;
18
21
  readonly sourceRoot: string;
19
22
  readonly sourceRoots?: Record<string, string>;
20
23
  readonly operations: readonly RenderOperation[];
@@ -63,4 +66,3 @@ export declare function planNextStepInstructionsForProjection(options: {
63
66
  readonly targetDir: string;
64
67
  readonly plan: PresetProjectionPlan;
65
68
  }): readonly NextStepInstruction[];
66
- //# sourceMappingURL=preset-projection.d.ts.map
@@ -12,9 +12,11 @@ export type PnpmTaskLayer = {
12
12
  readonly installCommand: "pnpm install";
13
13
  readonly checkCommand: "pnpm run check";
14
14
  };
15
- export type DependencyEcosystem = "npm" | "cargo" | "github-actions";
15
+ export type DependencyEcosystem = "npm" | "cargo" | "github-actions" | "docker" | "rust-toolchain";
16
+ export type DependabotDirectory = `/${string}`;
16
17
  export type DependencyMaintenancePolicy = {
17
18
  readonly ecosystems: DependencyEcosystem[];
19
+ readonly directories?: Partial<Record<DependencyEcosystem, DependabotDirectory>>;
18
20
  readonly interval: "weekly";
19
21
  };
20
22
  type ProjectCheckWorkflowOptions = {
@@ -26,4 +28,3 @@ type ProjectCheckWorkflowOptions = {
26
28
  export declare function projectCheckWorkflow(options: ProjectCheckWorkflowOptions): string;
27
29
  export declare function projectDependabotConfig(policy: DependencyMaintenancePolicy): string;
28
30
  export {};
29
- //# sourceMappingURL=project-github.d.ts.map
@@ -44,16 +44,34 @@ export function projectCheckWorkflow(options) {
44
44
  export function projectDependabotConfig(policy) {
45
45
  return [
46
46
  "version: 2",
47
+ "",
47
48
  "updates:",
48
- ...policy.ecosystems.flatMap((ecosystem) => [
49
- ` - package-ecosystem: ${ecosystem}`,
50
- " directory: /",
51
- " schedule:",
52
- ` interval: ${policy.interval}`,
53
- ]),
49
+ ...policy.ecosystems.flatMap((ecosystem) => renderDependabotUpdate(ecosystem, policy.directories?.[ecosystem] ??
50
+ defaultDependabotDirectory(ecosystem), policy.interval)),
54
51
  "",
55
52
  ].join("\n");
56
53
  }
54
+ function renderDependabotUpdate(ecosystem, directory, interval) {
55
+ const lines = [
56
+ ` - package-ecosystem: ${ecosystem}`,
57
+ ` directory: ${renderDependabotDirectory(ecosystem, directory)}`,
58
+ " schedule:",
59
+ ` interval: ${interval}`,
60
+ ];
61
+ if (ecosystem === "npm") {
62
+ lines.push(" ignore:", ' - dependency-name: "@types/node"', " update-types:", " - version-update:semver-major");
63
+ }
64
+ if (ecosystem === "docker") {
65
+ lines.push(" ignore:", " - dependency-name: mcr.microsoft.com/devcontainers/typescript-node", " update-types:", " - version-update:semver-major");
66
+ }
67
+ return lines;
68
+ }
69
+ function renderDependabotDirectory(ecosystem, directory) {
70
+ return ecosystem === "cargo" ? JSON.stringify(directory) : directory;
71
+ }
72
+ function defaultDependabotDirectory(ecosystem) {
73
+ return ecosystem === "docker" ? "/.devcontainer" : "/";
74
+ }
57
75
  function renderCiEnvironmentNeedCommand(need) {
58
76
  if (need.kind === "playwright-browser-assets" &&
59
77
  need.owner.path === "apps/web") {
@@ -21,6 +21,21 @@ export type WriteTextOperation = {
21
21
  to: string;
22
22
  text: string;
23
23
  };
24
+ export type WriteTextFromFragmentsOperation = {
25
+ kind: "writeTextFromFragments";
26
+ to: string;
27
+ fragments: readonly {
28
+ from: string;
29
+ sourceRoot?: string;
30
+ }[];
31
+ };
32
+ export type WriteTextTemplateOperation = {
33
+ kind: "writeTextTemplate";
34
+ from: string;
35
+ to: string;
36
+ sourceRoot?: string;
37
+ replacements: Record<string, string>;
38
+ };
24
39
  export type SetExecutableOperation = {
25
40
  kind: "setExecutable";
26
41
  path: string;
@@ -32,7 +47,7 @@ export type ReplaceAnchorsOperation = {
32
47
  language: "typescript";
33
48
  replacements: Record<string, string>;
34
49
  };
35
- export type RenderOperation = CopyFileOperation | WriteJsonOperation | MergeJsonOperation | WriteTextOperation | SetExecutableOperation | ReplaceAnchorsOperation;
50
+ export type RenderOperation = CopyFileOperation | WriteJsonOperation | MergeJsonOperation | WriteTextOperation | WriteTextFromFragmentsOperation | WriteTextTemplateOperation | SetExecutableOperation | ReplaceAnchorsOperation;
36
51
  export type RenderProjectOptions = {
37
52
  sourceRoot: string;
38
53
  sourceRoots?: Record<string, string>;
@@ -43,4 +58,3 @@ export type RenderProjectOptions = {
43
58
  export declare function renderProject(options: RenderProjectOptions): Promise<void>;
44
59
  export declare function renderNewProject(options: RenderProjectOptions): Promise<void>;
45
60
  export {};
46
- //# sourceMappingURL=renderer.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { constants } from "node:fs";
2
- import { chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
2
+ import { chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, writeFile, } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  function expandTemplatePath(templatePath, variables) {
5
5
  return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_, name) => {
@@ -19,7 +19,8 @@ function resolveContainedPath(root, relativePath) {
19
19
  }
20
20
  const resolvedRoot = path.resolve(root);
21
21
  const resolvedPath = path.resolve(resolvedRoot, relativePath);
22
- const insideRoot = resolvedPath === resolvedRoot || resolvedPath.startsWith(`${resolvedRoot}${path.sep}`);
22
+ const insideRoot = resolvedPath === resolvedRoot ||
23
+ resolvedPath.startsWith(`${resolvedRoot}${path.sep}`);
23
24
  if (!insideRoot) {
24
25
  throw new Error(`Renderer path escapes its root: ${relativePath}`);
25
26
  }
@@ -47,7 +48,7 @@ function isRecord(value) {
47
48
  return typeof value === "object" && value !== null && !Array.isArray(value);
48
49
  }
49
50
  function isScalarJsonValue(value) {
50
- return value === null || ["boolean", "number", "string"].includes(typeof value);
51
+ return (value === null || ["boolean", "number", "string"].includes(typeof value));
51
52
  }
52
53
  function serializeJsonValue(value, indentation, pathSegments, multilineArrays, rootKeyOrder) {
53
54
  if (Array.isArray(value)) {
@@ -81,6 +82,7 @@ const packageJsonRootKeyOrder = [
81
82
  "files",
82
83
  "type",
83
84
  "types",
85
+ "imports",
84
86
  "exports",
85
87
  "scripts",
86
88
  "dependencies",
@@ -88,7 +90,13 @@ const packageJsonRootKeyOrder = [
88
90
  "peerDependencies",
89
91
  "optionalDependencies",
90
92
  "engines",
91
- "packageManager"
93
+ "packageManager",
94
+ ];
95
+ const devcontainerJsonRootKeyOrder = [
96
+ "name",
97
+ "build",
98
+ "image",
99
+ "customizations",
92
100
  ];
93
101
  function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
94
102
  if (pathSegments.length === 0 && rootKeyOrder) {
@@ -101,10 +109,13 @@ function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
101
109
  return left.localeCompare(right);
102
110
  }
103
111
  function rootKeyOrderForPath(toPath) {
104
- if (path.basename(toPath) !== "package.json") {
105
- return undefined;
112
+ if (path.basename(toPath) === "package.json") {
113
+ return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
114
+ }
115
+ if (toPath.split(path.sep).join("/") === ".devcontainer/devcontainer.json") {
116
+ return new Map(devcontainerJsonRootKeyOrder.map((key, index) => [key, index]));
106
117
  }
107
- return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
118
+ return undefined;
108
119
  }
109
120
  function serializeJson(value, multilineArrays = [], rootKeyOrder) {
110
121
  return `${serializeJsonValue(value, 0, [], new Set(multilineArrays), rootKeyOrder)}\n`;
@@ -151,7 +162,7 @@ const foundationTextFiles = new Set([
151
162
  "README",
152
163
  "README.md",
153
164
  "LICENSE",
154
- "CHANGELOG.md"
165
+ "CHANGELOG.md",
155
166
  ]);
156
167
  function assertFoundationTextPath(relativePath) {
157
168
  const normalizedPath = relativePath.split(path.sep).join("/");
@@ -162,11 +173,20 @@ function assertFoundationTextPath(relativePath) {
162
173
  if (isRootLevel && relativePath.endsWith(".md")) {
163
174
  return;
164
175
  }
165
- if (isRootLevel && (relativePath.endsWith(".yaml") || relativePath.endsWith(".yml"))) {
176
+ if (isRootLevel &&
177
+ (relativePath.endsWith(".yaml") || relativePath.endsWith(".yml"))) {
166
178
  return;
167
179
  }
168
180
  if (isRootLevel &&
169
- ["Cargo.toml", "Cargo.lock", "rustfmt.toml"].includes(normalizedPath)) {
181
+ [
182
+ "Cargo.toml",
183
+ "Cargo.lock",
184
+ "rustfmt.toml",
185
+ "rust-toolchain.toml",
186
+ ].includes(normalizedPath)) {
187
+ return;
188
+ }
189
+ if (/^packages\/[A-Za-z0-9._-]+\/(?:Cargo\.toml|Cargo\.lock|rustfmt\.toml)$/.test(normalizedPath)) {
170
190
  return;
171
191
  }
172
192
  if (/^\.github\/workflows\/[A-Za-z0-9._-]+\.ya?ml$/.test(normalizedPath)) {
@@ -175,6 +195,9 @@ function assertFoundationTextPath(relativePath) {
175
195
  if (/^\.github\/dependabot\.ya?ml$/.test(normalizedPath)) {
176
196
  return;
177
197
  }
198
+ if (normalizedPath === ".devcontainer/Dockerfile") {
199
+ return;
200
+ }
178
201
  throw new Error(`Text output is limited to foundation files: ${relativePath}`);
179
202
  }
180
203
  async function renderWriteText(operation, options) {
@@ -184,6 +207,56 @@ async function renderWriteText(operation, options) {
184
207
  await mkdir(path.dirname(to), { recursive: true });
185
208
  await writeGeneratedFile(to, operation.text);
186
209
  }
210
+ async function renderWriteTextFromFragments(operation, options) {
211
+ const toPath = expandOperationPath(operation.to, options);
212
+ assertFoundationTextPath(toPath);
213
+ const to = resolveContainedPath(options.targetRoot, toPath);
214
+ const texts = await Promise.all(operation.fragments.map(async (fragment) => {
215
+ const sourceRoot = fragment.sourceRoot === undefined
216
+ ? options.sourceRoot
217
+ : options.sourceRoots?.[fragment.sourceRoot];
218
+ if (sourceRoot === undefined) {
219
+ throw new Error(`Unknown renderer source root: ${fragment.sourceRoot}`);
220
+ }
221
+ const fromPath = expandTemplatePath(fragment.from, options.variables ?? {});
222
+ const from = resolveContainedPath(sourceRoot, fromPath);
223
+ return readFile(from, "utf8");
224
+ }));
225
+ await mkdir(path.dirname(to), { recursive: true });
226
+ await writeGeneratedFile(to, texts.map((text) => text.trimEnd()).join("\n\n") + "\n");
227
+ }
228
+ function replaceTextTemplateVariables(sourceText, replacements) {
229
+ const used = new Set();
230
+ const rendered = sourceText.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_placeholder, name) => {
231
+ const replacement = replacements[name];
232
+ if (replacement === undefined) {
233
+ throw new Error(`Missing text template variable: ${name}`);
234
+ }
235
+ used.add(name);
236
+ return replacement;
237
+ });
238
+ for (const name of Object.keys(replacements)) {
239
+ if (!used.has(name)) {
240
+ throw new Error(`Unused text template variable: ${name}`);
241
+ }
242
+ }
243
+ return rendered;
244
+ }
245
+ async function renderWriteTextTemplate(operation, options) {
246
+ const sourceRoot = operation.sourceRoot === undefined
247
+ ? options.sourceRoot
248
+ : options.sourceRoots?.[operation.sourceRoot];
249
+ if (sourceRoot === undefined) {
250
+ throw new Error(`Unknown renderer source root: ${operation.sourceRoot}`);
251
+ }
252
+ const toPath = expandOperationPath(operation.to, options);
253
+ assertFoundationTextPath(toPath);
254
+ const from = resolveContainedPath(sourceRoot, expandTemplatePath(operation.from, options.variables ?? {}));
255
+ const to = resolveContainedPath(options.targetRoot, toPath);
256
+ const sourceText = await readFile(from, "utf8");
257
+ await mkdir(path.dirname(to), { recursive: true });
258
+ await writeGeneratedFile(to, replaceTextTemplateVariables(sourceText, operation.replacements));
259
+ }
187
260
  async function renderSetExecutable(operation, options) {
188
261
  const filePath = resolveContainedPath(options.targetRoot, expandOperationPath(operation.path, options));
189
262
  const currentMode = (await stat(filePath)).mode;
@@ -224,7 +297,7 @@ async function findTypeScriptAnchorRanges(sourceText) {
224
297
  anchors.push({
225
298
  name,
226
299
  start: comment.pos,
227
- end: comment.end
300
+ end: comment.end,
228
301
  });
229
302
  }
230
303
  ts.forEachChild(node, collectNodeAnchors);
@@ -296,7 +369,10 @@ async function copyGeneratedFile(from, to) {
296
369
  }
297
370
  async function writeGeneratedFile(to, text, overwrite = false) {
298
371
  try {
299
- await writeFile(to, text, { encoding: "utf8", flag: overwrite ? "w" : "wx" });
372
+ await writeFile(to, text, {
373
+ encoding: "utf8",
374
+ flag: overwrite ? "w" : "wx",
375
+ });
300
376
  }
301
377
  catch (error) {
302
378
  if (isNodeError(error) && error.code === "EEXIST") {
@@ -323,6 +399,14 @@ export async function renderProject(options) {
323
399
  await renderWriteText(operation, options);
324
400
  continue;
325
401
  }
402
+ if (operation.kind === "writeTextFromFragments") {
403
+ await renderWriteTextFromFragments(operation, options);
404
+ continue;
405
+ }
406
+ if (operation.kind === "writeTextTemplate") {
407
+ await renderWriteTextTemplate(operation, options);
408
+ continue;
409
+ }
326
410
  if (operation.kind === "setExecutable") {
327
411
  await renderSetExecutable(operation, options);
328
412
  continue;
@@ -1,2 +1 @@
1
1
  export declare function packageTemplateRoot(moduleDir: string, ...segments: string[]): string;
2
- //# sourceMappingURL=runtime-paths.d.ts.map
@@ -0,0 +1,34 @@
1
+ import type { PresetProjectionPlan } from "./preset-projection.js";
2
+ import type { RenderOperation } from "./renderer.js";
3
+ export type TemplateBoundaryDebt = {
4
+ readonly preset: string;
5
+ readonly generatedPath: string;
6
+ readonly owningFunction: string;
7
+ readonly reason: string;
8
+ };
9
+ export type TemplateBoundaryViolation = {
10
+ readonly preset: string;
11
+ readonly generatedPath: string;
12
+ readonly owningFunction: string;
13
+ readonly operationKind: RenderOperation["kind"];
14
+ readonly sourceFilePath: string;
15
+ readonly allowlistReason?: string;
16
+ };
17
+ export type TemplateBoundaryCheckProjection = {
18
+ readonly name: string;
19
+ readonly sourceFilePath: string;
20
+ readonly plan: PresetProjectionPlan;
21
+ };
22
+ export type TemplateBoundaryCheckResult = {
23
+ readonly ok: boolean;
24
+ readonly violations: readonly TemplateBoundaryViolation[];
25
+ readonly allowlistedDebt: readonly TemplateBoundaryViolation[];
26
+ readonly unusedAllowlistEntries: readonly TemplateBoundaryDebt[];
27
+ };
28
+ export type CheckTemplateSourceBoundaryOptions = {
29
+ readonly projections: readonly TemplateBoundaryCheckProjection[];
30
+ readonly allowlist?: readonly TemplateBoundaryDebt[];
31
+ };
32
+ export declare const templateBoundaryDebtAllowlist: readonly TemplateBoundaryDebt[];
33
+ export declare function isProtectedGeneratedPath(generatedPath: string): boolean;
34
+ export declare function checkTemplateSourceBoundary({ projections, allowlist, }: CheckTemplateSourceBoundaryOptions): Promise<TemplateBoundaryCheckResult>;