@ykdz/template 0.0.17 → 0.0.19

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/cli.js +61 -24
  2. package/node_modules/@ykdz/template-builtin-source/dist/templates/registry.js +0 -2
  3. package/node_modules/@ykdz/template-builtin-source/dist/templates/ts-lib/projection.js +4 -4
  4. package/node_modules/@ykdz/template-builtin-source/dist/templates/vike-app/projection.js +13 -2
  5. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-app/projection.js +6 -74
  6. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-hono-app/projection.js +23 -22
  7. package/node_modules/@ykdz/template-builtin-source/package.json +41 -8
  8. package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +69 -282
  9. package/node_modules/@ykdz/template-builtin-source/templates/rust-bin/src/main.rs +5 -2
  10. package/node_modules/@ykdz/template-builtin-source/templates/rust-bin/turbo.json +2 -1
  11. package/node_modules/@ykdz/template-builtin-source/templates/ts-lib/turbo.json +2 -1
  12. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{drizzle.config.ts → db/drizzle.config.ts} +2 -2
  13. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{database → db/src}/db.ts +1 -1
  14. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/index.ts +1 -0
  15. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{database → db/src}/queries/todos.ts +4 -8
  16. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{test → db/test}/todos.test.ts +2 -2
  17. package/node_modules/@ykdz/template-builtin-source/templates/{hono-api → vike-app/db}/turbo.json +2 -1
  18. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{pages → web/pages}/index/+Page.telefunc.ts +3 -1
  19. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{pages → web/pages}/index/+Page.vue +1 -2
  20. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/playwright.config.ts +43 -0
  21. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/scripts/run-playwright.ts +62 -0
  22. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{server → web/server}/app.ts +1 -1
  23. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{test → web/test}/e2e/app.spec.ts +1 -1
  24. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/turbo.json +12 -0
  25. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{types → web/types}/global.d.ts +1 -1
  26. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{vite.config.ts → web/vite.config.ts} +1 -0
  27. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/playwright.config.ts +16 -3
  28. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/scripts/run-playwright.ts +38 -1
  29. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/turbo.json +1 -0
  30. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/api/turbo.json +1 -0
  31. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/playwright.config.ts +7 -9
  32. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/scripts/run-playwright.ts +79 -1
  33. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/src/App.vue +4 -5
  34. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/src/api.ts +9 -3
  35. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/test/e2e/app.spec.ts +1 -1
  36. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/turbo.json +1 -0
  37. package/node_modules/@ykdz/template-core/dist/Cargo.lock +16 -0
  38. package/node_modules/@ykdz/template-core/dist/Cargo.toml +12 -0
  39. package/node_modules/@ykdz/template-core/dist/dependency-catalog.d.ts +8 -0
  40. package/node_modules/@ykdz/template-core/dist/dependency-catalog.js +63 -2
  41. package/node_modules/@ykdz/template-core/dist/generated-scenarios.d.ts +1 -1
  42. package/node_modules/@ykdz/template-core/dist/generated-scenarios.js +104 -26
  43. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +1 -0
  44. package/node_modules/@ykdz/template-core/dist/module-graph.js +19 -12
  45. package/node_modules/@ykdz/template-core/dist/next-step-instructions.d.ts +8 -0
  46. package/node_modules/@ykdz/template-core/dist/next-step-instructions.js +65 -0
  47. package/node_modules/@ykdz/template-core/dist/package-addition.js +6 -12
  48. package/node_modules/@ykdz/template-core/dist/package-linking.d.ts +24 -0
  49. package/node_modules/@ykdz/template-core/dist/package-linking.js +35 -0
  50. package/node_modules/@ykdz/template-core/dist/preset-source.d.ts +2 -2
  51. package/node_modules/@ykdz/template-core/dist/projection-capabilities.js +240 -153
  52. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +46 -10
  53. package/node_modules/@ykdz/template-core/package.json +9 -5
  54. package/node_modules/@ykdz/template-shared/dist/index.d.ts +3 -152
  55. package/node_modules/@ykdz/template-shared/dist/index.js +49 -305
  56. package/node_modules/@ykdz/template-shared/package.json +8 -4
  57. package/package.json +5 -5
  58. package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.d.ts +0 -6
  59. package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.js +0 -213
  60. package/node_modules/@ykdz/template-builtin-source/templates/hono-api/.github/dependabot.yml +0 -23
  61. package/node_modules/@ykdz/template-builtin-source/templates/hono-api/.github/workflows/check.yml +0 -19
  62. package/node_modules/@ykdz/template-builtin-source/templates/hono-api/src/app.ts +0 -5
  63. package/node_modules/@ykdz/template-builtin-source/templates/hono-api/src/server.ts +0 -15
  64. package/node_modules/@ykdz/template-builtin-source/templates/hono-api/test/app.test.ts +0 -10
  65. package/node_modules/@ykdz/template-builtin-source/templates/hono-api/vitest.config.ts +0 -15
  66. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/playwright.config.ts +0 -23
  67. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/playwright.package-addition.config.ts +0 -22
  68. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{database → db/src}/schema.ts +0 -0
  69. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{+server.ts → web/+server.ts} +0 -0
  70. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{.env.example → web/.env.example} +0 -0
  71. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{assets → web/assets}/logo.svg +0 -0
  72. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{components → web/components}/CounterButton.vue +0 -0
  73. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{components → web/components}/PageShell.vue +0 -0
  74. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{pages → web/pages}/+Head.vue +0 -0
  75. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{pages → web/pages}/+Layout.vue +0 -0
  76. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{pages → web/pages}/+config.ts +0 -0
  77. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{pages → web/pages}/tailwind.css +0 -0
  78. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{server → web/server}/api.ts +0 -0
  79. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{types → web/types}/env.d.ts +0 -0
  80. /package/node_modules/@ykdz/template-builtin-source/templates/vike-app/{vitest.config.ts → web/vitest.config.ts} +0 -0
