@ykdz/template 0.0.21 → 0.0.22

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 (26) hide show
  1. package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +4 -0
  2. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/.github/dependabot.yml +4 -0
  3. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/.github/workflows/check.yml +8 -0
  4. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/drizzle/migrations/20260709120325_old_captain_flint/migration.sql +6 -0
  5. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/drizzle/migrations/20260709120325_old_captain_flint/snapshot.json +67 -0
  6. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/scripts/seed-example.ts +7 -0
  7. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/db.ts +2 -1
  8. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/index.ts +1 -0
  9. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/queries/todos.ts +17 -1
  10. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/readiness.ts +22 -0
  11. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/schema.ts +1 -1
  12. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/src/seed/example.ts +23 -0
  13. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/db/test/todos.test.ts +2 -0
  14. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/+server.ts +2 -1
  15. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/Dockerfile +47 -0
  16. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/Dockerfile.dockerignore +14 -0
  17. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/pages/index/+Page.vue +6 -2
  18. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/playwright.config.ts +7 -7
  19. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/scripts/run-playwright.ts +25 -6
  20. package/node_modules/@ykdz/template-builtin-source/templates/vike-app/web/server/app.ts +22 -11
  21. package/node_modules/@ykdz/template-core/dist/preset-projection.d.ts +2 -1
  22. package/node_modules/@ykdz/template-core/dist/project-github.d.ts +9 -0
  23. package/node_modules/@ykdz/template-core/dist/project-github.js +9 -3
  24. package/node_modules/@ykdz/template-core/dist/projection-capabilities.js +54 -6
  25. package/node_modules/@ykdz/template-core/dist/renderer.js +3 -0
  26. package/package.json +1 -1
