@podosoft/podokit 0.4.0 → 0.5.0

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 (141) hide show
  1. package/README.md +2 -2
  2. package/dist/add.d.ts +2 -0
  3. package/dist/add.js +5 -1
  4. package/dist/assemble.d.ts +14 -0
  5. package/dist/assemble.js +60 -0
  6. package/dist/create.d.ts +2 -0
  7. package/dist/create.js +7 -0
  8. package/dist/eject.d.ts +11 -0
  9. package/dist/eject.js +27 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +122 -1
  12. package/dist/inspect.d.ts +43 -0
  13. package/dist/inspect.js +82 -0
  14. package/dist/lockfile.d.ts +80 -0
  15. package/dist/lockfile.js +193 -0
  16. package/dist/templates/base/dot-env.example +11 -6
  17. package/dist/templates/fullstack-nest-svelte/README.md +2 -2
  18. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +1 -0
  19. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +6 -3
  20. package/dist/templates/fullstack-nest-svelte/apps/api/src/common/app-exception.ts +4 -12
  21. package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +1 -1
  22. package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +1 -1
  23. package/dist/templates/fullstack-nest-svelte/apps/web/package.json +2 -2
  24. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +1 -1
  25. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +1 -1
  26. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/index.ts +7 -0
  27. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
  28. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +1 -1
  29. package/dist/templates/fullstack-nest-svelte/dot-env.example +10 -5
  30. package/dist/templates/fullstack-nest-svelte/infra/docker/docker-compose.yml +37 -0
  31. package/dist/templates/fullstack-nest-svelte/infra/docker/sms-sink.mjs +40 -0
  32. package/dist/templates/fullstack-nest-svelte/tests/README.md +1 -1
  33. package/dist/templates/fullstack-nest-svelte/tests/package.json +2 -1
  34. package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +1 -1
  35. package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +3 -0
  36. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +7 -1
  37. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +149 -0
  38. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +21 -22
  39. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +93 -0
  40. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +224 -8
  41. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +22 -0
  42. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +19 -3
  43. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/accept-invitation/[id]/+page.svelte +36 -0
  44. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +21 -1
  45. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +360 -49
  46. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +2 -4
  47. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +82 -37
  48. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.server.ts +6 -0
  49. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.svelte +264 -0
  50. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +3 -4
  51. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +79 -45
  52. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.server.ts +26 -0
  53. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +322 -0
  54. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +3 -5
  55. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +254 -108
  56. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +8 -1
  57. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.server.ts +14 -0
  58. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +127 -5
  59. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +18 -2
  60. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/verify-email/+page.svelte +63 -0
  61. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/oauth2/consent/+page.svelte +54 -0
  62. package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +51 -2
  63. package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +80 -0
  64. package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +264 -1
  65. package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +120 -0
  66. package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +10 -10
  67. package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +2 -2
  68. package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +49 -0
  69. package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +35 -0
  70. package/dist/templates/modules/admin-dashboard/files/tests/helpers/totp.ts +9 -0
  71. package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +1 -1
  72. package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +12 -1
  73. package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +77 -6
  74. package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +1 -1
  75. package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +32 -0
  76. package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +60 -0
  77. package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +42 -0
  78. package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +1 -0
  79. package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +57 -0
  80. package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +52 -0
  81. package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +36 -0
  82. package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +9 -2
  83. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +34 -0
  84. package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +32 -0
  85. package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +28 -12
  86. package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +35 -3
  87. package/dist/templates/modules/admin-dashboard/module.manifest.json +13 -7
  88. package/dist/templates/modules/api-key-auth/module.manifest.json +3 -3
  89. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-enabled.ts +16 -0
  90. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +4 -0
  91. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +2 -0
  92. package/dist/templates/modules/audit-log/module.manifest.json +15 -9
  93. package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +78 -13
  94. package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +2 -1
  95. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +73 -0
  96. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +197 -52
  97. package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +12 -0
  98. package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +56 -0
  99. package/dist/templates/modules/auth/files/apps/api/src/auth/org-permissions.ts +22 -0
  100. package/dist/templates/modules/auth/files/apps/api/src/auth/permissions.ts +23 -0
  101. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.controller.ts +33 -0
  102. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.entity.ts +26 -0
  103. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.module.ts +12 -0
  104. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +118 -0
  105. package/dist/templates/modules/auth/files/apps/api/src/mail/mailer.ts +65 -0
  106. package/dist/templates/modules/auth/files/apps/api/src/migrations/1720400000000-InitAppSettings.ts +38 -0
  107. package/dist/templates/modules/auth/files/apps/api/src/migrations/1720500000000-InitAuthConfig.ts +27 -0
  108. package/dist/templates/modules/auth/files/apps/api/src/settings/app-setting.entity.ts +16 -0
  109. package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +25 -0
  110. package/dist/templates/modules/auth/files/apps/api/src/settings/settings.module.ts +11 -0
  111. package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.ts +57 -0
  112. package/dist/templates/modules/auth/files/apps/api/src/sms/sms.ts +23 -0
  113. package/dist/templates/modules/auth/module.manifest.json +54 -20
  114. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +6 -3
  115. package/dist/templates/modules/bullmq/module.manifest.json +4 -4
  116. package/dist/templates/modules/file-upload/module.manifest.json +3 -3
  117. package/dist/templates/modules/job-progress/module.manifest.json +9 -9
  118. package/dist/templates/modules/logging/module.manifest.json +3 -3
  119. package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -3
  120. package/dist/templates/modules/object-storage-s3/module.manifest.json +5 -5
  121. package/dist/templates/modules/rate-limit/module.manifest.json +2 -2
  122. package/dist/templates/modules/redis/module.manifest.json +4 -4
  123. package/dist/templates/modules/sse/module.manifest.json +4 -4
  124. package/dist/templates/todo/README.md +2 -2
  125. package/dist/templates/todo/apps/api/package.json +1 -0
  126. package/dist/templates/todo/apps/api/src/app.module.ts +6 -3
  127. package/dist/templates/todo/apps/api/src/common/app-exception.ts +4 -12
  128. package/dist/templates/todo/apps/api/src/config/env.validation.ts +1 -1
  129. package/dist/templates/todo/apps/api/src/main.ts +1 -1
  130. package/dist/templates/todo/apps/web/package.json +2 -2
  131. package/dist/templates/todo/apps/web/src/lib/components/ui/switch/index.ts +7 -0
  132. package/dist/templates/todo/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
  133. package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +1 -1
  134. package/dist/templates/todo/dot-env.example +10 -5
  135. package/dist/templates/todo/infra/docker/docker-compose.yml +4 -0
  136. package/dist/templates/todo/tests/README.md +1 -1
  137. package/dist/templates/todo/tests/package.json +2 -1
  138. package/dist/templates/todo/tests/playwright.config.ts +1 -1
  139. package/dist/update.d.ts +49 -0
  140. package/dist/update.js +158 -0
  141. package/package.json +2 -2
