@podosoft/podokit 0.5.1 → 0.7.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 (98) hide show
  1. package/README.md +6 -0
  2. package/dist/add.d.ts +24 -2
  3. package/dist/add.js +77 -20
  4. package/dist/assemble.js +4 -1
  5. package/dist/create.d.ts +2 -0
  6. package/dist/create.js +6 -0
  7. package/dist/eject.js +9 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +7 -2
  10. package/dist/lib.d.ts +14 -0
  11. package/dist/lib.js +34 -0
  12. package/dist/lockfile.d.ts +7 -2
  13. package/dist/lockfile.js +37 -2
  14. package/dist/templates/base/AGENTS.md +30 -0
  15. package/dist/templates/base/CLAUDE.md +6 -0
  16. package/dist/templates/fullstack-nest-svelte/AGENTS.md +79 -0
  17. package/dist/templates/fullstack-nest-svelte/CLAUDE.md +6 -0
  18. package/dist/templates/fullstack-nest-svelte/Dockerfile.dev +25 -0
  19. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +4 -2
  20. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.extensions.ts +21 -0
  21. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +3 -0
  22. package/dist/templates/fullstack-nest-svelte/apps/web/src/app.html +2 -0
  23. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/index.ts +1 -3
  24. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +1 -1
  25. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +5 -25
  26. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +1 -4
  27. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs.svelte +1 -1
  28. package/dist/templates/fullstack-nest-svelte/apps/web/static/favicon.svg +13 -0
  29. package/dist/templates/fullstack-nest-svelte/apps/web/vite.config.ts +12 -0
  30. package/dist/templates/fullstack-nest-svelte/compose.dev.yaml +150 -0
  31. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-add-module/SKILL.md +27 -0
  32. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-nest-endpoint/SKILL.md +23 -0
  33. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-sveltekit-route/SKILL.md +23 -0
  34. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-update/SKILL.md +26 -0
  35. package/dist/templates/fullstack-nest-svelte/dot-cursor/rules/podokit.mdc +11 -0
  36. package/dist/templates/fullstack-nest-svelte/dot-devcontainer/devcontainer.json +20 -0
  37. package/dist/templates/fullstack-nest-svelte/dot-dockerignore +8 -0
  38. package/dist/templates/fullstack-nest-svelte/dot-env.docker +26 -0
  39. package/dist/templates/fullstack-nest-svelte/dot-github/copilot-instructions.md +6 -0
  40. package/dist/templates/fullstack-nest-svelte/dot-mcp.json +8 -0
  41. package/dist/templates/fullstack-nest-svelte/infra/traefik/dynamic.yml +13 -0
  42. package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.controller.ts +87 -0
  43. package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.module.ts +14 -0
  44. package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.service.ts +84 -0
  45. package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +2 -0
  46. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +12 -1
  47. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/registry.svelte.ts +37 -0
  48. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +25 -11
  49. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-footer.svelte +32 -0
  50. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +16 -3
  51. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +52 -2
  52. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/site.svelte.ts +50 -0
  53. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +38 -13
  54. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/general-settings.svelte +203 -0
  55. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +7 -3
  56. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +8 -3
  57. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +34 -4
  58. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +41 -0
  59. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +2 -0
  60. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/maintenance/+page.svelte +25 -0
  61. package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md +43 -0
  62. package/dist/templates/modules/admin-dashboard/files/tests/ui/general-effects.ui.spec.ts +81 -0
  63. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +42 -1
  64. package/dist/templates/modules/admin-dashboard/module.manifest.json +22 -4
  65. package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +27 -0
  66. package/dist/templates/modules/auth/module.manifest.json +9 -8
  67. package/dist/templates/modules/bullmq/module.manifest.json +1 -1
  68. package/dist/templates/modules/file-upload/files/apps/api/src/files/files.controller.ts +3 -0
  69. package/dist/templates/modules/mailer/files/apps/api/src/mail/db.ts +13 -0
  70. package/dist/templates/modules/{auth → mailer}/files/apps/api/src/mail/mailer.ts +41 -7
  71. package/dist/templates/modules/mailer/module.manifest.json +38 -0
  72. package/dist/templates/todo/AGENTS.md +80 -0
  73. package/dist/templates/todo/CLAUDE.md +6 -0
  74. package/dist/templates/todo/Dockerfile.dev +25 -0
  75. package/dist/templates/todo/apps/api/package.json +4 -2
  76. package/dist/templates/todo/apps/api/src/app.extensions.ts +21 -0
  77. package/dist/templates/todo/apps/api/src/app.module.ts +3 -0
  78. package/dist/templates/todo/apps/web/src/app.html +2 -0
  79. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/index.ts +1 -3
  80. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +1 -1
  81. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +5 -25
  82. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +1 -4
  83. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs.svelte +1 -1
  84. package/dist/templates/todo/apps/web/static/favicon.svg +13 -0
  85. package/dist/templates/todo/apps/web/vite.config.ts +12 -0
  86. package/dist/templates/todo/compose.dev.yaml +150 -0
  87. package/dist/templates/todo/dot-claude/skills/podokit-add-module/SKILL.md +27 -0
  88. package/dist/templates/todo/dot-claude/skills/podokit-nest-endpoint/SKILL.md +23 -0
  89. package/dist/templates/todo/dot-claude/skills/podokit-sveltekit-route/SKILL.md +23 -0
  90. package/dist/templates/todo/dot-claude/skills/podokit-update/SKILL.md +26 -0
  91. package/dist/templates/todo/dot-cursor/rules/podokit.mdc +11 -0
  92. package/dist/templates/todo/dot-devcontainer/devcontainer.json +20 -0
  93. package/dist/templates/todo/dot-dockerignore +8 -0
  94. package/dist/templates/todo/dot-env.docker +26 -0
  95. package/dist/templates/todo/dot-github/copilot-instructions.md +6 -0
  96. package/dist/templates/todo/dot-mcp.json +8 -0
  97. package/dist/templates/todo/infra/traefik/dynamic.yml +13 -0
  98. package/package.json +9 -1
