@ykdz/template 0.0.21 → 0.0.23

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 (40) hide show
  1. package/node_modules/@ykdz/template-builtin-source/package.json +1 -1
  2. package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +11 -0
  3. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/.github/dependabot.yml +4 -0
  4. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/.github/workflows/check.yml +2 -0
  5. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/drizzle/migrations/20260709120325_old_captain_flint/migration.sql +6 -0
  6. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/drizzle/migrations/20260709120325_old_captain_flint/snapshot.json +67 -0
  7. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/scripts/seed-example.ts +7 -0
  8. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/db.ts +2 -1
  9. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/index.ts +1 -0
  10. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/queries/todos.ts +17 -1
  11. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/readiness.ts +22 -0
  12. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/schema.ts +1 -1
  13. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/seed/example.ts +23 -0
  14. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/test/todos.test.ts +2 -0
  15. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/+server.ts +2 -1
  16. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/Dockerfile +83 -0
  17. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/Dockerfile.dockerignore +14 -0
  18. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/pages/index/+Page.telefunc.ts +1 -2
  19. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/pages/index/+Page.vue +6 -2
  20. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/playwright.config.ts +50 -14
  21. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/scripts/check-standalone-deployment.ts +449 -0
  22. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/scripts/container-entrypoint.sh +52 -0
  23. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/scripts/prepare-database.sh +6 -0
  24. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/scripts/run-playwright.ts +101 -12
  25. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/server/app.ts +23 -12
  26. package/node_modules/@ykdz/template-core/dist/dependency-catalog.js +13 -0
  27. package/node_modules/@ykdz/template-core/dist/generated-scenarios.d.ts +3 -2
  28. package/node_modules/@ykdz/template-core/dist/generated-scenarios.js +99 -13
  29. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +12 -0
  30. package/node_modules/@ykdz/template-core/dist/module-graph.js +18 -0
  31. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +7 -4
  32. package/node_modules/@ykdz/template-core/dist/project-github.d.ts +1 -0
  33. package/node_modules/@ykdz/template-core/dist/project-github.js +18 -5
  34. package/node_modules/@ykdz/template-core/dist/projection-capabilities.js +57 -7
  35. package/node_modules/@ykdz/template-core/dist/renderer.js +4 -1
  36. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +1 -1
  37. package/node_modules/@ykdz/template-core/package.json +2 -1
  38. package/node_modules/@ykdz/template-shared/dist/index.d.ts +6 -6
  39. package/node_modules/@ykdz/template-shared/package.json +1 -0
  40. package/package.json +5 -4
