@podosoft/podokit 0.11.2 → 0.12.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 +7 -3
- package/dist/dev.d.ts +26 -0
- package/dist/dev.js +297 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +82 -2
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +7 -1
- package/dist/locale.d.ts +22 -0
- package/dist/locale.js +184 -0
- package/dist/lockfile.js +4 -0
- package/dist/templates/fullstack-nest-svelte/README.md +42 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/README.md +7 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/catalogs/app/en.json +1 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/catalogs/app/ko.json +1 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/locales/en.json +6 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/locales/ko.json +6 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/vite.config.ts +3 -9
- package/dist/templates/fullstack-nest-svelte/compose.dev.yaml +6 -11
- package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-add-locale/SKILL.md +63 -0
- package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-add-locale/agents/openai.yaml +4 -0
- package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-add-locale/references/catalog-format.md +49 -0
- package/dist/templates/fullstack-nest-svelte/dot-env.docker +2 -2
- package/dist/templates/fullstack-nest-svelte/dot-gitignore +3 -0
- package/dist/templates/fullstack-nest-svelte/dot-podokit/dev.json +4 -0
- package/dist/templates/fullstack-nest-svelte/tests/README.md +10 -4
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.controller.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +5 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/auth-errors.ts +3 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/auth-redirect.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/account-menu.svelte +8 -3
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +1 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/en.json +564 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/ko.json +564 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +13 -12
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +90 -722
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/site.svelte.ts +8 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +4 -4
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.svelte +2 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +4 -4
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +8 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/general-settings.svelte +15 -4
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +4 -4
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +32 -35
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +10 -3
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +11 -7
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +78 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +28 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/general-effects.ui.spec.ts +15 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +59 -6
- package/dist/templates/modules/admin-dashboard/files/tests/ui/login-methods.ui.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings-dialogs.ui.spec.ts +5 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/two-factor.ui.spec.ts +2 -2
- package/dist/templates/modules/admin-dashboard/module.manifest.json +4 -0
- package/dist/templates/modules/auth/files/apps/api/scripts/configure-auth.mjs +22 -11
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +6 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +50 -25
- package/dist/templates/modules/auth/files/dot-claude/skills/podokit-configure-auth/SKILL.md +1 -1
- package/dist/templates/modules/auth/files/dot-claude/skills/podokit-configure-auth/references/google.md +44 -0
- package/dist/templates/todo/README.md +42 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/README.md +7 -5
- package/dist/templates/todo/apps/web/src/lib/i18n/catalogs/app/en.json +1 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/catalogs/app/ko.json +1 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/locales/en.json +6 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/locales/ko.json +6 -0
- package/dist/templates/todo/apps/web/vite.config.ts +3 -9
- package/dist/templates/todo/compose.dev.yaml +6 -11
- package/dist/templates/todo/dot-claude/skills/podokit-add-locale/SKILL.md +63 -0
- package/dist/templates/todo/dot-claude/skills/podokit-add-locale/agents/openai.yaml +4 -0
- package/dist/templates/todo/dot-claude/skills/podokit-add-locale/references/catalog-format.md +49 -0
- package/dist/templates/todo/dot-env.docker +2 -2
- package/dist/templates/todo/dot-gitignore +3 -0
- package/dist/templates/todo/dot-podokit/dev.json +4 -0
- package/dist/templates/todo/tests/README.md +10 -4
- package/dist/update.js +23 -5
- package/package.json +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/en.ts +0 -10
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/ko.ts +0 -11
- package/dist/templates/todo/apps/web/src/lib/i18n/en.ts +0 -10
- package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +0 -11
package/README.md
CHANGED
|
@@ -11,11 +11,13 @@ npx @podosoft/podokit create my-app
|
|
|
11
11
|
cd my-app
|
|
12
12
|
npm install
|
|
13
13
|
cp .env.example .env
|
|
14
|
-
|
|
14
|
+
npx @podosoft/podokit dev watch
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Open **http://my-app.localhost**. The first project starts one user-level
|
|
18
|
+
Traefik gateway on loopback port 80; later projects reuse it and route by
|
|
19
|
+
hostname. Stop this project with `npx @podosoft/podokit dev down`. The traditional
|
|
20
|
+
`npm run dev` loop remains available on web port 5001 and API port 5002.
|
|
19
21
|
|
|
20
22
|
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
23
|
|
|
@@ -32,8 +34,10 @@ The `todo` template (`--template todo`) generates a working todo app (SvelteKit
|
|
|
32
34
|
| `podo status` | Show the PodoKit version, modules, file tiers, and local edits |
|
|
33
35
|
| `podo diff` | List the PodoKit-managed files you've edited since generation |
|
|
34
36
|
| `podo doctor` | Check framework versions against the supported ranges |
|
|
37
|
+
| `podo locale <command>` | Add, validate, activate, deactivate, or list JSON locales |
|
|
35
38
|
| `podo update [--apply]` | Preview (or apply) what a version update would change |
|
|
36
39
|
| `podo eject <path…>` | Take ownership of a managed file so updates skip it |
|
|
40
|
+
| `podo dev <action>` | Watch, inspect, execute in, or stop a container stack behind the shared `*.localhost` gateway |
|
|
37
41
|
|
|
38
42
|
## Usage
|
|
39
43
|
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface DevConfig {
|
|
2
|
+
schemaVersion: 1;
|
|
3
|
+
hostname: string;
|
|
4
|
+
publicUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DevRuntime {
|
|
7
|
+
alias: string;
|
|
8
|
+
config: DevConfig;
|
|
9
|
+
projectRoot: string;
|
|
10
|
+
routeId: string;
|
|
11
|
+
runtimeComposePath: string;
|
|
12
|
+
}
|
|
13
|
+
interface CommandResult {
|
|
14
|
+
status: number | null;
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
}
|
|
18
|
+
export type CommandRunner = (command: string, args: string[], options: {
|
|
19
|
+
capture: boolean;
|
|
20
|
+
}) => CommandResult;
|
|
21
|
+
export declare function readDevConfig(projectRoot: string): DevConfig;
|
|
22
|
+
export declare function resolveDevRuntime(projectRoot: string): DevRuntime;
|
|
23
|
+
export declare function renderRuntimeCompose(runtime: DevRuntime): string;
|
|
24
|
+
export declare function renderRoute(runtime: DevRuntime): string;
|
|
25
|
+
export declare function runDevCommand(projectRoot: string, action: string | undefined, args: string[], runner?: CommandRunner): void;
|
|
26
|
+
export {};
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readDevConfig = readDevConfig;
|
|
4
|
+
exports.resolveDevRuntime = resolveDevRuntime;
|
|
5
|
+
exports.renderRuntimeCompose = renderRuntimeCompose;
|
|
6
|
+
exports.renderRoute = renderRoute;
|
|
7
|
+
exports.runDevCommand = runDevCommand;
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_os_1 = require("node:os");
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
const lockfile_1 = require("./lockfile");
|
|
14
|
+
const GATEWAY_CONTAINER = "podokit-dev-gateway";
|
|
15
|
+
const GATEWAY_IMAGE = "traefik:v3.5";
|
|
16
|
+
const GATEWAY_LABEL = "io.podosoft.podokit.dev-gateway";
|
|
17
|
+
const GATEWAY_NETWORK = "podokit-dev-gateway";
|
|
18
|
+
const RUNTIME_COMPOSE = ".podokit/runtime/dev.compose.yaml";
|
|
19
|
+
const DEV_CONFIG = ".podokit/dev.json";
|
|
20
|
+
function defaultRunner(command, args, options) {
|
|
21
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
stdio: options.capture ? "pipe" : "inherit",
|
|
24
|
+
});
|
|
25
|
+
if (result.error)
|
|
26
|
+
throw result.error;
|
|
27
|
+
return {
|
|
28
|
+
status: result.status,
|
|
29
|
+
stdout: options.capture ? (result.stdout ?? "") : "",
|
|
30
|
+
stderr: options.capture ? (result.stderr ?? "") : "",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function parseJson(path) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error(`Cannot read ${path}: ${error.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function validateHostname(hostname) {
|
|
42
|
+
if (hostname.length > 253 ||
|
|
43
|
+
!hostname.endsWith(".localhost") ||
|
|
44
|
+
!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?\.localhost$/.test(hostname)) {
|
|
45
|
+
throw new Error(`Invalid development hostname "${hostname}". Use a lowercase *.localhost hostname without a scheme or port.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function validatePublicUrl(publicUrl) {
|
|
49
|
+
let parsed;
|
|
50
|
+
try {
|
|
51
|
+
parsed = new URL(publicUrl);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`Invalid publicUrl "${publicUrl}". Use an absolute HTTPS URL.`);
|
|
55
|
+
}
|
|
56
|
+
if (parsed.protocol !== "https:" || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
57
|
+
throw new Error(`Invalid publicUrl "${publicUrl}". Use an HTTPS origin without a path.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function readDevConfig(projectRoot) {
|
|
61
|
+
const path = (0, node_path_1.join)(projectRoot, DEV_CONFIG);
|
|
62
|
+
const manifest = (0, lockfile_1.readManifest)(projectRoot);
|
|
63
|
+
if (!manifest)
|
|
64
|
+
throw new Error("Not a PodoKit project: .podokit/manifest.json is missing.");
|
|
65
|
+
const fallbackName = manifest.answers.projectName;
|
|
66
|
+
if (!fallbackName)
|
|
67
|
+
throw new Error("PodoKit manifest is missing answers.projectName.");
|
|
68
|
+
const config = (0, node_fs_1.existsSync)(path)
|
|
69
|
+
? parseJson(path)
|
|
70
|
+
: { schemaVersion: 1, hostname: `${fallbackName}.localhost` };
|
|
71
|
+
if (config.schemaVersion !== 1) {
|
|
72
|
+
throw new Error(`Unsupported ${DEV_CONFIG} schemaVersion: ${String(config.schemaVersion)}`);
|
|
73
|
+
}
|
|
74
|
+
validateHostname(config.hostname);
|
|
75
|
+
if (config.publicUrl)
|
|
76
|
+
validatePublicUrl(config.publicUrl);
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
79
|
+
function stableId(projectRoot) {
|
|
80
|
+
return (0, node_crypto_1.createHash)("sha256").update(projectRoot).digest("hex").slice(0, 10);
|
|
81
|
+
}
|
|
82
|
+
function slug(value) {
|
|
83
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
84
|
+
return normalized.slice(0, 36) || "app";
|
|
85
|
+
}
|
|
86
|
+
function resolveDevRuntime(projectRoot) {
|
|
87
|
+
const root = (0, node_path_1.resolve)(projectRoot);
|
|
88
|
+
const config = readDevConfig(root);
|
|
89
|
+
const id = stableId(root);
|
|
90
|
+
const projectSlug = slug(config.hostname.replace(/\.localhost$/, ""));
|
|
91
|
+
return {
|
|
92
|
+
alias: `podokit-${projectSlug}-${id}`,
|
|
93
|
+
config,
|
|
94
|
+
projectRoot: root,
|
|
95
|
+
routeId: `route-${projectSlug}-${id}`,
|
|
96
|
+
runtimeComposePath: (0, node_path_1.join)(root, RUNTIME_COMPOSE),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function renderRuntimeCompose(runtime) {
|
|
100
|
+
return `# Generated by podo dev. Do not edit.\nservices:\n traefik:\n profiles: [podokit-legacy-proxy]\n web:\n networks:\n ${GATEWAY_NETWORK}:\n aliases: [${runtime.alias}]\nnetworks:\n ${GATEWAY_NETWORK}:\n external: true\n name: ${GATEWAY_NETWORK}\n`;
|
|
101
|
+
}
|
|
102
|
+
function renderRoute(runtime) {
|
|
103
|
+
const { alias, config, routeId } = runtime;
|
|
104
|
+
return `http:\n routers:\n ${routeId}:\n entryPoints: [web]\n rule: Host(\`${config.hostname}\`)\n service: ${routeId}\n middlewares: [${routeId}-compression]\n services:\n ${routeId}:\n loadBalancer:\n servers:\n - url: http://${alias}:5001\n middlewares:\n ${routeId}-compression:\n compress: {}\n`;
|
|
105
|
+
}
|
|
106
|
+
function devHome() {
|
|
107
|
+
return process.env.PODOKIT_DEV_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".podokit", "dev-gateway");
|
|
108
|
+
}
|
|
109
|
+
function registryDirectory() {
|
|
110
|
+
return (0, node_path_1.join)(devHome(), "projects");
|
|
111
|
+
}
|
|
112
|
+
function routesDirectory() {
|
|
113
|
+
return (0, node_path_1.join)(devHome(), "routes");
|
|
114
|
+
}
|
|
115
|
+
function registryPath(runtime) {
|
|
116
|
+
return (0, node_path_1.join)(registryDirectory(), `${runtime.routeId}.json`);
|
|
117
|
+
}
|
|
118
|
+
function routePath(runtime) {
|
|
119
|
+
return (0, node_path_1.join)(routesDirectory(), `${runtime.routeId}.yml`);
|
|
120
|
+
}
|
|
121
|
+
function activeEntries() {
|
|
122
|
+
const dir = registryDirectory();
|
|
123
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
124
|
+
return [];
|
|
125
|
+
const entries = [];
|
|
126
|
+
for (const name of (0, node_fs_1.readdirSync)(dir).filter((entry) => entry.endsWith(".json"))) {
|
|
127
|
+
const path = (0, node_path_1.join)(dir, name);
|
|
128
|
+
const entry = parseJson(path);
|
|
129
|
+
if (!(0, node_fs_1.existsSync)(entry.projectRoot)) {
|
|
130
|
+
(0, node_fs_1.rmSync)(path, { force: true });
|
|
131
|
+
(0, node_fs_1.rmSync)((0, node_path_1.join)(routesDirectory(), `${entry.routeId}.yml`), { force: true });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
entries.push(entry);
|
|
135
|
+
}
|
|
136
|
+
return entries;
|
|
137
|
+
}
|
|
138
|
+
function writeRuntimeFiles(runtime) {
|
|
139
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(runtime.projectRoot, ".podokit", "runtime"), { recursive: true });
|
|
140
|
+
(0, node_fs_1.writeFileSync)(runtime.runtimeComposePath, renderRuntimeCompose(runtime));
|
|
141
|
+
}
|
|
142
|
+
function registerRoute(runtime) {
|
|
143
|
+
const collision = activeEntries().find((entry) => entry.hostname === runtime.config.hostname && entry.projectRoot !== runtime.projectRoot);
|
|
144
|
+
if (collision) {
|
|
145
|
+
throw new Error(`Development hostname ${runtime.config.hostname} is already registered by ${collision.projectRoot}. Change .podokit/dev.json or run podo dev down there.`);
|
|
146
|
+
}
|
|
147
|
+
(0, node_fs_1.mkdirSync)(registryDirectory(), { recursive: true });
|
|
148
|
+
(0, node_fs_1.mkdirSync)(routesDirectory(), { recursive: true });
|
|
149
|
+
(0, node_fs_1.writeFileSync)(routePath(runtime), renderRoute(runtime));
|
|
150
|
+
const entry = {
|
|
151
|
+
hostname: runtime.config.hostname,
|
|
152
|
+
projectRoot: runtime.projectRoot,
|
|
153
|
+
routeId: runtime.routeId,
|
|
154
|
+
};
|
|
155
|
+
(0, node_fs_1.writeFileSync)(registryPath(runtime), `${JSON.stringify(entry, null, 2)}\n`);
|
|
156
|
+
}
|
|
157
|
+
function unregisterRoute(runtime) {
|
|
158
|
+
(0, node_fs_1.rmSync)(registryPath(runtime), { force: true });
|
|
159
|
+
(0, node_fs_1.rmSync)(routePath(runtime), { force: true });
|
|
160
|
+
}
|
|
161
|
+
function commandSucceeded(result) {
|
|
162
|
+
return result.status === 0;
|
|
163
|
+
}
|
|
164
|
+
function ensureDocker(runner) {
|
|
165
|
+
const result = runner("docker", ["info", "--format", "{{.ServerVersion}}"], { capture: true });
|
|
166
|
+
if (!commandSucceeded(result)) {
|
|
167
|
+
throw new Error(`Docker is not available. ${result.stderr.trim()}`.trim());
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function ensureNetwork(runner) {
|
|
171
|
+
const inspect = runner("docker", ["network", "inspect", GATEWAY_NETWORK], { capture: true });
|
|
172
|
+
if (commandSucceeded(inspect))
|
|
173
|
+
return;
|
|
174
|
+
const create = runner("docker", ["network", "create", GATEWAY_NETWORK], { capture: true });
|
|
175
|
+
if (!commandSucceeded(create)) {
|
|
176
|
+
throw new Error(`Cannot create Docker network ${GATEWAY_NETWORK}: ${create.stderr.trim()}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function ensureGateway(runner) {
|
|
180
|
+
const inspect = runner("docker", ["inspect", "--format", `{{ index .Config.Labels \"${GATEWAY_LABEL}\" }} {{.State.Running}}`, GATEWAY_CONTAINER], { capture: true });
|
|
181
|
+
if (commandSucceeded(inspect)) {
|
|
182
|
+
const [label, running] = inspect.stdout.trim().split(/\s+/);
|
|
183
|
+
if (label !== "1") {
|
|
184
|
+
throw new Error(`Container ${GATEWAY_CONTAINER} exists but is not managed by PodoKit.`);
|
|
185
|
+
}
|
|
186
|
+
if (running !== "true") {
|
|
187
|
+
const start = runner("docker", ["start", GATEWAY_CONTAINER], { capture: true });
|
|
188
|
+
if (!commandSucceeded(start)) {
|
|
189
|
+
throw new Error(`Cannot start ${GATEWAY_CONTAINER}: ${start.stderr.trim()}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
(0, node_fs_1.mkdirSync)(routesDirectory(), { recursive: true });
|
|
195
|
+
const run = runner("docker", [
|
|
196
|
+
"run",
|
|
197
|
+
"--detach",
|
|
198
|
+
"--name",
|
|
199
|
+
GATEWAY_CONTAINER,
|
|
200
|
+
"--label",
|
|
201
|
+
`${GATEWAY_LABEL}=1`,
|
|
202
|
+
"--restart",
|
|
203
|
+
"unless-stopped",
|
|
204
|
+
"--network",
|
|
205
|
+
GATEWAY_NETWORK,
|
|
206
|
+
"--publish",
|
|
207
|
+
"127.0.0.1:80:80",
|
|
208
|
+
"--volume",
|
|
209
|
+
`${routesDirectory()}:/etc/traefik/dynamic:ro`,
|
|
210
|
+
GATEWAY_IMAGE,
|
|
211
|
+
"--providers.file.directory=/etc/traefik/dynamic",
|
|
212
|
+
"--providers.file.watch=true",
|
|
213
|
+
"--entrypoints.web.address=:80",
|
|
214
|
+
"--api.dashboard=false",
|
|
215
|
+
], { capture: true });
|
|
216
|
+
if (!commandSucceeded(run)) {
|
|
217
|
+
throw new Error(`Cannot start the shared PodoKit gateway on 127.0.0.1:80. Stop the current port owner, then retry. ${run.stderr.trim()}`.trim());
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function composeArgs(runtime, args) {
|
|
221
|
+
return ["compose", "-f", "compose.dev.yaml", "-f", runtime.runtimeComposePath, ...args];
|
|
222
|
+
}
|
|
223
|
+
function runCompose(runtime, args, runner) {
|
|
224
|
+
const result = runner("docker", composeArgs(runtime, args), { capture: false });
|
|
225
|
+
if (!commandSucceeded(result)) {
|
|
226
|
+
throw new Error(`docker compose ${args.join(" ")} exited with status ${String(result.status)}.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function composeCommand(action, args) {
|
|
230
|
+
const global = [];
|
|
231
|
+
const command = [];
|
|
232
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
233
|
+
const value = args[index];
|
|
234
|
+
if (value === undefined)
|
|
235
|
+
continue;
|
|
236
|
+
if (value === "--profile") {
|
|
237
|
+
const profile = args[index + 1];
|
|
238
|
+
if (!profile)
|
|
239
|
+
throw new Error("--profile requires a value.");
|
|
240
|
+
global.push(value, profile);
|
|
241
|
+
index += 1;
|
|
242
|
+
}
|
|
243
|
+
else if (value.startsWith("--profile=")) {
|
|
244
|
+
global.push(value);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
command.push(value);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const activeProfiles = action === "down" ? ["--profile", "*"] : [];
|
|
251
|
+
return [...activeProfiles, ...global, action, ...command];
|
|
252
|
+
}
|
|
253
|
+
function stopGatewayWhenUnused(runner) {
|
|
254
|
+
if (activeEntries().length > 0)
|
|
255
|
+
return;
|
|
256
|
+
runner("docker", ["rm", "--force", GATEWAY_CONTAINER], { capture: true });
|
|
257
|
+
runner("docker", ["network", "rm", GATEWAY_NETWORK], { capture: true });
|
|
258
|
+
}
|
|
259
|
+
function runDevCommand(projectRoot, action, args, runner = defaultRunner) {
|
|
260
|
+
const runtime = resolveDevRuntime(projectRoot);
|
|
261
|
+
writeRuntimeFiles(runtime);
|
|
262
|
+
if (action === "url") {
|
|
263
|
+
process.stdout.write(`http://${runtime.config.hostname}\n`);
|
|
264
|
+
if (runtime.config.publicUrl)
|
|
265
|
+
process.stdout.write(`${runtime.config.publicUrl}\n`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
ensureDocker(runner);
|
|
269
|
+
if (action === "watch") {
|
|
270
|
+
ensureNetwork(runner);
|
|
271
|
+
registerRoute(runtime);
|
|
272
|
+
try {
|
|
273
|
+
ensureGateway(runner);
|
|
274
|
+
process.stdout.write(`Development URL: http://${runtime.config.hostname}\n`);
|
|
275
|
+
if (runtime.config.publicUrl)
|
|
276
|
+
process.stdout.write(`Public OAuth URL: ${runtime.config.publicUrl}\n`);
|
|
277
|
+
runCompose(runtime, composeCommand("watch", args), runner);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
unregisterRoute(runtime);
|
|
281
|
+
stopGatewayWhenUnused(runner);
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (action === "down") {
|
|
287
|
+
runCompose(runtime, composeCommand("down", args), runner);
|
|
288
|
+
unregisterRoute(runtime);
|
|
289
|
+
stopGatewayWhenUnused(runner);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (action === "exec" || action === "logs" || action === "ps") {
|
|
293
|
+
runCompose(runtime, composeCommand(action, args), runner);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
throw new Error("Usage: podo dev <watch|exec|logs|ps|down|url> [docker compose options]");
|
|
297
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type PackageManager } from "./create";
|
|
3
|
+
import { type LocaleDirection } from "./locale";
|
|
3
4
|
interface ParsedArgs {
|
|
4
5
|
command?: string;
|
|
5
6
|
name?: string;
|
|
@@ -12,6 +13,9 @@ interface ParsedArgs {
|
|
|
12
13
|
yes: boolean;
|
|
13
14
|
help: boolean;
|
|
14
15
|
ai: boolean;
|
|
16
|
+
positionals: string[];
|
|
17
|
+
localeName?: string;
|
|
18
|
+
localeDirection?: LocaleDirection;
|
|
15
19
|
}
|
|
16
20
|
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
17
21
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const remove_1 = require("./remove");
|
|
|
12
12
|
const inspect_1 = require("./inspect");
|
|
13
13
|
const update_1 = require("./update");
|
|
14
14
|
const eject_1 = require("./eject");
|
|
15
|
+
const dev_1 = require("./dev");
|
|
16
|
+
const locale_1 = require("./locale");
|
|
15
17
|
const HELP = `podo — PodoKit project generator
|
|
16
18
|
|
|
17
19
|
Usage:
|
|
@@ -21,6 +23,8 @@ Usage:
|
|
|
21
23
|
podo status Show version, modules, file tiers, and local edits
|
|
22
24
|
podo diff List PodoKit-managed files you have edited
|
|
23
25
|
podo doctor Check framework versions against supported ranges
|
|
26
|
+
podo dev <action> [...] Run the shared, portless container development gateway
|
|
27
|
+
podo locale <command> Add, validate, activate, or list JSON locales
|
|
24
28
|
podo update [--apply] Preview (or apply) what a version update would change
|
|
25
29
|
podo eject <path...> Take ownership of managed files (update skips them)
|
|
26
30
|
|
|
@@ -28,6 +32,8 @@ Options:
|
|
|
28
32
|
--template <t> Template to scaffold (see below)
|
|
29
33
|
--dir <path> Target directory (default: ./<name>)
|
|
30
34
|
--pm <name> Package manager: npm | pnpm | yarn (default: npm)
|
|
35
|
+
--name <label> Display name for a locale
|
|
36
|
+
--direction <direction> Text direction: ltr | rtl (default: ltr)
|
|
31
37
|
--adopt Adopt colliding paths explicitly declared managed by a module
|
|
32
38
|
--no-ai Skip AI agent guidance (AGENTS.md, CLAUDE.md, editor rules)
|
|
33
39
|
-y, --yes Skip prompts and accept defaults
|
|
@@ -42,7 +48,14 @@ Example:
|
|
|
42
48
|
cd my-app && npx @podosoft/podokit add auth
|
|
43
49
|
`;
|
|
44
50
|
function parseArgs(argv) {
|
|
45
|
-
const parsed = {
|
|
51
|
+
const parsed = {
|
|
52
|
+
help: false,
|
|
53
|
+
yes: false,
|
|
54
|
+
apply: false,
|
|
55
|
+
adopt: false,
|
|
56
|
+
ai: true,
|
|
57
|
+
positionals: [],
|
|
58
|
+
};
|
|
46
59
|
const positionals = [];
|
|
47
60
|
for (let i = 0; i < argv.length; i += 1) {
|
|
48
61
|
const arg = argv[i];
|
|
@@ -73,12 +86,23 @@ function parseArgs(argv) {
|
|
|
73
86
|
else if (arg === "--pm") {
|
|
74
87
|
parsed.pm = argv[++i];
|
|
75
88
|
}
|
|
89
|
+
else if (arg === "--name") {
|
|
90
|
+
parsed.localeName = argv[++i];
|
|
91
|
+
}
|
|
92
|
+
else if (arg === "--direction") {
|
|
93
|
+
const direction = argv[++i];
|
|
94
|
+
if (direction !== "ltr" && direction !== "rtl") {
|
|
95
|
+
throw new Error('--direction must be either "ltr" or "rtl".');
|
|
96
|
+
}
|
|
97
|
+
parsed.localeDirection = direction;
|
|
98
|
+
}
|
|
76
99
|
else if (arg !== undefined && !arg.startsWith("-")) {
|
|
77
100
|
positionals.push(arg);
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
103
|
parsed.command = positionals[0];
|
|
81
104
|
parsed.name = positionals[1];
|
|
105
|
+
parsed.positionals = positionals;
|
|
82
106
|
return parsed;
|
|
83
107
|
}
|
|
84
108
|
function fail(message) {
|
|
@@ -203,6 +227,62 @@ async function main(argv) {
|
|
|
203
227
|
}
|
|
204
228
|
return;
|
|
205
229
|
}
|
|
230
|
+
if (args.command === "dev") {
|
|
231
|
+
try {
|
|
232
|
+
(0, dev_1.runDevCommand)(process.cwd(), args.name, argv.slice(2));
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
fail(err.message);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (args.command === "locale") {
|
|
240
|
+
const action = args.positionals[1] ?? "list";
|
|
241
|
+
const code = args.positionals[2];
|
|
242
|
+
try {
|
|
243
|
+
if (action === "add") {
|
|
244
|
+
if (!code)
|
|
245
|
+
fail("Usage: podo locale add <code> [--name <label>] [--direction ltr|rtl]");
|
|
246
|
+
const definition = (0, locale_1.addLocale)(process.cwd(), code, {
|
|
247
|
+
name: args.localeName,
|
|
248
|
+
direction: args.localeDirection,
|
|
249
|
+
});
|
|
250
|
+
process.stdout.write(`Added ${definition.code} (${definition.name}) as inactive. Translate catalogs, run ` +
|
|
251
|
+
`"podo locale validate ${definition.code}", then activate it.\n`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (action === "validate") {
|
|
255
|
+
const locales = code ? [code] : (0, locale_1.listLocales)(process.cwd()).map((locale) => locale.code);
|
|
256
|
+
for (const locale of locales) {
|
|
257
|
+
const coverage = (0, locale_1.validateLocale)(process.cwd(), locale);
|
|
258
|
+
process.stdout.write(`${coverage.definition.code.padEnd(10)} ${String(coverage.percent).padStart(3)}% ` +
|
|
259
|
+
`(${coverage.translated}/${coverage.total})` +
|
|
260
|
+
`${coverage.missing.length ? ` missing ${coverage.missing.length}` : ""}\n`);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (action === "activate" || action === "deactivate") {
|
|
265
|
+
if (!code)
|
|
266
|
+
fail(`Usage: podo locale ${action} <code>`);
|
|
267
|
+
const coverage = (0, locale_1.setLocaleEnabled)(process.cwd(), code, action === "activate");
|
|
268
|
+
process.stdout.write(`${action === "activate" ? "Activated" : "Deactivated"} ${coverage.definition.code} ` +
|
|
269
|
+
`(${coverage.percent}% translated; missing keys use the configured fallback).\n`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (action === "list") {
|
|
273
|
+
for (const locale of (0, locale_1.listLocales)(process.cwd())) {
|
|
274
|
+
const coverage = (0, locale_1.validateLocale)(process.cwd(), locale.code);
|
|
275
|
+
process.stdout.write(`${locale.enabled ? "active " : "inactive"} ${locale.code.padEnd(10)} ` +
|
|
276
|
+
`${locale.name} ${coverage.percent}%\n`);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
fail(`Unknown locale command "${action}". Use add, validate, activate, deactivate, or list.`);
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
fail(err.message);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
206
286
|
if (args.command === "update") {
|
|
207
287
|
const templatesDir = (0, node_path_1.join)(__dirname, "templates");
|
|
208
288
|
try {
|
|
@@ -238,7 +318,7 @@ async function main(argv) {
|
|
|
238
318
|
return;
|
|
239
319
|
}
|
|
240
320
|
if (args.command === "eject") {
|
|
241
|
-
const targets =
|
|
321
|
+
const targets = args.positionals.slice(1);
|
|
242
322
|
if (!targets.length) {
|
|
243
323
|
fail("Usage: podo eject <path...>");
|
|
244
324
|
}
|
package/dist/lib.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { status, diff, doctor, SUPPORTED_FRAMEWORKS } from "./inspect";
|
|
|
8
8
|
export type { StatusReport, DoctorFinding } from "./inspect";
|
|
9
9
|
export { planUpdate, summarize } from "./update";
|
|
10
10
|
export type { UpdatePlan, FileChange } from "./update";
|
|
11
|
+
export { readDevConfig, renderRoute, renderRuntimeCompose, resolveDevRuntime, runDevCommand } from "./dev";
|
|
12
|
+
export type { CommandRunner, DevConfig, DevRuntime } from "./dev";
|
|
11
13
|
/** Absolute path to the templates bundled in this installed package. */
|
|
12
14
|
export declare function builtinTemplatesDir(): string;
|
|
13
15
|
/** Absolute path to the modules bundled in this installed package. */
|
package/dist/lib.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
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;
|
|
3
|
+
exports.runDevCommand = exports.resolveDevRuntime = exports.renderRuntimeCompose = exports.renderRoute = exports.readDevConfig = 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
4
|
exports.builtinTemplatesDir = builtinTemplatesDir;
|
|
5
5
|
exports.builtinModulesDir = builtinModulesDir;
|
|
6
6
|
// Library entry point: the reusable functions behind the `podo` CLI, for other
|
|
@@ -24,6 +24,12 @@ Object.defineProperty(exports, "SUPPORTED_FRAMEWORKS", { enumerable: true, get:
|
|
|
24
24
|
var update_1 = require("./update");
|
|
25
25
|
Object.defineProperty(exports, "planUpdate", { enumerable: true, get: function () { return update_1.planUpdate; } });
|
|
26
26
|
Object.defineProperty(exports, "summarize", { enumerable: true, get: function () { return update_1.summarize; } });
|
|
27
|
+
var dev_1 = require("./dev");
|
|
28
|
+
Object.defineProperty(exports, "readDevConfig", { enumerable: true, get: function () { return dev_1.readDevConfig; } });
|
|
29
|
+
Object.defineProperty(exports, "renderRoute", { enumerable: true, get: function () { return dev_1.renderRoute; } });
|
|
30
|
+
Object.defineProperty(exports, "renderRuntimeCompose", { enumerable: true, get: function () { return dev_1.renderRuntimeCompose; } });
|
|
31
|
+
Object.defineProperty(exports, "resolveDevRuntime", { enumerable: true, get: function () { return dev_1.resolveDevRuntime; } });
|
|
32
|
+
Object.defineProperty(exports, "runDevCommand", { enumerable: true, get: function () { return dev_1.runDevCommand; } });
|
|
27
33
|
/** Absolute path to the templates bundled in this installed package. */
|
|
28
34
|
function builtinTemplatesDir() {
|
|
29
35
|
return (0, node_path_1.join)(__dirname, "templates");
|
package/dist/locale.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type LocaleDirection = "ltr" | "rtl";
|
|
2
|
+
export interface LocaleDefinition {
|
|
3
|
+
code: string;
|
|
4
|
+
name: string;
|
|
5
|
+
direction: LocaleDirection;
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface LocaleCoverage {
|
|
9
|
+
definition: LocaleDefinition;
|
|
10
|
+
translated: number;
|
|
11
|
+
total: number;
|
|
12
|
+
percent: number;
|
|
13
|
+
missing: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function normalizeLocaleCode(value: string): string;
|
|
16
|
+
export declare function listLocales(projectRoot: string): LocaleDefinition[];
|
|
17
|
+
export declare function validateLocale(projectRoot: string, rawCode: string): LocaleCoverage;
|
|
18
|
+
export declare function addLocale(projectRoot: string, rawCode: string, options?: {
|
|
19
|
+
name?: string;
|
|
20
|
+
direction?: LocaleDirection;
|
|
21
|
+
}): LocaleDefinition;
|
|
22
|
+
export declare function setLocaleEnabled(projectRoot: string, rawCode: string, enabled: boolean): LocaleCoverage;
|