@ykdz/template 0.0.19 → 0.0.21

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.
@@ -123,8 +123,8 @@ function packageAdditionOperations(packagePath, packageNameValue, nodeVersion) {
123
123
  exactOptionalPropertyTypes: true,
124
124
  forceConsistentCasingInFileNames: true,
125
125
  isolatedModules: true,
126
- module: "ESNext",
127
- moduleResolution: "Bundler",
126
+ module: "esnext",
127
+ moduleResolution: "bundler",
128
128
  noEmitOnError: true,
129
129
  noFallthroughCasesInSwitch: true,
130
130
  noImplicitOverride: true,
@@ -164,8 +164,8 @@ function packageAdditionOperations(packagePath, packageNameValue, nodeVersion) {
164
164
  exactOptionalPropertyTypes: true,
165
165
  forceConsistentCasingInFileNames: true,
166
166
  isolatedModules: true,
167
- module: "ESNext",
168
- moduleResolution: "Bundler",
167
+ module: "esnext",
168
+ moduleResolution: "bundler",
169
169
  noEmitOnError: true,
170
170
  lib: ["ESNext", "DOM", "DOM.Iterable"],
171
171
  noFallthroughCasesInSwitch: true,
@@ -5,6 +5,11 @@ updates:
5
5
  directory: /
6
6
  schedule:
7
7
  interval: weekly
8
+ groups:
9
+ drizzle:
10
+ patterns:
11
+ - "drizzle-*"
12
+ - "drizzle-orm"
8
13
  ignore:
9
14
  - dependency-name: "@types/node"
10
15
  update-types:
@@ -5,6 +5,11 @@ updates:
5
5
  directory: /
6
6
  schedule:
7
7
  interval: weekly
8
+ groups:
9
+ drizzle:
10
+ patterns:
11
+ - "drizzle-*"
12
+ - "drizzle-orm"
8
13
  ignore:
9
14
  - dependency-name: "@types/node"
10
15
  update-types:
@@ -5,6 +5,11 @@ updates:
5
5
  directory: /
6
6
  schedule:
7
7
  interval: weekly
8
+ groups:
9
+ drizzle:
10
+ patterns:
11
+ - "drizzle-*"
12
+ - "drizzle-orm"
8
13
  ignore:
9
14
  - dependency-name: "@types/node"
10
15
  update-types:
@@ -2,13 +2,16 @@ import { mkdirSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { drizzle } from "drizzle-orm/node-sqlite";
5
+ import { defineRelations } from "drizzle-orm/relations";
5
6
 
6
7
  import * as schema from "#db/schema";
7
8
 
9
+ const relations = defineRelations(schema);
10
+
8
11
  export type Database = ReturnType<typeof createDatabase>;
9
12
 
10
13
  export function createDatabase(databaseFile = process.env.DATABASE_FILE) {
11
14
  const file = databaseFile ?? "./data/app.sqlite";
12
15
  mkdirSync(path.dirname(file), { recursive: true });
13
- return drizzle(file, { schema });
16
+ return drizzle(file, { relations });
14
17
  }
@@ -14,5 +14,8 @@ describe("todo queries", () => {
14
14
  await expect(listTodos(db)).resolves.toEqual(
15
15
  expect.arrayContaining([expect.objectContaining({ title })]),
16
16
  );
17
+ await expect(db.query.todos.findMany()).resolves.toEqual(
18
+ expect.arrayContaining([expect.objectContaining({ title })]),
19
+ );
17
20
  });
18
21
  });
@@ -5,6 +5,11 @@ updates:
5
5
  directory: /
6
6
  schedule:
7
7
  interval: weekly
8
+ groups:
9
+ drizzle:
10
+ patterns:
11
+ - "drizzle-*"
12
+ - "drizzle-orm"
8
13
  ignore:
9
14
  - dependency-name: "@types/node"
10
15
  update-types:
@@ -5,6 +5,11 @@ updates:
5
5
  directory: /
6
6
  schedule:
7
7
  interval: weekly
8
+ groups:
9
+ drizzle:
10
+ patterns:
11
+ - "drizzle-*"
12
+ - "drizzle-orm"
8
13
  ignore:
9
14
  - dependency-name: "@types/node"
10
15
  update-types:
@@ -46,10 +46,16 @@ export type GeneratedScenarioRunnerOptions = {
46
46
  readonly reporter?: GeneratedScenarioReporter;
47
47
  readonly deterministicToolchainEnv?: Record<string, string>;
48
48
  readonly rootCheckLock?: ExclusiveLock;
49
+ readonly replayCache?: GeneratedScenarioReplayCache | undefined;
49
50
  };
50
51
  export type ExclusiveLock = {
51
52
  readonly run: <T>(callback: () => Promise<T>) => Promise<T>;
52
53
  };
54
+ export type GeneratedScenarioReplayCache = {
55
+ readonly directory: string;
56
+ readonly read: boolean;
57
+ readonly write: boolean;
58
+ };
53
59
  export declare function generatedScenarioChildProcessEnv(env?: Record<string, string>): NodeJS.ProcessEnv;
54
60
  export declare function createExclusiveLock(): ExclusiveLock;
55
61
  export declare function generatedScenarioId(scenario: Pick<GeneratedScenario, "basePreset" | "addedPreset" | "linkFrom">): string;
@@ -1,4 +1,5 @@
1
- import { readdir, readFile } from "node:fs/promises";
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { PackageAdditionSupport } from "@ykdz/template-shared";
4
5
  import { execa } from "execa";
@@ -13,6 +14,7 @@ const defaultDeterministicToolchainEnv = {
13
14
  TEMPLATE_TOOLCHAIN_RESOLUTION: "bundled-fallback",
14
15
  };
15
16
  const defaultFixtureConcurrency = 2;
17
+ const scenarioFingerprintVersion = 1;
16
18
  const storedBlueprintSchema = v.object({
17
19
  packages: v.optional(v.array(v.object({
18
20
  name: v.string(),
@@ -171,6 +173,68 @@ function packageLeaf(packagePath) {
171
173
  function hasSourceDirectoryPath(sourceFile) {
172
174
  return sourceFile.startsWith("src/") || sourceFile.includes("/src/");
173
175
  }
176
+ function scenarioFingerprintPrefix(scenario) {
177
+ return JSON.stringify({
178
+ version: scenarioFingerprintVersion,
179
+ scenario,
180
+ platform: process.platform,
181
+ arch: process.arch,
182
+ nodeMajor: process.versions.node.split(".")[0],
183
+ qualityGateProtocol: "lockfile-only-snapshot-then-fix-check",
184
+ });
185
+ }
186
+ async function hashGeneratedScenarioDirectory(scenario, projectDir) {
187
+ const hash = createHash("sha256");
188
+ hash.update(scenarioFingerprintPrefix(scenario));
189
+ await hashDirectoryEntries(projectDir, projectDir, hash);
190
+ return hash.digest("hex");
191
+ }
192
+ async function hashDirectoryEntries(rootDir, currentDir, hash) {
193
+ const entries = await readdir(currentDir, { withFileTypes: true });
194
+ for (const entry of entries.toSorted((left, right) => left.name.localeCompare(right.name))) {
195
+ if (entry.name === "node_modules" || entry.name === ".git") {
196
+ continue;
197
+ }
198
+ const entryPath = path.join(currentDir, entry.name);
199
+ const relativePath = path
200
+ .relative(rootDir, entryPath)
201
+ .split(path.sep)
202
+ .join("/");
203
+ if (entry.isDirectory()) {
204
+ hash.update(`dir\0${relativePath}\0`);
205
+ await hashDirectoryEntries(rootDir, entryPath, hash);
206
+ continue;
207
+ }
208
+ if (!entry.isFile()) {
209
+ const fileStat = await stat(entryPath);
210
+ hash.update(`special\0${relativePath}\0${fileStat.mode.toString(8)}\0`);
211
+ continue;
212
+ }
213
+ const fileStat = await stat(entryPath);
214
+ hash.update(`file\0${relativePath}\0${fileStat.mode.toString(8)}\0`);
215
+ hash.update(await readFile(entryPath));
216
+ hash.update("\0");
217
+ }
218
+ }
219
+ async function scenarioReplayMarkerExists(cache, fingerprint) {
220
+ try {
221
+ await readFile(path.join(cache.directory, `${fingerprint}.passed`), "utf8");
222
+ return true;
223
+ }
224
+ catch (error) {
225
+ if (error &&
226
+ typeof error === "object" &&
227
+ "code" in error &&
228
+ error.code === "ENOENT") {
229
+ return false;
230
+ }
231
+ throw error;
232
+ }
233
+ }
234
+ async function writeScenarioReplayMarker(cache, fingerprint, scenario) {
235
+ await mkdir(cache.directory, { recursive: true });
236
+ await writeFile(path.join(cache.directory, `${fingerprint}.passed`), `${JSON.stringify({ scenario, fingerprint })}\n`, "utf8");
237
+ }
174
238
  export function packageLeafNameForAddedPreset(manifest, presetName) {
175
239
  const preset = manifest.presets.find((entry) => entry.name === presetName);
176
240
  if (!preset) {
@@ -383,9 +447,14 @@ export function generatedScenarioQualityGateSteps(manifest, scenario, projectDir
383
447
  async function runGeneratedScenarioQualityGate(manifest, scenario, projectDir, addedPackagePath, options) {
384
448
  const steps = generatedScenarioQualityGateSteps(manifest, scenario, projectDir, addedPackagePath, options);
385
449
  const requiresSerializedPlaywrightRootCheck = generatedScenarioRequiresSerializedRootCheck(steps);
450
+ let replayFingerprint;
386
451
  for (const step of steps) {
387
452
  if (step.id === "install-dependencies") {
388
- await runGeneratedScenarioInstallStep(step, scenario, options);
453
+ const installResult = await runGeneratedScenarioInstallStep(step, scenario, options);
454
+ replayFingerprint = installResult.fingerprint;
455
+ if (installResult.replayed) {
456
+ return;
457
+ }
389
458
  continue;
390
459
  }
391
460
  const env = step.id === "run-root-check" ? { CI: "1" } : undefined;
@@ -401,13 +470,27 @@ async function runGeneratedScenarioQualityGate(manifest, scenario, projectDir, a
401
470
  }
402
471
  await runStep();
403
472
  }
473
+ if (options.replayCache?.write && replayFingerprint !== undefined) {
474
+ await writeScenarioReplayMarker(options.replayCache, replayFingerprint, scenario);
475
+ options.reporter.info?.(`-- Stored passed fixture replay marker for ${scenario.label}: ${replayFingerprint}`);
476
+ }
404
477
  }
405
478
  async function runGeneratedScenarioInstallStep(step, scenario, options) {
406
479
  await options.runCommand(step.command, ["install", "--lockfile-only", "--prefer-offline", "--no-frozen-lockfile"], step.cwd, { logPrefix: scenario.label });
480
+ let replayFingerprint;
481
+ if (options.replayCache) {
482
+ replayFingerprint = await hashGeneratedScenarioDirectory(scenario, step.cwd);
483
+ if (options.replayCache.read &&
484
+ (await scenarioReplayMarkerExists(options.replayCache, replayFingerprint))) {
485
+ options.reporter.info?.(`-- Replayed passed fixture ${scenario.label}: ${replayFingerprint}`);
486
+ return { fingerprint: replayFingerprint, replayed: true };
487
+ }
488
+ }
407
489
  await options.runCommand(step.command, ["fetch"], step.cwd, {
408
490
  logPrefix: scenario.label,
409
491
  });
410
492
  await options.runCommand(step.command, ["install", "--offline", "--frozen-lockfile"], step.cwd, { logPrefix: scenario.label });
493
+ return { fingerprint: replayFingerprint, replayed: false };
411
494
  }
412
495
  async function checkGeneratedScenario(manifest, scenario, workspace, options) {
413
496
  options.reporter.info?.(`\n== ${scenario.label} ==`);
@@ -446,6 +529,7 @@ function normalizeRunnerOptions(options) {
446
529
  },
447
530
  deterministicToolchainEnv: options.deterministicToolchainEnv ?? defaultDeterministicToolchainEnv,
448
531
  rootCheckLock: options.rootCheckLock ?? createExclusiveLock(),
532
+ replayCache: options.replayCache,
449
533
  };
450
534
  }
451
535
  export async function runGeneratedScenariosConcurrently(manifest, scenarios, workspace, concurrency, options) {
@@ -12,16 +12,16 @@ catalog:
12
12
  "@vitejs/plugin-vue": ^6.0.7
13
13
  "@vikejs/hono": ^0.2.1
14
14
  "@vue/tsconfig": ^0.9.1
15
- drizzle-kit: 1.0.0-beta.22
16
- drizzle-orm: 1.0.0-beta.22
15
+ drizzle-kit: 1.0.0-rc.4
16
+ drizzle-orm: 1.0.0-rc.4
17
17
  execa: ^9.6.1
18
18
  hono: ^4.12.27
19
- oxfmt: ^0.57.0
19
+ oxfmt: ^0.58.0
20
20
  oxlint: ^1.72.0
21
21
  oxlint-tsgolint: ^0.24.0
22
22
  pinia: ^3.0.4
23
23
  tailwindcss: ^4.3.2
24
- telefunc: ^0.2.21
24
+ telefunc: ^0.2.22
25
25
  tsc-alias: ^1.8.17
26
26
  tsx: ^4.22.4
27
27
  turbo: 2.10.2
@@ -30,7 +30,7 @@ catalog:
30
30
  vite: ^8.1.2
31
31
  vike: ^0.4.260
32
32
  vike-vue: ^0.9.12
33
- vitest: ^4.1.9
33
+ vitest: ^4.1.10
34
34
  vue: ^3.5.39
35
35
  vue-tsc: ^3.3.6
36
36
  yaml: ^2.9.0
@@ -60,7 +60,7 @@ function renderDependabotUpdate(ecosystem, directory, interval) {
60
60
  ` interval: ${interval}`,
61
61
  ];
62
62
  if (ecosystem === "npm") {
63
- lines.push(" ignore:", ' - dependency-name: "@types/node"', " update-types:", " - version-update:semver-major");
63
+ lines.push(" groups:", " drizzle:", " patterns:", ' - "drizzle-*"', ' - "drizzle-orm"', " ignore:", ' - dependency-name: "@types/node"', " update-types:", " - version-update:semver-major");
64
64
  }
65
65
  if (ecosystem === "docker") {
66
66
  lines.push(" ignore:", " - dependency-name: mcr.microsoft.com/devcontainers/typescript-node", " update-types:", " - version-update:semver-major");
@@ -1616,8 +1616,8 @@ function nodePackageTsconfigOperations(nodePackage, workspace) {
1616
1616
  compilerOptions: {
1617
1617
  ...strictTypeScriptCompilerOptions({
1618
1618
  composite: true,
1619
- module: "ESNext",
1620
- moduleResolution: "Bundler",
1619
+ module: "esnext",
1620
+ moduleResolution: "bundler",
1621
1621
  paths: { "#/*": ["./*"] },
1622
1622
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
1623
1623
  types: ["node"],
@@ -1658,8 +1658,8 @@ function nodePackageTsconfigOperations(nodePackage, workspace) {
1658
1658
  compilerOptions: {
1659
1659
  ...strictTypeScriptCompilerOptions({
1660
1660
  composite: true,
1661
- module: "ESNext",
1662
- moduleResolution: "Bundler",
1661
+ module: "esnext",
1662
+ moduleResolution: "bundler",
1663
1663
  lib: ["ESNext", "DOM", "DOM.Iterable"],
1664
1664
  paths: { "#/*": ["./*"] },
1665
1665
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
@@ -1730,8 +1730,8 @@ function nodePackageTsconfigOperations(nodePackage, workspace) {
1730
1730
  extends: "@vue/tsconfig/tsconfig.dom.json",
1731
1731
  compilerOptions: strictTypeScriptCompilerOptions({
1732
1732
  composite: true,
1733
- module: "ESNext",
1734
- moduleResolution: "Bundler",
1733
+ module: "esnext",
1734
+ moduleResolution: "bundler",
1735
1735
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
1736
1736
  types: ["web-bluetooth"],
1737
1737
  }),
@@ -1758,8 +1758,8 @@ function nodePackageTsconfigOperations(nodePackage, workspace) {
1758
1758
  value: {
1759
1759
  compilerOptions: strictTypeScriptCompilerOptions({
1760
1760
  composite: true,
1761
- module: "ESNext",
1762
- moduleResolution: "Bundler",
1761
+ module: "esnext",
1762
+ moduleResolution: "bundler",
1763
1763
  lib: ["ESNext", "DOM", "DOM.Iterable"],
1764
1764
  ...(workspace.packages.length > 1
1765
1765
  ? { outDir: "./node_modules/.tmp/tsconfig.node" }
@@ -1898,8 +1898,8 @@ function vikeDbTsconfigJson() {
1898
1898
  composite: true,
1899
1899
  declaration: true,
1900
1900
  declarationMap: true,
1901
- module: "ESNext",
1902
- moduleResolution: "Bundler",
1901
+ module: "esnext",
1902
+ moduleResolution: "bundler",
1903
1903
  paths: { "#db/*": ["./src/*"] },
1904
1904
  rootDir: ".",
1905
1905
  types: ["node"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykdz/template",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "private": false,
5
5
  "description": "Project template generator for YKDZ starter projects.",
6
6
  "keywords": [
@@ -34,13 +34,13 @@
34
34
  "dependencies": {
35
35
  "typescript": "^6.0.3",
36
36
  "valibot": "^1.4.2",
37
- "@ykdz/template-builtin-source": "0.0.0",
38
37
  "@ykdz/template-core": "0.0.0",
38
+ "@ykdz/template-builtin-source": "0.0.0",
39
39
  "@ykdz/template-shared": "0.0.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^24.0.0",
43
- "oxfmt": "^0.57.0",
43
+ "oxfmt": "^0.58.0",
44
44
  "oxlint": "^1.72.0",
45
45
  "oxlint-tsgolint": "^0.24.0"
46
46
  },