@@ -1,10 +1,12 @@
1
1
  import { readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { PackageAdditionSupport } from "@ykdz/template-shared";
3
4
  import { execa } from "execa";
4
5
  import * as v from "valibot";
5
6
  import { assembleGenerationContext } from "./generation-context.js";
6
7
  import { playwrightBrowserAssetsEnvironmentNeed, } from "./module-graph.js";
7
8
  import { planNextStepInstructions } from "./next-step-instructions.js";
9
+ import { canPlanPackageLinkIntent, } from "./package-linking.js";
8
10
  import { findPresetSourceManifestPreset } from "./preset-source.js";
9
11
  import { blueprintForPresetSourcePreset, projectPresetSourcePreset, } from "./projection-capabilities.js";
10
12
  const defaultDeterministicToolchainEnv = {
@@ -83,40 +85,105 @@ function createGeneratedScenario(set, input) {
83
85
  };
84
86
  }
85
87
  export function selectGeneratedScenarios(manifest, set) {
86
- const contract = manifest.fixtureMatrix;
87
- if (!contract) {
88
- throw new Error(`Preset Source ${manifest.name} does not declare a Fixture Matrix Contract`);
89
- }
90
- const initScenarios = contract.initSupport.map((support) => createGeneratedScenario(set, { basePreset: support.preset }));
88
+ const supportedInitPresets = manifest.presets.filter((preset) => preset.generation === "supported");
89
+ const initScenarios = supportedInitPresets.map((preset) => createGeneratedScenario("init", { basePreset: preset.name }));
91
90
  if (set === "init") {
92
91
  return { runnable: initScenarios, skipped: [] };
93
92
  }
94
- const matrixScenarios = contract.supportedCombinations.map((combination) => createGeneratedScenario(set, {
95
- basePreset: combination.basePreset,
96
- addedPreset: combination.addedPreset,
97
- ...(combination.linkFrom === undefined
98
- ? {}
99
- : { linkFrom: combination.linkFrom }),
100
- }));
101
- const skipped = contract.semanticSkips.map((skip) => ({
102
- set,
103
- basePreset: skip.basePreset,
104
- addedPreset: skip.addedPreset,
105
- id: generatedScenarioId(skip),
106
- label: generatedScenarioLabel(skip),
107
- reason: skip.reason,
108
- }));
93
+ const addablePresets = manifest.presets.filter((preset) => preset.packageAdditionSupport === PackageAdditionSupport.Supported);
94
+ const focusedScenarios = selectFocusedGeneratedScenarios(manifest, addablePresets);
95
+ if (set === "focused") {
96
+ return {
97
+ runnable: focusedScenarios,
98
+ skipped: [],
99
+ };
100
+ }
101
+ const matrixScenarios = supportedInitPresets.flatMap((basePreset) => addablePresets.map((addedPreset) => createGeneratedScenario("package-addition-matrix", {
102
+ basePreset: basePreset.name,
103
+ addedPreset: addedPreset.name,
104
+ })));
109
105
  return {
110
- runnable: matrixScenarios,
111
- skipped,
106
+ runnable: [...matrixScenarios, ...focusedScenarios],
107
+ skipped: [],
112
108
  };
113
109
  }
110
+ function selectFocusedGeneratedScenarios(manifest, addablePresets) {
111
+ return manifest.presets
112
+ .filter((preset) => preset.generation === "supported")
113
+ .flatMap((basePreset) => addablePresets.flatMap((addedPreset) => {
114
+ const consumerPackagePath = focusedPackageLinkConsumerPath(basePreset, addedPreset);
115
+ return consumerPackagePath === undefined
116
+ ? []
117
+ : [
118
+ createGeneratedScenario("focused", {
119
+ basePreset: basePreset.name,
120
+ addedPreset: addedPreset.name,
121
+ linkFrom: [consumerPackagePath],
122
+ }),
123
+ ];
124
+ }));
125
+ }
126
+ function focusedPackageLinkConsumerPath(basePreset, addedPreset) {
127
+ const providers = packageLinkProviderDefinitions(addedPreset);
128
+ return packageLinkConsumerDefinitions(basePreset).find((consumer) => providers.some((provider) => canPlanPackageLinkIntent({ consumer, provider })))?.path;
129
+ }
130
+ function packageLinkProviderDefinitions(preset) {
131
+ return (preset.projection?.capabilities.flatMap((capability) => {
132
+ if (capability.kind !== "workspace-library-package") {
133
+ return [];
134
+ }
135
+ const packageLeafName = `fixture-${preset.name}`;
136
+ return [
137
+ {
138
+ name: `@fixture/${packageLeafName}`,
139
+ path: `${packageCollection(capability.workspacePackageGlob)}/${packageLeafName}`,
140
+ role: capability.packageRole,
141
+ sourcePreset: capability.packageSourcePreset,
142
+ },
143
+ ];
144
+ }) ?? []);
145
+ }
146
+ function packageLinkConsumerDefinitions(preset) {
147
+ return (preset.projection?.capabilities.flatMap((capability) => {
148
+ if (capability.kind !== "workspace-node-packages") {
149
+ return [];
150
+ }
151
+ return capability.packages
152
+ .filter((nodePackage) => nodePackage.sourceFiles.some((sourceFile) => hasSourceDirectoryPath(sourceFile)))
153
+ .map((nodePackage) => ({
154
+ name: `@fixture/${packageLeaf(nodePackage.path)}`,
155
+ path: nodePackage.path,
156
+ role: "runtime-service",
157
+ sourcePreset: nodePackage.kind,
158
+ }));
159
+ }) ?? []);
160
+ }
161
+ function packageCollection(workspacePackageGlob) {
162
+ const [collection, wildcard] = workspacePackageGlob.split("/");
163
+ if (!collection || wildcard !== "*") {
164
+ throw new Error(`Unsupported workspace package glob: ${workspacePackageGlob}`);
165
+ }
166
+ return collection;
167
+ }
168
+ function packageLeaf(packagePath) {
169
+ return packagePath.split("/").at(-1) ?? packagePath;
170
+ }
171
+ function hasSourceDirectoryPath(sourceFile) {
172
+ return sourceFile.startsWith("src/") || sourceFile.includes("/src/");
173
+ }
114
174
  export function packageLeafNameForAddedPreset(manifest, presetName) {
115
- const support = manifest.fixtureMatrix?.packageAdditionSupport.find((entry) => entry.preset === presetName);
116
- if (!support) {
117
- throw new Error(`Missing fixture Package Addition leaf name for preset: ${presetName}`);
175
+ const preset = manifest.presets.find((entry) => entry.name === presetName);
176
+ if (!preset) {
177
+ throw new Error(`Unknown fixture Package Addition preset: ${presetName}`);
178
+ }
179
+ if (preset.packageAdditionSupport !== PackageAdditionSupport.Supported) {
180
+ throw new Error(`Preset ${presetName} cannot be used for fixture Package Addition`);
118
181
  }
119
- return support.packageLeafName;
182
+ const packageLeafName = `fixture-${presetName}`;
183
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(packageLeafName)) {
184
+ throw new Error(`Fixture Package Addition leaf name for preset ${presetName} must be a lowercase package leaf name using letters, numbers, and hyphens`);
185
+ }
186
+ return packageLeafName;
120
187
  }
121
188
  export async function defaultGeneratedScenarioCommandRunner(command, args, cwd, options = {}) {
122
189
  const prefix = options.logPrefix ? `[${options.logPrefix}] ` : "";
@@ -317,6 +384,10 @@ async function runGeneratedScenarioQualityGate(manifest, scenario, projectDir, a
317
384
  const steps = generatedScenarioQualityGateSteps(manifest, scenario, projectDir, addedPackagePath, options);
318
385
  const requiresSerializedPlaywrightRootCheck = generatedScenarioRequiresSerializedRootCheck(steps);
319
386
  for (const step of steps) {
387
+ if (step.id === "install-dependencies") {
388
+ await runGeneratedScenarioInstallStep(step, scenario, options);
389
+ continue;
390
+ }
320
391
  const env = step.id === "run-root-check" ? { CI: "1" } : undefined;
321
392
  const runStep = async () => {
322
393
  await options.runCommand(step.command, [...step.args], step.cwd, {
@@ -331,6 +402,13 @@ async function runGeneratedScenarioQualityGate(manifest, scenario, projectDir, a
331
402
  await runStep();
332
403
  }
333
404
  }
405
+ async function runGeneratedScenarioInstallStep(step, scenario, options) {
406
+ await options.runCommand(step.command, ["install", "--lockfile-only", "--prefer-offline", "--no-frozen-lockfile"], step.cwd, { logPrefix: scenario.label });
407
+ await options.runCommand(step.command, ["fetch"], step.cwd, {
408
+ logPrefix: scenario.label,
409
+ });
410
+ await options.runCommand(step.command, ["install", "--offline", "--frozen-lockfile"], step.cwd, { logPrefix: scenario.label });
411
+ }
334
412
  async function checkGeneratedScenario(manifest, scenario, workspace, options) {
335
413
  options.reporter.info?.(`\n== ${scenario.label} ==`);
336
414
  const projectDir = await generateScenario(scenario, workspace, options);
@@ -37,6 +37,7 @@ export type CheckPlan = {
37
37
  export type FixPlan = {
38
38
  readonly components: FixComponent[];
39
39
  };
40
+ export declare function renderTurboRunCommand(taskNames: readonly string[], args?: readonly string[]): string;
40
41
  export declare function checkComponentTaskName(component: CheckComponent): string;
41
42
  export declare function fixComponentTaskName(component: FixComponent): string;
42
43
  export declare function renderCheckLeafCommand(component: CheckComponent): string;
@@ -4,6 +4,13 @@ function renderTurboPackageFilter(owner) {
4
4
  }
5
5
  return `--filter './${owner.path}'`;
6
6
  }
7
+ const turboAgentOutputArgs = [
8
+ "--output-logs=errors-only",
9
+ "--log-order=grouped",
10
+ ];
11
+ export function renderTurboRunCommand(taskNames, args = []) {
12
+ return ["turbo run", ...taskNames, ...args, ...turboAgentOutputArgs].join(" ");
13
+ }
7
14
  export function checkComponentTaskName(component) {
8
15
  return checkComponentTaskNames(component)[0] ?? "check:run";
9
16
  }
@@ -73,17 +80,17 @@ export function renderCheckLeafCommand(component) {
73
80
  case "e2e-test":
74
81
  return "pnpm run test:e2e";
75
82
  case "turbo-check":
76
- return "turbo run check";
83
+ return renderTurboRunCommand(["check"]);
77
84
  case "turbo-package-typecheck":
78
- return `turbo run typecheck ${renderTurboPackageFilter(component.owner)}`;
85
+ return renderTurboRunCommand(["typecheck"], [renderTurboPackageFilter(component.owner)]);
79
86
  case "turbo-package-build":
80
- return `turbo run build ${renderTurboPackageFilter(component.owner)}`;
87
+ return renderTurboRunCommand(["build"], [renderTurboPackageFilter(component.owner)]);
81
88
  case "turbo-package-test":
82
- return `turbo run test ${renderTurboPackageFilter(component.owner)}`;
89
+ return renderTurboRunCommand(["test"], [renderTurboPackageFilter(component.owner)]);
83
90
  case "turbo-package-e2e-test":
84
- return `turbo run test:e2e ${renderTurboPackageFilter(component.owner)}`;
91
+ return renderTurboRunCommand(["test:e2e"], [renderTurboPackageFilter(component.owner)]);
85
92
  case "turbo-package-check":
86
- return `turbo run check ${renderTurboPackageFilter(component.owner)}`;
93
+ return renderTurboRunCommand(["check"], [renderTurboPackageFilter(component.owner)]);
87
94
  case "rustfmt-check":
88
95
  return "cargo fmt --all -- --check";
89
96
  case "cargo-clippy":
@@ -99,24 +106,24 @@ export function renderFixLeafCommand(component) {
99
106
  case "oxc-lint-fix":
100
107
  return "pnpm run lint:fix";
101
108
  case "turbo-fix":
102
- return "turbo run fix";
109
+ return renderTurboRunCommand(["fix"]);
103
110
  case "turbo-package-fix":
104
- return `turbo run fix ${renderTurboPackageFilter(component.owner)}`;
111
+ return renderTurboRunCommand(["fix"], [renderTurboPackageFilter(component.owner)]);
105
112
  case "rustfmt-write":
106
113
  return "cargo fmt --all";
107
114
  }
108
115
  }
109
116
  export function renderRootCheckCommand(plan) {
110
- return `turbo run ${uniqueTaskNames([
117
+ return renderTurboRunCommand(uniqueTaskNames([
111
118
  ...plan.components.flatMap(checkComponentTaskNames),
112
119
  "check:run",
113
- ]).join(" ")}`;
120
+ ]));
114
121
  }
115
122
  export function renderFixCommand(plan) {
116
- return `turbo run ${uniqueTaskNames([
123
+ return renderTurboRunCommand(uniqueTaskNames([
117
124
  ...plan.components.flatMap(fixComponentTaskNames),
118
125
  "fix:run",
119
- ]).join(" ")}`;
126
+ ]));
120
127
  }
121
128
  function playwrightBrowserInstallArgs(need, options = {}) {
122
129
  const installArgs = [
@@ -1,5 +1,6 @@
1
1
  import type { CheckEnvironmentNeed } from "./module-graph.js";
2
2
  import type { PresetProjectionPlan } from "./preset-projection.js";
3
+ import type { RenderOperation } from "./renderer.js";
3
4
  export type NextStepInstruction = {
4
5
  readonly id: string;
5
6
  readonly label: string;
@@ -19,4 +20,11 @@ export type PlanNextStepInstructionsOptions = {
19
20
  readonly targetDir: string;
20
21
  readonly projectionPlan: PresetProjectionPlan;
21
22
  };
23
+ export type FollowUpDocumentPlan = {
24
+ readonly enabled: boolean;
25
+ readonly path?: "TODO.md";
26
+ };
22
27
  export declare function planNextStepInstructions(options: PlanNextStepInstructionsOptions): NextStepInstructionPlan;
28
+ export declare function formatNextStepInstructionsForCli(plan: NextStepInstructionPlan): string;
29
+ export declare function formatGeneratedFollowUpDocument(plan: NextStepInstructionPlan): string;
30
+ export declare function generatedFollowUpDocumentOperation(plan: NextStepInstructionPlan): RenderOperation;
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  function rootCheckInstruction() {
2
3
  return {
3
4
  id: "run-root-check",
@@ -114,3 +115,67 @@ export function planNextStepInstructions(options) {
114
115
  steps: Object.freeze([navigationStep, ...commandSteps].map((step) => Object.freeze(step))),
115
116
  });
116
117
  }
118
+ export function formatNextStepInstructionsForCli(plan) {
119
+ return [
120
+ "Next Step Instructions:",
121
+ "",
122
+ ...plan.steps.flatMap((step, index) => [
123
+ ` ${index + 1}. ${step.label}`,
124
+ ` ${step.display}`,
125
+ ]),
126
+ ].join("\n");
127
+ }
128
+ function isOptionalGitInstruction(step) {
129
+ return step.id.startsWith("optional-git-");
130
+ }
131
+ function relativeCwd(plan, step) {
132
+ const targetDir = path.resolve(plan.targetDir);
133
+ const cwd = path.resolve(step.cwd);
134
+ if (cwd === targetDir) {
135
+ return undefined;
136
+ }
137
+ const relative = path.relative(targetDir, cwd);
138
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
139
+ return cwd;
140
+ }
141
+ return relative;
142
+ }
143
+ function taskTitle(step) {
144
+ const title = step.label.replace(/^Optional:\s*/, "");
145
+ return `${title.slice(0, 1).toUpperCase()}${title.slice(1)}`;
146
+ }
147
+ function formatTodoTask(plan, step) {
148
+ const taskLines = [`- [ ] ${taskTitle(step)}`];
149
+ const cwd = relativeCwd(plan, step);
150
+ if (cwd !== undefined) {
151
+ taskLines.push(` From \`${cwd}\`:`);
152
+ }
153
+ taskLines.push(` \`${step.display}\``);
154
+ return taskLines;
155
+ }
156
+ export function formatGeneratedFollowUpDocument(plan) {
157
+ const projectLocalSteps = plan.steps.filter((step) => step.kind === "command");
158
+ const nextSteps = projectLocalSteps.filter((step) => !isOptionalGitInstruction(step));
159
+ const optionalGitSteps = projectLocalSteps.filter(isOptionalGitInstruction);
160
+ return [
161
+ "# TODO",
162
+ "",
163
+ "Generated follow-up tasks for this repository.",
164
+ "",
165
+ "### Next Steps",
166
+ ...nextSteps.flatMap((step) => formatTodoTask(plan, step)),
167
+ "",
168
+ "### Optional Git Setup",
169
+ ...optionalGitSteps.flatMap((step) => formatTodoTask(plan, step)),
170
+ "",
171
+ "### Done ✓",
172
+ "",
173
+ ].join("\n");
174
+ }
175
+ export function generatedFollowUpDocumentOperation(plan) {
176
+ return {
177
+ kind: "writeText",
178
+ to: "TODO.md",
179
+ text: formatGeneratedFollowUpDocument(plan),
180
+ };
181
+ }
@@ -3,7 +3,8 @@ import path from "node:path";
3
3
  import { validateProjectBlueprint, } from "@ykdz/template-shared";
4
4
  import { PackageAdditionSupport, } from "@ykdz/template-shared";
5
5
  import { collectGeneratedManifestCatalogReferences, pnpmWorkspaceYamlWithCatalogDependencies, } from "./dependency-catalog.js";
6
- import { assertTypeScriptPackageBoundaryForLinkIntent, packageTurboTasks, planPackageLinks, } from "./package-linking.js";
6
+ import { renderTurboRunCommand } from "./module-graph.js";
7
+ import { assertTypeScriptPackageBoundaryForLinkIntent, packageTurboConfig, planPackageLinks, } from "./package-linking.js";
7
8
  import { findPresetSourceManifestPreset, } from "./preset-source.js";
8
9
  import { defaultPackagePathForPresetSourcePackageAddition, planPresetSourcePackageAddition, } from "./projection-capabilities.js";
9
10
  import { renderProject } from "./renderer.js";
@@ -385,11 +386,7 @@ function rootScriptWithTurboPackageTasks(options) {
385
386
  const rootCommands = options.script
386
387
  .split(" && ")
387
388
  .filter((command) => !command.startsWith("turbo run "));
388
- const turboCommand = [
389
- "turbo run",
390
- ...options.taskNames,
391
- ...(options.concurrency === undefined ? [] : ["--concurrency=1"]),
392
- ].join(" ");
389
+ const turboCommand = renderTurboRunCommand(options.taskNames);
393
390
  return [...rootCommands, turboCommand].join(" && ");
394
391
  }
395
392
  function rootPackageJsonWithPackageTaskFilters(input, blueprint) {
@@ -415,7 +412,6 @@ function rootPackageJsonWithPackageTaskFilters(input, blueprint) {
415
412
  "test:e2e:run",
416
413
  "check:run",
417
414
  ],
418
- concurrency: 1,
419
415
  }),
420
416
  fix: rootScriptWithTurboPackageTasks({
421
417
  script: fixScript,
@@ -446,11 +442,9 @@ function packageManifestsNeedDependencyBuilds(manifests) {
446
442
  return manifests.some((manifest) => Object.entries(manifest.dependencies ?? {}).some(([dependencyName, specifier]) => specifier === "workspace:*" && compiledPackageNames.has(dependencyName)));
447
443
  }
448
444
  function turboConfigForPackageManifests(manifests) {
449
- return {
450
- tasks: packageTurboTasks({
451
- dependencyBuildsRequired: packageManifestsNeedDependencyBuilds(manifests),
452
- }),
453
- };
445
+ return packageTurboConfig({
446
+ dependencyBuildsRequired: packageManifestsNeedDependencyBuilds(manifests),
447
+ });
454
448
  }
455
449
  function rootPackageJsonWithSharedOxcRuntimeDependencies(input, requiresSharedOxcConfiguration) {
456
450
  if (!requiresSharedOxcConfiguration) {
@@ -32,12 +32,33 @@ export type PackageManifestExposureFields = {
32
32
  readonly types?: string;
33
33
  };
34
34
  export type PackageLinkBoundaryDirection = "consumer" | "provider";
35
+ export type PackageLinkIntentCompatibility = {
36
+ readonly consumer: PackageDefinition;
37
+ readonly provider: PackageDefinition;
38
+ };
35
39
  export type TurboTaskDefinition = {
36
40
  readonly dependsOn?: readonly string[];
37
41
  readonly outputs?: readonly string[];
38
42
  readonly cache?: boolean;
39
43
  };
40
44
  export type TurboTaskGraph = Readonly<Record<string, TurboTaskDefinition>>;
45
+ export type TurboBoundaryRule = {
46
+ readonly dependencies?: {
47
+ readonly allow?: readonly string[];
48
+ readonly deny?: readonly string[];
49
+ };
50
+ readonly dependents?: {
51
+ readonly allow?: readonly string[];
52
+ readonly deny?: readonly string[];
53
+ };
54
+ };
55
+ export type TurboBoundaries = {
56
+ readonly tags: Readonly<Record<string, TurboBoundaryRule>>;
57
+ };
58
+ export type TurboConfig = {
59
+ readonly tasks: TurboTaskGraph;
60
+ readonly boundaries: TurboBoundaries;
61
+ };
41
62
  export type PackageTurboTaskOptions = {
42
63
  readonly dependencyBuildsRequired: boolean;
43
64
  };
@@ -46,6 +67,9 @@ export declare function assertTypeScriptPackageBoundaryForLinkIntent(definition:
46
67
  readonly role: PackageRole;
47
68
  readonly sourcePreset: PackageSourcePreset;
48
69
  };
70
+ export declare function canPlanPackageLinkIntent({ consumer, provider, }: PackageLinkIntentCompatibility): boolean;
49
71
  export declare function planPackageLinks(definitions: readonly PackageDefinition[], intents?: readonly PackageLinkIntent[]): PackageLinkPlan;
50
72
  export declare function packageTurboTasks({ dependencyBuildsRequired, }: PackageTurboTaskOptions): TurboTaskGraph;
73
+ export declare function generatedRepositoryTurboBoundaries(): TurboBoundaries;
74
+ export declare function packageTurboConfig(options: PackageTurboTaskOptions): TurboConfig;
51
75
  export declare function packageManifestExposureFields(exposure: PackageExposure): PackageManifestExposureFields;
@@ -28,6 +28,17 @@ export function assertTypeScriptPackageBoundaryForLinkIntent(definition, directi
28
28
  const relationship = direction === "consumer" ? "from native package" : "to native package";
29
29
  throw new Error(`Package Link Intent ${relationship} ${definition.path} is unsupported in V1 TypeScript-only Project Linking`);
30
30
  }
31
+ export function canPlanPackageLinkIntent({ consumer, provider, }) {
32
+ try {
33
+ assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
34
+ assertTypeScriptPackageBoundaryForLinkIntent(provider, "provider");
35
+ derivePackageExposure(provider);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
31
42
  export function planPackageLinks(definitions, intents = []) {
32
43
  const definitionsByPath = new Map(definitions.map((definition) => [definition.path, definition]));
33
44
  const manifestDependenciesByPackagePath = new Map();
@@ -85,6 +96,30 @@ export function packageTurboTasks({ dependencyBuildsRequired, }) {
85
96
  "fix:run": { cache: false },
86
97
  };
87
98
  }
99
+ // Boundary violations are architecture problems. Do not loosen these rules to
100
+ // make a failing dependency graph pass; fix the dependency direction instead.
101
+ export function generatedRepositoryTurboBoundaries() {
102
+ return {
103
+ tags: {
104
+ app: {
105
+ dependencies: {
106
+ deny: ["app"],
107
+ },
108
+ },
109
+ library: {
110
+ dependencies: {
111
+ deny: ["app"],
112
+ },
113
+ },
114
+ },
115
+ };
116
+ }
117
+ export function packageTurboConfig(options) {
118
+ return {
119
+ tasks: packageTurboTasks(options),
120
+ boundaries: generatedRepositoryTurboBoundaries(),
121
+ };
122
+ }
88
123
  export function packageManifestExposureFields(exposure) {
89
124
  switch (exposure.kind) {
90
125
  case "jit-source":
@@ -1,8 +1,8 @@
1
- import type { BuiltInPreset, PresetSourceFixtureMatrixContract, PresetSourceManifest, PresetSourceManifestPreset, PresetSourceManifestPresetSource, PresetSourceManifestSharedResource, ValidationResult } from "@ykdz/template-shared";
1
+ import type { BuiltInPreset, PresetSourceManifest, PresetSourceManifestPreset, PresetSourceManifestPresetSource, PresetSourceManifestSharedResource, ValidationResult } from "@ykdz/template-shared";
2
2
  import { presetSourceManifestJsonSchema } from "@ykdz/template-shared";
3
3
  import { type TemplateDependencyCatalog } from "./dependency-catalog.js";
4
4
  export { presetSourceManifestJsonSchema };
5
- export type { BuiltInPreset, PresetSourceFixtureMatrixContract, PresetSourceManifest, PresetSourceManifestPreset, PresetSourceManifestPresetSource, PresetSourceManifestSharedResource, };
5
+ export type { BuiltInPreset, PresetSourceManifest, PresetSourceManifestPreset, PresetSourceManifestPresetSource, PresetSourceManifestSharedResource, };
6
6
  export type PresetSourceManifestValidationOptions = {
7
7
  readonly sourceRoot?: string;
8
8
  readonly dependencyCatalog?: TemplateDependencyCatalog;