@@ -22,7 +22,7 @@ async function bootstrap(): Promise<void> {
22
22
  .build();
23
23
  SwaggerModule.setup("api-docs", app, SwaggerModule.createDocument(app, config));
24
24
 
25
- const port = Number(process.env.PORT ?? 3000);
25
+ const port = Number(process.env.PORT ?? 5002);
26
26
  await app.listen(port);
27
27
  }
28
28
 
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "vite dev --port 5173",
7
+ "dev": "vite dev --port 5001",
8
8
  "build": "vite build",
9
9
  "preview": "vite preview",
10
10
  "lint": "svelte-check --tsconfig ./tsconfig.json",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@internationalized/date": "^3.12.2",
15
15
  "@lucide/svelte": "^1.23.0",
16
- "@podosoft/podokit-api-client": "^0.4.0",
16
+ "@podosoft/podokit-api-client": "^0.5.0",
17
17
  "bits-ui": "^2.18.1",
18
18
  "clsx": "^2.1.1",
19
19
  "mode-watcher": "^1.1.0",
@@ -0,0 +1,7 @@
1
+ import Root from "./switch.svelte";
2
+
3
+ export {
4
+ Root,
5
+ //
6
+ Root as Switch,
7
+ };
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { Switch as SwitchPrimitive } from "bits-ui";
3
+ import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ checked = $bindable(false),
8
+ class: className,
9
+ ...restProps
10
+ }: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
11
+ </script>
12
+
13
+ <SwitchPrimitive.Root
14
+ bind:ref
15
+ data-slot="switch"
16
+ class={cn(
17
+ "peer focus-visible:border-ring focus-visible:ring-ring/50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ bind:checked
21
+ {...restProps}
22
+ >
23
+ <SwitchPrimitive.Thumb
24
+ data-slot="switch-thumb"
25
+ class={cn(
26
+ "bg-background pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
27
+ )}
28
+ />
29
+ </SwitchPrimitive.Root>
@@ -5,7 +5,7 @@ const FORWARDED_HEADERS = ["authorization", "cookie", "content-type", "accept",
5
5
  const RELAYED_RESPONSE_HEADERS = ["content-type", "location", "cache-control"];
6
6
 
7
7
  export function backendBaseUrl(): string {
8
- return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:3000";
8
+ return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:5002";
9
9
  }
10
10
 
11
11
  // Normalize the resolved client IP for the forwarded header. better-auth stores
@@ -1,6 +1,8 @@
1
1
  # Copy to .env and adjust. Never commit real secrets.
2
+
3
+ # ── Required (runtime) ───────────────────────────────────────────────
2
4
  NODE_ENV=development
3
- PORT=3000
5
+ PORT=5002
4
6
 
5
7
  POSTGRES_HOST=localhost
6
8
  POSTGRES_PORT=5432
@@ -8,9 +10,12 @@ POSTGRES_USER=podokit
8
10
  POSTGRES_PASSWORD=podokit
9
11
  POSTGRES_DB=podokit
10
12
 
13
+ # Web -> API boundary (server-side only; not exposed to the browser)
14
+ BACKEND_INTERNAL_URL=http://localhost:5002
15
+ CORS_ORIGIN=http://localhost:5001
16
+
17
+ # ── Optional / development ───────────────────────────────────────────
18
+ # Redis — only needed by cache/queue modules. Start it with:
19
+ # docker compose --profile cache up
11
20
  REDIS_HOST=localhost
12
21
  REDIS_PORT=6379
13
-
14
- # Web -> API boundary (server-side only; not exposed to the browser)
15
- BACKEND_INTERNAL_URL=http://localhost:3000
16
- CORS_ORIGIN=http://localhost:5173
@@ -1,4 +1,5 @@
1
1
  services:
2
+ # --- Runtime (always started by `docker compose up`) ---
2
3
  postgres:
3
4
  image: postgres:16-alpine
4
5
  environment:
@@ -15,8 +16,11 @@ services:
15
16
  timeout: 5s
16
17
  retries: 5
17
18
 
19
+ # --- Runtime, but only needed by cache/queue modules. Start with:
20
+ # docker compose --profile cache up
18
21
  redis:
19
22
  image: redis:7-alpine
23
+ profiles: ["cache"]
20
24
  ports:
21
25
  - "${REDIS_PORT:-6379}:6379"
22
26
  healthcheck:
@@ -10,6 +10,6 @@ npm run test:e2e:report # open the HTML report
10
10
  ```
11
11
 
12
12
  The suite runs against a live stack (web on `E2E_BASE_URL`, default
13
- `http://localhost:5173`, proxying `/api/*` to the API). Start PostgreSQL, run the
13
+ `http://localhost:5001`, proxying `/api/*` to the API). Start PostgreSQL, run the
14
14
  auth migration, and start the API + web first. Files ending in `*.api.spec.ts` run
15
15
  in the request-only `api` project; `*.ui.spec.ts` run in the chromium `ui` project.
@@ -9,6 +9,7 @@
9
9
  "test:e2e:report": "playwright show-report"
10
10
  },
11
11
  "devDependencies": {
12
- "@playwright/test": "^1.61.1"
12
+ "@playwright/test": "^1.61.1",
13
+ "otpauth": "^9.5.1"
13
14
  }
14
15
  }
