@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.
- package/README.md +2 -2
- package/dist/add.d.ts +2 -0
- package/dist/add.js +5 -1
- package/dist/assemble.d.ts +14 -0
- package/dist/assemble.js +60 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.js +7 -0
- package/dist/eject.d.ts +11 -0
- package/dist/eject.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +122 -1
- package/dist/inspect.d.ts +43 -0
- package/dist/inspect.js +82 -0
- package/dist/lockfile.d.ts +80 -0
- package/dist/lockfile.js +193 -0
- package/dist/templates/base/dot-env.example +11 -6
- package/dist/templates/fullstack-nest-svelte/README.md +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +1 -0
- package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +6 -3
- package/dist/templates/fullstack-nest-svelte/apps/api/src/common/app-exception.ts +4 -12
- package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +1 -1
- package/dist/templates/fullstack-nest-svelte/dot-env.example +10 -5
- package/dist/templates/fullstack-nest-svelte/infra/docker/docker-compose.yml +37 -0
- package/dist/templates/fullstack-nest-svelte/infra/docker/sms-sink.mjs +40 -0
- package/dist/templates/fullstack-nest-svelte/tests/README.md +1 -1
- package/dist/templates/fullstack-nest-svelte/tests/package.json +2 -1
- package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +3 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +7 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +149 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +21 -22
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +93 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +224 -8
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +19 -3
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/accept-invitation/[id]/+page.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +21 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +360 -49
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +2 -4
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +82 -37
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.server.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.svelte +264 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +3 -4
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +79 -45
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.server.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +322 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +3 -5
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +254 -108
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +8 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.server.ts +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +127 -5
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +18 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/verify-email/+page.svelte +63 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/oauth2/consent/+page.svelte +54 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +51 -2
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +80 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +264 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +120 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +10 -10
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +2 -2
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +49 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/totp.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +12 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +77 -6
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +32 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +60 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +42 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +1 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +57 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +52 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +36 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +9 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +32 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +28 -12
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +35 -3
- package/dist/templates/modules/admin-dashboard/module.manifest.json +13 -7
- package/dist/templates/modules/api-key-auth/module.manifest.json +3 -3
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-enabled.ts +16 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +4 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +2 -0
- package/dist/templates/modules/audit-log/module.manifest.json +15 -9
- package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +78 -13
- package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +2 -1
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +73 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +197 -52
- package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +12 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +56 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/org-permissions.ts +22 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/permissions.ts +23 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.controller.ts +33 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.entity.ts +26 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.module.ts +12 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +118 -0
- package/dist/templates/modules/auth/files/apps/api/src/mail/mailer.ts +65 -0
- package/dist/templates/modules/auth/files/apps/api/src/migrations/1720400000000-InitAppSettings.ts +38 -0
- package/dist/templates/modules/auth/files/apps/api/src/migrations/1720500000000-InitAuthConfig.ts +27 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/app-setting.entity.ts +16 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +25 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.module.ts +11 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.ts +57 -0
- package/dist/templates/modules/auth/files/apps/api/src/sms/sms.ts +23 -0
- package/dist/templates/modules/auth/module.manifest.json +54 -20
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +6 -3
- package/dist/templates/modules/bullmq/module.manifest.json +4 -4
- package/dist/templates/modules/file-upload/module.manifest.json +3 -3
- package/dist/templates/modules/job-progress/module.manifest.json +9 -9
- package/dist/templates/modules/logging/module.manifest.json +3 -3
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -3
- package/dist/templates/modules/object-storage-s3/module.manifest.json +5 -5
- package/dist/templates/modules/rate-limit/module.manifest.json +2 -2
- package/dist/templates/modules/redis/module.manifest.json +4 -4
- package/dist/templates/modules/sse/module.manifest.json +4 -4
- package/dist/templates/todo/README.md +2 -2
- package/dist/templates/todo/apps/api/package.json +1 -0
- package/dist/templates/todo/apps/api/src/app.module.ts +6 -3
- package/dist/templates/todo/apps/api/src/common/app-exception.ts +4 -12
- package/dist/templates/todo/apps/api/src/config/env.validation.ts +1 -1
- package/dist/templates/todo/apps/api/src/main.ts +1 -1
- package/dist/templates/todo/apps/web/package.json +2 -2
- package/dist/templates/todo/apps/web/src/lib/components/ui/switch/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +1 -1
- package/dist/templates/todo/dot-env.example +10 -5
- package/dist/templates/todo/infra/docker/docker-compose.yml +4 -0
- package/dist/templates/todo/tests/README.md +1 -1
- package/dist/templates/todo/tests/package.json +2 -1
- package/dist/templates/todo/tests/playwright.config.ts +1 -1
- package/dist/update.d.ts +49 -0
- package/dist/update.js +158 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ cp .env.example .env
|
|
|
14
14
|
npm run dev
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
- API: http://localhost:
|
|
18
|
-
- Web: http://localhost:
|
|
17
|
+
- API: http://localhost:5002 (health at `/health`)
|
|
18
|
+
- Web: http://localhost:5001
|
|
19
19
|
|
|
20
20
|
When run in a terminal, `podo create` lists the templates with descriptions and asks which one (and which package manager) to use. Pass flags (or `--yes`) to skip the prompts.
|
|
21
21
|
|
package/dist/add.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface AddOptions {
|
|
|
19
19
|
projectRoot: string;
|
|
20
20
|
module: string;
|
|
21
21
|
modulesDir: string;
|
|
22
|
+
/** PodoKit version stamped into the lockfile. Defaults to the CLI version. */
|
|
23
|
+
podokitVersion?: string;
|
|
22
24
|
}
|
|
23
25
|
export interface AddResult {
|
|
24
26
|
module: string;
|
package/dist/add.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.addModule = addModule;
|
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
6
|
const node_path_1 = require("node:path");
|
|
7
7
|
const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
|
|
8
|
+
const lockfile_1 = require("./lockfile");
|
|
8
9
|
/** List modules available under `modulesDir` (each has a module.manifest.json). */
|
|
9
10
|
function listModules(modulesDir) {
|
|
10
11
|
if (!(0, node_fs_1.existsSync)(modulesDir))
|
|
@@ -56,7 +57,10 @@ function isApplied(projectRoot, modulesDir, module) {
|
|
|
56
57
|
* and inject wiring at markers. Missing required modules are added first.
|
|
57
58
|
*/
|
|
58
59
|
function addModule(options) {
|
|
59
|
-
|
|
60
|
+
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);
|
|
63
|
+
return result;
|
|
60
64
|
}
|
|
61
65
|
function applyModule(projectRoot, module, modulesDir, applied) {
|
|
62
66
|
const moduleDir = (0, node_path_1.join)(modulesDir, module);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type TemplateVars, type VfsTree } from "@podosoft/podokit-template-engine";
|
|
2
|
+
/**
|
|
3
|
+
* Reconstruct a generated project in memory (no disk writes) from a template
|
|
4
|
+
* plus an ordered module list, mirroring `create` + `add`. `podo update` uses
|
|
5
|
+
* this to build the new-version tree and diff it against the working copy.
|
|
6
|
+
*/
|
|
7
|
+
export interface AssembleOptions {
|
|
8
|
+
templatesDir: string;
|
|
9
|
+
template: string;
|
|
10
|
+
answers: TemplateVars;
|
|
11
|
+
modules: string[];
|
|
12
|
+
}
|
|
13
|
+
/** Assemble the project tree for a template + ordered modules, in memory. */
|
|
14
|
+
export declare function assembleProject(options: AssembleOptions): VfsTree;
|
package/dist/assemble.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assembleProject = assembleProject;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
|
|
7
|
+
function readManifest(moduleDir) {
|
|
8
|
+
return JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(moduleDir, "module.manifest.json"), "utf8"));
|
|
9
|
+
}
|
|
10
|
+
function textOf(tree, path) {
|
|
11
|
+
return String(tree.get(path)?.content ?? "");
|
|
12
|
+
}
|
|
13
|
+
function setText(tree, path, content) {
|
|
14
|
+
tree.set(path, { content, text: true });
|
|
15
|
+
}
|
|
16
|
+
function applyModuleToTree(tree, moduleDir, vars) {
|
|
17
|
+
const manifest = readManifest(moduleDir);
|
|
18
|
+
// 1) overlay files (module files override the base)
|
|
19
|
+
const filesDir = (0, node_path_1.join)(moduleDir, "files");
|
|
20
|
+
if ((0, node_fs_1.existsSync)(filesDir)) {
|
|
21
|
+
for (const [path, file] of (0, podokit_template_engine_1.renderTemplate)(filesDir, vars))
|
|
22
|
+
tree.set(path, file);
|
|
23
|
+
}
|
|
24
|
+
// 2) merge dependencies/scripts into the target app's package.json
|
|
25
|
+
if (manifest.dependencies || manifest.devDependencies || manifest.scripts) {
|
|
26
|
+
const pkgPath = `apps/${manifest.targetApp}/package.json`;
|
|
27
|
+
const base = JSON.parse(textOf(tree, pkgPath) || "{}");
|
|
28
|
+
const overlay = {};
|
|
29
|
+
if (manifest.dependencies)
|
|
30
|
+
overlay.dependencies = manifest.dependencies;
|
|
31
|
+
if (manifest.devDependencies)
|
|
32
|
+
overlay.devDependencies = manifest.devDependencies;
|
|
33
|
+
if (manifest.scripts)
|
|
34
|
+
overlay.scripts = manifest.scripts;
|
|
35
|
+
setText(tree, pkgPath, `${JSON.stringify((0, podokit_template_engine_1.mergePackageJson)(base, overlay), null, 2)}\n`);
|
|
36
|
+
}
|
|
37
|
+
// 3) append env example lines (deduped)
|
|
38
|
+
if (manifest.env?.length) {
|
|
39
|
+
const current = textOf(tree, ".env.example");
|
|
40
|
+
const existing = current.split("\n");
|
|
41
|
+
const missing = manifest.env.filter((line) => !existing.includes(line));
|
|
42
|
+
if (missing.length) {
|
|
43
|
+
const sep = current.endsWith("\n") ? "" : "\n";
|
|
44
|
+
setText(tree, ".env.example", `${current}${sep}\n${missing.join("\n")}\n`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// 4) inject wiring at markers
|
|
48
|
+
for (const injection of manifest.inject ?? []) {
|
|
49
|
+
const target = injection.file;
|
|
50
|
+
setText(tree, target, (0, podokit_template_engine_1.insertAtMarker)(textOf(tree, target), injection.marker, injection.text));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Assemble the project tree for a template + ordered modules, in memory. */
|
|
54
|
+
function assembleProject(options) {
|
|
55
|
+
const tree = (0, podokit_template_engine_1.renderTemplate)((0, node_path_1.join)(options.templatesDir, options.template), options.answers);
|
|
56
|
+
for (const mod of options.modules) {
|
|
57
|
+
applyModuleToTree(tree, (0, node_path_1.join)(options.templatesDir, "modules", mod), options.answers);
|
|
58
|
+
}
|
|
59
|
+
return tree;
|
|
60
|
+
}
|
package/dist/create.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface CreateOptions {
|
|
|
12
12
|
targetDir?: string;
|
|
13
13
|
/** Package manager recorded in the generated project. Defaults to `npm`. */
|
|
14
14
|
packageManager?: PackageManager;
|
|
15
|
+
/** PodoKit version stamped into the lockfile. Defaults to the CLI version. */
|
|
16
|
+
podokitVersion?: string;
|
|
15
17
|
}
|
|
16
18
|
export interface CreateResult {
|
|
17
19
|
projectDir: string;
|
package/dist/create.js
CHANGED
|
@@ -8,6 +8,7 @@ const node_path_1 = require("node:path");
|
|
|
8
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
|
+
const lockfile_1 = require("./lockfile");
|
|
11
12
|
const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-._]*[a-z0-9])?$/i;
|
|
12
13
|
/** Validate a project name: no path separators, npm-friendly characters. */
|
|
13
14
|
function assertValidName(name) {
|
|
@@ -41,5 +42,11 @@ function create(options) {
|
|
|
41
42
|
}
|
|
42
43
|
const vars = { projectName: name, packageManager };
|
|
43
44
|
(0, podokit_template_engine_1.copyTemplate)(templateDir, projectDir, vars);
|
|
45
|
+
(0, lockfile_1.initLockfile)(projectDir, {
|
|
46
|
+
template,
|
|
47
|
+
packageManager,
|
|
48
|
+
answers: { projectName: name, packageManager },
|
|
49
|
+
version: options.podokitVersion,
|
|
50
|
+
});
|
|
44
51
|
return { projectDir, packageManager, template };
|
|
45
52
|
}
|
package/dist/eject.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Take ownership of PodoKit-managed files: flip their lock tier to `owned` so
|
|
3
|
+
* `podo update` skips them from then on (they show up only in `podo diff`).
|
|
4
|
+
* File-level and reversible by re-recording, unlike CRA's one-way eject.
|
|
5
|
+
*/
|
|
6
|
+
export interface EjectResult {
|
|
7
|
+
ejected: string[];
|
|
8
|
+
/** Paths not tracked in the lock (nothing to eject). */
|
|
9
|
+
unknown: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function eject(projectRoot: string, paths: string[]): EjectResult;
|
package/dist/eject.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.eject = eject;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const lockfile_1 = require("./lockfile");
|
|
7
|
+
const inspect_1 = require("./inspect");
|
|
8
|
+
function eject(projectRoot, paths) {
|
|
9
|
+
const lock = (0, lockfile_1.readFilesLock)(projectRoot);
|
|
10
|
+
if (!lock)
|
|
11
|
+
throw new inspect_1.NotAProjectError();
|
|
12
|
+
const result = { ejected: [], unknown: [] };
|
|
13
|
+
for (const path of paths) {
|
|
14
|
+
const entry = lock.files[path];
|
|
15
|
+
if (!entry || !(0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, path))) {
|
|
16
|
+
result.unknown.push(path);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (entry.tier !== "owned") {
|
|
20
|
+
entry.tier = "owned";
|
|
21
|
+
result.ejected.push(path);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (result.ejected.length)
|
|
25
|
+
(0, lockfile_1.writeFilesLock)(projectRoot, lock);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -8,11 +8,19 @@ const create_1 = require("./create");
|
|
|
8
8
|
const prompt_1 = require("./prompt");
|
|
9
9
|
const templates_1 = require("./templates");
|
|
10
10
|
const add_1 = require("./add");
|
|
11
|
+
const inspect_1 = require("./inspect");
|
|
12
|
+
const update_1 = require("./update");
|
|
13
|
+
const eject_1 = require("./eject");
|
|
11
14
|
const HELP = `podo — PodoKit project generator
|
|
12
15
|
|
|
13
16
|
Usage:
|
|
14
17
|
podo create <name> [options]
|
|
15
18
|
podo add <module>
|
|
19
|
+
podo status Show version, modules, file tiers, and local edits
|
|
20
|
+
podo diff List PodoKit-managed files you have edited
|
|
21
|
+
podo doctor Check framework versions against supported ranges
|
|
22
|
+
podo update [--apply] Preview (or apply) what a version update would change
|
|
23
|
+
podo eject <path...> Take ownership of managed files (update skips them)
|
|
16
24
|
|
|
17
25
|
Options:
|
|
18
26
|
--template <t> Template to scaffold (see below)
|
|
@@ -30,7 +38,7 @@ Example:
|
|
|
30
38
|
cd my-app && npx @podosoft/podokit add auth
|
|
31
39
|
`;
|
|
32
40
|
function parseArgs(argv) {
|
|
33
|
-
const parsed = { help: false, yes: false };
|
|
41
|
+
const parsed = { help: false, yes: false, apply: false };
|
|
34
42
|
const positionals = [];
|
|
35
43
|
for (let i = 0; i < argv.length; i += 1) {
|
|
36
44
|
const arg = argv[i];
|
|
@@ -40,12 +48,18 @@ function parseArgs(argv) {
|
|
|
40
48
|
else if (arg === "-y" || arg === "--yes") {
|
|
41
49
|
parsed.yes = true;
|
|
42
50
|
}
|
|
51
|
+
else if (arg === "--apply") {
|
|
52
|
+
parsed.apply = true;
|
|
53
|
+
}
|
|
43
54
|
else if (arg === "--template") {
|
|
44
55
|
parsed.template = argv[++i];
|
|
45
56
|
}
|
|
46
57
|
else if (arg === "--dir") {
|
|
47
58
|
parsed.dir = argv[++i];
|
|
48
59
|
}
|
|
60
|
+
else if (arg === "--from") {
|
|
61
|
+
parsed.from = argv[++i];
|
|
62
|
+
}
|
|
49
63
|
else if (arg === "--pm") {
|
|
50
64
|
parsed.pm = argv[++i];
|
|
51
65
|
}
|
|
@@ -93,6 +107,113 @@ async function main(argv) {
|
|
|
93
107
|
}
|
|
94
108
|
return;
|
|
95
109
|
}
|
|
110
|
+
if (args.command === "status") {
|
|
111
|
+
try {
|
|
112
|
+
const s = (0, inspect_1.status)(process.cwd());
|
|
113
|
+
const tiers = `managed ${s.tiers.managed}, assembled ${s.tiers.assembled}, owned ${s.tiers.owned}`;
|
|
114
|
+
process.stdout.write(`PodoKit ${s.podokitVersion} (template: ${s.template}, ${s.packageManager})\n` +
|
|
115
|
+
`Modules: ${s.modules.length ? s.modules.join(", ") : "(none)"}\n` +
|
|
116
|
+
`Files: ${tiers}\n` +
|
|
117
|
+
`Edited: ${s.drifted.length} managed file(s)${s.missing.length ? `, ${s.missing.length} missing` : ""}\n` +
|
|
118
|
+
(s.drifted.length ? s.drifted.map((f) => ` ~ ${f}`).join("\n") + "\n" : ""));
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
fail(err.message);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (args.command === "diff") {
|
|
126
|
+
try {
|
|
127
|
+
const { drifted, missing } = (0, inspect_1.diff)(process.cwd());
|
|
128
|
+
if (!drifted.length && !missing.length) {
|
|
129
|
+
process.stdout.write("No local edits to PodoKit-managed files.\n");
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
process.stdout.write([...drifted.map((f) => `edited ${f}`), ...missing.map((f) => `missing ${f}`)].join("\n") + "\n");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
fail(err.message);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (args.command === "doctor") {
|
|
141
|
+
try {
|
|
142
|
+
const findings = (0, inspect_1.doctor)(process.cwd());
|
|
143
|
+
if (!findings.length) {
|
|
144
|
+
process.stdout.write("No known frameworks found to check.\n");
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
for (const f of findings) {
|
|
148
|
+
const mark = f.ok ? "ok " : "WARN";
|
|
149
|
+
process.stdout.write(`${mark} ${f.package} ${f.installed} (supported: ${f.supported})\n`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (findings.some((f) => !f.ok)) {
|
|
153
|
+
process.stdout.write("\nSome frameworks are outside the supported range; @podosoft/* extensions may not match.\n");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
fail(err.message);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (args.command === "update") {
|
|
162
|
+
const templatesDir = (0, node_path_1.join)(__dirname, "templates");
|
|
163
|
+
try {
|
|
164
|
+
if (args.apply) {
|
|
165
|
+
const result = (0, update_1.applyUpdate)(process.cwd(), templatesDir, { oldTemplatesDir: args.from });
|
|
166
|
+
process.stdout.write(`Applied: ${result.written.length} written, ${result.removed.length} removed, ` +
|
|
167
|
+
`${result.merged.length} merged, ${result.conflicts.length} conflict.\n`);
|
|
168
|
+
if (result.conflicts.length) {
|
|
169
|
+
process.stdout.write("\nResolve the following, then commit:\n" +
|
|
170
|
+
result.conflicts.map((f) => ` ${f}`).join("\n") +
|
|
171
|
+
"\n");
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const plan = (0, update_1.planUpdate)(process.cwd(), templatesDir);
|
|
176
|
+
const counts = (0, update_1.summarize)(plan);
|
|
177
|
+
const shown = plan.changes.filter((c) => c.action !== "up-to-date" && c.action !== "skip");
|
|
178
|
+
process.stdout.write(`podo update ${plan.fromVersion} -> ${plan.toVersion} (template: ${plan.template}; modules: ${plan.modules.join(", ") || "none"})\n\n`);
|
|
179
|
+
if (!shown.length) {
|
|
180
|
+
process.stdout.write("Everything is up to date.\n");
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
for (const c of shown) {
|
|
184
|
+
process.stdout.write(` ${c.action.padEnd(9)} ${c.path} (${c.note})\n`);
|
|
185
|
+
}
|
|
186
|
+
process.stdout.write(`\n${counts.update} update, ${counts.add} add, ${counts.remove} remove, ${counts.conflict} conflict. ` +
|
|
187
|
+
`Dry-run — nothing was written. Re-run with --apply to write (use --from <dir> for a 3-way merge).\n`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
fail(err.message);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (args.command === "eject") {
|
|
196
|
+
const targets = argv.filter((a) => !a.startsWith("-")).slice(1);
|
|
197
|
+
if (!targets.length) {
|
|
198
|
+
fail("Usage: podo eject <path...>");
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const result = (0, eject_1.eject)(process.cwd(), targets);
|
|
202
|
+
if (result.ejected.length) {
|
|
203
|
+
process.stdout.write(`Ejected (now owned): ${result.ejected.join(", ")}\n`);
|
|
204
|
+
}
|
|
205
|
+
if (result.unknown.length) {
|
|
206
|
+
process.stdout.write(`Not tracked, skipped: ${result.unknown.join(", ")}\n`);
|
|
207
|
+
}
|
|
208
|
+
if (!result.ejected.length && !result.unknown.length) {
|
|
209
|
+
process.stdout.write("Nothing to eject (already owned).\n");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
fail(err.message);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
96
217
|
if (args.command !== "create") {
|
|
97
218
|
fail(`Unknown command "${args.command}". Run "podo --help".`);
|
|
98
219
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Tier } from "./lockfile";
|
|
2
|
+
/**
|
|
3
|
+
* Read-only project inspection for `podo status`, `podo diff`, and
|
|
4
|
+
* `podo doctor`. None of these write to disk.
|
|
5
|
+
*/
|
|
6
|
+
export interface StatusReport {
|
|
7
|
+
podokitVersion: string;
|
|
8
|
+
template: string;
|
|
9
|
+
packageManager: string;
|
|
10
|
+
modules: string[];
|
|
11
|
+
tiers: Record<Tier, number>;
|
|
12
|
+
drifted: string[];
|
|
13
|
+
missing: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare class NotAProjectError extends Error {
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
export declare function status(projectRoot: string): StatusReport;
|
|
19
|
+
/** Managed/assembled files the user has edited or deleted since generation. */
|
|
20
|
+
export declare function diff(projectRoot: string): {
|
|
21
|
+
drifted: string[];
|
|
22
|
+
missing: string[];
|
|
23
|
+
};
|
|
24
|
+
/** Framework version-compatibility check. See ADR-0008 (4-layer defense). */
|
|
25
|
+
export interface DoctorFinding {
|
|
26
|
+
package: string;
|
|
27
|
+
installed: string;
|
|
28
|
+
supported: string;
|
|
29
|
+
ok: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Known-good framework ranges for the current PodoKit line. A generated app may
|
|
33
|
+
* bump these itself; doctor warns when it moves outside the supported range so
|
|
34
|
+
* `@podosoft/*` extensions still match.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SUPPORTED_FRAMEWORKS: Record<string, {
|
|
37
|
+
app: "api" | "web";
|
|
38
|
+
range: string;
|
|
39
|
+
majors: number[];
|
|
40
|
+
}>;
|
|
41
|
+
/** Parse the leading major from a semver range like `^11.1.0` or `>=1.6.23 <1.7`. */
|
|
42
|
+
export declare function leadingMajor(range: string): number | null;
|
|
43
|
+
export declare function doctor(projectRoot: string): DoctorFinding[];
|
package/dist/inspect.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUPPORTED_FRAMEWORKS = exports.NotAProjectError = void 0;
|
|
4
|
+
exports.status = status;
|
|
5
|
+
exports.diff = diff;
|
|
6
|
+
exports.leadingMajor = leadingMajor;
|
|
7
|
+
exports.doctor = doctor;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const lockfile_1 = require("./lockfile");
|
|
11
|
+
class NotAProjectError extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super("No .podokit/manifest.json found. Run inside a project created by `podo create`.");
|
|
14
|
+
this.name = "NotAProjectError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.NotAProjectError = NotAProjectError;
|
|
18
|
+
function status(projectRoot) {
|
|
19
|
+
const manifest = (0, lockfile_1.readManifest)(projectRoot);
|
|
20
|
+
const lock = (0, lockfile_1.readFilesLock)(projectRoot);
|
|
21
|
+
if (!manifest || !lock)
|
|
22
|
+
throw new NotAProjectError();
|
|
23
|
+
const tiers = { managed: 0, assembled: 0, owned: 0 };
|
|
24
|
+
for (const entry of Object.values(lock.files))
|
|
25
|
+
tiers[entry.tier] += 1;
|
|
26
|
+
const { drifted, missing } = (0, lockfile_1.computeDrift)(projectRoot);
|
|
27
|
+
return {
|
|
28
|
+
podokitVersion: manifest.podokitVersion,
|
|
29
|
+
template: manifest.template,
|
|
30
|
+
packageManager: manifest.packageManager,
|
|
31
|
+
modules: manifest.modules.map((m) => m.name),
|
|
32
|
+
tiers,
|
|
33
|
+
drifted,
|
|
34
|
+
missing,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Managed/assembled files the user has edited or deleted since generation. */
|
|
38
|
+
function diff(projectRoot) {
|
|
39
|
+
if (!(0, lockfile_1.readManifest)(projectRoot))
|
|
40
|
+
throw new NotAProjectError();
|
|
41
|
+
return (0, lockfile_1.computeDrift)(projectRoot);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Known-good framework ranges for the current PodoKit line. A generated app may
|
|
45
|
+
* bump these itself; doctor warns when it moves outside the supported range so
|
|
46
|
+
* `@podosoft/*` extensions still match.
|
|
47
|
+
*/
|
|
48
|
+
exports.SUPPORTED_FRAMEWORKS = {
|
|
49
|
+
"@nestjs/core": { app: "api", range: "^11", majors: [11] },
|
|
50
|
+
"better-auth": { app: "api", range: ">=1.6.23 <1.7", majors: [1] },
|
|
51
|
+
svelte: { app: "web", range: "^5", majors: [5] },
|
|
52
|
+
};
|
|
53
|
+
function declaredVersion(projectRoot, app, pkg) {
|
|
54
|
+
const file = (0, node_path_1.join)(projectRoot, "apps", app, "package.json");
|
|
55
|
+
if (!(0, node_fs_1.existsSync)(file))
|
|
56
|
+
return null;
|
|
57
|
+
const json = JSON.parse((0, node_fs_1.readFileSync)(file, "utf8"));
|
|
58
|
+
return json.dependencies?.[pkg] ?? json.devDependencies?.[pkg] ?? null;
|
|
59
|
+
}
|
|
60
|
+
/** Parse the leading major from a semver range like `^11.1.0` or `>=1.6.23 <1.7`. */
|
|
61
|
+
function leadingMajor(range) {
|
|
62
|
+
const match = range.match(/(\d+)\./);
|
|
63
|
+
return match ? Number(match[1]) : null;
|
|
64
|
+
}
|
|
65
|
+
function doctor(projectRoot) {
|
|
66
|
+
if (!(0, lockfile_1.readManifest)(projectRoot))
|
|
67
|
+
throw new NotAProjectError();
|
|
68
|
+
const findings = [];
|
|
69
|
+
for (const [pkg, spec] of Object.entries(exports.SUPPORTED_FRAMEWORKS)) {
|
|
70
|
+
const installed = declaredVersion(projectRoot, spec.app, pkg);
|
|
71
|
+
if (installed === null)
|
|
72
|
+
continue; // package not in this app
|
|
73
|
+
const major = leadingMajor(installed);
|
|
74
|
+
findings.push({
|
|
75
|
+
package: pkg,
|
|
76
|
+
installed,
|
|
77
|
+
supported: spec.range,
|
|
78
|
+
ok: major !== null && spec.majors.includes(major),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return findings;
|
|
82
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generation lockfile (`.podokit/`) records how a project was assembled so
|
|
3
|
+
* `podo update` can rebuild the same inputs and detect user edits. See
|
|
4
|
+
* ADR-0009 (update mechanism) and ADR-0010 (ownership model).
|
|
5
|
+
*/
|
|
6
|
+
export declare const LOCK_SCHEMA_VERSION = 1;
|
|
7
|
+
/** File ownership tier. See ADR-0010. */
|
|
8
|
+
export type Tier = "managed" | "assembled" | "owned";
|
|
9
|
+
export interface ManifestModule {
|
|
10
|
+
name: string;
|
|
11
|
+
/** Application order; preserves layered injection determinism. */
|
|
12
|
+
order: number;
|
|
13
|
+
/** PodoKit version that applied the module. */
|
|
14
|
+
addedWith: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PodokitManifest {
|
|
17
|
+
schemaVersion: number;
|
|
18
|
+
podokitVersion: string;
|
|
19
|
+
template: string;
|
|
20
|
+
packageManager: string;
|
|
21
|
+
/** Render vars / prompt answers, replayed on update (copier-style). */
|
|
22
|
+
answers: Record<string, string>;
|
|
23
|
+
modules: ManifestModule[];
|
|
24
|
+
/** Globs whose files are user-owned and never touched by update. */
|
|
25
|
+
ownedGlobs: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface FileEntry {
|
|
28
|
+
tier: Tier;
|
|
29
|
+
/** Hash of the exact bytes PodoKit last wrote; user-edit detector. */
|
|
30
|
+
outHash: string;
|
|
31
|
+
}
|
|
32
|
+
export interface FilesLock {
|
|
33
|
+
schemaVersion: number;
|
|
34
|
+
files: Record<string, FileEntry>;
|
|
35
|
+
}
|
|
36
|
+
/** Default user-owned globs seeded into a new project. See ADR-0010. */
|
|
37
|
+
export declare const DEFAULT_OWNED_GLOBS: string[];
|
|
38
|
+
export declare function manifestPath(projectRoot: string): string;
|
|
39
|
+
export declare function filesLockPath(projectRoot: string): string;
|
|
40
|
+
export declare function readManifest(projectRoot: string): PodokitManifest | null;
|
|
41
|
+
export declare function writeManifest(projectRoot: string, manifest: PodokitManifest): void;
|
|
42
|
+
export declare function writeFilesLock(projectRoot: string, lock: FilesLock): void;
|
|
43
|
+
/** Read the PodoKit CLI's own version to stamp into the manifest. */
|
|
44
|
+
export declare function podokitVersion(): string;
|
|
45
|
+
/** Match a POSIX relative path against a simple glob supporting `*` and `**`. */
|
|
46
|
+
export declare function matchGlob(path: string, glob: string): boolean;
|
|
47
|
+
/** Classify a file's ownership tier. See ADR-0010 priority order. */
|
|
48
|
+
export declare function classifyTier(relPath: string, content: string | Buffer, ownedGlobs: string[]): Tier;
|
|
49
|
+
/** List project files as POSIX-relative paths, skipping generated/vendored dirs. */
|
|
50
|
+
export declare function walkFiles(projectRoot: string, prefix?: string): string[];
|
|
51
|
+
/** Recompute the files.lock by hashing and classifying every project file. */
|
|
52
|
+
export declare function computeFilesLock(projectRoot: string, ownedGlobs: string[]): FilesLock;
|
|
53
|
+
export declare function readFilesLock(projectRoot: string): FilesLock | null;
|
|
54
|
+
export interface Drift {
|
|
55
|
+
/** Managed/assembled files whose content differs from what PodoKit wrote. */
|
|
56
|
+
drifted: string[];
|
|
57
|
+
/** Locked files that no longer exist on disk. */
|
|
58
|
+
missing: string[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Compare the current disk against the lock: report managed/assembled files the
|
|
62
|
+
* user has edited (hash mismatch) or deleted. Owned files are never reported —
|
|
63
|
+
* they belong to the user. Returns empty drift when there is no lock.
|
|
64
|
+
*/
|
|
65
|
+
export declare function computeDrift(projectRoot: string): Drift;
|
|
66
|
+
export interface InitLockOptions {
|
|
67
|
+
template: string;
|
|
68
|
+
packageManager: string;
|
|
69
|
+
answers: Record<string, string>;
|
|
70
|
+
version?: string;
|
|
71
|
+
ownedGlobs?: string[];
|
|
72
|
+
}
|
|
73
|
+
/** Write the initial `.podokit/` lockfile for a freshly created project. */
|
|
74
|
+
export declare function initLockfile(projectRoot: string, options: InitLockOptions): void;
|
|
75
|
+
/**
|
|
76
|
+
* Record applied modules in the manifest and recompute the files.lock. Modules
|
|
77
|
+
* 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`).
|
|
79
|
+
*/
|
|
80
|
+
export declare function recordModules(projectRoot: string, moduleNames: string[], version?: string): void;
|