@ykdz/template 0.0.2 → 0.0.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.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/dist/src/cli.d.ts +2 -0
  3. package/dist/{cli.js → src/cli.js} +91 -58
  4. package/dist/{declarations.d.ts → src/declarations.d.ts} +3 -5
  5. package/dist/{declarations.js → src/declarations.js} +48 -145
  6. package/dist/src/dependency-catalog.d.ts +18 -0
  7. package/dist/src/dependency-catalog.js +132 -0
  8. package/dist/src/devcontainer.d.ts +58 -0
  9. package/dist/src/devcontainer.js +202 -0
  10. package/dist/src/editor-customization.d.ts +12 -0
  11. package/dist/src/editor-customization.js +108 -0
  12. package/dist/{generation-context.d.ts → src/generation-context.d.ts} +2 -3
  13. package/dist/{generation-context.js → src/generation-context.js} +4 -1
  14. package/dist/src/module-graph.d.ts +34 -0
  15. package/dist/src/module-graph.js +58 -0
  16. package/dist/{next-step-instructions.d.ts → src/next-step-instructions.d.ts} +2 -4
  17. package/dist/{next-step-instructions.js → src/next-step-instructions.js} +49 -24
  18. package/dist/{package-addition.d.ts → src/package-addition.d.ts} +1 -1
  19. package/dist/src/package-addition.js +343 -0
  20. package/dist/src/preset-projection.d.ts +66 -0
  21. package/dist/src/preset-projection.js +7 -0
  22. package/dist/{project-github.d.ts → src/project-github.d.ts} +3 -9
  23. package/dist/{project-github.js → src/project-github.js} +27 -42
  24. package/dist/{renderer.d.ts → src/renderer.d.ts} +16 -2
  25. package/dist/{renderer.js → src/renderer.js} +95 -12
  26. package/dist/src/runtime-paths.d.ts +1 -0
  27. package/dist/src/runtime-paths.js +11 -0
  28. package/dist/src/template-boundary-check.d.ts +34 -0
  29. package/dist/src/template-boundary-check.js +448 -0
  30. package/dist/{toolchain-resolution.d.ts → src/toolchain-resolution.d.ts} +0 -1
  31. package/dist/{toolchain-resolution.js → src/toolchain-resolution.js} +1 -1
  32. package/dist/templates/hono-api/projection.d.ts +6 -0
  33. package/dist/templates/hono-api/projection.js +538 -0
  34. package/dist/templates/projection-plans.d.ts +7 -0
  35. package/dist/templates/projection-plans.js +94 -0
  36. package/dist/templates/registry.d.ts +7 -0
  37. package/dist/templates/registry.js +42 -0
  38. package/dist/templates/rust-bin/projection.d.ts +9 -0
  39. package/dist/templates/rust-bin/projection.js +390 -0
  40. package/dist/templates/ts-lib/projection.d.ts +6 -0
  41. package/dist/templates/ts-lib/projection.js +523 -0
  42. package/dist/templates/vue-app/projection.d.ts +6 -0
  43. package/dist/templates/vue-app/projection.js +757 -0
  44. package/dist/templates/vue-hono-app/projection.d.ts +8 -0
  45. package/dist/templates/vue-hono-app/projection.js +693 -0
  46. package/package.json +38 -28
  47. package/pnpm-workspace.yaml +35 -0
  48. package/templates/hono-api/.github/dependabot.yml +13 -0
  49. package/templates/rust-bin/.github/dependabot.yml +18 -1
  50. package/templates/rust-bin/rust-toolchain.toml +3 -0
  51. package/templates/rust-bin/rustfmt.toml +2 -0
  52. package/templates/shared/devcontainer/browser-test.Dockerfile +2 -0
  53. package/templates/shared/devcontainer/node-pnpm.Dockerfile +6 -0
  54. package/templates/shared/devcontainer/rust.Dockerfile +16 -0
  55. package/templates/shared/editor-customization/capabilities.json +56 -0
  56. package/templates/ts-lib/.github/dependabot.yml +13 -0
  57. package/templates/ts-lib/src/index.ts +5 -1
  58. package/templates/vue-app/.github/dependabot.yml +13 -0
  59. package/templates/vue-app/.github/workflows/check.yml +1 -1
  60. package/templates/vue-app/test/e2e/app.spec.ts +4 -1
  61. package/templates/vue-hono-app/.github/dependabot.yml +13 -0
  62. package/templates/vue-hono-app/web/test/e2e/app.spec.ts +4 -1
  63. package/dist/cli.d.ts +0 -3
  64. package/dist/cli.d.ts.map +0 -1
  65. package/dist/declarations.d.ts.map +0 -1
  66. package/dist/devcontainer.d.ts +0 -17
  67. package/dist/devcontainer.d.ts.map +0 -1
  68. package/dist/devcontainer.js +0 -23
  69. package/dist/generation-context.d.ts.map +0 -1
  70. package/dist/hono-api.d.ts +0 -3
  71. package/dist/hono-api.d.ts.map +0 -1
  72. package/dist/hono-api.js +0 -210
  73. package/dist/module-graph.d.ts +0 -45
  74. package/dist/module-graph.d.ts.map +0 -1
  75. package/dist/module-graph.js +0 -187
  76. package/dist/next-step-instructions.d.ts.map +0 -1
  77. package/dist/package-addition.d.ts.map +0 -1
  78. package/dist/package-addition.js +0 -560
  79. package/dist/project-github.d.ts.map +0 -1
  80. package/dist/renderer.d.ts.map +0 -1
  81. package/dist/rust-bin.d.ts +0 -8
  82. package/dist/rust-bin.d.ts.map +0 -1
  83. package/dist/rust-bin.js +0 -208
  84. package/dist/toolchain-resolution.d.ts.map +0 -1
  85. package/dist/ts-lib.d.ts +0 -8
  86. package/dist/ts-lib.d.ts.map +0 -1
  87. package/dist/ts-lib.js +0 -214
  88. package/dist/vue-app.d.ts +0 -3
  89. package/dist/vue-app.d.ts.map +0 -1
  90. package/dist/vue-app.js +0 -301
  91. package/dist/vue-hono-app.d.ts +0 -7
  92. package/dist/vue-hono-app.d.ts.map +0 -1
  93. package/dist/vue-hono-app.js +0 -440
  94. package/templates/shared/oxc/package.json +0 -18
  95. package/templates/shared/oxc/tsconfig.json +0 -11
