@ykdz/template 0.0.0 → 0.0.1

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 (54) hide show
  1. package/dist/cli.js +101 -28
  2. package/dist/hono-api.d.ts.map +1 -1
  3. package/dist/hono-api.js +17 -56
  4. package/dist/package-addition.d.ts.map +1 -1
  5. package/dist/package-addition.js +31 -51
  6. package/dist/post-commands.d.ts +40 -0
  7. package/dist/post-commands.d.ts.map +1 -0
  8. package/dist/post-commands.js +154 -0
  9. package/dist/renderer.d.ts +2 -0
  10. package/dist/renderer.d.ts.map +1 -1
  11. package/dist/renderer.js +7 -1
  12. package/dist/rust-bin.d.ts.map +1 -1
  13. package/dist/rust-bin.js +4 -36
  14. package/dist/ts-lib.d.ts.map +1 -1
  15. package/dist/ts-lib.js +17 -56
  16. package/dist/vue-app.d.ts.map +1 -1
  17. package/dist/vue-app.js +17 -57
  18. package/dist/vue-hono-app.d.ts.map +1 -1
  19. package/dist/vue-hono-app.js +28 -76
  20. package/package.json +23 -16
  21. package/templates/hono-api/.github/dependabot.yml +10 -0
  22. package/templates/hono-api/.github/workflows/check.yml +19 -0
  23. package/templates/hono-api/src/server.ts +3 -2
  24. package/templates/hono-api/vitest.config.ts +5 -4
  25. package/templates/rust-bin/.github/dependabot.yml +10 -0
  26. package/templates/rust-bin/.github/workflows/check.yml +18 -0
  27. package/templates/shared/oxc/node/oxlint.config.ts +9 -0
  28. package/templates/shared/oxc/oxfmt.config.ts +8 -0
  29. package/templates/shared/oxc/package.json +18 -0
  30. package/templates/shared/oxc/tsconfig.json +11 -0
  31. package/templates/shared/oxc/vue/oxlint.config.ts +9 -0
  32. package/templates/ts-lib/.github/dependabot.yml +10 -0
  33. package/templates/ts-lib/.github/workflows/check.yml +19 -0
  34. package/templates/vue-app/.github/dependabot.yml +10 -0
  35. package/templates/vue-app/.github/workflows/check.yml +20 -0
  36. package/templates/vue-app/playwright.config.ts +5 -5
  37. package/templates/vue-app/src/App.vue +15 -5
  38. package/templates/vue-app/src/main.ts +2 -0
  39. package/templates/vue-app/test/app.test.ts +1 -0
  40. package/templates/vue-app/test/e2e/app.spec.ts +3 -1
  41. package/templates/vue-app/vite.config.ts +6 -5
  42. package/templates/vue-app/vitest.config.ts +5 -4
  43. package/templates/vue-hono-app/.github/dependabot.yml +10 -0
  44. package/templates/vue-hono-app/.github/workflows/check.yml +20 -0
  45. package/templates/vue-hono-app/api/src/server.ts +3 -2
  46. package/templates/vue-hono-app/api/vitest.config.ts +2 -2
  47. package/templates/vue-hono-app/web/playwright.config.ts +9 -7
  48. package/templates/vue-hono-app/web/src/App.vue +12 -4
  49. package/templates/vue-hono-app/web/src/api.ts +1 -1
  50. package/templates/vue-hono-app/web/src/main.ts +2 -0
  51. package/templates/vue-hono-app/web/test/app.test.ts +1 -0
  52. package/templates/vue-hono-app/web/test/e2e/app.spec.ts +3 -1
  53. package/templates/vue-hono-app/web/vite.config.ts +13 -10
  54. package/templates/vue-hono-app/web/vitest.config.ts +6 -5
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import { initTsLibProject } from "./ts-lib.js";
8
8
  import { initVueHonoAppProject } from "./vue-hono-app.js";
9
9
  import { initVueAppProject } from "./vue-app.js";
10
10
  import { addPackage } from "./package-addition.js";
11
+ import { planPostCommands, runPostCommands } from "./post-commands.js";
11
12
  import { blueprintJsonSchema, builtInPresets, presetFileJsonSchema, validatePresetFile, validateProjectBlueprint } from "./declarations.js";