package/README.md CHANGED
@@ -47,10 +47,16 @@ Options:
47
47
  - base : minimal npm workspace
48
48
  --dir <path> Target directory (default: ./<name>)
49
49
  --pm <name> Package manager: npm | pnpm | yarn (default: npm)
50
+ --no-ai Skip AI agent guidance (AGENTS.md, CLAUDE.md, editor rules)
50
51
  -y, --yes Skip prompts and accept defaults
51
52
  -h, --help Show help
52
53
  ```
53
54
 
55
+ Generated projects include agent guidance for AI coding tools — an
56
+ [`AGENTS.md`](https://agents.md) (the open standard), a `CLAUDE.md` that imports
57
+ it, `.cursor`/`.github` pointers, and Claude Code skills under `.claude/skills/`.
58
+ Modules extend `AGENTS.md` as you add them. Use `--no-ai` to skip.
59
+
54
60
  Examples:
55
61
 
56
62
  ```bash
package/dist/add.d.ts CHANGED
@@ -2,8 +2,15 @@ interface Injection {
2
2
  file: string;
3
3
  marker: string;
4
4
  text: string;
5
+ /** Skip (instead of failing) if the target file or marker is absent. */
6
+ optional?: boolean;
5
7
  }
8
+ /** Highest `module.manifest.json` schema version this CLI understands. A package
9
+ * module declaring a higher version is rejected rather than mis-applied. */
10
+ export declare const SUPPORTED_MANIFEST_VERSION = 1;
6
11
  export interface ModuleManifest {
12
+ /** Manifest schema version. Absent (bundled modules) means "same as the CLI". */
13
+ manifestVersion?: number;
7
14
  name: string;
8
15
  description: string;
9
16
  requires?: string[];
@@ -14,6 +21,11 @@ export interface ModuleManifest {
14
21
  env?: string[];
15
22
  inject?: Injection[];
16
23
  instructions?: string[];
24
+ /** App-relative globs this module's files should own (durably user-editable,
25
+ * never touched by `podo update`). Merged into the project's ownedGlobs so
26
+ * they survive lock recompute. Use for public presentation pages a consumer
27
+ * restyles, while keeping the module's `$lib` logic managed. */
28
+ ownedGlobs?: string[];
17
29
  }
18
30
  export interface AddOptions {
19
31
  projectRoot: string;
@@ -27,9 +39,19 @@ export interface AddResult {
27
39
  instructions: string[];
28
40
  /** Required modules that were auto-added because they were missing. */
29
41
  added: string[];
42
+ /** ownedGlobs declared by this module and every module it pulled in. */
43
+ ownedGlobs: string[];
30
44
  }
31
- /** List modules available under `modulesDir` (each has a module.manifest.json). */
32
- export declare function listModules(modulesDir: string): {
45
+ /**
46
+ * Resolve a module's directory by name: first the bundled `modulesDir`, then a
47
+ * node-resolvable npm package `@podosoft/podokit-module-<name>` installed in the
48
+ * project (or a fully-qualified `@scope/pkg` name). Returns null if not found.
49
+ */
50
+ export declare function resolveModuleDir(name: string, modulesDir: string, projectRoot: string): string | null;
51
+ /** List modules available to a project: the bundled ones under `modulesDir` plus,
52
+ * when `projectRoot` is given, any `@podosoft/podokit-module-*` packages installed
53
+ * in it. Bundled names take precedence on a clash. */
54
+ export declare function listModules(modulesDir: string, projectRoot?: string): {
33
55
  name: string;
34
56
  description: string;
35
57
  }[];
package/dist/add.js CHANGED
@@ -1,21 +1,69 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SUPPORTED_MANIFEST_VERSION = void 0;
4
+ exports.resolveModuleDir = resolveModuleDir;
3
5
  exports.listModules = listModules;
4
6
  exports.addModule = addModule;
5
7
  const node_fs_1 = require("node:fs");
6
8
  const node_path_1 = require("node:path");
9
+ const node_module_1 = require("node:module");
7
10
  const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
8
11
  const lockfile_1 = require("./lockfile");
9
- /** List modules available under `modulesDir` (each has a module.manifest.json). */
10
- function listModules(modulesDir) {
11
- if (!(0, node_fs_1.existsSync)(modulesDir))
12
- return [];
13
- return (0, node_fs_1.readdirSync)(modulesDir)
14
- .filter((name) => (0, node_fs_1.existsSync)((0, node_path_1.join)(modulesDir, name, "module.manifest.json")))
15
- .map((name) => {
16
- const manifest = readManifest((0, node_path_1.join)(modulesDir, name));
17
- return { name, description: manifest.description };
18
- });
12
+ /** Highest `module.manifest.json` schema version this CLI understands. A package
13
+ * module declaring a higher version is rejected rather than mis-applied. */
14
+ exports.SUPPORTED_MANIFEST_VERSION = 1;
15
+ const PACKAGE_PREFIX = "@podosoft/podokit-module-";
16
+ /**
17
+ * Resolve a module's directory by name: first the bundled `modulesDir`, then a
18
+ * node-resolvable npm package `@podosoft/podokit-module-<name>` installed in the
19
+ * project (or a fully-qualified `@scope/pkg` name). Returns null if not found.
20
+ */
21
+ function resolveModuleDir(name, modulesDir, projectRoot) {
22
+ const bundled = (0, node_path_1.join)(modulesDir, name);
23
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(bundled, "module.manifest.json")))
24
+ return bundled;
25
+ const pkg = name.includes("/") ? name : `${PACKAGE_PREFIX}${name}`;
26
+ try {
27
+ const req = (0, node_module_1.createRequire)((0, node_path_1.join)(projectRoot, "package.json"));
28
+ return (0, node_path_1.dirname)(req.resolve(`${pkg}/module.manifest.json`));
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ function assertManifestVersion(name, manifest) {
35
+ if ((manifest.manifestVersion ?? exports.SUPPORTED_MANIFEST_VERSION) > exports.SUPPORTED_MANIFEST_VERSION) {
36
+ throw new Error(`Module "${name}" needs a newer PodoKit (manifest v${manifest.manifestVersion}, this CLI supports v${exports.SUPPORTED_MANIFEST_VERSION}). Update @podosoft/podokit.`);
37
+ }
38
+ }
39
+ /** List modules available to a project: the bundled ones under `modulesDir` plus,
40
+ * when `projectRoot` is given, any `@podosoft/podokit-module-*` packages installed
41
+ * in it. Bundled names take precedence on a clash. */
42
+ function listModules(modulesDir, projectRoot) {
43
+ const out = new Map();
44
+ if ((0, node_fs_1.existsSync)(modulesDir)) {
45
+ for (const name of (0, node_fs_1.readdirSync)(modulesDir)) {
46
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(modulesDir, name, "module.manifest.json"))) {
47
+ out.set(name, readManifest((0, node_path_1.join)(modulesDir, name)).description);
48
+ }
49
+ }
50
+ }
51
+ if (projectRoot) {
52
+ const scope = (0, node_path_1.join)(projectRoot, "node_modules", "@podosoft");
53
+ if ((0, node_fs_1.existsSync)(scope)) {
54
+ for (const dir of (0, node_fs_1.readdirSync)(scope)) {
55
+ if (!dir.startsWith("podokit-module-"))
56
+ continue;
57
+ const manifestPath = (0, node_path_1.join)(scope, dir, "module.manifest.json");
58
+ if (!(0, node_fs_1.existsSync)(manifestPath))
59
+ continue;
60
+ const manifest = readManifest((0, node_path_1.join)(scope, dir));
61
+ if (!out.has(manifest.name))
62
+ out.set(manifest.name, manifest.description);
63
+ }
64
+ }
65
+ }
66
+ return [...out].map(([name, description]) => ({ name, description }));
19
67
  }
20
68
  function readManifest(moduleDir) {
21
69
  return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(moduleDir, "module.manifest.json"), "utf8"));
@@ -40,10 +88,10 @@ function appendEnv(projectRoot, lines) {
40
88
  }
41
89
  /** Heuristic: is `module` already applied to the project? */
42
90
  function isApplied(projectRoot, modulesDir, module) {
43
- const manifestPath = (0, node_path_1.join)(modulesDir, module, "module.manifest.json");
44
- if (!(0, node_fs_1.existsSync)(manifestPath))
91
+ const moduleDir = resolveModuleDir(module, modulesDir, projectRoot);
92
+ if (!moduleDir)
45
93
  return false;
46
- const manifest = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, "utf8"));
94
+ const manifest = readManifest(moduleDir);
47
95
  const firstInject = manifest.inject?.[0];