@@ -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)) {
@@ -88,7 +89,13 @@ const packageJsonRootKeyOrder = [
88
89
  "peerDependencies",
89
90
  "optionalDependencies",
90
91
  "engines",
91
- "packageManager"
92
+ "packageManager",
93
+ ];
94
+ const devcontainerJsonRootKeyOrder = [
95
+ "name",
96
+ "build",
97
+ "image",
98
+ "customizations",
92
99
  ];
93
100
  function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
94
101
  if (pathSegments.length === 0 && rootKeyOrder) {
@@ -101,10 +108,13 @@ function compareJsonKeys(left, right, pathSegments, rootKeyOrder) {
101
108
  return left.localeCompare(right);
102
109
  }
103
110
  function rootKeyOrderForPath(toPath) {
104
- if (path.basename(toPath) !== "package.json") {
105
- return undefined;
111
+ if (path.basename(toPath) === "package.json") {
112
+ return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
113
+ }
114
+ if (toPath.split(path.sep).join("/") === ".devcontainer/devcontainer.json") {
115
+ return new Map(devcontainerJsonRootKeyOrder.map((key, index) => [key, index]));
106
116
  }
107
- return new Map(packageJsonRootKeyOrder.map((key, index) => [key, index]));
117
+ return undefined;
108
118
  }
109
119
  function serializeJson(value, multilineArrays = [], rootKeyOrder) {
110
120
  return `${serializeJsonValue(value, 0, [], new Set(multilineArrays), rootKeyOrder)}\n`;
@@ -151,7 +161,7 @@ const foundationTextFiles = new Set([
151
161
  "README",
152
162
  "README.md",
153
163
  "LICENSE",
154
- "CHANGELOG.md"
164
+ "CHANGELOG.md",
155
165
  ]);
156
166
  function assertFoundationTextPath(relativePath) {
157
167
  const normalizedPath = relativePath.split(path.sep).join("/");
@@ -162,11 +172,20 @@ function assertFoundationTextPath(relativePath) {
162
172
  if (isRootLevel && relativePath.endsWith(".md")) {
163
173
  return;
164
174
  }
165
- if (isRootLevel && (relativePath.endsWith(".yaml") || relativePath.endsWith(".yml"))) {
175
+ if (isRootLevel &&
176
+ (relativePath.endsWith(".yaml") || relativePath.endsWith(".yml"))) {
166
177
  return;
167
178
  }
168
179
  if (isRootLevel &&
169
- ["Cargo.toml", "Cargo.lock", "rustfmt.toml"].includes(normalizedPath)) {
180
+ [
181
+ "Cargo.toml",
182
+ "Cargo.lock",
183
+ "rustfmt.toml",
184
+ "rust-toolchain.toml",
185
+ ].includes(normalizedPath)) {
186
+ return;
187
+ }
188
+ if (/^packages\/[A-Za-z0-9._-]+\/(?:Cargo\.toml|Cargo\.lock|rustfmt\.toml)$/.test(normalizedPath)) {
170
189
  return;
171
190
  }
172
191
  if (/^\.github\/workflows\/[A-Za-z0-9._-]+\.ya?ml$/.test(normalizedPath)) {
@@ -175,6 +194,9 @@ function assertFoundationTextPath(relativePath) {
175
194
  if (/^\.github\/dependabot\.ya?ml$/.test(normalizedPath)) {
176
195
  return;
177
196
  }
197
+ if (normalizedPath === ".devcontainer/Dockerfile") {
198
+ return;
199
+ }
178
200
  throw new Error(`Text output is limited to foundation files: ${relativePath}`);
179
201
  }
180
202
  async function renderWriteText(operation, options) {
@@ -184,6 +206,56 @@ async function renderWriteText(operation, options) {
184
206
  await mkdir(path.dirname(to), { recursive: true });
185
207
  await writeGeneratedFile(to, operation.text);
186
208
  }
209
+ async function renderWriteTextFromFragments(operation, options) {
210
+ const toPath = expandOperationPath(operation.to, options);
211
+ assertFoundationTextPath(toPath);
212
+ const to = resolveContainedPath(options.targetRoot, toPath);
213
+ const texts = await Promise.all(operation.fragments.map(async (fragment) => {
214
+ const sourceRoot = fragment.sourceRoot === undefined
215
+ ? options.sourceRoot
216
+ : options.sourceRoots?.[fragment.sourceRoot];
217
+ if (sourceRoot === undefined) {
218
+ throw new Error(`Unknown renderer source root: ${fragment.sourceRoot}`);
219
+ }
220
+ const fromPath = expandTemplatePath(fragment.from, options.variables ?? {});
221
+ const from = resolveContainedPath(sourceRoot, fromPath);
222
+ return readFile(from, "utf8");
223
+ }));
224
+ await mkdir(path.dirname(to), { recursive: true });
225
+ await writeGeneratedFile(to, texts.map((text) => text.trimEnd()).join("\n\n") + "\n");
226
+ }
227
+ function replaceTextTemplateVariables(sourceText, replacements) {
228
+ const used = new Set();
229
+ const rendered = sourceText.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_placeholder, name) => {
230
+ const replacement = replacements[name];
231
+ if (replacement === undefined) {
232
+ throw new Error(`Missing text template variable: ${name}`);
233
+ }
234
+ used.add(name);
235
+ return replacement;
236
+ });
237
+ for (const name of Object.keys(replacements)) {
238
+ if (!used.has(name)) {
239
+ throw new Error(`Unused text template variable: ${name}`);
240
+ }
241
+ }
242
+ return rendered;
243
+ }
244
+ async function renderWriteTextTemplate(operation, options) {
245
+ const sourceRoot = operation.sourceRoot === undefined
246
+ ? options.sourceRoot
247
+ : options.sourceRoots?.[operation.sourceRoot];
248
+ if (sourceRoot === undefined) {
249
+ throw new Error(`Unknown renderer source root: ${operation.sourceRoot}`);
250
+ }
251
+ const toPath = expandOperationPath(operation.to, options);
252
+ assertFoundationTextPath(toPath);
253
+ const from = resolveContainedPath(sourceRoot, expandTemplatePath(operation.from, options.variables ?? {}));
254
+ const to = resolveContainedPath(options.targetRoot, toPath);
255
+ const sourceText = await readFile(from, "utf8");
256
+ await mkdir(path.dirname(to), { recursive: true });
257
+ await writeGeneratedFile(to, replaceTextTemplateVariables(sourceText, operation.replacements));
258
+ }
187
259
  async function renderSetExecutable(operation, options) {
188
260
  const filePath = resolveContainedPath(options.targetRoot, expandOperationPath(operation.path, options));
189
261
  const currentMode = (await stat(filePath)).mode;
@@ -224,7 +296,7 @@ async function findTypeScriptAnchorRanges(sourceText) {
224
296
  anchors.push({
225
297
  name,
226
298
  start: comment.pos,
227
- end: comment.end
299
+ end: comment.end,
228
300
  });
229
301
  }
230
302
  ts.forEachChild(node, collectNodeAnchors);
@@ -296,7 +368,10 @@ async function copyGeneratedFile(from, to) {
296
368
  }
297
369
  async function writeGeneratedFile(to, text, overwrite = false) {
298
370
  try {
299
- await writeFile(to, text, { encoding: "utf8", flag: overwrite ? "w" : "wx" });
371
+ await writeFile(to, text, {
372
+ encoding: "utf8",
373
+ flag: overwrite ? "w" : "wx",
374
+ });
300
375
  }
301
376
  catch (error) {
302
377
  if (isNodeError(error) && error.code === "EEXIST") {
@@ -323,6 +398,14 @@ export async function renderProject(options) {
323
398
  await renderWriteText(operation, options);
324
399
  continue;
325
400
  }
401
+ if (operation.kind === "writeTextFromFragments") {
402
+ await renderWriteTextFromFragments(operation, options);
403
+ continue;
404
+ }
405
+ if (operation.kind === "writeTextTemplate") {
406
+ await renderWriteTextTemplate(operation, options);
407
+ continue;
408
+ }
326
409
  if (operation.kind === "setExecutable") {
327
410
  await renderSetExecutable(operation, options);
328
411
  continue;
@@ -0,0 +1 @@
1
+ export declare function packageTemplateRoot(moduleDir: string, ...segments: string[]): string;
@@ -0,0 +1,11 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ export function packageTemplateRoot(moduleDir, ...segments) {
4
+ const candidates = [
5
+ path.join(moduleDir, "..", "..", "templates", ...segments),
6
+ path.join(moduleDir, "..", "templates", ...segments),
7
+ path.join(moduleDir, "..", "..", "..", "templates", ...segments),
8
+ ];
9
+ const found = candidates.find((candidate) => existsSync(candidate));
10
+ return found ?? candidates[0];
11
+ }
@@ -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>;