@@ -4,7 +4,7 @@ import { defineConfig, devices } from "@playwright/test";
4
4
  // the API). Start it first (see docs/testing.md) — locally via the dev harness,
5
5
  // in CI via scripts/e2e-ci.mjs. Two projects: `api` (request-only, backend e2e)
6
6
  // and `ui` (chromium, pages). Files are routed by suffix (*.api/*.ui.spec.ts).
7
- const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:5173";
7
+ const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:5001";
8
8
 
9
9
  export default defineConfig({
10
10
  testDir: ".",
@@ -0,0 +1,49 @@
1
+ import { type Tier } from "./lockfile";
2
+ /**
3
+ * `podo update` planner. For now it produces a dry-run plan only: it assembles
4
+ * the new-version tree from the installed CLI's templates and compares it to the
5
+ * working copy, classifying each file. Applying the plan (writing files, 3-way
6
+ * merging) is a later step (see ADR-0009).
7
+ */
8
+ export type Action = "update" | "merge" | "conflict" | "add" | "remove" | "skip" | "up-to-date";
9
+ export interface FileChange {
10
+ path: string;
11
+ tier: Tier;
12
+ action: Action;
13
+ /** Why the action was chosen (for the report). */
14
+ note: string;
15
+ }
16
+ export interface UpdatePlan {
17
+ fromVersion: string;
18
+ toVersion: string;
19
+ template: string;
20
+ modules: string[];
21
+ changes: FileChange[];
22
+ }
23
+ /**
24
+ * Build the update plan. `templatesDir` is the installed CLI's template set (the
25
+ * new version); the lock records what PodoKit last wrote (to detect user edits).
26
+ */
27
+ export declare function planUpdate(projectRoot: string, templatesDir: string): UpdatePlan;
28
+ export interface ApplyOptions {
29
+ /** Templates for the version the project is currently on, for 3-way merges. */
30
+ oldTemplatesDir?: string;
31
+ }
32
+ export interface ApplyResult {
33
+ written: string[];
34
+ removed: string[];
35
+ /** Files 3-way merged cleanly. */
36
+ merged: string[];
37
+ /** Files written with conflict markers for manual resolution. */
38
+ conflicts: string[];
39
+ }
40
+ /**
41
+ * Apply an update to the working copy. Clean updates and additions are written,
42
+ * upstream removals deleted, and user-edited files 3-way merged against the old
43
+ * version (when `oldTemplatesDir` is given) or written with git-style conflict
44
+ * markers otherwise. Owned files are never touched. Refreshes the lockfile and
45
+ * stamps the new version.
46
+ */
47
+ export declare function applyUpdate(projectRoot: string, templatesDir: string, options?: ApplyOptions): ApplyResult;
48
+ /** Count actions for a one-line summary. */
49
+ export declare function summarize(plan: UpdatePlan): Record<Action, number>;
package/dist/update.js ADDED
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.planUpdate = planUpdate;
4
+ exports.applyUpdate = applyUpdate;
5
+ exports.summarize = summarize;
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
9
+ const assemble_1 = require("./assemble");
10
+ const lockfile_1 = require("./lockfile");
11
+ const inspect_1 = require("./inspect");
12
+ function treeText(tree, path) {
13
+ const file = tree.get(path);
14
+ if (!file)
15
+ return null;
16
+ return typeof file.content === "string" ? file.content : file.content.toString("utf8");
17
+ }
18
+ function diskContent(projectRoot, path) {
19
+ const abs = (0, node_path_1.join)(projectRoot, path);
20
+ return (0, node_fs_1.existsSync)(abs) ? (0, node_fs_1.readFileSync)(abs) : null;
21
+ }
22
+ /**
23
+ * Build the update plan. `templatesDir` is the installed CLI's template set (the
24
+ * new version); the lock records what PodoKit last wrote (to detect user edits).
25
+ */
26
+ function planUpdate(projectRoot, templatesDir) {
27
+ const manifest = (0, lockfile_1.readManifest)(projectRoot);
28
+ const lock = (0, lockfile_1.readFilesLock)(projectRoot);
29
+ if (!manifest || !lock)
30
+ throw new inspect_1.NotAProjectError();
31
+ const newTree = (0, assemble_1.assembleProject)({
32
+ templatesDir,
33
+ template: manifest.template,
34
+ answers: manifest.answers,
35
+ modules: manifest.modules.map((m) => m.name),
36
+ });
37
+ const changes = [];
38
+ const paths = new Set([...newTree.keys(), ...Object.keys(lock.files)]);
39
+ for (const path of [...paths].sort()) {
40
+ const locked = lock.files[path];
41
+ const newText = treeText(newTree, path);
42
+ const disk = diskContent(projectRoot, path);
43
+ // tier: prefer the lock; else classify the new file.
44
+ const tier = locked?.tier ??
45
+ (newText !== null ? (0, lockfile_1.classifyTier)(path, newText, manifest.ownedGlobs) : "managed");
46
+ if (tier === "owned") {
47
+ changes.push({ path, tier, action: "skip", note: "owned — never modified" });
48
+ continue;
49
+ }
50
+ // File removed upstream.
51
+ if (newText === null) {
52
+ if (disk === null)
53
+ continue; // already gone
54
+ const edited = locked && (0, podokit_template_engine_1.hashContent)(disk) !== locked.outHash;
55
+ changes.push({
56
+ path,
57
+ tier,
58
+ action: edited ? "conflict" : "remove",
59
+ note: edited ? "removed upstream but edited locally" : "removed upstream",
60
+ });
61
+ continue;
62
+ }
63
+ const newHash = (0, podokit_template_engine_1.hashContent)(newText);
64
+ // New file added upstream.
65
+ if (disk === null) {
66
+ changes.push({ path, tier, action: "add", note: "new in this version" });
67
+ continue;
68
+ }
69
+ const diskHash = (0, podokit_template_engine_1.hashContent)(disk);
70
+ if (diskHash === newHash) {
71
+ changes.push({ path, tier, action: "up-to-date", note: "already current" });
72
+ continue;
73
+ }
74
+ const edited = !locked || diskHash !== locked.outHash;
75
+ changes.push({
76
+ path,
77
+ tier,
78
+ action: edited ? "conflict" : "update",
79
+ note: edited ? "you edited this; update would need a merge" : "clean update",
80
+ });
81
+ }
82
+ return {
83
+ fromVersion: manifest.podokitVersion,
84
+ toVersion: (0, lockfile_1.podokitVersion)(),
85
+ template: manifest.template,
86
+ modules: manifest.modules.map((m) => m.name),
87
+ changes,
88
+ };
89
+ }
90
+ function writeFile(projectRoot, path, content) {
91
+ const abs = (0, node_path_1.join)(projectRoot, path);
92
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(abs), { recursive: true });
93
+ (0, node_fs_1.writeFileSync)(abs, content);
94
+ }
95
+ /**
96
+ * Apply an update to the working copy. Clean updates and additions are written,
97
+ * upstream removals deleted, and user-edited files 3-way merged against the old
98
+ * version (when `oldTemplatesDir` is given) or written with git-style conflict
99
+ * markers otherwise. Owned files are never touched. Refreshes the lockfile and
100
+ * stamps the new version.
101
+ */
102
+ function applyUpdate(projectRoot, templatesDir, options = {}) {
103
+ const manifest = (0, lockfile_1.readManifest)(projectRoot);
104
+ if (!manifest)
105
+ throw new inspect_1.NotAProjectError();
106
+ const plan = planUpdate(projectRoot, templatesDir);
107
+ const modules = manifest.modules.map((m) => m.name);
108
+ const newTree = (0, assemble_1.assembleProject)({ templatesDir, template: manifest.template, answers: manifest.answers, modules });
109
+ const oldTree = options.oldTemplatesDir
110
+ ? (0, assemble_1.assembleProject)({ templatesDir: options.oldTemplatesDir, template: manifest.template, answers: manifest.answers, modules })
111
+ : null;
112
+ const result = { written: [], removed: [], merged: [], conflicts: [] };
113
+ for (const change of plan.changes) {
114
+ const newText = treeText(newTree, change.path);
115
+ if (change.action === "update" || change.action === "add") {
116
+ writeFile(projectRoot, change.path, newText ?? "");
117
+ result.written.push(change.path);
118
+ }
119
+ else if (change.action === "remove") {
120
+ (0, node_fs_1.rmSync)((0, node_path_1.join)(projectRoot, change.path), { force: true });
121
+ result.removed.push(change.path);
122
+ }
123
+ else if (change.action === "conflict" && newText !== null) {
124
+ const disk = (0, node_fs_1.readFileSync)((0, node_path_1.join)(projectRoot, change.path), "utf8");
125
+ const base = oldTree ? treeText(oldTree, change.path) : null;
126
+ if (base === null) {
127
+ // No old version to merge against — never clobber the user's edits.
128
+ result.conflicts.push(change.path);
129
+ continue;
130
+ }
131
+ const merge = (0, podokit_template_engine_1.threeWayMerge)(base, disk, newText, { current: "current", next: "podokit" });
132
+ writeFile(projectRoot, change.path, merge.merged);
133
+ if (merge.conflicts > 0)
134
+ result.conflicts.push(change.path);
135
+ else
136
+ result.merged.push(change.path);
137
+ }
138
+ }
139
+ // Refresh the lock to match what we just wrote and stamp the new version.
140
+ (0, lockfile_1.writeFilesLock)(projectRoot, (0, lockfile_1.computeFilesLock)(projectRoot, manifest.ownedGlobs));
141
+ (0, lockfile_1.writeManifest)(projectRoot, { ...manifest, podokitVersion: (0, lockfile_1.podokitVersion)() });
142
+ return result;
143
+ }
144
+ /** Count actions for a one-line summary. */
145
+ function summarize(plan) {
146
+ const counts = {
147
+ update: 0,
148
+ merge: 0,
149
+ conflict: 0,
150
+ add: 0,
151
+ remove: 0,
152
+ skip: 0,
153
+ "up-to-date": 0,
154
+ };
155
+ for (const change of plan.changes)
156
+ counts[change.action] += 1;
157
+ return counts;
158
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@podosoft/podokit",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "An opinionated but extensible starter toolkit and CLI for full-stack TypeScript apps with NestJS, SvelteKit, TailwindCSS, shadcn-svelte, Docker, and k3s.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -36,6 +36,6 @@
36
36
  "test": "vitest run"
37
37
  },
38
38
  "dependencies": {
39
- "@podosoft/podokit-template-engine": "^0.3.0"
39
+ "@podosoft/podokit-template-engine": "^0.4.0"
40
40
  }
41
41
  }