48
96
  if (firstInject) {
49
97
  const target = (0, node_path_1.join)(projectRoot, firstInject.file);
@@ -58,17 +106,19 @@ function isApplied(projectRoot, modulesDir, module) {
58
106
  */
59
107
  function addModule(options) {
60
108
  const result = applyModule(options.projectRoot, options.module, options.modulesDir, new Set());
61
- // Record the module (and any auto-added requirements) and refresh the lock.
62
- (0, lockfile_1.recordModules)(options.projectRoot, [...result.added, result.module], options.podokitVersion);
109
+ // Record the module (and any auto-added requirements), fold in the modules'
110
+ // declared ownedGlobs, and refresh the lock.
111
+ (0, lockfile_1.recordModules)(options.projectRoot, [...result.added, result.module], options.podokitVersion, result.ownedGlobs);
63
112
  return result;
64
113
  }
65
114
  function applyModule(projectRoot, module, modulesDir, applied) {
66
- const moduleDir = (0, node_path_1.join)(modulesDir, module);
67
- if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(moduleDir, "module.manifest.json"))) {
68
- const available = listModules(modulesDir).map((m) => m.name);
115
+ const moduleDir = resolveModuleDir(module, modulesDir, projectRoot);
116
+ if (!moduleDir) {
117
+ const available = listModules(modulesDir, projectRoot).map((m) => m.name);
69
118
  throw new Error(`Unknown module "${module}".${available.length ? ` Available: ${available.join(", ")}.` : ""}`);
70
119
  }
71
120
  const manifest = readManifest(moduleDir);
121
+ assertManifestVersion(module, manifest);
72
122
  const appPkgPath = (0, node_path_1.join)(projectRoot, "apps", manifest.targetApp, "package.json");
73
123
  if (!(0, node_fs_1.existsSync)(appPkgPath)) {
74
124
  throw new Error(`This does not look like a PodoKit project: ${(0, node_path_1.join)("apps", manifest.targetApp, "package.json")} not found. Run inside a generated project.`);
@@ -76,11 +126,13 @@ function applyModule(projectRoot, module, modulesDir, applied) {
76
126
  applied.add(module);
77
127
  // 0) apply required modules first (auto-add if missing)
78
128
  const added = [];
129
+ const ownedGlobs = [...(manifest.ownedGlobs ?? [])];
79
130
  for (const required of manifest.requires ?? []) {
80
131
  if (applied.has(required) || isApplied(projectRoot, modulesDir, required))
81
132
  continue;
82
133
  const result = applyModule(projectRoot, required, modulesDir, applied);
83
134
  added.push(required, ...result.added);
135
+ ownedGlobs.push(...result.ownedGlobs);
84
136
  }
85
137
  const appName = projectName(projectRoot);
86
138
  const vars = { projectName: appName };
@@ -109,10 +161,15 @@ function applyModule(projectRoot, module, modulesDir, applied) {
109
161
  for (const injection of manifest.inject ?? []) {
110
162
  const target = (0, node_path_1.join)(projectRoot, injection.file);
111
163
  if (!(0, node_fs_1.existsSync)(target)) {
164
+ if (injection.optional)
165
+ continue;
112
166
  throw new Error(`Cannot wire module: ${injection.file} not found.`);
113
167
  }
114
- (0, node_fs_1.writeFileSync)(target, (0, podokit_template_engine_1.insertAtMarker)((0, node_fs_1.readFileSync)(target, "utf8"), injection.marker, injection.text));
168
+ const content = (0, node_fs_1.readFileSync)(target, "utf8");
169
+ if (injection.optional && !content.includes(injection.marker))
170
+ continue;
171
+ (0, node_fs_1.writeFileSync)(target, (0, podokit_template_engine_1.insertAtMarker)(content, injection.marker, injection.text));
115
172
  }
116
173
  const instructions = (manifest.instructions ?? []).map((line) => line.replace(/<app>/g, appName));
117
- return { module, instructions, added };
174
+ return { module, instructions, added, ownedGlobs };
118
175
  }
package/dist/assemble.js CHANGED
@@ -47,7 +47,10 @@ function applyModuleToTree(tree, moduleDir, vars) {
47
47
  // 4) inject wiring at markers
48
48
  for (const injection of manifest.inject ?? []) {
49
49
  const target = injection.file;
50
- setText(tree, target, (0, podokit_template_engine_1.insertAtMarker)(textOf(tree, target), injection.marker, injection.text));
50
+ const content = textOf(tree, target);
51
+ if (injection.optional && !content.includes(injection.marker))
52
+ continue;
53
+ setText(tree, target, (0, podokit_template_engine_1.insertAtMarker)(content, injection.marker, injection.text));
51
54
  }
52
55
  }
53
56
  /** Assemble the project tree for a template + ordered modules, in memory. */
package/dist/create.d.ts CHANGED
@@ -14,6 +14,8 @@ export interface CreateOptions {
14
14
  packageManager?: PackageManager;
15
15
  /** PodoKit version stamped into the lockfile. Defaults to the CLI version. */
16
16
  podokitVersion?: string;
17
+ /** Include AI agent guidance (AGENTS.md, CLAUDE.md, editor rules). Defaults to true. */
18
+ ai?: boolean;
17
19
  }
18
20
  export interface CreateResult {
19
21
  projectDir: string;
package/dist/create.js CHANGED
@@ -9,6 +9,8 @@ const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
9
9
  const templates_1 = require("./templates");
10
10
  Object.defineProperty(exports, "DEFAULT_TEMPLATE", { enumerable: true, get: function () { return templates_1.DEFAULT_TEMPLATE; } });
11
11
  const lockfile_1 = require("./lockfile");
12
+ /** AI agent guidance files, removed when scaffolding with `--no-ai`. */
13
+ const AI_ARTIFACTS = ["AGENTS.md", "CLAUDE.md", ".claude", ".cursor", ".github/copilot-instructions.md"];
12
14
  const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-._]*[a-z0-9])?$/i;
13
15
  /** Validate a project name: no path separators, npm-friendly characters. */
14
16
  function assertValidName(name) {
@@ -42,6 +44,10 @@ function create(options) {
42
44
  }
43
45
  const vars = { projectName: name, packageManager };
44
46
  (0, podokit_template_engine_1.copyTemplate)(templateDir, projectDir, vars);
47
+ if (options.ai === false) {
48
+ for (const artifact of AI_ARTIFACTS)
49
+ (0, node_fs_1.rmSync)((0, node_path_1.join)(projectDir, artifact), { recursive: true, force: true });
50
+ }
45
51
  (0, lockfile_1.initLockfile)(projectDir, {
46
52
  template,
47
53
  packageManager,
package/dist/eject.js CHANGED
@@ -21,7 +21,15 @@ function eject(projectRoot, paths) {
21
21
  result.ejected.push(path);
22
22
  }
23
23
  }
24
- if (result.ejected.length)
24
+ if (result.ejected.length) {
25
25
  (0, lockfile_1.writeFilesLock)(projectRoot, lock);
26
+ // Persist ejected paths into the manifest's ownedGlobs so they stay owned
27
+ // across the next add/update, which rebuilds every tier from ownedGlobs.
28
+ const manifest = (0, lockfile_1.readManifest)(projectRoot);
29
+ if (manifest) {
30
+ manifest.ownedGlobs = (0, lockfile_1.mergeOwnedGlobs)(manifest.ownedGlobs, result.ejected);
31
+ (0, lockfile_1.writeManifest)(projectRoot, manifest);
32
+ }
33
+ }
26
34
  return result;
27
35
  }
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ interface ParsedArgs {
10
10
  apply: boolean;
11
11
  yes: boolean;
12
12
  help: boolean;
13
+ ai: boolean;
13
14
  }
14
15
  export declare function parseArgs(argv: string[]): ParsedArgs;
15
16
  export {};
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ Options:
26
26
  --template <t> Template to scaffold (see below)
27
27
  --dir <path> Target directory (default: ./<name>)
28
28
  --pm <name> Package manager: npm | pnpm | yarn (default: npm)
29
+ --no-ai Skip AI agent guidance (AGENTS.md, CLAUDE.md, editor rules)
29
30
  -y, --yes Skip prompts and accept defaults
30
31
  -h, --help Show this help
31
32
 
@@ -38,7 +39,7 @@ Example:
38
39
  cd my-app && npx @podosoft/podokit add auth
39
40
  `;
40
41
  function parseArgs(argv) {
41
- const parsed = { help: false, yes: false, apply: false };
42
+ const parsed = { help: false, yes: false, apply: false, ai: true };
42
43
  const positionals = [];
43
44
  for (let i = 0; i < argv.length; i += 1) {
44
45
  const arg = argv[i];
@@ -48,6 +49,9 @@ function parseArgs(argv) {
48
49
  else if (arg === "-y" || arg === "--yes") {
49
50
  parsed.yes = true;
50
51
  }
52
+ else if (arg === "--no-ai") {
53
+ parsed.ai = false;
54
+ }
51
55
  else if (arg === "--apply") {
52
56
  parsed.apply = true;
53
57
  }
@@ -85,7 +89,7 @@ async function main(argv) {
85
89
  if (args.command === "add") {
86
90
  const moduleName = args.name;
87
91
  if (!moduleName) {
88
- const available = (0, add_1.listModules)(modulesDir);
92
+ const available = (0, add_1.listModules)(modulesDir, process.cwd());
89
93
  const list = available.length
90
94
  ? available.map((m) => ` ${m.name} ${m.description}`).join("\n")
91
95
  : " (none available)";
@@ -242,6 +246,7 @@ async function main(argv) {
242
246
  template: resolved.template,
243
247
  targetDir: args.dir,
244
248
  packageManager: resolved.packageManager,
249
+ ai: args.ai,
245
250
  });
246
251
  const relPath = (0, node_path_1.relative)(process.cwd(), result.projectDir) || ".";
247
252
  const rel = relPath.startsWith("..") ? result.projectDir : relPath;
package/dist/lib.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export { create } from "./create";
2
+ export type { CreateOptions, CreateResult } from "./create";
3
+ export { TEMPLATES, DEFAULT_TEMPLATE, TEMPLATE_NAMES, isKnownTemplate } from "./templates";
4
+ export type { TemplateInfo } from "./templates";
5
+ export { listModules, addModule } from "./add";
6
+ export type { ModuleManifest, AddOptions, AddResult } from "./add";
7
+ export { status, diff, doctor, SUPPORTED_FRAMEWORKS } from "./inspect";
8
+ export type { StatusReport, DoctorFinding } from "./inspect";
9
+ export { planUpdate, summarize } from "./update";
10
+ export type { UpdatePlan, FileChange } from "./update";
11
+ /** Absolute path to the templates bundled in this installed package. */
12
+ export declare function builtinTemplatesDir(): string;
13
+ /** Absolute path to the modules bundled in this installed package. */
14
+ export declare function builtinModulesDir(): string;
package/dist/lib.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarize = exports.planUpdate = exports.SUPPORTED_FRAMEWORKS = exports.doctor = exports.diff = exports.status = exports.addModule = exports.listModules = exports.isKnownTemplate = exports.TEMPLATE_NAMES = exports.DEFAULT_TEMPLATE = exports.TEMPLATES = exports.create = void 0;
4
+ exports.builtinTemplatesDir = builtinTemplatesDir;
5
+ exports.builtinModulesDir = builtinModulesDir;
6
+ // Library entry point: the reusable functions behind the `podo` CLI, for other
7
+ // packages (e.g. @podosoft/podokit-mcp) to call without spawning the binary.
8
+ const node_path_1 = require("node:path");
9
+ var create_1 = require("./create");
10
+ Object.defineProperty(exports, "create", { enumerable: true, get: function () { return create_1.create; } });
11
+ var templates_1 = require("./templates");
12
+ Object.defineProperty(exports, "TEMPLATES", { enumerable: true, get: function () { return templates_1.TEMPLATES; } });
13
+ Object.defineProperty(exports, "DEFAULT_TEMPLATE", { enumerable: true, get: function () { return templates_1.DEFAULT_TEMPLATE; } });
14
+ Object.defineProperty(exports, "TEMPLATE_NAMES", { enumerable: true, get: function () { return templates_1.TEMPLATE_NAMES; } });
15
+ Object.defineProperty(exports, "isKnownTemplate", { enumerable: true, get: function () { return templates_1.isKnownTemplate; } });
16
+ var add_1 = require("./add");
17
+ Object.defineProperty(exports, "listModules", { enumerable: true, get: function () { return add_1.listModules; } });
18
+ Object.defineProperty(exports, "addModule", { enumerable: true, get: function () { return add_1.addModule; } });
19
+ var inspect_1 = require("./inspect");
20
+ Object.defineProperty(exports, "status", { enumerable: true, get: function () { return inspect_1.status; } });
21
+ Object.defineProperty(exports, "diff", { enumerable: true, get: function () { return inspect_1.diff; } });
22
+ Object.defineProperty(exports, "doctor", { enumerable: true, get: function () { return inspect_1.doctor; } });
23
+ Object.defineProperty(exports, "SUPPORTED_FRAMEWORKS", { enumerable: true, get: function () { return inspect_1.SUPPORTED_FRAMEWORKS; } });
24
+ var update_1 = require("./update");
25
+ Object.defineProperty(exports, "planUpdate", { enumerable: true, get: function () { return update_1.planUpdate; } });
26
+ Object.defineProperty(exports, "summarize", { enumerable: true, get: function () { return update_1.summarize; } });
27
+ /** Absolute path to the templates bundled in this installed package. */
28
+ function builtinTemplatesDir() {
29
+ return (0, node_path_1.join)(__dirname, "templates");
30
+ }
31
+ /** Absolute path to the modules bundled in this installed package. */
32
+ function builtinModulesDir() {
33
+ return (0, node_path_1.join)(__dirname, "templates", "modules");
34
+ }
@@ -72,9 +72,14 @@ export interface InitLockOptions {
72
72
  }
73
73
  /** Write the initial `.podokit/` lockfile for a freshly created project. */
74
74
  export declare function initLockfile(projectRoot: string, options: InitLockOptions): void;
75
+ /** Union two glob lists, preserving order and dropping duplicates. Used to fold
76
+ * module-declared and ejected owned paths into the project's `ownedGlobs`, which
77
+ * is the durable source of truth `computeFilesLock` re-reads on every recompute. */
78
+ export declare function mergeOwnedGlobs(existing: string[], add: string[]): string[];
75
79
  /**
76
80
  * Record applied modules in the manifest and recompute the files.lock. Modules
77
81
  * already present are left in place (idempotent); the lock always reflects disk.
78
- * No-op on projects without a manifest (pre-lockfile projects; see `podo adopt`).
82
+ * `ownedGlobs` a module declares are merged into the manifest so its owned paths
83
+ * survive every future recompute. No-op on projects without a manifest.
79
84
  */
80
- export declare function recordModules(projectRoot: string, moduleNames: string[], version?: string): void;
85
+ export declare function recordModules(projectRoot: string, moduleNames: string[], version?: string, ownedGlobs?: string[]): void;
package/dist/lockfile.js CHANGED
@@ -14,6 +14,7 @@ exports.computeFilesLock = computeFilesLock;
14
14
  exports.readFilesLock = readFilesLock;
15
15
  exports.computeDrift = computeDrift;
16
16
  exports.initLockfile = initLockfile;
17
+ exports.mergeOwnedGlobs = mergeOwnedGlobs;
17
18
  exports.recordModules = recordModules;
18
19
  const node_fs_1 = require("node:fs");
19
20
  const node_path_1 = require("node:path");
@@ -44,6 +45,23 @@ const WALK_IGNORE = new Set([
44
45
  exports.DEFAULT_OWNED_GLOBS = [
45
46
  "apps/web/src/routes/**",
46
47
  "apps/web/src/lib/components/ui/**",
48
+ // Owned Nest DI extension slot — override providers here; update never touches it.
49
+ "apps/api/src/app.extensions.ts",
50
+ // AI agent guidance — yours to customize; update never touches it.
51
+ "AGENTS.md",
52
+ "CLAUDE.md",
53
+ ".claude/**",
54
+ ".cursor/**",
55
+ ".github/copilot-instructions.md",
56
+ ".mcp.json",
57
+ // Containerized dev-environment scaffolding: users tweak ports/services, so
58
+ // updates never overwrite these.
59
+ "compose.dev.yaml",
60
+ "Dockerfile.dev",
61
+ ".dockerignore",
62
+ ".devcontainer/**",
63
+ ".env.docker",
64
+ "infra/traefik/**",
47
65
  ];
48
66
  const TEXT_EXT = /\.(json|md|ts|tsx|js|mjs|cjs|css|html|yml|yaml|txt|env|svelte|gitignore|example)$/i;
49
67
  function podokitDir(projectRoot) {
@@ -171,12 +189,27 @@ function initLockfile(projectRoot, options) {
171
189
  writeManifest(projectRoot, manifest);
172
190
  writeFilesLock(projectRoot, computeFilesLock(projectRoot, ownedGlobs));
173
191
  }
192
+ /** Union two glob lists, preserving order and dropping duplicates. Used to fold
193
+ * module-declared and ejected owned paths into the project's `ownedGlobs`, which
194
+ * is the durable source of truth `computeFilesLock` re-reads on every recompute. */
195
+ function mergeOwnedGlobs(existing, add) {
196
+ const seen = new Set(existing);
197
+ const out = [...existing];
198
+ for (const glob of add) {
199
+ if (seen.has(glob))
200
+ continue;
201
+ seen.add(glob);
202
+ out.push(glob);
203
+ }
204
+ return out;
205
+ }
174
206
  /**
175
207
  * Record applied modules in the manifest and recompute the files.lock. Modules
176
208
  * already present are left in place (idempotent); the lock always reflects disk.
177
- * No-op on projects without a manifest (pre-lockfile projects; see `podo adopt`).
209
+ * `ownedGlobs` a module declares are merged into the manifest so its owned paths
210
+ * survive every future recompute. No-op on projects without a manifest.
178
211
  */
179
- function recordModules(projectRoot, moduleNames, version) {
212
+ function recordModules(projectRoot, moduleNames, version, ownedGlobs) {
180
213
  const manifest = readManifest(projectRoot);
181
214
  if (!manifest)
182
215
  return;
@@ -188,6 +221,8 @@ function recordModules(projectRoot, moduleNames, version) {
188
221
  manifest.modules.push({ name, order: manifest.modules.length, addedWith: stampedWith });
189
222
  known.add(name);
190
223
  }
224
+ if (ownedGlobs?.length)
225
+ manifest.ownedGlobs = mergeOwnedGlobs(manifest.ownedGlobs, ownedGlobs);
191
226
  writeManifest(projectRoot, manifest);
192
227
  writeFilesLock(projectRoot, computeFilesLock(projectRoot, manifest.ownedGlobs));
193
228
  }
@@ -0,0 +1,30 @@
1
+ # AGENTS.md — {{projectName}}
2
+
3
+ Guidance for AI coding agents (Claude Code, Codex, Cursor, Copilot, …). Generated
4
+ by [PodoKit](https://github.com/podosoft-dev/podokit).
5
+
6
+ ## Project overview
7
+
8
+ A minimal npm-workspace starter. Build it up from here — add features with the
9
+ `podo` CLI (`podo add <module>`; run `podo add` with no argument to list them).
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ npm install
15
+ npm run dev # runs workspace dev scripts
16
+ npm run build
17
+ npm run lint # type-check
18
+ npm test
19
+ ```
20
+
21
+ ## Code style
22
+
23
+ - TypeScript `strict`. **No `any`** (use `unknown` + narrowing), no `@ts-ignore`.
24
+ - Explicit function return types. Conventional Commits, imperative mood, no emojis.
25
+
26
+ ## PodoKit tooling
27
+
28
+ This project is managed by the `podo` CLI; `.podokit/` records how it was
29
+ assembled (do not edit by hand). Use `podo status`/`podo diff` to see your local
30
+ edits and `podo update` to pull in improvements without losing your work.
@@ -0,0 +1,6 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this project lives in [AGENTS.md](./AGENTS.md). It is imported
4
+ below so Claude Code loads the same rules the other AI tools read.
5
+
6
+ @AGENTS.md
@@ -0,0 +1,79 @@
1
+ # AGENTS.md — {{projectName}}
2
+
3
+ Guidance for AI coding agents (Claude Code, Codex, Cursor, Copilot, …) working in
4
+ this repository. It was generated by [PodoKit](https://github.com/podosoft-dev/podokit).
5
+ Follow it so your changes match the project's conventions.
6
+
7
+ ## Project overview
8
+
9
+ A full-stack TypeScript monorepo (npm workspaces):
10
+
11
+ - `apps/api` — **NestJS** backend (`{{projectName}}-api`), TypeORM + PostgreSQL, Swagger at `/api-docs`.
12
+ - `apps/web` — **SvelteKit** frontend (`{{projectName}}-web`), Tailwind v4 + shadcn-svelte + typesafe-i18n.
13
+ - `infra/` — Docker Compose (PostgreSQL/Redis) and example k3s manifests.
14
+ - `.podokit/` — PodoKit's generation lockfile (see "PodoKit tooling" below). Do not edit by hand.
15
+
16
+ ## Setup & commands
17
+
18
+ ```bash
19
+ npm install
20
+ cp .env.example .env
21
+ docker compose -f infra/docker/docker-compose.yml up -d # PostgreSQL (+ Redis)
22
+ npm run dev # api on http://localhost:5002, web on http://localhost:5001
23
+ ```
24
+
25
+ - **Ports: web = 5001, api = 5002.** The web app reaches the API only through its
26
+ SvelteKit server proxy (`/api/*`), never with a direct browser `fetch`.
27
+ - Build: `npm run build` · Type-check/lint: `npm run lint` · Unit tests: `npm test`
28
+ - Per workspace: `npm run build -w {{projectName}}-api` / `-w {{projectName}}-web`.
29
+ - DB migrations (TypeORM): `npm run migration:run -w {{projectName}}-api`.
30
+ - e2e (Playwright, ships in `tests/`): `npm run test:e2e`.
31
+
32
+ **Always** verify a change by building the affected workspace (and `svelte-check`
33
+ for web) before considering it done. UI changes need a Playwright test; API
34
+ changes need an e2e/unit test.
35
+
36
+ ## Code style
37
+
38
+ ### TypeScript (both apps)
39
+ - `strict` mode. **No `any`** — use `unknown` + narrowing. **No `@ts-ignore`/`@ts-expect-error`** and no `eslint-disable`.
40
+ - Explicit function return types. DTOs use `class-validator` decorators.
41
+ - Keep `noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noUncheckedIndexedAccess`.
42
+
43
+ ### Svelte 5 (web)
44
+ - **Runes only**: `$state()`, `$derived()`, `$effect()`, `$props()`. **Never** use `$:` (deprecated).
45
+ - Reactive stores go in `.svelte.ts` files; don't destructure a getter-exported rune (it loses reactivity) — wrap in `$derived`.
46
+
47
+ ### UI — Tailwind + shadcn-svelte (web)
48
+ - Prefer **shadcn-svelte components** over raw HTML (`<button>`, `<input>`, `<select>`, …).
49
+ - **Do not edit `src/lib/components/ui/**`** (the shadcn primitives). Customize in a separate wrapper.
50
+ - Tailwind v4 (CSS-first, no `tailwind.config`). Use semantic classes: `bg-background`, `text-foreground`, `border-border`. Dark mode via `mode-watcher`. Responsive (`sm:`/`md:`/`lg:`).
51
+
52
+ ### i18n & errors
53
+ - **Every user-facing string goes through i18n** (`src/lib/i18n/messages.ts`) — no hardcoded text. Add keys to **all** locales at once.
54
+ - The backend returns a stable **error envelope**: `{ success: false, error: { code, message, statusCode, path, timestamp, details? } }`. Throw `AppException("SOME_CODE", ...)` on the backend; **branch on `error.code`, not the message,** on the frontend.
55
+ - Logging is structured; no emojis/icons. Start messages with an action verb.
56
+
57
+ ## PodoKit tooling & file ownership
58
+
59
+ This project is managed by the `podo` CLI. Files have an ownership tier recorded
60
+ in `.podokit/files.lock` — respect it:
61
+
62
+ - **owned** (your code: `apps/web/src/routes/**`, your components/services, `apps/api/src/app.extensions.ts`, and this `AGENTS.md`) — edit freely. To add or override Nest providers/modules, use `app.extensions.ts` (spread into `AppModule` after the module-wired providers, so a same-token override wins) rather than editing `app.module.ts`.
63
+ - **assembled** (e.g. `apps/api/src/app.module.ts`, `auth.ts`) — contain `// podokit:begin:… / …:end` fenced regions that the toolkit recomputes. Edit outside the fences only; **never hand-edit inside the fences**.
64
+ - **managed** (bootstrap/config like `main.ts`) — the toolkit may update these; avoid gratuitous edits.
65
+
66
+ Useful commands:
67
+
68
+ - `podo add <module>` — add a feature (auth, admin-dashboard, redis, bullmq, …). It wires itself into the app; run `podo add` with no argument to list modules.
69
+ - `podo status` / `podo diff` — see your local edits vs. what PodoKit generated.
70
+ - `podo update` — pull in template/module improvements (3-way merges your edits; never touches owned files).
71
+
72
+ ## Commit conventions
73
+
74
+ Conventional Commits (`feat|fix|docs|refactor|test|chore`), imperative mood,
75
+ ≤50-char subject, no trailing period, no emojis. One logical change per commit.
76
+
77
+ <!-- podokit:begin:agents-modules -->
78
+ <!-- Module-specific guidance is injected here by `podo add`. -->
79
+ <!-- podokit:end:agents-modules -->
@@ -0,0 +1,6 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this project lives in [AGENTS.md](./AGENTS.md). It is imported
4
+ below so Claude Code loads the same rules the other AI tools read.
5
+
6
+ @AGENTS.md
@@ -0,0 +1,25 @@
1
+ # Development image for the PodoKit monorepo (web + api).
2
+ # Dependencies and source are baked in; `docker compose watch` syncs your
3
+ # edits into the running container so the native file watchers fire.
4
+ FROM node:22-bookworm-slim
5
+
6
+ # git for the toolchain (editors, AI agents); build tools for native modules.
7
+ RUN apt-get update \
8
+ && apt-get install -y --no-install-recommends git python3 make g++ ca-certificates \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ WORKDIR /app
12
+
13
+ # Install workspace dependencies (cached unless a package.json changes).
14
+ # NB: the host package-lock.json is intentionally NOT copied — a lockfile
15
+ # generated on macOS/Windows prunes platform-specific optional deps and can drop
16
+ # packages (e.g. @swc/cli) on linux. Resolving fresh in-container avoids that.
17
+ COPY package.json ./
18
+ COPY apps/api/package.json ./apps/api/package.json
19
+ COPY apps/web/package.json ./apps/web/package.json
20
+ RUN npm install
21
+
22
+ # Baked source (kept in sync during development by `docker compose watch`).
23
+ COPY . .
24
+
25
+ EXPOSE 5001 5002
@@ -3,7 +3,7 @@
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "nest start --watch",
6
+ "dev": "nest start --watch --builder swc",
7
7
  "build": "nest build",
8
8
  "start": "node dist/main",
9
9
  "lint": "tsc -p tsconfig.json --noEmit",
@@ -40,7 +40,9 @@
40
40
  "supertest": "^7.0.0",
41
41
  "ts-jest": "^29.2.5",
42
42
  "ts-node": "^10.9.2",
43
- "typescript": "^6.0.3"
43
+ "typescript": "^6.0.3",
44
+ "@swc/core": "^1.7.0",
45
+ "@swc/cli": "^0.4.0"
44
46
  },
45
47
  "jest": {
46
48
  "moduleFileExtensions": [