@@ -1,6 +1,17 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ const templateInternalDependencyIdentities = new Set([
5
+ "@typescript/native",
6
+ "@typescript/typescript6",
7
+ ]);
8
+ function assertGeneratedDependencyCatalogBoundary(dependencies) {
9
+ for (const dependency of dependencies) {
10
+ if (templateInternalDependencyIdentities.has(dependency)) {
11
+ throw new Error(`Generated Repository Dependency Catalog cannot include template-internal dependency: ${dependency}`);
12
+ }
13
+ }
14
+ }
4
15
  function templateRepositoryRoot() {
5
16
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
6
17
  const candidates = [
@@ -151,6 +162,7 @@ function renderCatalogKey(key) {
151
162
  return key.startsWith("@") ? JSON.stringify(key) : key;
152
163
  }
153
164
  export function pnpmWorkspaceYamlWithCatalogDependencies(workspaceYaml, dependencies, templateCatalog = loadTemplateDependencyCatalog()) {
165
+ assertGeneratedDependencyCatalogBoundary(dependencies);
154
166
  const catalogStart = workspaceYaml
155
167
  .split(/\r?\n/)
156
168
  .findIndex((line) => line === "catalog:");
@@ -183,6 +195,7 @@ export function pnpmWorkspaceYamlWithCatalogDependencies(workspaceYaml, dependen
183
195
  ].join("\n");
184
196
  }
185
197
  export function renderGeneratedPnpmWorkspaceYaml(options) {
198
+ assertGeneratedDependencyCatalogBoundary(options.dependencies);
186
199
  const catalog = selectTemplateDependencyCatalogEntries(options.dependencies);
187
200
  const packages = options.packages ?? ["."];
188
201
  const lines = [
@@ -1,4 +1,4 @@
1
- import { type CheckEnvironmentNeed } from "./module-graph.js";
1
+ import { type CheckEnvironmentNeed, type DeploymentCheckEnvironmentNeed } from "./module-graph.js";
2
2
  import type { PresetSourceManifest } from "./preset-source.js";
3
3
  import { type PresetProjectionSourceRoots } from "./projection-capabilities.js";
4
4
  export type GeneratedScenarioSet = "init" | "package-addition-matrix" | "focused";
@@ -28,7 +28,8 @@ export type GeneratedScenarioCommandStep = {
28
28
  readonly args: readonly string[];
29
29
  readonly cwd: string;
30
30
  readonly display: string;
31
- readonly environmentNeedKind?: CheckEnvironmentNeed["kind"];
31
+ readonly environmentNeedKind?: CheckEnvironmentNeed["kind"] | DeploymentCheckEnvironmentNeed["kind"];
32
+ readonly phase?: "deployment-preparation" | "deployment";
32
33
  };
33
34
  export type GeneratedScenarioCommandRunner = (command: string, args: readonly string[], cwd: string, options?: {
34
35
  readonly env?: Record<string, string> | undefined;
@@ -5,7 +5,7 @@ import { PackageAdditionSupport } from "@ykdz/template-shared";
5
5
  import { execa } from "execa";
6
6
  import * as v from "valibot";
7
7
  import { assembleGenerationContext } from "./generation-context.js";
8
- import { playwrightBrowserAssetsEnvironmentNeed, } from "./module-graph.js";
8
+ import { deploymentCheckEnvironmentNeeds, playwrightBrowserAssetsEnvironmentNeed, } from "./module-graph.js";
9
9
  import { planNextStepInstructions } from "./next-step-instructions.js";
10
10
  import { canPlanPackageLinkIntent, } from "./package-linking.js";
11
11
  import { findPresetSourceManifestPreset } from "./preset-source.js";
@@ -14,7 +14,7 @@ const defaultDeterministicToolchainEnv = {
14
14
  TEMPLATE_TOOLCHAIN_RESOLUTION: "bundled-fallback",
15
15
  };
16
16
  const defaultFixtureConcurrency = 2;
17
- const scenarioFingerprintVersion = 1;
17
+ const scenarioFingerprintVersion = 2;
18
18
  const storedBlueprintSchema = v.object({
19
19
  packages: v.optional(v.array(v.object({
20
20
  name: v.string(),
@@ -235,6 +235,11 @@ async function writeScenarioReplayMarker(cache, fingerprint, scenario) {
235
235
  await mkdir(cache.directory, { recursive: true });
236
236
  await writeFile(path.join(cache.directory, `${fingerprint}.passed`), `${JSON.stringify({ scenario, fingerprint })}\n`, "utf8");
237
237
  }
238
+ function deploymentReplayFingerprint(fingerprint) {
239
+ return createHash("sha256")
240
+ .update(`${fingerprint}\0docker-engine-available`)
241
+ .digest("hex");
242
+ }
238
243
  export function packageLeafNameForAddedPreset(manifest, presetName) {
239
244
  const preset = manifest.presets.find((entry) => entry.name === presetName);
240
245
  if (!preset) {
@@ -392,6 +397,36 @@ function machineVerifiableNextStepsForPreset(presetName, projectDir, manifest, o
392
397
  };
393
398
  });
394
399
  }
400
+ function deploymentStepsForPreset(presetName, scenario, projectDir, manifest, options) {
401
+ if (scenario.set === "init") {
402
+ return [];
403
+ }
404
+ const deploymentChecks = projectionPlanForPreset(presetName, projectDir, manifest, options).checkPlan
405
+ .deploymentChecks ?? [];
406
+ const needsDocker = deploymentChecks.some((check) => deploymentCheckEnvironmentNeeds(check).some((need) => need.kind === "docker-engine"));
407
+ if (!needsDocker) {
408
+ return [];
409
+ }
410
+ return [
411
+ {
412
+ id: "check-docker-engine",
413
+ command: "docker",
414
+ args: ["info", "--format", "{{.ServerVersion}}"],
415
+ cwd: projectDir,
416
+ display: "docker info --format {{.ServerVersion}}",
417
+ environmentNeedKind: "docker-engine",
418
+ phase: "deployment-preparation",
419
+ },
420
+ {
421
+ id: "run-deployment-check",
422
+ command: "pnpm",
423
+ args: ["run", "check:deployment"],
424
+ cwd: projectDir,
425
+ display: "pnpm run check:deployment",
426
+ phase: "deployment",
427
+ },
428
+ ];
429
+ }
395
430
  function addedPresetEnvironmentNeeds(manifest, scenario, projectDir, addedPackagePath, options) {
396
431
  if (!scenario.addedPreset || !addedPackagePath) {
397
432
  return [];
@@ -431,30 +466,37 @@ export function generatedScenarioQualityGateSteps(manifest, scenario, projectDir
431
466
  const addedEnvironmentSteps = generatedScenarioEnvironmentNeedSteps(addedPresetEnvironmentNeeds(manifest, scenario, projectDir, addedPackagePath, normalizedOptions), projectDir);
432
467
  const existingDisplays = new Set(steps.map((step) => step.display));
433
468
  const extraEnvironmentSteps = addedEnvironmentSteps.filter((step) => !existingDisplays.has(step.display));
469
+ const deploymentSteps = deploymentStepsForPreset(scenario.basePreset, scenario, projectDir, manifest, normalizedOptions);
434
470
  if (extraEnvironmentSteps.length === 0) {
435
- return steps;
471
+ return [...steps, ...deploymentSteps];
436
472
  }
437
473
  const rootCheckIndex = steps.findIndex((step) => step.id === "run-root-check");
438
474
  if (rootCheckIndex === -1) {
439
- return [...steps, ...extraEnvironmentSteps];
475
+ return [...steps, ...extraEnvironmentSteps, ...deploymentSteps];
440
476
  }
441
477
  return [
442
478
  ...steps.slice(0, rootCheckIndex),
443
479
  ...extraEnvironmentSteps,
444
480
  ...steps.slice(rootCheckIndex),
481
+ ...deploymentSteps,
445
482
  ];
446
483
  }
447
484
  async function runGeneratedScenarioQualityGate(manifest, scenario, projectDir, addedPackagePath, options) {
448
485
  const steps = generatedScenarioQualityGateSteps(manifest, scenario, projectDir, addedPackagePath, options);
449
486
  const requiresSerializedPlaywrightRootCheck = generatedScenarioRequiresSerializedRootCheck(steps);
450
487
  let replayFingerprint;
488
+ let baseQualityGateReplayed = false;
489
+ let baseQualityGateMarkerWritten = false;
451
490
  for (const step of steps) {
452
491
  if (step.id === "install-dependencies") {
453
492
  const installResult = await runGeneratedScenarioInstallStep(step, scenario, options);
454
493
  replayFingerprint = installResult.fingerprint;
455
- if (installResult.replayed) {
456
- return;
457
- }
494
+ baseQualityGateReplayed = installResult.baseQualityGateReplayed;
495
+ continue;
496
+ }
497
+ if (baseQualityGateReplayed &&
498
+ step.phase !== "deployment-preparation" &&
499
+ step.phase !== "deployment") {
458
500
  continue;
459
501
  }
460
502
  const env = step.id === "run-root-check" ? { CI: "1" } : undefined;
@@ -464,13 +506,57 @@ async function runGeneratedScenarioQualityGate(manifest, scenario, projectDir, a
464
506
  logPrefix: scenario.label,
465
507
  });
466
508
  };
509
+ if (step.environmentNeedKind === "docker-engine") {
510
+ try {
511
+ await runStep();
512
+ }
513
+ catch (error) {
514
+ const reason = error instanceof Error ? error.message : String(error);
515
+ options.reporter.info?.(`-- Skipping deployment check for ${scenario.label}: Docker engine is unavailable (${reason})`);
516
+ break;
517
+ }
518
+ continue;
519
+ }
520
+ if (step.phase === "deployment") {
521
+ const deploymentFingerprint = replayFingerprint === undefined
522
+ ? undefined
523
+ : deploymentReplayFingerprint(replayFingerprint);
524
+ if (deploymentFingerprint !== undefined &&
525
+ options.replayCache?.read &&
526
+ (await scenarioReplayMarkerExists(options.replayCache, deploymentFingerprint))) {
527
+ options.reporter.info?.(`-- Replayed passed deployment fixture ${scenario.label}: ${deploymentFingerprint}`);
528
+ continue;
529
+ }
530
+ try {
531
+ await runStep();
532
+ }
533
+ catch (error) {
534
+ throw new Error(`Generated deployment command failed for ${scenario.label}; command: ${step.display}. See the cause for the deployment mode, phase, command output, and retained container logs.`, { cause: error });
535
+ }
536
+ if (deploymentFingerprint !== undefined && options.replayCache?.write) {
537
+ await writeScenarioReplayMarker(options.replayCache, deploymentFingerprint, scenario);
538
+ options.reporter.info?.(`-- Stored passed deployment fixture replay marker for ${scenario.label}: ${deploymentFingerprint}`);
539
+ }
540
+ continue;
541
+ }
467
542
  if (step.id === "run-root-check" && requiresSerializedPlaywrightRootCheck) {
468
543
  await options.rootCheckLock.run(runStep);
469
- continue;
470
544
  }
471
- await runStep();
545
+ else {
546
+ await runStep();
547
+ }
548
+ if (step.id === "run-root-check" &&
549
+ options.replayCache?.write &&
550
+ replayFingerprint !== undefined) {
551
+ await writeScenarioReplayMarker(options.replayCache, replayFingerprint, scenario);
552
+ baseQualityGateMarkerWritten = true;
553
+ options.reporter.info?.(`-- Stored passed fixture replay marker for ${scenario.label}: ${replayFingerprint}`);
554
+ }
472
555
  }
473
- if (options.replayCache?.write && replayFingerprint !== undefined) {
556
+ if (options.replayCache?.write &&
557
+ replayFingerprint !== undefined &&
558
+ !baseQualityGateMarkerWritten &&
559
+ !baseQualityGateReplayed) {
474
560
  await writeScenarioReplayMarker(options.replayCache, replayFingerprint, scenario);
475
561
  options.reporter.info?.(`-- Stored passed fixture replay marker for ${scenario.label}: ${replayFingerprint}`);
476
562
  }
@@ -482,15 +568,15 @@ async function runGeneratedScenarioInstallStep(step, scenario, options) {
482
568
  replayFingerprint = await hashGeneratedScenarioDirectory(scenario, step.cwd);
483
569
  if (options.replayCache.read &&
484
570
  (await scenarioReplayMarkerExists(options.replayCache, replayFingerprint))) {
485
- options.reporter.info?.(`-- Replayed passed fixture ${scenario.label}: ${replayFingerprint}`);
486
- return { fingerprint: replayFingerprint, replayed: true };
571
+ options.reporter.info?.(`-- Replayed passed base quality gate for ${scenario.label}: ${replayFingerprint}`);
572
+ return { fingerprint: replayFingerprint, baseQualityGateReplayed: true };
487
573
  }
488
574
  }
489
575
  await options.runCommand(step.command, ["fetch"], step.cwd, {
490
576
  logPrefix: scenario.label,
491
577
  });
492
578
  await options.runCommand(step.command, ["install", "--offline", "--frozen-lockfile"], step.cwd, { logPrefix: scenario.label });
493
- return { fingerprint: replayFingerprint, replayed: false };
579
+ return { fingerprint: replayFingerprint, baseQualityGateReplayed: false };
494
580
  }
495
581
  async function checkGeneratedScenario(manifest, scenario, workspace, options) {
496
582
  options.reporter.info?.(`\n== ${scenario.label} ==`);
@@ -26,6 +26,13 @@ export type CheckComponent = {
26
26
  readonly kind: CheckComponentKind;
27
27
  readonly owner: ComponentOwner;
28
28
  };
29
+ export type DeploymentCheckComponent = {
30
+ readonly kind: "deployment-image";
31
+ readonly owner: PackageBoundaryOwner;
32
+ };
33
+ export type DeploymentCheckEnvironmentNeed = {
34
+ readonly kind: "docker-engine";
35
+ };
29
36
  export type FixComponent = {
30
37
  readonly kind: FixComponentKind;
31
38
  readonly owner: ComponentOwner;
@@ -33,6 +40,7 @@ export type FixComponent = {
33
40
  export type CheckPlan = {
34
41
  readonly components: CheckComponent[];
35
42
  readonly environmentNeeds: CheckEnvironmentNeed[];
43
+ readonly deploymentChecks?: DeploymentCheckComponent[] | undefined;
36
44
  };
37
45
  export type FixPlan = {
38
46
  readonly components: FixComponent[];
@@ -43,6 +51,10 @@ export declare function fixComponentTaskName(component: FixComponent): string;
43
51
  export declare function renderCheckLeafCommand(component: CheckComponent): string;
44
52
  export declare function renderFixLeafCommand(component: FixComponent): string;
45
53
  export declare function renderRootCheckCommand(plan: CheckPlan): string;
54
+ export declare function deploymentCheckEnvironmentNeeds(check: DeploymentCheckComponent): DeploymentCheckEnvironmentNeed[];
55
+ export declare function deploymentCheckTaskName(check: DeploymentCheckComponent): "check:deployment";
56
+ export declare function renderDeploymentCheckLeafCommand(check: DeploymentCheckComponent): string;
57
+ export declare function renderDeploymentCheckCommand(plan: Pick<CheckPlan, "deploymentChecks">): string;
46
58
  export declare function renderFixCommand(plan: FixPlan): string;
47
59
  export declare function renderPlaywrightBrowserInstallCommand(need: CheckEnvironmentNeed, options?: {
48
60
  readonly withDeps?: boolean;
@@ -119,6 +119,24 @@ export function renderRootCheckCommand(plan) {
119
119
  "check:run",
120
120
  ]));
121
121
  }
122
+ export function deploymentCheckEnvironmentNeeds(check) {
123
+ switch (check.kind) {
124
+ case "deployment-image":
125
+ return [{ kind: "docker-engine" }];
126
+ }
127
+ }
128
+ export function deploymentCheckTaskName(check) {
129
+ switch (check.kind) {
130
+ case "deployment-image":
131
+ return "check:deployment";
132
+ }
133
+ }
134
+ export function renderDeploymentCheckLeafCommand(check) {
135
+ return `pnpm --filter './${check.owner.path}' run ${deploymentCheckTaskName(check)}`;
136
+ }
137
+ export function renderDeploymentCheckCommand(plan) {
138
+ return uniqueTaskNames((plan.deploymentChecks ?? []).map(renderDeploymentCheckLeafCommand)).join(" && ");
139
+ }
122
140
  export function renderFixCommand(plan) {
123
141
  return renderTurboRunCommand(uniqueTaskNames([
124
142
  ...plan.components.flatMap(fixComponentTaskNames),
@@ -4,10 +4,12 @@ packages:
4
4
  nodeLinker: hoisted
5
5
 
6
6
  catalog:
7
+ "@typescript/native": npm:typescript@^7.0.2
8
+ "@typescript/typescript6": ^6.0.2
7
9
  "@hono/node-server": ^2.0.6
8
10
  "@playwright/test": ^1.61.1
9
11
  "@tailwindcss/vite": ^4.3.2
10
- "@types/node": ^24.0.0
12
+ "@types/node": ^24.13.3
11
13
  "@types/web-bluetooth": ^0.0.21
12
14
  "@vitejs/plugin-vue": ^6.0.7
13
15
  "@vikejs/hono": ^0.2.1
@@ -17,17 +19,18 @@ catalog:
17
19
  execa: ^9.6.1
18
20
  hono: ^4.12.27
19
21
  oxfmt: ^0.58.0
20
- oxlint: ^1.72.0
22
+ oxlint: ^1.73.0
21
23
  oxlint-tsgolint: ^0.24.0
22
24
  pinia: ^3.0.4
25
+ srvx: ^0.11.21
23
26
  tailwindcss: ^4.3.2
24
27
  telefunc: ^0.2.22
25
28
  tsc-alias: ^1.8.17
26
- tsx: ^4.22.4
29
+ tsx: ^4.23.0
27
30
  turbo: 2.10.2
28
31
  typescript: ^6.0.3
29
32
  valibot: ^1.4.2
30
- vite: ^8.1.2
33
+ vite: ^8.1.4
31
34
  vike: ^0.4.260
32
35
  vike-vue: ^0.9.12
33
36
  vitest: ^4.1.10
@@ -17,6 +17,7 @@ export type DependabotDirectory = `/${string}`;
17
17
  export type DependencyMaintenancePolicy = {
18
18
  readonly ecosystems: DependencyEcosystem[];
19
19
  readonly directories?: Partial<Record<DependencyEcosystem, DependabotDirectory>>;
20
+ readonly extraDirectories?: Partial<Record<DependencyEcosystem, readonly DependabotDirectory[]>>;
20
21
  readonly interval: "weekly";
21
22
  };
22
23
  type ProjectCheckWorkflowOptions = {
@@ -1,4 +1,4 @@
1
- import { renderPlaywrightBrowserInstallCommand, } from "./module-graph.js";
1
+ import { deploymentCheckEnvironmentNeeds, deploymentCheckTaskName, renderPlaywrightBrowserInstallCommand, } from "./module-graph.js";
2
2
  const defaultCiCapability = {
3
3
  workflowName: "Check",
4
4
  jobName: "check",
@@ -16,6 +16,8 @@ export function projectCheckWorkflow(options) {
16
16
  ...options.environmentPreparation,
17
17
  };
18
18
  const taskLayer = options.taskLayer ?? pnpmTaskLayer;
19
+ const deploymentChecks = options.checkPlan.deploymentChecks ?? [];
20
+ const needsDocker = deploymentChecks.some((check) => deploymentCheckEnvironmentNeeds(check).some((need) => need.kind === "docker-engine"));
19
21
  const lines = [
20
22
  `name: ${capability.workflowName}`,
21
23
  "",
@@ -34,12 +36,20 @@ export function projectCheckWorkflow(options) {
34
36
  if (environmentPreparation.nodeFromPackageMetadata) {
35
37
  lines.push(" - uses: actions/setup-node@v6", " with:", " node-version-file: package.json", " - run: corepack enable");
36
38
  }
39
+ if (needsDocker) {
40
+ lines.push(" - uses: docker/setup-buildx-action@v3");
41
+ }
37
42
  if (environmentPreparation.rustToolchain) {
38
43
  lines.push(" - uses: dtolnay/rust-toolchain@stable", " with:", " components: rustfmt, clippy", " - uses: Swatinem/rust-cache@v2");
39
44
  }
40
45
  lines.push(` - run: ${taskLayer.installCommand}`);
41
46
  lines.push(...options.checkPlan.environmentNeeds.map((need) => ` - run: ${renderCiEnvironmentNeedCommand(need)}`));
42
- lines.push(` - run: ${taskLayer.checkCommand}`, "");
47
+ lines.push(` - run: ${taskLayer.checkCommand}`);
48
+ const deploymentCheck = deploymentChecks[0];
49
+ if (deploymentCheck !== undefined) {
50
+ lines.push(` - run: pnpm run ${deploymentCheckTaskName(deploymentCheck)}`);
51
+ }
52
+ lines.push("");
43
53
  return lines.join("\n");
44
54
  }
45
55
  export function projectDependabotConfig(policy) {
@@ -47,8 +57,11 @@ export function projectDependabotConfig(policy) {
47
57
  "version: 2",
48
58
  "",
49
59
  "updates:",
50
- ...policy.ecosystems.flatMap((ecosystem) => renderDependabotUpdate(ecosystem, policy.directories?.[ecosystem] ??
51
- defaultDependabotDirectory(ecosystem), policy.interval)),
60
+ ...policy.ecosystems.flatMap((ecosystem) => [
61
+ policy.directories?.[ecosystem] ??
62
+ defaultDependabotDirectory(ecosystem),
63
+ ...(policy.extraDirectories?.[ecosystem] ?? []),
64
+ ].flatMap((directory) => renderDependabotUpdate(ecosystem, directory, policy.interval))),
52
65
  "",
53
66
  ].join("\n");
54
67
  }
@@ -62,7 +75,7 @@ function renderDependabotUpdate(ecosystem, directory, interval) {
62
75
  if (ecosystem === "npm") {
63
76
  lines.push(" groups:", " drizzle:", " patterns:", ' - "drizzle-*"', ' - "drizzle-orm"', " ignore:", ' - dependency-name: "@types/node"', " update-types:", " - version-update:semver-major");
64
77
  }
65
- if (ecosystem === "docker") {
78
+ if (ecosystem === "docker" && directory === "/.devcontainer") {
66
79
  lines.push(" ignore:", " - dependency-name: mcr.microsoft.com/devcontainers/typescript-node", " update-types:", " - version-update:semver-major");
67
80
  }
68
81
  return lines;
@@ -4,7 +4,7 @@ import { normalizePresetProjectionDeclaration, projectionCapabilityIssues, valid
4
4
  import { collectGeneratedManifestCatalogDependencies, collectGeneratedManifestCatalogReferences, renderCargoLockForPackage, renderGeneratedPnpmWorkspaceYaml, renderCargoDependencyTomlEntries, } from "./dependency-catalog.js";
5
5
  import { browserTestToolLayer, checkedDockerfileFirstNodePnpmDevcontainer, dockerfileFirstRustPnpmDevcontainer, nodePnpmToolLayer, rustToolLayer, } from "./devcontainer.js";
6
6
  import { editorCustomizationForCapabilities, loadEditorCustomizationDeclarations, } from "./editor-customization.js";
7
- import { playwrightBrowserAssetsEnvironmentNeed, renderCheckLeafCommand, renderFixCommand, renderFixLeafCommand, renderRootCheckCommand, renderTurboRunCommand, } from "./module-graph.js";
7
+ import { playwrightBrowserAssetsEnvironmentNeed, renderDeploymentCheckCommand, renderCheckLeafCommand, renderFixCommand, renderFixLeafCommand, renderRootCheckCommand, renderTurboRunCommand, } from "./module-graph.js";
8
8
  import { packageManifestExposureFields, packageTurboConfig, planPackageLinks, } from "./package-linking.js";
9
9
  export { normalizePresetProjectionDeclaration, projectionCapabilityIssues, validateProjectionCapabilities, };
10
10
  const strictTypescriptRootBoundary = {
@@ -77,6 +77,13 @@ const capabilityInterpreters = {
77
77
  state.nodeWorkspace = capability;
78
78
  state.sourceRoot = templateSourceRootForPreset(state, sourceRootPreset(capability));
79
79
  state.operationFactories.push(workspaceNodePackagesOperations);
80
+ if (hasVikePackage(capability)) {
81
+ state.dependencyMaintenanceExtraDirectories.docker = ["/apps/web"];
82
+ state.deploymentCheckComponents.push({
83
+ kind: "deployment-image",
84
+ owner: { kind: "package-boundary", path: "apps/web" },
85
+ });
86
+ }
80
87
  if (capability.packages.length > 1) {
81
88
  state.rootScriptFragments.dev = "turbo run dev --parallel";
82
89
  }
@@ -277,6 +284,9 @@ export function interpretPresetProjectionDeclaration(options) {
277
284
  const checkPlan = {
278
285
  components: state.rootCheckComponents,
279
286
  environmentNeeds: state.rootCheckEnvironmentNeeds,
287
+ ...(state.deploymentCheckComponents.length === 0
288
+ ? {}
289
+ : { deploymentChecks: state.deploymentCheckComponents }),
280
290
  };
281
291
  const fixPlan = {
282
292
  components: state.rootFixComponents,
@@ -285,6 +295,7 @@ export function interpretPresetProjectionDeclaration(options) {
285
295
  const packageScriptsByPath = packageScriptsByWorkspacePath(state);
286
296
  const dependencyMaintenancePolicy = {
287
297
  ecosystems: uniqueValues(state.dependencyMaintenanceEcosystems),
298
+ extraDirectories: state.dependencyMaintenanceExtraDirectories,
288
299
  ...(state.rustWorkspace === undefined
289
300
  ? {}
290
301
  : {
@@ -383,6 +394,7 @@ function createProjectionCompositionState(sourceRoots) {
383
394
  sourceRoots: {},
384
395
  rootCheckComponents: [],
385
396
  rootCheckEnvironmentNeeds: [],
397
+ deploymentCheckComponents: [],
386
398
  packageCheckComponents: [],
387
399
  rootFixComponents: [],
388
400
  packageFixComponents: [],
@@ -392,6 +404,7 @@ function createProjectionCompositionState(sourceRoots) {
392
404
  packageDependencies: new Set(),
393
405
  packageDevDependencies: new Set(),
394
406
  dependencyMaintenanceEcosystems: [],
407
+ dependencyMaintenanceExtraDirectories: {},
395
408
  editorCustomizationCapabilities: [],
396
409
  operationFactories: [],
397
410
  flags: {},
@@ -752,6 +765,9 @@ function projectRootPackageScripts(checkPlan, fixPlan, fragments) {
752
765
  const directFragments = directScriptFragments(fragments);
753
766
  return {
754
767
  check: `pnpm run check:boundaries && ${renderRootCheckCommand(checkPlan)}`,
768
+ ...(checkPlan.deploymentChecks === undefined
769
+ ? {}
770
+ : { "check:deployment": renderDeploymentCheckCommand(checkPlan) }),
755
771
  fix: renderFixCommand(fixPlan),
756
772
  ...rootTaskEntrypoints(checkPlan, fixPlan),
757
773
  ...directFragments,
@@ -1057,7 +1073,9 @@ function vikePackageJson(context, packageName, scripts, packageLinkDependencies
1057
1073
  dependencies: {
1058
1074
  ...packageLinkDependencies,
1059
1075
  "@vikejs/hono": "catalog:",
1076
+ "drizzle-orm": "catalog:",
1060
1077
  hono: "catalog:",
1078
+ srvx: "catalog:",
1061
1079
  telefunc: "catalog:",
1062
1080
  vike: "catalog:",
1063
1081
  "vike-vue": "catalog:",
@@ -1110,6 +1128,10 @@ function vikeDbPackageJson(context) {
1110
1128
  default: "./src/queries/todos.ts",
1111
1129
  types: "./src/queries/todos.ts",
1112
1130
  },
1131
+ "./readiness": {
1132
+ default: "./src/readiness.ts",
1133
+ types: "./src/readiness.ts",
1134
+ },
1113
1135
  },
1114
1136
  dependencies: {
1115
1137
  "drizzle-orm": "catalog:",
@@ -1132,15 +1154,19 @@ function vikeDbPackageJson(context) {
1132
1154
  function vikeDbPackageScripts() {
1133
1155
  return {
1134
1156
  "build:run": "tsc -p tsconfig.json --noEmit",
1157
+ "db:generate": "drizzle-kit generate",
1158
+ "db:migrate": "drizzle-kit migrate",
1159
+ "db:prepare:deploy": "pnpm run db:migrate",
1160
+ "db:prepare:dev": "pnpm run db:push && pnpm run db:seed:example",
1161
+ "db:prepare:test": 'rm -f "${DATABASE_FILE:-./node_modules/.tmp/test.sqlite}" && pnpm run db:push && pnpm run db:seed:example',
1135
1162
  "db:push": "mkdir -p data node_modules/.tmp && drizzle-kit push",
1136
- "drizzle:generate": "drizzle-kit generate",
1137
- "drizzle:migrate": "drizzle-kit migrate",
1138
- "drizzle:studio": "drizzle-kit studio",
1163
+ "db:seed:example": "node --experimental-strip-types scripts/seed-example.ts",
1164
+ "db:studio": "drizzle-kit studio",
1139
1165
  "format:check:run": "oxfmt --list-different --config ../../oxfmt.config.ts .",
1140
1166
  "format:write:run": "oxfmt --write --config ../../oxfmt.config.ts .",
1141
1167
  "lint:run": "oxlint --quiet --format=unix --config ../../oxlint.config.ts .",
1142
1168
  "lint:fix:run": "oxlint --format=unix --config ../../oxlint.config.ts . --fix",
1143
- "test:run": "DATABASE_FILE=./node_modules/.tmp/test.sqlite pnpm run db:push && DATABASE_FILE=./node_modules/.tmp/test.sqlite vitest run --reporter=agent --silent=passed-only",
1169
+ "test:run": "DATABASE_FILE=./node_modules/.tmp/test.sqlite pnpm run db:prepare:test && DATABASE_FILE=./node_modules/.tmp/test.sqlite vitest run --reporter=agent --silent=passed-only; status=$?; rm -f ./node_modules/.tmp/test.sqlite; exit $status",
1144
1170
  "typecheck:run": "tsc -p tsconfig.json --noEmit --pretty false",
1145
1171
  };
1146
1172
  }
@@ -1565,9 +1591,10 @@ function nodePackageScripts(nodePackage, workspace) {
1565
1591
  };
1566
1592
  }
1567
1593
  if (nodePackage.kind === "vike-app") {
1568
- const dbCommand = "DATABASE_FILE=../../apps/web/data/app.sqlite pnpm --dir ../../packages/db run db:push";
1594
+ const dbCommand = "DATABASE_FILE=../../apps/web/data/app.sqlite pnpm --dir ../../packages/db run db:prepare:dev";
1569
1595
  return {
1570
1596
  "build:run": "vike build",
1597
+ "check:deployment": "node --experimental-strip-types scripts/check-standalone-deployment.ts",
1571
1598
  dev: `${dbCommand} && vike dev`,
1572
1599
  "format:check:run": "oxfmt --list-different --config ../../oxfmt.config.ts .",
1573
1600
  "format:write:run": "oxfmt --write --config ../../oxfmt.config.ts .",
@@ -1836,6 +1863,21 @@ function vikeTemplateSourceOperation(options) {
1836
1863
  from: options.sourceFile,
1837
1864
  to,
1838
1865
  };
1866
+ if (options.nodePackage.kind === "vike-app" &&
1867
+ options.sourceFile === "web/Dockerfile") {
1868
+ return [
1869
+ {
1870
+ kind: "writeTextTemplate",
1871
+ from: options.sourceFile,
1872
+ to,
1873
+ replacements: {
1874
+ NODE_VERSION: options.context.toolchain.nodeLtsMajor.value,
1875
+ PACKAGE_MANAGER_PIN: options.context.toolchain.packageManagerPin.value,
1876
+ WEB_PACKAGE_NAME: nodePackageName(options.context, options.nodePackage),
1877
+ },
1878
+ },
1879
+ ];
1880
+ }
1839
1881
  if (options.nodePackage.kind === "vike-app" &&
1840
1882
  [
1841
1883
  "web/pages/index/+Page.telefunc.ts",
@@ -1845,7 +1887,10 @@ function vikeTemplateSourceOperation(options) {
1845
1887
  const dbPackageName = vikeDbPackageName(options.context);
1846
1888
  const replacements = {
1847
1889
  "web/pages/index/+Page.telefunc.ts": `import { createTodo, listTodos } from "${dbPackageName}/queries/todos";`,
1848
- "web/server/app.ts": `import { createDatabase } from "${dbPackageName}";`,
1890
+ "web/server/app.ts": [
1891
+ `import { createDatabase } from "${dbPackageName}";`,
1892
+ `import { assertDatabaseReady } from "${dbPackageName}/readiness";`,
1893
+ ].join("\n"),
1849
1894
  "web/types/global.d.ts": `import type { Database } from "${dbPackageName}";`,
1850
1895
  };
1851
1896
  return [
@@ -1865,9 +1910,14 @@ function vikeTemplateSourceOperation(options) {
1865
1910
  const vikeDbSourceFiles = [
1866
1911
  "db/turbo.json",
1867
1912
  "db/drizzle.config.ts",
1913
+ "db/drizzle/migrations/20260709120325_old_captain_flint/migration.sql",
1914
+ "db/drizzle/migrations/20260709120325_old_captain_flint/snapshot.json",
1915
+ "db/scripts/seed-example.ts",
1868
1916
  "db/src/db.ts",
1869
1917
  "db/src/index.ts",
1870
1918
  "db/src/queries/todos.ts",
1919
+ "db/src/readiness.ts",
1920
+ "db/src/seed/example.ts",
1871
1921
  "db/src/schema.ts",
1872
1922
  "db/test/todos.test.ts",
1873
1923
  ];
@@ -216,6 +216,9 @@ function assertTextTemplatePath(relativePath) {
216
216
  if (/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/(?:playwright|vite|vitest|oxfmt|oxlint)\.config\.ts$/.test(normalizedPath)) {
217
217
  return;
218
218
  }
219
+ if (/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/Dockerfile$/.test(normalizedPath)) {
220
+ return;
221
+ }
219
222
  assertFoundationTextPath(relativePath);
220
223
  }
221
224
  async function renderWriteText(operation, options) {
@@ -293,7 +296,7 @@ function parseAnchorComment(commentText) {
293
296
  return multiline?.[1];
294
297
  }
295
298
  async function findTypeScriptAnchorRanges(sourceText) {
296
- const ts = await import("typescript");
299
+ const ts = await import("@typescript/typescript6");
297
300
  const sourceFile = ts.createSourceFile("template-anchor.ts", sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
298
301
  const anchors = [];
299
302
  const seenRanges = new Set();
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import ts from "typescript";
3
+ import ts from "@typescript/typescript6";
4
4
  const inlineOperationKinds = new Set([
5
5
  "writeText",
6
6
  "writeJson",
@@ -25,12 +25,13 @@
25
25
  "typecheck": "tsc -p tsconfig.json --noEmit --pretty false"
26
26
  },
27
27
  "dependencies": {
28
+ "@typescript/typescript6": "catalog:",
28
29
  "@ykdz/template-shared": "workspace:*",
29
- "typescript": "catalog:",
30
30
  "valibot": "catalog:"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "catalog:",
34
+ "@typescript/native": "catalog:",
34
35
  "execa": "catalog:",
35
36
  "oxfmt": "catalog:",
36
37
  "oxlint": "catalog:",