12
13
  function usage() {
13
14
  return [
@@ -25,6 +26,7 @@ function usage() {
25
26
  " --name <name> Package name to add",
26
27
  " --scope <name> Package scope for workspace package names",
27
28
  " --yes Accept defaults for non-interactive generation",
29
+ " --ready Run template-maintained Post Commands after writing files",
28
30
  " --dry-run Print the planned generation without writing files",
29
31
  " --json Print machine-readable output"
30
32
  ].join("\n");
@@ -61,6 +63,7 @@ function parseInitOptions(args) {
61
63
  let yes = false;
62
64
  let dryRun = false;
63
65
  let json = false;
66
+ let ready = false;
64
67
  let scope;
65
68
  for (let index = 2; index < args.length; index += 1) {
66
69
  const arg = args[index];
@@ -72,6 +75,10 @@ function parseInitOptions(args) {
72
75
  dryRun = true;
73
76
  continue;
74
77
  }
78
+ if (arg === "--ready") {
79
+ ready = true;
80
+ continue;
81
+ }
75
82
  if (arg === "--json") {
76
83
  json = true;
77
84
  continue;
@@ -99,7 +106,7 @@ function parseInitOptions(args) {
99
106
  if (!dir) {
100
107
  throw new Error("init requires a target directory");
101
108
  }
102
- return { dir, preset, yes, dryRun, json, scope };
109
+ return { dir, preset, yes, dryRun, json, ready, scope };
103
110
  }
104
111
  function normalizeNpmScope(value) {
105
112
  if (value !== value.trim() || /\s/.test(value)) {
@@ -164,6 +171,18 @@ function formatBlueprintSummary(targetDir, blueprint) {
164
171
  lines.push(` Features: ${blueprint.features.join(", ")}`);
165
172
  return lines.join("\n");
166
173
  }
174
+ function formatPostCommand(command) {
175
+ return [command.command, ...command.args].join(" ");
176
+ }
177
+ function formatPostCommandPlan(commands) {
178
+ if (commands.length === 0) {
179
+ return ["Post Commands:", " (none)"].join("\n");
180
+ }
181
+ return [
182
+ "Post Commands:",
183
+ ...commands.map((command) => ` ${command.label}: ${formatPostCommand(command)}`)
184
+ ].join("\n");
185
+ }
167
186
  function nextStepsForPreset(preset) {
168
187
  if (preset === "rust-bin") {
169
188
  return ["cd <target>", "./scripts/check"];
@@ -176,19 +195,78 @@ function formatNextSteps(targetDir, preset) {
176
195
  ...nextStepsForPreset(preset).map((step) => ` ${step.replace("<target>", targetDir)}`)
177
196
  ].join("\n");
178
197
  }
179
- function printInitComplete(options, blueprint) {
198
+ function postCommandReport(planned, executions, skipped) {
199
+ return {
200
+ planned: planned.map((command) => ({
201
+ id: command.id,
202
+ label: command.label,
203
+ command: command.command,
204
+ args: [...command.args],
205
+ cwd: command.cwd
206
+ })),
207
+ run: executions
208
+ .filter((execution) => execution.status === "run")
209
+ .map((execution) => ({
210
+ id: execution.command.id,
211
+ exitCode: execution.exitCode
212
+ })),
213
+ failed: executions
214
+ .filter((execution) => execution.status === "failed")
215
+ .map((execution) => ({
216
+ id: execution.command.id,
217
+ exitCode: execution.exitCode,
218
+ error: execution.error
219
+ })),
220
+ skipped
221
+ };
222
+ }
223
+ function failedPostCommand(executions) {
224
+ return executions.find((execution) => execution.status === "failed");
225
+ }
226
+ function skippedPostCommands(commands, reason) {
227
+ return commands.map((command) => ({ id: command.id, reason }));
228
+ }
229
+ async function generateInitProject(options) {
230
+ if (options.preset === "ts-lib") {
231
+ await initTsLibProject(options.dir);
232
+ return;
233
+ }
234
+ if (options.preset === "hono-api") {
235
+ await initHonoApiProject(options.dir);
236
+ return;
237
+ }
238
+ if (options.preset === "vue-app") {
239
+ await initVueAppProject(options.dir);
240
+ return;
241
+ }
242
+ if (options.preset === "vue-hono-app") {
243
+ await initVueHonoAppProject(options.dir, { scope: options.scope });
244
+ return;
245
+ }
246
+ if (options.preset === "rust-bin") {
247
+ await initRustBinProject(options.dir);
248
+ return;
249
+ }
250
+ throw new Error("Only the ts-lib, hono-api, vue-app, vue-hono-app, and rust-bin presets are supported in this version");
251
+ }
252
+ function printInitComplete(options, blueprint, report) {
180
253
  const preset = blueprint.preset;
181
254
  if (options.json) {
182
255
  printJson({
183
256
  command: "init",
184
257
  dryRun: false,
258
+ ready: options.ready,
185
259
  targetDir: options.dir,
186
260
  blueprint,
261
+ postCommands: report,
187
262
  nextSteps: nextStepsForPreset(preset).map((step) => step.replace("<target>", options.dir))
188
263
  });
189
264
  return;
190
265
  }
191
266
  console.log(`Initialized ${options.preset} project in ${options.dir}`);
267
+ if (options.ready) {
268
+ console.log(formatPostCommandPlan(report.planned));
269
+ }
192
270
  console.log(formatNextSteps(options.dir, preset));
193
271
  }
194
272
  function isInteractiveTerminal() {
@@ -287,16 +365,23 @@ async function main(args) {
287
365
  const options = parseInitOptions(args);
288
366
  const blueprint = blueprintForInit(options);
289
367
  if (options.dryRun) {
368
+ const postCommandPlan = planPostCommands({
369
+ preset: blueprint.preset,
370
+ targetDir: options.dir
371
+ });
290
372
  if (options.json) {
291
373
  printJson({
292
374
  command: "init",
293
375
  dryRun: true,
376
+ ready: options.ready,
294
377
  targetDir: options.dir,
295
- blueprint
378
+ blueprint,
379
+ postCommands: postCommandReport(postCommandPlan.commands, [], skippedPostCommands(postCommandPlan.commands, "dry-run"))
296
380
  });
297
381
  return;
298
382
  }
299
383
  console.log(formatBlueprintSummary(options.dir, blueprint));
384
+ console.log(formatPostCommandPlan(postCommandPlan.commands));
300
385
  return;
301
386
  }
302
387
  if (!options.yes && (options.json || !isInteractiveTerminal())) {
@@ -305,32 +390,20 @@ async function main(args) {
305
390
  if (!options.yes && !(await confirmInit(options.dir, blueprint))) {
306
391
  throw new Error("Init cancelled");
307
392
  }
308
- if (options.preset === "ts-lib") {
309
- await initTsLibProject(options.dir);
310
- printInitComplete(options, blueprint);
311
- return;
312
- }
313
- if (options.preset === "hono-api") {
314
- await initHonoApiProject(options.dir);
315
- printInitComplete(options, blueprint);
316
- return;
317
- }
318
- if (options.preset === "vue-app") {
319
- await initVueAppProject(options.dir);
320
- printInitComplete(options, blueprint);
321
- return;
322
- }
323
- if (options.preset === "vue-hono-app") {
324
- await initVueHonoAppProject(options.dir, { scope: options.scope });
325
- printInitComplete(options, blueprint);
326
- return;
327
- }
328
- if (options.preset === "rust-bin") {
329
- await initRustBinProject(options.dir);
330
- printInitComplete(options, blueprint);
331
- return;
393
+ const postCommandPlan = planPostCommands({
394
+ preset: blueprint.preset,
395
+ targetDir: options.dir
396
+ });
397
+ await generateInitProject(options);
398
+ const executions = options.ready ? await runPostCommands({ plan: postCommandPlan }) : [];
399
+ const failedExecution = failedPostCommand(executions);
400
+ printInitComplete(options, blueprint, postCommandReport(postCommandPlan.commands, executions, options.ready
401
+ ? []
402
+ : skippedPostCommands(postCommandPlan.commands, "ready-mode-not-requested")));
403
+ if (failedExecution) {
404
+ throw new Error(failedExecution.error);
332
405
  }
333
- throw new Error("Only the ts-lib, hono-api, vue-app, vue-hono-app, and rust-bin presets are supported in this version");
406
+ return;
334
407
  }
335
408
  if (command === "add" && args[1] === "package") {
336
409
  const options = parseAddPackageOptions(args);
@@ -1 +1 @@
1
- {"version":3,"file":"hono-api.d.ts","sourceRoot":"","sources":["../src/hono-api.ts"],"names":[],"mappings":"AAwPA,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzE"}
1
+ {"version":3,"file":"hono-api.d.ts","sourceRoot":"","sources":["../src/hono-api.ts"],"names":[],"mappings":"AAiNA,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzE"}
package/dist/hono-api.js CHANGED
@@ -50,7 +50,7 @@ function packageJson(projectName) {
50
50
  vitest: "catalog:"
51
51
  },
52
52
  engines: {
53
- node: ">=22.0.0"
53
+ node: "22"
54
54
  },
55
55
  packageManager: "pnpm@10.0.0"
56
56
  };
@@ -114,24 +114,16 @@ function operationsForHonoApi(projectName) {
114
114
  }
115
115
  },
116
116
  {
117
- kind: "writeJson",
118
- to: ".oxlintrc.json",
119
- value: {
120
- categories: {
121
- correctness: "error",
122
- suspicious: "error"
123
- },
124
- plugins: ["typescript", "oxc"]
125
- }
117
+ kind: "copyFile",
118
+ sourceRoot: "sharedOxc",
119
+ from: "node/oxlint.config.ts",
120
+ to: "oxlint.config.ts"
126
121
  },
127
122
  {
128
- kind: "writeJson",
129
- to: ".oxfmtrc.json",
130
- value: {
131
- printWidth: 100,
132
- singleQuote: false,
133
- trailingComma: "none"
134
- }
123
+ kind: "copyFile",
124
+ sourceRoot: "sharedOxc",
125
+ from: "oxfmt.config.ts",
126
+ to: "oxfmt.config.ts"
135
127
  },
136
128
  {
137
129
  kind: "writeText",
@@ -189,58 +181,27 @@ function operationsForHonoApi(projectName) {
189
181
  }
190
182
  },
191
183
  {
192
- kind: "writeText",
184
+ kind: "copyFile",
185
+ from: ".github/workflows/check.yml",
193
186
  to: ".github/workflows/check.yml",
194
- text: [
195
- "name: Check",
196
- "",
197
- "on:",
198
- " pull_request:",
199
- " push:",
200
- " branches:",
201
- " - main",
202
- "",
203
- "jobs:",
204
- " check:",
205
- " runs-on: ubuntu-latest",
206
- " steps:",
207
- " - uses: actions/checkout@v4",
208
- " - uses: pnpm/action-setup@v4",
209
- " with:",
210
- " version: 10.0.0",
211
- " - uses: actions/setup-node@v4",
212
- " with:",
213
- " node-version: 22",
214
- " - run: pnpm install",
215
- " - run: pnpm run check",
216
- ""
217
- ].join("\n")
218
187
  },
219
188
  {
220
- kind: "writeText",
189
+ kind: "copyFile",
190
+ from: ".github/dependabot.yml",
221
191
  to: ".github/dependabot.yml",
222
- text: [
223
- "version: 2",
224
- "updates:",
225
- " - package-ecosystem: npm",
226
- " directory: /",
227
- " schedule:",
228
- " interval: weekly",
229
- " - package-ecosystem: github-actions",
230
- " directory: /",
231
- " schedule:",
232
- " interval: weekly",
233
- ""
234
- ].join("\n")
235
192
  }
236
193
  ];
237
194
  }
238
195
  function templateSourceRoot() {
239
196
  return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "templates", "hono-api");
240
197
  }
198
+ function sharedOxcSourceRoot() {
199
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "templates", "shared", "oxc");
200
+ }
241
201
  export async function initHonoApiProject(targetDir) {
242
202
  await renderNewProject({
243
203
  sourceRoot: templateSourceRoot(),
204
+ sourceRoots: { sharedOxc: sharedOxcSourceRoot() },
244
205
  targetRoot: targetDir,
245
206
  operations: operationsForHonoApi(projectNameFromDir(targetDir))
246
207
  });
@@ -1 +1 @@
1
- {"version":3,"file":"package-addition.d.ts","sourceRoot":"","sources":["../src/package-addition.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAyoBF,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1E"}
1
+ {"version":3,"file":"package-addition.d.ts","sourceRoot":"","sources":["../src/package-addition.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAqnBF,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C1E"}
@@ -71,7 +71,7 @@ function tsLibPackageJson(packageName) {
71
71
  typescript: "catalog:"
72
72
  },
73
73
  engines: {
74
- node: ">=22.0.0"
74
+ node: "22"
75
75
  }
76
76
  };
77
77
  }
@@ -108,24 +108,16 @@ function tsLibPackageOperations(packagePath, packageName) {
108
108
  }
109
109
  },
110
110
  {
111
- kind: "writeJson",
112
- to: `${packagePath}/.oxlintrc.json`,
113
- value: {
114
- categories: {
115
- correctness: "error",
116
- suspicious: "error"
117
- },
118
- plugins: ["typescript", "oxc"]
119
- }
111
+ kind: "copyFile",
112
+ sourceRoot: "sharedOxc",
113
+ from: "node/oxlint.config.ts",
114
+ to: `${packagePath}/oxlint.config.ts`
120
115
  },
121
116
  {
122
- kind: "writeJson",
123
- to: `${packagePath}/.oxfmtrc.json`,
124
- value: {
125
- printWidth: 100,
126
- singleQuote: false,
127
- trailingComma: "none"
128
- }
117
+ kind: "copyFile",
118
+ sourceRoot: "sharedOxc",
119
+ from: "oxfmt.config.ts",
120
+ to: `${packagePath}/oxfmt.config.ts`
129
121
  },
130
122
  {
131
123
  kind: "copyFile",
@@ -165,7 +157,7 @@ function honoApiPackageJson(packageName) {
165
157
  vitest: "catalog:"
166
158
  },
167
159
  engines: {
168
- node: ">=22.0.0"
160
+ node: "22"
169
161
  }
170
162
  };
171
163
  }
@@ -210,24 +202,16 @@ function honoApiPackageOperations(packagePath, packageName) {
210
202
  }
211
203
  },
212
204
  {
213
- kind: "writeJson",
214
- to: `${packagePath}/.oxlintrc.json`,
215
- value: {
216
- categories: {
217
- correctness: "error",
218
- suspicious: "error"
219
- },
220
- plugins: ["typescript", "oxc"]
221
- }
205
+ kind: "copyFile",
206
+ sourceRoot: "sharedOxc",
207
+ from: "node/oxlint.config.ts",
208
+ to: `${packagePath}/oxlint.config.ts`
222
209
  },
223
210
  {
224
- kind: "writeJson",
225
- to: `${packagePath}/.oxfmtrc.json`,
226
- value: {
227
- printWidth: 100,
228
- singleQuote: false,
229
- trailingComma: "none"
230
- }
211
+ kind: "copyFile",
212
+ sourceRoot: "sharedOxc",
213
+ from: "oxfmt.config.ts",
214
+ to: `${packagePath}/oxfmt.config.ts`
231
215
  },
232
216
  { kind: "copyFile", from: "src/app.ts", to: `${packagePath}/src/app.ts` },
233
217
  { kind: "copyFile", from: "src/server.ts", to: `${packagePath}/src/server.ts` },
@@ -276,7 +260,7 @@ function vueAppPackageJson(packageName) {
276
260
  "vue-tsc": "catalog:"
277
261
  },
278
262
  engines: {
279
- node: ">=22.0.0"
263
+ node: "22"
280
264
  }
281
265
  };
282
266
  }
@@ -351,24 +335,16 @@ function vueAppPackageOperations(packagePath, packageName) {
351
335
  }
352
336
  },
353
337
  {
354
- kind: "writeJson",
355
- to: `${packagePath}/.oxlintrc.json`,
356
- value: {
357
- categories: {
358
- correctness: "error",
359
- suspicious: "error"
360
- },
361
- plugins: ["typescript", "oxc", "vue"]
362
- }
338
+ kind: "copyFile",
339
+ sourceRoot: "sharedOxc",
340
+ from: "vue/oxlint.config.ts",
341
+ to: `${packagePath}/oxlint.config.ts`
363
342
  },
364
343
  {
365
- kind: "writeJson",
366
- to: `${packagePath}/.oxfmtrc.json`,
367
- value: {
368
- printWidth: 100,
369
- singleQuote: false,
370
- trailingComma: "none"
371
- }
344
+ kind: "copyFile",
345
+ sourceRoot: "sharedOxc",
346
+ from: "oxfmt.config.ts",
347
+ to: `${packagePath}/oxfmt.config.ts`
372
348
  },
373
349
  { kind: "copyFile", from: "env.d.ts", to: `${packagePath}/env.d.ts` },
374
350
  { kind: "copyFile", from: "index.html", to: `${packagePath}/index.html` },
@@ -417,6 +393,9 @@ function rootTsReferencesForPreset(preset, packagePath) {
417
393
  function templateSourceRoot(preset) {
418
394
  return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "templates", preset);
419
395
  }
396
+ function sharedOxcSourceRoot() {
397
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "templates", "shared", "oxc");
398
+ }
420
399
  function localPortsFromText(text) {
421
400
  return [
422
401
  ...text.matchAll(/port:\s*(\d+)/g),
@@ -568,6 +547,7 @@ export async function addPackage(options) {
568
547
  await mkdir(path.join(root, packagePath), { recursive: true });
569
548
  await renderProject({
570
549
  sourceRoot: templateSourceRoot(options.preset),
550
+ sourceRoots: { sharedOxc: sharedOxcSourceRoot() },
571
551
  targetRoot: root,
572
552
  operations: packageOperationsForPreset(options.preset, packagePath, packageName)
573
553
  });
@@ -0,0 +1,40 @@
1
+ import type { PresetName } from "./declarations.js";
2
+ export type PostCommand = {
3
+ id: string;
4
+ label: string;
5
+ command: string;
6
+ args: readonly string[];
7
+ cwd: string;
8
+ };
9
+ export type PlanPostCommandsOptions = {
10
+ preset: PresetName;
11
+ targetDir: string;
12
+ };
13
+ declare const postCommandPlanBrand: unique symbol;
14
+ export type PostCommandPlan = {
15
+ readonly preset: PresetName;
16
+ readonly targetDir: string;
17
+ readonly commands: readonly PostCommand[];
18
+ readonly [postCommandPlanBrand]: true;
19
+ };
20
+ export type PostCommandExecution = {
21
+ command: PostCommand;
22
+ status: "run";
23
+ exitCode: 0;
24
+ } | {
25
+ command: PostCommand;
26
+ status: "failed";
27
+ exitCode: number | null;
28
+ error: string;
29
+ };
30
+ export type PostCommandExecutor = (command: PostCommand) => Promise<{
31
+ exitCode: number;
32
+ }>;
33
+ export type RunPostCommandsOptions = {
34
+ plan: PostCommandPlan;
35
+ executor?: PostCommandExecutor;
36
+ };
37
+ export declare function planPostCommands(options: PlanPostCommandsOptions): PostCommandPlan;
38
+ export declare function runPostCommands(options: RunPostCommandsOptions): Promise<PostCommandExecution[]>;
39
+ export {};
40
+ //# sourceMappingURL=post-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-commands.d.ts","sourceRoot":"","sources":["../src/post-commands.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,QAAA,MAAM,oBAAoB,EAAE,OAAO,MAAkC,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAC5B;IACE,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC;IACd,QAAQ,EAAE,CAAC,CAAC;CACb,GACD;IACE,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnC,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,CAAC;AAmEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,eAAe,CAalF;AAoED,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAqCjC"}
@@ -0,0 +1,154 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ const postCommandPlanBrand = Symbol("PostCommandPlan");
4
+ const packageManagerPin = "pnpm@10.0.0";
5
+ const nodeReadyCommands = [
6
+ {
7
+ id: "node-enable-corepack",
8
+ label: "Enable Corepack",
9
+ command: "corepack",
10
+ args: ["enable"]
11
+ },
12
+ {
13
+ id: "node-refresh-package-manager-pin",
14
+ label: "Refresh Package Manager Pin and Install Dependencies",
15
+ command: "corepack",
16
+ args: ["use", packageManagerPin]
17
+ },
18
+ {
19
+ id: "node-run-fix",
20
+ label: "Run Fix Command",
21
+ command: "pnpm",
22
+ args: ["run", "fix"]
23
+ }
24
+ ];
25
+ const vueReadyCommand = {
26
+ id: "vue-install-playwright-browsers",
27
+ label: "Install Playwright browser assets",
28
+ command: "pnpm",
29
+ args: ["exec", "playwright", "install", "chromium"]
30
+ };
31
+ const vueHonoReadyCommand = {
32
+ id: "vue-hono-install-playwright-browsers",
33
+ label: "Install Playwright browser assets for web workspace",
34
+ command: "pnpm",
35
+ args: ["--filter", "./apps/web", "exec", "playwright", "install", "chromium"]
36
+ };
37
+ function postCommandsForPreset(options) {
38
+ if (options.preset !== "ts-lib" &&
39
+ options.preset !== "hono-api" &&
40
+ options.preset !== "vue-app" &&
41
+ options.preset !== "vue-hono-app") {
42
+ return [];
43
+ }
44
+ const commands = [...nodeReadyCommands];
45
+ if (options.preset === "vue-app") {
46
+ commands.push(vueReadyCommand);
47
+ }
48
+ if (options.preset === "vue-hono-app") {
49
+ commands.push(vueHonoReadyCommand);
50
+ }
51
+ return commands.map((command) => ({
52
+ ...command,
53
+ cwd: options.targetDir
54
+ }));
55
+ }
56
+ export function planPostCommands(options) {
57
+ const targetDir = path.resolve(options.targetDir);
58
+ const commands = postCommandsForPreset({
59
+ preset: options.preset,
60
+ targetDir
61
+ }).map((command) => Object.freeze(command));
62
+ return Object.freeze({
63
+ preset: options.preset,
64
+ targetDir,
65
+ commands: Object.freeze(commands),
66
+ [postCommandPlanBrand]: true
67
+ });
68
+ }
69
+ async function defaultExecutor(command) {
70
+ return await new Promise((resolve, reject) => {
71
+ const child = spawn(command.command, [...command.args], {
72
+ cwd: command.cwd,
73
+ stdio: "ignore"
74
+ });
75
+ child.once("error", reject);
76
+ child.once("close", (exitCode) => {
77
+ resolve({ exitCode: exitCode ?? 1 });
78
+ });
79
+ });
80
+ }
81
+ function isWithinDirectory(parentDir, childPath) {
82
+ const relativePath = path.relative(path.resolve(parentDir), path.resolve(childPath));
83
+ return (relativePath === "" ||
84
+ (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)));
85
+ }
86
+ function sameCommand(left, right) {
87
+ return (left.id === right.id &&
88
+ left.label === right.label &&
89
+ left.command === right.command &&
90
+ left.args.length === right.args.length &&
91
+ left.args.every((arg, index) => arg === right.args[index]) &&
92
+ path.resolve(left.cwd) === path.resolve(right.cwd));
93
+ }
94
+ function validatePostCommandPlan(plan) {
95
+ const expectedCommands = postCommandsForPreset({
96
+ preset: plan.preset,
97
+ targetDir: path.resolve(plan.targetDir)
98
+ });
99
+ const expectedCommandIds = new Set(expectedCommands.map((command) => command.id));
100
+ for (const command of plan.commands) {
101
+ if (!isWithinDirectory(plan.targetDir, command.cwd)) {
102
+ throw new Error(`Post Command cwd must stay within the target directory: ${command.id}`);
103
+ }
104
+ if (!expectedCommandIds.has(command.id)) {
105
+ throw new Error(`Unplanned Post Command: ${command.id}`);
106
+ }
107
+ }
108
+ if (plan.commands.length !== expectedCommands.length) {
109
+ throw new Error("Post Command plan must match the complete planned sequence");
110
+ }
111
+ for (const [index, command] of plan.commands.entries()) {
112
+ const expectedCommand = expectedCommands[index];
113
+ if (!expectedCommand || command.id !== expectedCommand.id) {
114
+ throw new Error("Post Command plan must match the complete planned sequence");
115
+ }
116
+ if (!sameCommand(command, expectedCommand)) {
117
+ throw new Error(`Post Command does not match the planned template command: ${command.id}`);
118
+ }
119
+ }
120
+ }
121
+ export async function runPostCommands(options) {
122
+ const executor = options.executor ?? defaultExecutor;
123
+ const results = [];
124
+ validatePostCommandPlan(options.plan);
125
+ for (const command of options.plan.commands) {
126
+ try {
127
+ const result = await executor(command);
128
+ if (result.exitCode !== 0) {
129
+ results.push({
130
+ command,
131
+ status: "failed",
132
+ exitCode: result.exitCode,
133
+ error: `Post Command failed with exit code ${result.exitCode}: ${command.id}`
134
+ });
135
+ break;
136
+ }
137
+ results.push({ command, status: "run", exitCode: 0 });
138
+ }
139
+ catch (error) {
140
+ const exitCode = error instanceof Error && "exitCode" in error && typeof error.exitCode === "number"
141
+ ? error.exitCode
142
+ : null;
143
+ const message = error instanceof Error ? error.message : String(error);
144
+ results.push({
145
+ command,
146
+ status: "failed",
147
+ exitCode,
148
+ error: `Post Command failed: ${command.id}: ${message}`
149
+ });
150
+ break;
151
+ }
152
+ }
153
+ return results;
154
+ }