@ykdz/template 0.0.20 → 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.
- package/node_modules/@ykdz/template-builtin-source/templates/rust-bin/.github/dependabot.yml +5 -0
- package/node_modules/@ykdz/template-builtin-source/templates/ts-lib/.github/dependabot.yml +5 -0
- package/node_modules/@ykdz/template-builtin-source/templates/vike-app/.github/dependabot.yml +5 -0
- package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/db.ts +4 -1
- package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/test/todos.test.ts +3 -0
- package/node_modules/@ykdz/template-builtin-source/templates/vue-app/.github/dependabot.yml +5 -0
- package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/.github/dependabot.yml +5 -0
- package/node_modules/@ykdz/template-core/dist/generated-scenarios.d.ts +6 -0
- package/node_modules/@ykdz/template-core/dist/generated-scenarios.js +86 -2
- package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +5 -5
- package/node_modules/@ykdz/template-core/dist/project-github.js +1 -1
- package/package.json +3 -3
|
@@ -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, {
|
|
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
|
});
|
|
@@ -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 {
|
|
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-
|
|
16
|
-
drizzle-orm: 1.0.0-
|
|
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.
|
|
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.
|
|
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.
|
|
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");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ykdz/template",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
43
|
+
"oxfmt": "^0.58.0",
|
|
44
44
|
"oxlint": "^1.72.0",
|
|
45
45
|
"oxlint-tsgolint": "^0.24.0"
|
|
46
46
|
},
|