@@ -251,6 +251,8 @@
251
251
  "sourceFiles": [
252
252
  "web/+server.ts",
253
253
  "web/.env.example",
254
+ "web/Dockerfile",
255
+ "web/Dockerfile.dockerignore",
254
256
  "web/assets/logo.svg",
255
257
  "web/components/CounterButton.vue",
256
258
  "web/components/PageShell.vue",
@@ -306,6 +308,8 @@
306
308
  "vike-app/db/turbo.json",
307
309
  "vike-app/web/+server.ts",
308
310
  "vike-app/web/.env.example",
311
+ "vike-app/web/Dockerfile",
312
+ "vike-app/web/Dockerfile.dockerignore",
309
313
  "vike-app/web/playwright.config.ts",
310
314
  "vike-app/web/vite.config.ts",
311
315
  "vike-app/web/turbo.json",
@@ -26,3 +26,7 @@ updates:
26
26
  - dependency-name: mcr.microsoft.com/devcontainers/typescript-node
27
27
  update-types:
28
28
  - version-update:semver-major
29
+ - package-ecosystem: docker
30
+ directory: /apps/web
31
+ schedule:
32
+ interval: weekly
@@ -18,3 +18,11 @@ jobs:
18
18
  - run: pnpm install
19
19
  - run: pnpm --filter ./apps/web exec playwright install --with-deps chromium
20
20
  - run: pnpm run check
21
+
22
+ docker-image:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+ - uses: docker/setup-buildx-action@v3
27
+ - run: docker buildx build --load --file apps/web/Dockerfile --target runtime --tag vike-app:check .
28
+ - run: docker buildx build --load --file apps/web/Dockerfile --target database-preparation --tag vike-app-db-prepare:check .
@@ -0,0 +1,6 @@
1
+ CREATE TABLE `todos` (
2
+ `id` integer PRIMARY KEY AUTOINCREMENT,
3
+ `title` text NOT NULL UNIQUE,
4
+ `completed` integer DEFAULT false NOT NULL,
5
+ `created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL
6
+ );
@@ -0,0 +1,67 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "id": "0a8f7496-a1ed-412a-a634-7989609bcbe0",
5
+ "prevIds": ["00000000-0000-0000-0000-000000000000"],
6
+ "ddl": [
7
+ {
8
+ "name": "todos",
9
+ "entityType": "tables"
10
+ },
11
+ {
12
+ "type": "integer",
13
+ "notNull": false,
14
+ "autoincrement": true,
15
+ "default": null,
16
+ "generated": null,
17
+ "name": "id",
18
+ "entityType": "columns",
19
+ "table": "todos"
20
+ },
21
+ {
22
+ "type": "text",
23
+ "notNull": true,
24
+ "autoincrement": false,
25
+ "default": null,
26
+ "generated": null,
27
+ "name": "title",
28
+ "entityType": "columns",
29
+ "table": "todos"
30
+ },
31
+ {
32
+ "type": "integer",
33
+ "notNull": true,
34
+ "autoincrement": false,
35
+ "default": "false",
36
+ "generated": null,
37
+ "name": "completed",
38
+ "entityType": "columns",
39
+ "table": "todos"
40
+ },
41
+ {
42
+ "type": "text",
43
+ "notNull": true,
44
+ "autoincrement": false,
45
+ "default": "(CURRENT_TIMESTAMP)",
46
+ "generated": null,
47
+ "name": "created_at",
48
+ "entityType": "columns",
49
+ "table": "todos"
50
+ },
51
+ {
52
+ "columns": ["id"],
53
+ "nameExplicit": false,
54
+ "name": "todos_pk",
55
+ "table": "todos",
56
+ "entityType": "pks"
57
+ },
58
+ {
59
+ "columns": ["title"],
60
+ "nameExplicit": false,
61
+ "name": "todos_title_unique",
62
+ "entityType": "uniques",
63
+ "table": "todos"
64
+ }
65
+ ],
66
+ "renames": []
67
+ }
@@ -0,0 +1,7 @@
1
+ import { createDatabase } from "#db/db";
2
+ import { assertDatabaseReady } from "#db/readiness";
3
+ import { seedExampleData } from "#db/seed/example";
4
+
5
+ const db = createDatabase();
6
+ assertDatabaseReady(db);
7
+ await seedExampleData(db);
@@ -7,11 +7,12 @@ import { defineRelations } from "drizzle-orm/relations";
7
7
  import * as schema from "#db/schema";
8
8
 
9
9
  const relations = defineRelations(schema);
10
+ const defaultDatabaseFile = "./data/app.sqlite";
10
11
 
11
12
  export type Database = ReturnType<typeof createDatabase>;
12
13
 
13
14
  export function createDatabase(databaseFile = process.env.DATABASE_FILE) {
14
- const file = databaseFile ?? "./data/app.sqlite";
15
+ const file = databaseFile ?? defaultDatabaseFile;
15
16
  mkdirSync(path.dirname(file), { recursive: true });
16
17
  return drizzle(file, { relations });
17
18
  }
@@ -1 +1,2 @@
1
1
  export { createDatabase, type Database } from "#db/db";
2
+ export { assertDatabaseReady } from "#db/readiness";
@@ -3,8 +3,20 @@ import { desc, eq } from "drizzle-orm";
3
3
  import type { Database } from "#db/db";
4
4
  import { todos, type NewTodo } from "#db/schema";
5
5
 
6
+ const listTodosStatements = new WeakMap<
7
+ Database,
8
+ ReturnType<typeof prepareListTodos>
9
+ >();
10
+
6
11
  export async function listTodos(db: Database) {
7
- return db.select().from(todos).orderBy(desc(todos.createdAt));
12
+ let statement = listTodosStatements.get(db);
13
+
14
+ if (!statement) {
15
+ statement = prepareListTodos(db);
16
+ listTodosStatements.set(db, statement);
17
+ }
18
+
19
+ return statement.all();
8
20
  }
9
21
 
10
22
  export async function createTodo(db: Database, values: NewTodo) {
@@ -26,3 +38,7 @@ export async function completeTodo(db: Database, id: number) {
26
38
 
27
39
  return todo;
28
40
  }
41
+
42
+ function prepareListTodos(db: Database) {
43
+ return db.select().from(todos).orderBy(desc(todos.createdAt)).prepare();
44
+ }
@@ -0,0 +1,22 @@
1
+ import { sql } from "drizzle-orm";
2
+
3
+ import type { Database } from "#db/db";
4
+ import { todos } from "#db/schema";
5
+
6
+ export function assertDatabaseReady(db: Database) {
7
+ try {
8
+ db.select({ ready: sql`1` })
9
+ .from(todos)
10
+ .limit(1)
11
+ .all();
12
+ } catch (cause) {
13
+ throw new Error(
14
+ [
15
+ "Database is not ready.",
16
+ "For local development, run `pnpm --dir packages/db run db:prepare:dev`.",
17
+ "For deployment, run `pnpm --dir packages/db run db:prepare:deploy` before starting the app.",
18
+ ].join(" "),
19
+ { cause },
20
+ );
21
+ }
22
+ }
@@ -3,7 +3,7 @@ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
3
 
4
4
  export const todos = sqliteTable("todos", {
5
5
  id: integer({ mode: "number" }).primaryKey({ autoIncrement: true }),
6
- title: text().notNull(),
6
+ title: text().notNull().unique(),
7
7
  completed: integer({ mode: "boolean" }).default(false).notNull(),
8
8
  createdAt: text("created_at")
9
9
  .default(sql`(CURRENT_TIMESTAMP)`)
@@ -0,0 +1,23 @@
1
+ import { eq } from "drizzle-orm";
2
+
3
+ import type { Database } from "#db/db";
4
+ import { todos } from "#db/schema";
5
+
6
+ const exampleTodos = [
7
+ "Read the generated TODO.md",
8
+ "Try the Telefunc todo flow",
9
+ ] as const;
10
+
11
+ export async function seedExampleData(db: Database) {
12
+ for (const title of exampleTodos) {
13
+ const existing = await db
14
+ .select({ id: todos.id })
15
+ .from(todos)
16
+ .where(eq(todos.title, title))
17
+ .limit(1);
18
+
19
+ if (existing.length === 0) {
20
+ await db.insert(todos).values({ title });
21
+ }
22
+ }
23
+ }
@@ -2,10 +2,12 @@ import { describe, expect, it } from "vitest";
2
2
 
3
3
  import { createDatabase } from "#db/db";
4
4
  import { createTodo, listTodos } from "#db/queries/todos";
5
+ import { assertDatabaseReady } from "#db/readiness";
5
6
 
6
7
  describe("todo queries", () => {
7
8
  it("creates and lists todos through Drizzle SQLite", async () => {
8
9
  const db = createDatabase();
10
+ assertDatabaseReady(db);
9
11
  const title = `发布模板 ${Date.now()}`;
10
12
 
11
13
  await expect(createTodo(db, { title })).resolves.toMatchObject({
@@ -1,8 +1,9 @@
1
1
  import type { Server } from "vike/types";
2
2
 
3
- import { app } from "#/server/app";
3
+ import { createApp } from "#/server/app";
4
4
 
5
5
  const port = Number.parseInt(process.env.PORT ?? "3000", 10);
6
+ const app = createApp();
6
7
 
7
8
  export default {
8
9
  fetch: app.fetch,
@@ -0,0 +1,47 @@
1
+ # syntax=docker/dockerfile:1.7
2
+
3
+ FROM node:{{NODE_VERSION}}-bookworm-slim AS base
4
+ ARG PACKAGE_MANAGER_PIN="{{PACKAGE_MANAGER_PIN}}"
5
+ ENV PNPM_HOME="/pnpm"
6
+ ENV PATH="$PNPM_HOME:$PATH"
7
+ WORKDIR /repo
8
+ RUN corepack enable && corepack prepare "$PACKAGE_MANAGER_PIN" --activate
9
+
10
+ FROM base AS pruner
11
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
12
+ RUN pnpm fetch
13
+ COPY package.json turbo.json ./
14
+ COPY apps/web/package.json apps/web/package.json
15
+ COPY packages/db/package.json packages/db/package.json
16
+ RUN pnpm install --offline --frozen-lockfile
17
+ COPY . .
18
+ RUN pnpm exec turbo prune {{WEB_PACKAGE_NAME}} --docker
19
+
20
+ FROM base AS build
21
+ COPY --from=pruner /repo/out/json/pnpm-lock.yaml ./pnpm-lock.yaml
22
+ COPY --from=pruner /repo/out/json/pnpm-workspace.yaml ./pnpm-workspace.yaml
23
+ RUN pnpm fetch
24
+ COPY --from=pruner /repo/out/json/ ./
25
+ RUN pnpm install --offline --frozen-lockfile
26
+ COPY --from=pruner /repo/out/full/ ./
27
+ RUN pnpm --filter ./apps/web run build:run
28
+
29
+ FROM base AS database-preparation
30
+ ENV NODE_ENV="production"
31
+ COPY --from=pruner /repo/out/json/pnpm-lock.yaml ./pnpm-lock.yaml
32
+ COPY --from=pruner /repo/out/json/pnpm-workspace.yaml ./pnpm-workspace.yaml
33
+ RUN pnpm fetch
34
+ COPY --from=pruner /repo/out/json/ ./
35
+ RUN pnpm install --offline --frozen-lockfile
36
+ COPY --from=pruner /repo/out/full/ ./
37
+ CMD ["pnpm", "--dir", "packages/db", "run", "db:prepare:deploy"]
38
+
39
+ FROM node:{{NODE_VERSION}}-bookworm-slim AS runtime
40
+ ENV NODE_ENV="production"
41
+ WORKDIR /app
42
+ RUN groupadd --system --gid 1001 nodejs \
43
+ && useradd --system --uid 1001 --gid nodejs --home-dir /app --shell /usr/sbin/nologin app
44
+ COPY --from=build --chown=app:nodejs /repo/apps/web/dist ./dist
45
+ USER app
46
+ EXPOSE 3000
47
+ CMD ["node", "dist/server/index.mjs"]
@@ -0,0 +1,14 @@
1
+ .git
2
+ .template
3
+ node_modules
4
+ **/node_modules
5
+ **/.turbo
6
+ **/dist
7
+ **/playwright-report
8
+ **/test-results
9
+ apps/web/data
10
+ apps/web/node_modules/.tmp
11
+ packages/db/data
12
+ packages/db/node_modules/.tmp
13
+ *.sqlite
14
+ *.sqlite-*
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { ref } from "vue";
2
+ import { onMounted, ref } from "vue";
3
3
 
4
4
  import CounterButton from "#/components/CounterButton.vue";
5
5
 
@@ -32,6 +32,10 @@ async function submitTodo() {
32
32
  title.value = "";
33
33
  await refreshTodos();
34
34
  }
35
+
36
+ onMounted(() => {
37
+ void refreshTodos();
38
+ });
35
39
  </script>
36
40
 
37
41
  <template>
@@ -65,7 +69,7 @@ async function submitTodo() {
65
69
  type="button"
66
70
  @click="refreshTodos"
67
71
  >
68
- 加载待办事项
72
+ 刷新待办事项
69
73
  </button>
70
74
 
71
75
  <p
@@ -1,22 +1,22 @@
1
- import { fileURLToPath } from "node:url";
2
-
3
1
  import { defineConfig, devices } from "@playwright/test";
4
2
 
5
3
  function requiredPort(name: string): number {
4
+ return Number(requiredEnv(name));
5
+ }
6
+
7
+ function requiredEnv(name: string): string {
6
8
  const value = process.env[name];
7
9
 
8
10
  if (!value) {
9
11
  throw new Error(`${name} must be set by scripts/run-playwright.ts`);
10
12
  }
11
13
 
12
- return Number(value);
14
+ return value;
13
15
  }
14
16
 
15
17
  const previewPort = requiredPort("PLAYWRIGHT_WEB_PORT");
16
18
  const previewUrl = `http://127.0.0.1:${previewPort}`;
17
- const databaseFile = fileURLToPath(
18
- new URL("./node_modules/.tmp/e2e.sqlite", import.meta.url),
19
- );
19
+ const databaseFile = requiredEnv("DATABASE_FILE");
20
20
 
21
21
  function shellValue(value: string): string {
22
22
  return `'${value.replaceAll("'", "'\\''")}'`;
@@ -30,7 +30,7 @@ export default defineConfig({
30
30
  trace: "on-first-retry",
31
31
  },
32
32
  webServer: {
33
- command: `DATABASE_FILE=${shellValue(databaseFile)} pnpm --dir ../../packages/db run db:push && DATABASE_FILE=${shellValue(databaseFile)} PORT=${previewPort} node dist/server/index.mjs`,
33
+ command: `DATABASE_FILE=${shellValue(databaseFile)} pnpm --dir ../../packages/db run db:prepare:test && PORT=${previewPort} node dist/server/index.mjs`,
34
34
  reuseExistingServer: !process.env.CI,
35
35
  url: previewUrl,
36
36
  },
@@ -1,5 +1,11 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { rm } from "node:fs/promises";
2
3
  import { createServer } from "node:net";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const databaseFile = fileURLToPath(
7
+ new URL("../node_modules/.tmp/e2e.sqlite", import.meta.url),
8
+ );
3
9
 
4
10
  async function availablePort(): Promise<string> {
5
11
  return await new Promise((resolve, reject) => {
@@ -32,6 +38,7 @@ async function availablePort(): Promise<string> {
32
38
  async function playwrightEnv(): Promise<NodeJS.ProcessEnv> {
33
39
  return {
34
40
  ...process.env,
41
+ DATABASE_FILE: process.env.DATABASE_FILE ?? databaseFile,
35
42
  PLAYWRIGHT_WEB_PORT:
36
43
  process.env.PLAYWRIGHT_WEB_PORT ?? (await availablePort()),
37
44
  };
@@ -39,6 +46,7 @@ async function playwrightEnv(): Promise<NodeJS.ProcessEnv> {
39
46
 
40
47
  const env = await playwrightEnv();
41
48
  delete env.NO_COLOR;
49
+ await rm(env.DATABASE_FILE!, { force: true });
42
50
 
43
51
  const command = process.platform === "win32" ? "playwright.cmd" : "playwright";
44
52
  const child = spawn(command, ["test", ...process.argv.slice(2)], {
@@ -52,11 +60,22 @@ child.once("error", (error: Error) => {
52
60
  });
53
61
 
54
62
  child.once("exit", (code: number | null, signal: NodeJS.Signals | null) => {
55
- if (signal) {
56
- console.error(`Playwright exited with signal ${signal}`);
57
- process.exitCode = 1;
58
- return;
59
- }
63
+ void rm(env.DATABASE_FILE!, { force: true }).finally(() => {
64
+ if (signal) {
65
+ console.error(`Playwright exited with signal ${signal}`);
66
+ process.exitCode = 1;
67
+ return;
68
+ }
60
69
 
61
- process.exitCode = code ?? 1;
70
+ process.exitCode = code ?? 1;
71
+ });
62
72
  });
73
+
74
+ for (const signal of ["SIGINT", "SIGTERM"] as const) {
75
+ process.once(signal, () => {
76
+ child.kill(signal);
77
+ void rm(env.DATABASE_FILE!, { force: true }).finally(() => {
78
+ process.exitCode = 1;
79
+ });
80
+ });
81
+ }
@@ -7,19 +7,30 @@ import { api } from "#/server/api";
7
7
 
8
8
  const telefunc = new Telefunc();
9
9
 
10
- export const app = new Hono();
10
+ export function createApp() {
11
+ try {
12
+ assertDatabaseReady(createDatabase());
13
+ } catch (error) {
14
+ console.error(error);
15
+ process.exit(1);
16
+ }
11
17
 
12
- app.route("/api", api);
18
+ const app = new Hono();
13
19
 
14
- app.all("/_telefunc", async (c) => {
15
- const response = await telefunc.serve({
16
- request: c.req.raw,
17
- context: {
18
- db: createDatabase(),
19
- },
20
+ app.route("/api", api);
21
+
22
+ app.all("/_telefunc", async (c) => {
23
+ const response = await telefunc.serve({
24
+ request: c.req.raw,
25
+ context: {
26
+ db: createDatabase(),
27
+ },
28
+ });
29
+
30
+ return response ?? c.notFound();
20
31
  });
21
32
 
22
- return response ?? c.notFound();
23
- });
33
+ vike(app);
24
34
 
25
- vike(app);
35
+ return app;
36
+ }
@@ -2,7 +2,7 @@ import type { BuiltInPreset, PackageRole, PackageSourcePreset, ProjectBlueprint
2
2
  import type { GenerationContext } from "./generation-context.js";
3
3
  import { type CheckPlan, type FixPlan } from "./module-graph.js";
4
4
  import { type NextStepInstruction } from "./next-step-instructions.js";
5
- import type { DependencyMaintenancePolicy } from "./project-github.js";
5
+ import type { CiDockerImageBuild, DependencyMaintenancePolicy } from "./project-github.js";
6
6
  import type { RenderOperation } from "./renderer.js";
7
7
  export type PresetPackageAdditionOptions = {
8
8
  readonly root: string;
@@ -35,6 +35,7 @@ export type PresetProjectionPlan = {
35
35
  readonly operations: readonly RenderOperation[];
36
36
  readonly checkPlan: CheckPlan;
37
37
  readonly fixPlan: FixPlan;
38
+ readonly ciDockerImageBuild?: CiDockerImageBuild | undefined;
38
39
  readonly dependencyMaintenancePolicy: DependencyMaintenancePolicy;
39
40
  readonly packageScripts: Record<string, string>;
40
41
  readonly capabilities: {
@@ -12,11 +12,19 @@ export type PnpmTaskLayer = {
12
12
  readonly installCommand: "pnpm install";
13
13
  readonly checkCommand: "pnpm run check";
14
14
  };
15
+ export type CiDockerImageBuild = {
16
+ readonly dockerfile: string;
17
+ readonly targets: readonly {
18
+ readonly name: string;
19
+ readonly tag: string;
20
+ }[];
21
+ };
15
22
  export type DependencyEcosystem = "npm" | "cargo" | "github-actions" | "docker" | "rust-toolchain";
16
23
  export type DependabotDirectory = `/${string}`;
17
24
  export type DependencyMaintenancePolicy = {
18
25
  readonly ecosystems: DependencyEcosystem[];
19
26
  readonly directories?: Partial<Record<DependencyEcosystem, DependabotDirectory>>;
27
+ readonly extraDirectories?: Partial<Record<DependencyEcosystem, readonly DependabotDirectory[]>>;
20
28
  readonly interval: "weekly";
21
29
  };
22
30
  type ProjectCheckWorkflowOptions = {
@@ -24,6 +32,7 @@ type ProjectCheckWorkflowOptions = {
24
32
  readonly capability?: CiCapability | undefined;
25
33
  readonly environmentPreparation?: Partial<CiEnvironmentPreparation> | undefined;
26
34
  readonly taskLayer?: PnpmTaskLayer | undefined;
35
+ readonly dockerImageBuild?: CiDockerImageBuild | undefined;
27
36
  };
28
37
  export declare function projectCheckWorkflow(options: ProjectCheckWorkflowOptions): string;
29
38
  export declare function projectDependabotConfig(policy: DependencyMaintenancePolicy): string;
@@ -40,6 +40,9 @@ export function projectCheckWorkflow(options) {
40
40
  lines.push(` - run: ${taskLayer.installCommand}`);
41
41
  lines.push(...options.checkPlan.environmentNeeds.map((need) => ` - run: ${renderCiEnvironmentNeedCommand(need)}`));
42
42
  lines.push(` - run: ${taskLayer.checkCommand}`, "");
43
+ if (options.dockerImageBuild !== undefined) {
44
+ lines.push(" docker-image:", ` runs-on: ${capability.runner}`, " steps:", " - uses: actions/checkout@v7", " - uses: docker/setup-buildx-action@v3", ...options.dockerImageBuild.targets.map((target) => ` - run: docker buildx build --load --file ${options.dockerImageBuild.dockerfile} --target ${target.name} --tag ${target.tag} .`), "");
45
+ }
43
46
  return lines.join("\n");
44
47
  }
45
48
  export function projectDependabotConfig(policy) {
@@ -47,8 +50,11 @@ export function projectDependabotConfig(policy) {
47
50
  "version: 2",
48
51
  "",
49
52
  "updates:",
50
- ...policy.ecosystems.flatMap((ecosystem) => renderDependabotUpdate(ecosystem, policy.directories?.[ecosystem] ??
51
- defaultDependabotDirectory(ecosystem), policy.interval)),
53
+ ...policy.ecosystems.flatMap((ecosystem) => [
54
+ policy.directories?.[ecosystem] ??
55
+ defaultDependabotDirectory(ecosystem),
56
+ ...(policy.extraDirectories?.[ecosystem] ?? []),
57
+ ].flatMap((directory) => renderDependabotUpdate(ecosystem, directory, policy.interval))),
52
58
  "",
53
59
  ].join("\n");
54
60
  }
@@ -62,7 +68,7 @@ function renderDependabotUpdate(ecosystem, directory, interval) {
62
68
  if (ecosystem === "npm") {
63
69
  lines.push(" groups:", " drizzle:", " patterns:", ' - "drizzle-*"', ' - "drizzle-orm"', " ignore:", ' - dependency-name: "@types/node"', " update-types:", " - version-update:semver-major");
64
70
  }
65
- if (ecosystem === "docker") {
71
+ if (ecosystem === "docker" && directory === "/.devcontainer") {
66
72
  lines.push(" ignore:", " - dependency-name: mcr.microsoft.com/devcontainers/typescript-node", " update-types:", " - version-update:semver-major");
67
73
  }
68
74
  return lines;
@@ -77,6 +77,19 @@ 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.ciDockerImageBuild = {
83
+ dockerfile: "apps/web/Dockerfile",
84
+ targets: [
85
+ { name: "runtime", tag: "vike-app:check" },
86
+ {
87
+ name: "database-preparation",
88
+ tag: "vike-app-db-prepare:check",
89
+ },
90
+ ],
91
+ };
92
+ }
80
93
  if (capability.packages.length > 1) {
81
94
  state.rootScriptFragments.dev = "turbo run dev --parallel";
82
95
  }
@@ -285,6 +298,7 @@ export function interpretPresetProjectionDeclaration(options) {
285
298
  const packageScriptsByPath = packageScriptsByWorkspacePath(state);
286
299
  const dependencyMaintenancePolicy = {
287
300
  ecosystems: uniqueValues(state.dependencyMaintenanceEcosystems),
301
+ extraDirectories: state.dependencyMaintenanceExtraDirectories,
288
302
  ...(state.rustWorkspace === undefined
289
303
  ? {}
290
304
  : {
@@ -308,6 +322,7 @@ export function interpretPresetProjectionDeclaration(options) {
308
322
  })),
309
323
  checkPlan,
310
324
  fixPlan,
325
+ ciDockerImageBuild: state.ciDockerImageBuild,
311
326
  dependencyMaintenancePolicy,
312
327
  packageScripts,
313
328
  capabilities: completePlanCapabilityFlags(state.flags),
@@ -392,6 +407,8 @@ function createProjectionCompositionState(sourceRoots) {
392
407
  packageDependencies: new Set(),
393
408
  packageDevDependencies: new Set(),
394
409
  dependencyMaintenanceEcosystems: [],
410
+ dependencyMaintenanceExtraDirectories: {},
411
+ ciDockerImageBuild: undefined,
395
412
  editorCustomizationCapabilities: [],
396
413
  operationFactories: [],
397
414
  flags: {},
@@ -1110,6 +1127,10 @@ function vikeDbPackageJson(context) {
1110
1127
  default: "./src/queries/todos.ts",
1111
1128
  types: "./src/queries/todos.ts",
1112
1129
  },
1130
+ "./readiness": {
1131
+ default: "./src/readiness.ts",
1132
+ types: "./src/readiness.ts",
1133
+ },
1113
1134
  },
1114
1135
  dependencies: {
1115
1136
  "drizzle-orm": "catalog:",
@@ -1132,15 +1153,19 @@ function vikeDbPackageJson(context) {
1132
1153
  function vikeDbPackageScripts() {
1133
1154
  return {
1134
1155
  "build:run": "tsc -p tsconfig.json --noEmit",
1156
+ "db:generate": "drizzle-kit generate",
1157
+ "db:migrate": "drizzle-kit migrate",
1158
+ "db:prepare:deploy": "pnpm run db:migrate",
1159
+ "db:prepare:dev": "pnpm run db:push && pnpm run db:seed:example",
1160
+ "db:prepare:test": 'rm -f "${DATABASE_FILE:-./node_modules/.tmp/test.sqlite}" && pnpm run db:push && pnpm run db:seed:example',
1135
1161
  "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",
1162
+ "db:seed:example": "node --experimental-strip-types scripts/seed-example.ts",
1163
+ "db:studio": "drizzle-kit studio",
1139
1164
  "format:check:run": "oxfmt --list-different --config ../../oxfmt.config.ts .",
1140
1165
  "format:write:run": "oxfmt --write --config ../../oxfmt.config.ts .",
1141
1166
  "lint:run": "oxlint --quiet --format=unix --config ../../oxlint.config.ts .",
1142
1167
  "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",
1168
+ "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
1169
  "typecheck:run": "tsc -p tsconfig.json --noEmit --pretty false",
1145
1170
  };
1146
1171
  }
@@ -1565,7 +1590,7 @@ function nodePackageScripts(nodePackage, workspace) {
1565
1590
  };
1566
1591
  }
1567
1592
  if (nodePackage.kind === "vike-app") {
1568
- const dbCommand = "DATABASE_FILE=../../apps/web/data/app.sqlite pnpm --dir ../../packages/db run db:push";
1593
+ const dbCommand = "DATABASE_FILE=../../apps/web/data/app.sqlite pnpm --dir ../../packages/db run db:prepare:dev";
1569
1594
  return {
1570
1595
  "build:run": "vike build",
1571
1596
  dev: `${dbCommand} && vike dev`,
@@ -1836,6 +1861,21 @@ function vikeTemplateSourceOperation(options) {
1836
1861
  from: options.sourceFile,
1837
1862
  to,
1838
1863
  };
1864
+ if (options.nodePackage.kind === "vike-app" &&
1865
+ options.sourceFile === "web/Dockerfile") {
1866
+ return [
1867
+ {
1868
+ kind: "writeTextTemplate",
1869
+ from: options.sourceFile,
1870
+ to,
1871
+ replacements: {
1872
+ NODE_VERSION: options.context.toolchain.nodeLtsMajor.value,
1873
+ PACKAGE_MANAGER_PIN: options.context.toolchain.packageManagerPin.value,
1874
+ WEB_PACKAGE_NAME: nodePackageName(options.context, options.nodePackage),
1875
+ },
1876
+ },
1877
+ ];
1878
+ }
1839
1879
  if (options.nodePackage.kind === "vike-app" &&
1840
1880
  [
1841
1881
  "web/pages/index/+Page.telefunc.ts",
@@ -1845,7 +1885,10 @@ function vikeTemplateSourceOperation(options) {
1845
1885
  const dbPackageName = vikeDbPackageName(options.context);
1846
1886
  const replacements = {
1847
1887
  "web/pages/index/+Page.telefunc.ts": `import { createTodo, listTodos } from "${dbPackageName}/queries/todos";`,
1848
- "web/server/app.ts": `import { createDatabase } from "${dbPackageName}";`,
1888
+ "web/server/app.ts": [
1889
+ `import { createDatabase } from "${dbPackageName}";`,
1890
+ `import { assertDatabaseReady } from "${dbPackageName}/readiness";`,
1891
+ ].join("\n"),
1849
1892
  "web/types/global.d.ts": `import type { Database } from "${dbPackageName}";`,
1850
1893
  };
1851
1894
  return [
@@ -1865,9 +1908,14 @@ function vikeTemplateSourceOperation(options) {
1865
1908
  const vikeDbSourceFiles = [
1866
1909
  "db/turbo.json",
1867
1910
  "db/drizzle.config.ts",
1911
+ "db/drizzle/migrations/20260709120325_old_captain_flint/migration.sql",
1912
+ "db/drizzle/migrations/20260709120325_old_captain_flint/snapshot.json",
1913
+ "db/scripts/seed-example.ts",
1868
1914
  "db/src/db.ts",
1869
1915
  "db/src/index.ts",
1870
1916
  "db/src/queries/todos.ts",
1917
+ "db/src/readiness.ts",
1918
+ "db/src/seed/example.ts",
1871
1919
  "db/src/schema.ts",
1872
1920
  "db/test/todos.test.ts",
1873
1921
  ];
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykdz/template",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "private": false,
5
5
  "description": "Project template generator for YKDZ starter projects.",
6
6
  "keywords": [