@podosoft/podokit 1.2.0 → 1.4.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/dist/add.d.ts +13 -2
- package/dist/add.js +135 -20
- package/dist/api-paths.d.ts +34 -0
- package/dist/api-paths.js +103 -0
- package/dist/deploy-compose-profile.d.ts +4 -0
- package/dist/deploy-compose-profile.js +13 -2
- package/dist/deploy-compose-render.d.ts +1 -1
- package/dist/deploy-compose-render.js +23 -7
- package/dist/deploy-compose.js +2 -1
- package/dist/deploy-profile.d.ts +3 -0
- package/dist/deploy-profile.js +12 -4
- package/dist/deploy-render.js +54 -8
- package/dist/deploy.js +21 -1
- package/dist/dev.d.ts +14 -0
- package/dist/dev.js +152 -10
- package/dist/index.js +23 -2
- package/dist/lib.d.ts +4 -2
- package/dist/lib.js +5 -2
- package/dist/lockfile.d.ts +6 -1
- package/dist/lockfile.js +26 -6
- package/dist/module-api-paths.d.ts +52 -0
- package/dist/module-api-paths.js +189 -0
- package/dist/remove.d.ts +3 -0
- package/dist/remove.js +12 -3
- package/dist/templates/fullstack/AGENTS.md +17 -2
- package/dist/templates/fullstack/README.md +19 -5
- package/dist/templates/fullstack/apps/api/package.json +1 -1
- package/dist/templates/fullstack/apps/api/scripts/build.mjs +1 -0
- package/dist/templates/fullstack/apps/api/src/app.extensions.ts +7 -0
- package/dist/templates/fullstack/apps/api/src/app.spec.ts +20 -1
- package/dist/templates/fullstack/apps/api/src/app.ts +11 -0
- package/dist/templates/fullstack/apps/api/src/config/env.validation.ts +3 -0
- package/dist/templates/fullstack/apps/api/src/config/redis.connection.spec.ts +48 -2
- package/dist/templates/fullstack/apps/api/src/config/redis.connection.ts +45 -0
- package/dist/templates/fullstack/apps/api/src/config/server-options.spec.ts +46 -0
- package/dist/templates/fullstack/apps/api/src/config/server-options.ts +48 -0
- package/dist/templates/fullstack/apps/api/src/core/api-contract.spec.ts +1 -0
- package/dist/templates/fullstack/apps/api/src/core/api-contract.ts +10 -1
- package/dist/templates/fullstack/apps/api/src/core/services.spec.ts +196 -0
- package/dist/templates/fullstack/apps/api/src/core/services.ts +181 -2
- package/dist/templates/fullstack/apps/api/src/database/data-source.spec.ts +15 -0
- package/dist/templates/fullstack/apps/api/src/database/data-source.ts +3 -0
- package/dist/templates/fullstack/apps/api/src/database/database.spec.ts +8 -1
- package/dist/templates/fullstack/apps/api/src/database/database.ts +29 -10
- package/dist/templates/fullstack/apps/api/src/database/migrate.ts +1 -1
- package/dist/templates/fullstack/apps/api/src/database/pool-guard.spec.ts +188 -0
- package/dist/templates/fullstack/apps/api/src/database/pool-guard.ts +151 -0
- package/dist/templates/fullstack/apps/api/src/health/health.plugin.spec.ts +28 -4
- package/dist/templates/fullstack/apps/api/src/health/health.plugin.ts +10 -2
- package/dist/templates/fullstack/apps/api/src/main.ts +2 -1
- package/dist/templates/fullstack/apps/web/components.json +3 -1
- package/dist/templates/fullstack/apps/web/package.json +1 -1
- package/dist/templates/fullstack/apps/web/src/lib/components/ui/textarea/index.ts +7 -0
- package/dist/templates/fullstack/apps/web/src/lib/components/ui/textarea/textarea.svelte +22 -0
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.test.ts +276 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.ts +194 -9
- package/dist/templates/fullstack/apps/web/src/lib/server/body-limits.ts +15 -0
- package/dist/templates/fullstack/apps/web/src/lib/server/proxy-headers.ts +12 -0
- package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/references/profile.md +10 -3
- package/dist/templates/fullstack/dot-claude/skills/podokit-elysia-endpoint/SKILL.md +5 -0
- package/dist/templates/fullstack/dot-claude/skills/podokit-sveltekit-route/SKILL.md +14 -1
- package/dist/templates/fullstack/dot-devcontainer/devcontainer.json +1 -0
- package/dist/templates/fullstack/{dot-env.docker → dot-env.docker.example} +7 -4
- package/dist/templates/fullstack/dot-env.example +10 -0
- package/dist/templates/fullstack/dot-gitignore +1 -0
- package/dist/templates/fullstack/dot-podokit/dev.json +2 -1
- package/dist/templates/fullstack/infra/k3s/README.md +3 -1
- package/dist/templates/fullstack/tests/playwright.extensions.ts +2 -2
- package/dist/templates/modules/admin-dashboard/files/apps/api/scripts/bootstrap-admin.mjs +21 -26
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.migration.spec.ts +74 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.routes.spec.ts +76 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.routes.ts +41 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.service.spec.ts +124 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.service.ts +153 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin.module.ts +5 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/bootstrap-admin-runtime.ts +60 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/migrations/1792060000000-IndexSessionCreatedAt.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +27 -9
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/users.test.ts +59 -32
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/users.ts +81 -11
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/asset-paths.ts +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.test.ts +23 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +2 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/public-paths.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/runtime-context.test.ts +125 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/runtime-context.ts +107 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/users/+page.svelte +13 -19
- package/dist/templates/modules/admin-dashboard/files/tests/api/admin-users.api.spec.ts +86 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +1 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/profile-image.api.spec.ts +2 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/site-settings.api.spec.ts +2 -1
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/disposable-users.ts +15 -12
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/object-response.ts +12 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +2 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +0 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +1 -3
- package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +3 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/request-budget.ui.spec.ts +14 -15
- package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +13 -8
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +14 -3
- package/dist/templates/modules/admin-dashboard/module.manifest.json +4 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.migration.spec.ts +53 -11
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1792000000000-IndexAuditLogFilters.ts +26 -4
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.spec.ts +26 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +17 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.service.ts +8 -4
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +2 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/database-outage.spec.ts +114 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/database-outage.ts +50 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/db.spec.ts +4 -1
- package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +11 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/maintenance-command.ts +5 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/maintenance.ts +14 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/postgres-pool.spec.ts +136 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/postgres-pool.ts +40 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.rows.spec.ts +53 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.rows.ts +65 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +16 -37
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/session-expiry.spec.ts +33 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/session-expiry.ts +29 -0
- package/dist/templates/modules/auth/files/apps/api/src/migrate.ts +3 -2
- package/dist/templates/modules/auth/files/dot-claude/skills/podokit-configure-auth/references/bootstrap-admin.md +2 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.spec.ts +121 -1
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.ts +108 -10
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.module.ts +3 -1
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/queue.ts +31 -3
- package/dist/templates/modules/bullmq/module.manifest.json +5 -1
- package/dist/templates/modules/events-redis/files/apps/api/src/events-provider-redis/events-redis.module.ts +2 -4
- package/dist/templates/modules/events-redis/files/apps/api/src/events-provider-redis/redis-event.bus.spec.ts +224 -0
- package/dist/templates/modules/events-redis/files/apps/api/src/events-provider-redis/redis-event.bus.ts +124 -35
- package/dist/templates/modules/events-redis/module.manifest.json +4 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/chunked-uploads.routes.ts +136 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/chunked-uploads.spec.ts +636 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/chunked-uploads.ts +673 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.spec.ts +10 -4
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.ts +37 -8
- package/dist/templates/modules/file-upload/files/apps/api/src/files/upload-config.ts +99 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/migrations/1792100000000-InitFileUploadSessions.ts +49 -0
- package/dist/templates/modules/file-upload/files/apps/web/src/lib/uploads.test.ts +456 -0
- package/dist/templates/modules/file-upload/files/apps/web/src/lib/uploads.ts +356 -0
- package/dist/templates/modules/file-upload/files/tests/api/chunked-uploads.api.spec.ts +147 -0
- package/dist/templates/modules/file-upload/module.manifest.json +34 -6
- package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/jobs-local.module.ts +1 -1
- package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.spec.ts +23 -0
- package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.ts +30 -20
- package/dist/templates/modules/jobs-local/module.manifest.json +3 -0
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/download-url.ts +20 -0
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/storage-local.module.spec.ts +79 -12
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/storage-local.module.ts +16 -1
- package/dist/templates/modules/object-storage-local/module.manifest.json +11 -2
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-multipart.spec.ts +115 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-multipart.ts +171 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-object.store.spec.ts +222 -9
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-object.store.ts +55 -19
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-signer.spec.ts +71 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-signer.ts +98 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage-demo.spec.ts +6 -3
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.config.ts +7 -2
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts +187 -44
- package/dist/templates/modules/object-storage-s3/module.manifest.json +10 -2
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +11 -22
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.routes.spec.ts +113 -0
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.routes.ts +122 -0
- package/dist/templates/modules/rate-limit/files/tests/api/rate-limit.api.spec.ts +24 -0
- package/dist/templates/modules/rate-limit/module.manifest.json +7 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis-cache.store.spec.ts +14 -1
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis-cache.store.ts +9 -12
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.module.ts +16 -4
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.service.spec.ts +233 -5
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.service.ts +134 -32
- package/dist/templates/modules/redis/module.manifest.json +4 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/event-stream.ts +11 -4
- package/dist/templates/modules/sse/files/apps/api/src/events/events.module.spec.ts +48 -1
- package/dist/templates/modules/sse/files/apps/api/src/events/events.module.ts +15 -2
- package/dist/templates/modules/sse/files/apps/api/src/events/events.service.spec.ts +68 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.service.ts +25 -1
- package/dist/templates/modules/sse/files/tests/api/events.api.spec.ts +46 -0
- package/dist/templates/modules/sse/module.manifest.json +3 -1
- package/dist/templates/todo/AGENTS.md +9 -1
- package/dist/templates/todo/README.md +18 -5
- package/dist/update.d.ts +22 -0
- package/dist/update.js +68 -8
- package/package.json +2 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/schema-validation.ts +0 -13
- package/dist/templates/modules/auth/files/apps/api/src/auth/skip-schema-validation.ts +0 -5
package/dist/deploy-profile.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.profilePath = exports.profileDirectory = void 0;
|
|
4
|
+
exports.parseDeploymentProfile = parseDeploymentProfile;
|
|
4
5
|
exports.loadDeploymentProfile = loadDeploymentProfile;
|
|
5
6
|
exports.listDeploymentProfiles = listDeploymentProfiles;
|
|
6
7
|
exports.initializeDeploymentProfile = initializeDeploymentProfile;
|
|
@@ -11,6 +12,7 @@ const node_fs_1 = require("node:fs");
|
|
|
11
12
|
const node_path_1 = require("node:path");
|
|
12
13
|
const lockfile_1 = require("./lockfile");
|
|
13
14
|
const deploy_schema_1 = require("./deploy-schema");
|
|
15
|
+
const api_paths_1 = require("./api-paths");
|
|
14
16
|
const websocket_paths_1 = require("./websocket-paths");
|
|
15
17
|
var deploy_schema_2 = require("./deploy-schema");
|
|
16
18
|
Object.defineProperty(exports, "profileDirectory", { enumerable: true, get: function () { return deploy_schema_2.profileDirectory; } });
|
|
@@ -84,7 +86,7 @@ function parseStatefulDependency(value, field, extraKeys = []) {
|
|
|
84
86
|
function parseRedisDependency(value, field) {
|
|
85
87
|
return parseStatefulDependency(value, field);
|
|
86
88
|
}
|
|
87
|
-
function
|
|
89
|
+
function parseDeploymentProfile(value) {
|
|
88
90
|
if (!(0, deploy_schema_1.isRecord)(value))
|
|
89
91
|
throw new Error("Deployment profile must be a JSON object.");
|
|
90
92
|
(0, deploy_schema_1.assertOnlyKeys)(value, [
|
|
@@ -117,7 +119,7 @@ function parseProfile(value) {
|
|
|
117
119
|
const runtimeConfig = (0, deploy_schema_1.requiredRecord)(value, "runtimeConfig");
|
|
118
120
|
(0, deploy_schema_1.assertOnlyKeys)(target, ["context", "clusterFingerprint", "namespace", "release", "createNamespace"], "target");
|
|
119
121
|
(0, deploy_schema_1.assertOnlyKeys)(release, ["strategy", "tagPattern", "apiRepository", "webRepository"], "release");
|
|
120
|
-
(0, deploy_schema_1.assertOnlyKeys)(exposure, ["mode", "host", "ingressClassName", "tlsSecretName", "webSocketPaths"], "exposure");
|
|
122
|
+
(0, deploy_schema_1.assertOnlyKeys)(exposure, ["mode", "host", "ingressClassName", "tlsSecretName", "webSocketPaths", "apiPaths"], "exposure");
|
|
121
123
|
(0, deploy_schema_1.assertOnlyKeys)(workloads, ["api", "web", "worker"], "workloads");
|
|
122
124
|
(0, deploy_schema_1.assertOnlyKeys)(dependencies, ["postgres", "redis", "objectStorage"], "dependencies");
|
|
123
125
|
(0, deploy_schema_1.assertOnlyKeys)(secrets, ["api", "web", "imagePull"], "secrets");
|
|
@@ -167,6 +169,10 @@ function parseProfile(value) {
|
|
|
167
169
|
if (exposureMode === "nodePort" && webSocketPaths.length > 0) {
|
|
168
170
|
throw new Error("Deployment exposure.webSocketPaths requires exposure.mode ingress.");
|
|
169
171
|
}
|
|
172
|
+
const apiPaths = (0, api_paths_1.parseApiPaths)(exposure.apiPaths ?? [], "exposure.apiPaths", webSocketPaths);
|
|
173
|
+
if (exposureMode === "nodePort" && apiPaths.length > 0) {
|
|
174
|
+
throw new Error("Deployment exposure.apiPaths requires exposure.mode ingress.");
|
|
175
|
+
}
|
|
170
176
|
const postgres = parseStatefulDependency((0, deploy_schema_1.requiredRecord)(dependencies, "postgres"), "dependencies.postgres");
|
|
171
177
|
const redis = parseRedisDependency((0, deploy_schema_1.requiredRecord)(dependencies, "redis"), "dependencies.redis");
|
|
172
178
|
const objectStorageRecord = (0, deploy_schema_1.requiredRecord)(dependencies, "objectStorage");
|
|
@@ -210,6 +216,7 @@ function parseProfile(value) {
|
|
|
210
216
|
ingressClassName,
|
|
211
217
|
tlsSecretName,
|
|
212
218
|
webSocketPaths,
|
|
219
|
+
apiPaths,
|
|
213
220
|
},
|
|
214
221
|
workloads: {
|
|
215
222
|
api: parseWorkload((0, deploy_schema_1.requiredRecord)(workloads, "api"), "workloads.api"),
|
|
@@ -246,7 +253,7 @@ function loadDeploymentProfile(projectRoot, name) {
|
|
|
246
253
|
catch (error) {
|
|
247
254
|
throw new Error(`Cannot read deployment profile "${name}": ${error.message}`);
|
|
248
255
|
}
|
|
249
|
-
return
|
|
256
|
+
return parseDeploymentProfile(parsed);
|
|
250
257
|
}
|
|
251
258
|
function listDeploymentProfiles(projectRoot) {
|
|
252
259
|
const directory = (0, deploy_schema_1.profileDirectory)(projectRoot);
|
|
@@ -319,6 +326,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
319
326
|
ingressClassName: "traefik",
|
|
320
327
|
tlsSecretName: null,
|
|
321
328
|
webSocketPaths: [],
|
|
329
|
+
apiPaths: (0, api_paths_1.installedApiPaths)(manifest.modules),
|
|
322
330
|
},
|
|
323
331
|
workloads: {
|
|
324
332
|
api: {
|
|
@@ -418,7 +426,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
418
426
|
],
|
|
419
427
|
},
|
|
420
428
|
};
|
|
421
|
-
const validated =
|
|
429
|
+
const validated = parseDeploymentProfile(defaultProfile);
|
|
422
430
|
(0, node_fs_1.mkdirSync)((0, deploy_schema_1.profileDirectory)(projectRoot), { recursive: true });
|
|
423
431
|
(0, node_fs_1.writeFileSync)(path, `${JSON.stringify(validated, null, 2)}\n`, { flag: "wx" });
|
|
424
432
|
return { name, path, profile: validated };
|
package/dist/deploy-render.js
CHANGED
|
@@ -5,6 +5,7 @@ const node_fs_1 = require("node:fs");
|
|
|
5
5
|
const node_crypto_1 = require("node:crypto");
|
|
6
6
|
const node_path_1 = require("node:path");
|
|
7
7
|
const deploy_profile_1 = require("./deploy-profile");
|
|
8
|
+
const api_paths_1 = require("./api-paths");
|
|
8
9
|
const lockfile_1 = require("./lockfile");
|
|
9
10
|
const toolchain_1 = require("./toolchain");
|
|
10
11
|
function quote(value) {
|
|
@@ -525,6 +526,57 @@ spec:
|
|
|
525
526
|
app.kubernetes.io/name: ${name}
|
|
526
527
|
`;
|
|
527
528
|
}
|
|
529
|
+
function ingressPath(path, pathType, service) {
|
|
530
|
+
return ` - path: ${quote(path)}
|
|
531
|
+
pathType: ${pathType}
|
|
532
|
+
backend:
|
|
533
|
+
service:
|
|
534
|
+
name: ${service}
|
|
535
|
+
port: { number: 3000 }
|
|
536
|
+
`;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Direct API paths need /api removed on the way to the API, which a standard
|
|
540
|
+
* Ingress cannot express, so they get their own Ingress with a Traefik
|
|
541
|
+
* stripPrefix middleware. ImplementationSpecific makes Traefik match the
|
|
542
|
+
* trailing-slash prefix as a plain PathPrefix, and the Exact path adds the bare
|
|
543
|
+
* path, so every controller version routes the same set of requests.
|
|
544
|
+
*/
|
|
545
|
+
function apiPathIngress(profile, tls) {
|
|
546
|
+
if (profile.exposure.apiPaths.length === 0)
|
|
547
|
+
return "";
|
|
548
|
+
const apiService = `${profile.target.release}-api`;
|
|
549
|
+
const middleware = `${profile.target.release}-strip-api-prefix`;
|
|
550
|
+
const paths = profile.exposure.apiPaths
|
|
551
|
+
.map((path) => {
|
|
552
|
+
const base = (0, api_paths_1.apiPathBase)(path);
|
|
553
|
+
return (ingressPath(base, "Exact", apiService) +
|
|
554
|
+
ingressPath(`${base}/`, "ImplementationSpecific", apiService));
|
|
555
|
+
})
|
|
556
|
+
.join("");
|
|
557
|
+
return `---
|
|
558
|
+
apiVersion: traefik.io/v1alpha1
|
|
559
|
+
kind: Middleware
|
|
560
|
+
metadata:
|
|
561
|
+
name: ${middleware}
|
|
562
|
+
spec:
|
|
563
|
+
stripPrefix:
|
|
564
|
+
prefixes: [${quote(api_paths_1.API_PATH_PREFIX)}]
|
|
565
|
+
---
|
|
566
|
+
apiVersion: networking.k8s.io/v1
|
|
567
|
+
kind: Ingress
|
|
568
|
+
metadata:
|
|
569
|
+
name: ${profile.target.release}-api-paths
|
|
570
|
+
annotations:
|
|
571
|
+
traefik.ingress.kubernetes.io/router.middlewares: ${quote(`${profile.target.namespace}-${middleware}@kubernetescrd`)}
|
|
572
|
+
spec:
|
|
573
|
+
ingressClassName: ${quote(profile.exposure.ingressClassName)}
|
|
574
|
+
${tls} rules:
|
|
575
|
+
- host: ${quote(profile.exposure.host)}
|
|
576
|
+
http:
|
|
577
|
+
paths:
|
|
578
|
+
${paths}`;
|
|
579
|
+
}
|
|
528
580
|
function exposure(profile) {
|
|
529
581
|
const webService = `${profile.target.release}-web`;
|
|
530
582
|
const apiService = `${profile.target.release}-api`;
|
|
@@ -548,13 +600,7 @@ spec:
|
|
|
548
600
|
`
|
|
549
601
|
: "";
|
|
550
602
|
const webSocketPaths = profile.exposure.webSocketPaths
|
|
551
|
-
.map((path) =>
|
|
552
|
-
pathType: Exact
|
|
553
|
-
backend:
|
|
554
|
-
service:
|
|
555
|
-
name: ${apiService}
|
|
556
|
-
port: { number: 3000 }
|
|
557
|
-
`)
|
|
603
|
+
.map((path) => ingressPath(path, "Exact", apiService))
|
|
558
604
|
.join("");
|
|
559
605
|
return `apiVersion: networking.k8s.io/v1
|
|
560
606
|
kind: Ingress
|
|
@@ -572,7 +618,7 @@ ${webSocketPaths} - path: /
|
|
|
572
618
|
service:
|
|
573
619
|
name: ${webService}
|
|
574
620
|
port: { number: 3000 }
|
|
575
|
-
`;
|
|
621
|
+
${apiPathIngress(profile, tls)}`;
|
|
576
622
|
}
|
|
577
623
|
function applicationManifest(profile, images, rolloutStateDigest, workerCommand) {
|
|
578
624
|
const runtimeConfigDigest = derivedRuntimeConfigDigest(profile);
|
package/dist/deploy.js
CHANGED
|
@@ -13,6 +13,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
13
13
|
const node_fs_1 = require("node:fs");
|
|
14
14
|
const node_os_1 = require("node:os");
|
|
15
15
|
const node_path_1 = require("node:path");
|
|
16
|
+
const api_paths_1 = require("./api-paths");
|
|
16
17
|
const deploy_profile_1 = require("./deploy-profile");
|
|
17
18
|
const deploy_render_1 = require("./deploy-render");
|
|
18
19
|
function defaultRunner(command, args, options) {
|
|
@@ -168,6 +169,25 @@ function doctorDeployment(projectRoot, profileName, runner = defaultRunner) {
|
|
|
168
169
|
? `IngressClass ${profile.exposure.ingressClassName} exists.`
|
|
169
170
|
: `IngressClass ${profile.exposure.ingressClassName} is missing or inaccessible.`,
|
|
170
171
|
});
|
|
172
|
+
if (profile.exposure.apiPaths.length > 0) {
|
|
173
|
+
// Direct API paths remove /api with a Traefik Middleware resource.
|
|
174
|
+
const middlewareCrd = runner("kubectl", [
|
|
175
|
+
"--context",
|
|
176
|
+
profile.target.context,
|
|
177
|
+
"get",
|
|
178
|
+
"crd",
|
|
179
|
+
"middlewares.traefik.io",
|
|
180
|
+
"-o",
|
|
181
|
+
"name",
|
|
182
|
+
], { capture: true });
|
|
183
|
+
findings.push({
|
|
184
|
+
code: "crd:middlewares.traefik.io",
|
|
185
|
+
ok: middlewareCrd.status === 0,
|
|
186
|
+
message: middlewareCrd.status === 0
|
|
187
|
+
? "Traefik Middleware resources are available for exposure.apiPaths."
|
|
188
|
+
: "exposure.apiPaths needs the Traefik Middleware CRD (middlewares.traefik.io); serve the IngressClass with Traefik or remove exposure.apiPaths.",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
171
191
|
if (profile.exposure.tlsSecretName) {
|
|
172
192
|
const tlsType = runner("kubectl", kubectlArgs(profile, [
|
|
173
193
|
"get",
|
|
@@ -465,7 +485,7 @@ function planDeployment(projectRoot, profileName, release, runner = defaultRunne
|
|
|
465
485
|
{
|
|
466
486
|
order: 3,
|
|
467
487
|
kind: "application",
|
|
468
|
-
description: `Roll out API, web${profile.workloads.worker ? ", and worker" : ""} workloads with zero unavailable replicas
|
|
488
|
+
description: `Roll out API, web${profile.workloads.worker ? ", and worker" : ""} workloads with zero unavailable replicas.${(0, api_paths_1.directApiRoutes)(profile.exposure.apiPaths)}`,
|
|
469
489
|
},
|
|
470
490
|
{
|
|
471
491
|
order: 4,
|
package/dist/dev.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export interface DevConfig {
|
|
|
3
3
|
hostname: string;
|
|
4
4
|
publicUrl?: string;
|
|
5
5
|
webSocketPaths: string[];
|
|
6
|
+
/** Path prefixes under /api/ that skip the web proxy and reach the API directly. */
|
|
7
|
+
apiPaths: string[];
|
|
6
8
|
}
|
|
7
9
|
export interface DevRuntime {
|
|
8
10
|
alias: string;
|
|
@@ -24,5 +26,17 @@ export declare function resolveDevRuntime(projectRoot: string): DevRuntime;
|
|
|
24
26
|
export declare function installedDevProfiles(projectRoot: string): string[];
|
|
25
27
|
export declare function renderRuntimeCompose(runtime: DevRuntime): string;
|
|
26
28
|
export declare function renderRoute(runtime: DevRuntime): string;
|
|
29
|
+
/**
|
|
30
|
+
* Create the untracked `.env.docker` from the committed `.env.docker.example`
|
|
31
|
+
* when it is missing, filling every empty `*_SECRET` value with a random one.
|
|
32
|
+
* An existing file is never changed. Returns true when the file was created.
|
|
33
|
+
*/
|
|
34
|
+
export declare function ensureContainerEnv(projectRoot: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Process IDs of the Compose Watch that `podo dev watch` started for this
|
|
37
|
+
* project. The project-specific runtime overlay path in the command line
|
|
38
|
+
* identifies it, so watches of other projects never match.
|
|
39
|
+
*/
|
|
40
|
+
export declare function findWatchProcesses(runtime: DevRuntime, psOutput: string): number[];
|
|
27
41
|
export declare function runDevCommand(projectRoot: string, action: string | undefined, args: string[], runner?: CommandRunner): void;
|
|
28
42
|
export {};
|
package/dist/dev.js
CHANGED
|
@@ -5,12 +5,15 @@ exports.resolveDevRuntime = resolveDevRuntime;
|
|
|
5
5
|
exports.installedDevProfiles = installedDevProfiles;
|
|
6
6
|
exports.renderRuntimeCompose = renderRuntimeCompose;
|
|
7
7
|
exports.renderRoute = renderRoute;
|
|
8
|
+
exports.ensureContainerEnv = ensureContainerEnv;
|
|
9
|
+
exports.findWatchProcesses = findWatchProcesses;
|
|
8
10
|
exports.runDevCommand = runDevCommand;
|
|
9
11
|
const node_child_process_1 = require("node:child_process");
|
|
10
12
|
const node_crypto_1 = require("node:crypto");
|
|
11
13
|
const node_fs_1 = require("node:fs");
|
|
12
14
|
const node_os_1 = require("node:os");
|
|
13
15
|
const node_path_1 = require("node:path");
|
|
16
|
+
const api_paths_1 = require("./api-paths");
|
|
14
17
|
const lockfile_1 = require("./lockfile");
|
|
15
18
|
const websocket_paths_1 = require("./websocket-paths");
|
|
16
19
|
const GATEWAY_CONTAINER = "podokit-dev-gateway";
|
|
@@ -21,6 +24,9 @@ const GATEWAY_VERSION_LABEL = "io.podosoft.podokit.dev-gateway.version";
|
|
|
21
24
|
const GATEWAY_NETWORK = "podokit-dev-gateway";
|
|
22
25
|
const RUNTIME_COMPOSE = ".podokit/runtime/dev.compose.yaml";
|
|
23
26
|
const DEV_CONFIG = ".podokit/dev.json";
|
|
27
|
+
const CONTAINER_ENV = ".env.docker";
|
|
28
|
+
const CONTAINER_ENV_EXAMPLE = ".env.docker.example";
|
|
29
|
+
const GENERATED_SECRET = /^([A-Z][A-Z0-9_]*_SECRET)=[ \t]*(\r?)$/gm;
|
|
24
30
|
const DEV_PROFILE_BY_MODULE = new Map([
|
|
25
31
|
["redis", "cache"],
|
|
26
32
|
["object-storage-s3", "storage"],
|
|
@@ -81,16 +87,23 @@ function readDevConfig(projectRoot) {
|
|
|
81
87
|
throw new Error("PodoKit manifest is missing answers.projectName.");
|
|
82
88
|
const config = (0, node_fs_1.existsSync)(path)
|
|
83
89
|
? parseJson(path)
|
|
84
|
-
: {
|
|
90
|
+
: {
|
|
91
|
+
schemaVersion: 1,
|
|
92
|
+
hostname: `${fallbackName}.localhost`,
|
|
93
|
+
webSocketPaths: [],
|
|
94
|
+
apiPaths: [],
|
|
95
|
+
};
|
|
85
96
|
if (config.schemaVersion !== 1) {
|
|
86
97
|
throw new Error(`Unsupported ${DEV_CONFIG} schemaVersion: ${String(config.schemaVersion)}`);
|
|
87
98
|
}
|
|
88
99
|
validateHostname(config.hostname);
|
|
89
100
|
if (config.publicUrl)
|
|
90
101
|
validatePublicUrl(config.publicUrl);
|
|
102
|
+
const webSocketPaths = (0, websocket_paths_1.parseExactWebSocketPaths)(config.webSocketPaths ?? [], `${DEV_CONFIG}.webSocketPaths`);
|
|
91
103
|
return {
|
|
92
104
|
...config,
|
|
93
|
-
webSocketPaths
|
|
105
|
+
webSocketPaths,
|
|
106
|
+
apiPaths: (0, api_paths_1.parseApiPaths)(config.apiPaths ?? [], `${DEV_CONFIG}.apiPaths`, webSocketPaths),
|
|
94
107
|
};
|
|
95
108
|
}
|
|
96
109
|
function stableId(projectRoot) {
|
|
@@ -123,8 +136,15 @@ function installedDevProfiles(projectRoot) {
|
|
|
123
136
|
.filter((profile) => profile !== undefined)),
|
|
124
137
|
];
|
|
125
138
|
}
|
|
139
|
+
function routesToApi(config) {
|
|
140
|
+
return config.webSocketPaths.length > 0 || config.apiPaths.length > 0;
|
|
141
|
+
}
|
|
142
|
+
/** Traefik v3 matchers take one value each, so several paths are joined with ||. */
|
|
143
|
+
function anyOf(matchers) {
|
|
144
|
+
return matchers.length > 1 ? `(${matchers.join(" || ")})` : (matchers[0] ?? "");
|
|
145
|
+
}
|
|
126
146
|
function renderRuntimeCompose(runtime) {
|
|
127
|
-
const api = runtime.config
|
|
147
|
+
const api = routesToApi(runtime.config)
|
|
128
148
|
? ` api:\n networks:\n ${GATEWAY_NETWORK}:\n aliases: [${runtime.alias}-api]\n`
|
|
129
149
|
: "";
|
|
130
150
|
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}]\n${api}networks:\n ${GATEWAY_NETWORK}:\n external: true\n name: ${GATEWAY_NETWORK}\n`;
|
|
@@ -133,14 +153,24 @@ function renderRoute(runtime) {
|
|
|
133
153
|
const { alias, config, routeId } = runtime;
|
|
134
154
|
const hosts = routeHosts(config);
|
|
135
155
|
const hostRule = hosts.map((host) => `Host(\`${host}\`)`).join(" || ");
|
|
136
|
-
const
|
|
156
|
+
const scopedHostRule = hosts.length > 1 ? `(${hostRule})` : hostRule;
|
|
137
157
|
const webSocketRouter = config.webSocketPaths.length
|
|
138
|
-
? ` ${routeId}-websocket:\n entryPoints: [web]\n rule: ${
|
|
158
|
+
? ` ${routeId}-websocket:\n entryPoints: [web]\n rule: ${scopedHostRule} && ${anyOf(config.webSocketPaths.map((path) => `Path(\`${path}\`)`))}\n priority: 100\n service: ${routeId}-api\n`
|
|
159
|
+
: "";
|
|
160
|
+
const apiPathMatchers = config.apiPaths.flatMap((path) => {
|
|
161
|
+
const base = (0, api_paths_1.apiPathBase)(path);
|
|
162
|
+
return [`Path(\`${base}\`)`, `PathPrefix(\`${base}/\`)`];
|
|
163
|
+
});
|
|
164
|
+
const apiPathRouter = config.apiPaths.length
|
|
165
|
+
? ` ${routeId}-api-paths:\n entryPoints: [web]\n rule: ${scopedHostRule} && ${anyOf(apiPathMatchers)}\n priority: 100\n middlewares: [${routeId}-api-prefix]\n service: ${routeId}-api\n`
|
|
139
166
|
: "";
|
|
140
|
-
const apiService = config
|
|
167
|
+
const apiService = routesToApi(config)
|
|
141
168
|
? ` ${routeId}-api:\n loadBalancer:\n servers:\n - url: http://${alias}-api:5002\n`
|
|
142
169
|
: "";
|
|
143
|
-
|
|
170
|
+
const apiPrefixMiddleware = config.apiPaths.length
|
|
171
|
+
? ` ${routeId}-api-prefix:\n stripPrefix:\n prefixes: ["${api_paths_1.API_PATH_PREFIX}"]\n`
|
|
172
|
+
: "";
|
|
173
|
+
return `http:\n routers:\n${webSocketRouter}${apiPathRouter} ${routeId}:\n entryPoints: [web]\n rule: ${hostRule}\n service: ${routeId}\n middlewares: [${routeId}-compression]\n services:\n${apiService} ${routeId}:\n loadBalancer:\n servers:\n - url: http://${alias}:5001\n middlewares:\n ${routeId}-compression:\n compress: {}\n${apiPrefixMiddleware}`;
|
|
144
174
|
}
|
|
145
175
|
function devHome() {
|
|
146
176
|
return process.env.PODOKIT_DEV_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".podokit", "dev-gateway");
|
|
@@ -182,6 +212,28 @@ function activeEntries() {
|
|
|
182
212
|
}
|
|
183
213
|
return entries;
|
|
184
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Create the untracked `.env.docker` from the committed `.env.docker.example`
|
|
217
|
+
* when it is missing, filling every empty `*_SECRET` value with a random one.
|
|
218
|
+
* An existing file is never changed. Returns true when the file was created.
|
|
219
|
+
*/
|
|
220
|
+
function ensureContainerEnv(projectRoot) {
|
|
221
|
+
const target = (0, node_path_1.join)(projectRoot, CONTAINER_ENV);
|
|
222
|
+
if ((0, node_fs_1.existsSync)(target))
|
|
223
|
+
return false;
|
|
224
|
+
const example = (0, node_path_1.join)(projectRoot, CONTAINER_ENV_EXAMPLE);
|
|
225
|
+
if (!(0, node_fs_1.existsSync)(example)) {
|
|
226
|
+
const compose = (0, node_path_1.join)(projectRoot, "compose.dev.yaml");
|
|
227
|
+
if ((0, node_fs_1.existsSync)(compose) && (0, node_fs_1.readFileSync)(compose, "utf8").includes(CONTAINER_ENV)) {
|
|
228
|
+
throw new Error(`${CONTAINER_ENV} is missing and there is no ${CONTAINER_ENV_EXAMPLE} to create it from. Run podo update --apply to add the example, or create ${CONTAINER_ENV} by hand.`);
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
const content = (0, node_fs_1.readFileSync)(example, "utf8").replace(GENERATED_SECRET, (_line, name, carriageReturn) => `${name}=${(0, node_crypto_1.randomBytes)(32).toString("base64url")}${carriageReturn}`);
|
|
233
|
+
(0, node_fs_1.writeFileSync)(target, content, { mode: 0o600 });
|
|
234
|
+
process.stdout.write(`Created ${CONTAINER_ENV} from ${CONTAINER_ENV_EXAMPLE} with generated secrets.\n`);
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
185
237
|
function writeRuntimeFiles(runtime) {
|
|
186
238
|
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(runtime.projectRoot, ".podokit", "runtime"), { recursive: true });
|
|
187
239
|
(0, node_fs_1.writeFileSync)(runtime.runtimeComposePath, renderRuntimeCompose(runtime));
|
|
@@ -296,6 +348,69 @@ function runCompose(runtime, args, runner) {
|
|
|
296
348
|
throw new Error(`docker compose ${args.join(" ")} exited with status ${String(result.status)}.`);
|
|
297
349
|
}
|
|
298
350
|
}
|
|
351
|
+
// Exit statuses of a Compose process stopped by SIGINT or SIGTERM.
|
|
352
|
+
const STOPPED_STATUSES = new Set([130, 143]);
|
|
353
|
+
const WATCH_STOP_TIMEOUT_MS = 10_000;
|
|
354
|
+
function pause(milliseconds) {
|
|
355
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Process IDs of the Compose Watch that `podo dev watch` started for this
|
|
359
|
+
* project. The project-specific runtime overlay path in the command line
|
|
360
|
+
* identifies it, so watches of other projects never match.
|
|
361
|
+
*/
|
|
362
|
+
function findWatchProcesses(runtime, psOutput) {
|
|
363
|
+
const prefix = `compose ${composeArgs(runtime, []).slice(1).join(" ")} `;
|
|
364
|
+
const pids = [];
|
|
365
|
+
for (const line of psOutput.split("\n")) {
|
|
366
|
+
const match = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
367
|
+
if (!match?.[1] || !match[2])
|
|
368
|
+
continue;
|
|
369
|
+
const start = match[2].indexOf(prefix);
|
|
370
|
+
if (start === -1)
|
|
371
|
+
continue;
|
|
372
|
+
const tokens = match[2].slice(start + prefix.length).split(/\s+/);
|
|
373
|
+
let index = 0;
|
|
374
|
+
while (tokens[index] === "--profile" || tokens[index]?.startsWith("--profile=")) {
|
|
375
|
+
index += tokens[index] === "--profile" ? 2 : 1;
|
|
376
|
+
}
|
|
377
|
+
if (tokens[index] === "watch")
|
|
378
|
+
pids.push(Number(match[1]));
|
|
379
|
+
}
|
|
380
|
+
return pids;
|
|
381
|
+
}
|
|
382
|
+
function anyAlive(pids, runner) {
|
|
383
|
+
return pids.some((pid) => commandSucceeded(runner("kill", ["-0", String(pid)], { capture: true })));
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Stop this project's Compose Watch. Compose holds a per-project lock for as
|
|
387
|
+
* long as it watches, so a watch left behind by `down` made the next
|
|
388
|
+
* `podo dev watch` fail with "cannot take exclusive lock".
|
|
389
|
+
*/
|
|
390
|
+
function stopWatch(runtime, runner) {
|
|
391
|
+
let listing;
|
|
392
|
+
try {
|
|
393
|
+
listing = runner("ps", ["-A", "-ww", "-o", "pid=,args="], { capture: true });
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (!commandSucceeded(listing))
|
|
399
|
+
return;
|
|
400
|
+
const pids = findWatchProcesses(runtime, listing.stdout);
|
|
401
|
+
if (pids.length === 0)
|
|
402
|
+
return;
|
|
403
|
+
runner("kill", ["-TERM", ...pids.map(String)], { capture: true });
|
|
404
|
+
const deadline = Date.now() + WATCH_STOP_TIMEOUT_MS;
|
|
405
|
+
while (anyAlive(pids, runner)) {
|
|
406
|
+
if (Date.now() >= deadline) {
|
|
407
|
+
runner("kill", ["-KILL", ...pids.map(String)], { capture: true });
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
pause(100);
|
|
411
|
+
}
|
|
412
|
+
process.stdout.write(`Stopped podo dev watch for this project (pid ${pids.join(", ")}).\n`);
|
|
413
|
+
}
|
|
299
414
|
function composeCommand(action, args, installedProfiles) {
|
|
300
415
|
const global = [];
|
|
301
416
|
const command = [];
|
|
@@ -320,6 +435,12 @@ function composeCommand(action, args, installedProfiles) {
|
|
|
320
435
|
command.push(value);
|
|
321
436
|
}
|
|
322
437
|
}
|
|
438
|
+
// Compose Watch syncs edits into the running containers, not into the image.
|
|
439
|
+
// A plain `up` that recreates a container would start it from the last
|
|
440
|
+
// built image and drop those edits, so `up` rebuilds unless told otherwise.
|
|
441
|
+
if (action === "up" && !command.includes("--build") && !command.includes("--no-build")) {
|
|
442
|
+
command.unshift("--build");
|
|
443
|
+
}
|
|
323
444
|
const activeProfiles = action === "down"
|
|
324
445
|
? ["--profile", "*"]
|
|
325
446
|
: installedProfiles
|
|
@@ -333,6 +454,7 @@ function stopGatewayWhenUnused(runner) {
|
|
|
333
454
|
runner("docker", ["rm", "--force", GATEWAY_CONTAINER], { capture: true });
|
|
334
455
|
runner("docker", ["network", "rm", GATEWAY_NETWORK], { capture: true });
|
|
335
456
|
}
|
|
457
|
+
const DOCKER_ACTIONS = new Set(["watch", "up", "exec", "logs", "ps", "down"]);
|
|
336
458
|
function runDevCommand(projectRoot, action, args, runner = defaultRunner) {
|
|
337
459
|
const runtime = resolveDevRuntime(projectRoot);
|
|
338
460
|
const profiles = installedDevProfiles(projectRoot);
|
|
@@ -343,6 +465,16 @@ function runDevCommand(projectRoot, action, args, runner = defaultRunner) {
|
|
|
343
465
|
process.stdout.write(`${runtime.config.publicUrl}\n`);
|
|
344
466
|
return;
|
|
345
467
|
}
|
|
468
|
+
if (!DOCKER_ACTIONS.has(action ?? "") && action !== "env") {
|
|
469
|
+
throw new Error("Usage: podo dev <watch|up|exec|logs|ps|down|url|env> [docker compose options]");
|
|
470
|
+
}
|
|
471
|
+
// Git ignores .env.docker, so a fresh clone gets it from the committed example.
|
|
472
|
+
const createdEnv = ensureContainerEnv(projectRoot);
|
|
473
|
+
if (action === "env") {
|
|
474
|
+
if (!createdEnv)
|
|
475
|
+
process.stdout.write(`${CONTAINER_ENV} already exists; left unchanged.\n`);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
346
478
|
ensureDocker(runner);
|
|
347
479
|
if (action === "watch") {
|
|
348
480
|
ensureNetwork(runner);
|
|
@@ -352,7 +484,18 @@ function runDevCommand(projectRoot, action, args, runner = defaultRunner) {
|
|
|
352
484
|
process.stdout.write(`Development URL: http://${runtime.config.hostname}\n`);
|
|
353
485
|
if (runtime.config.publicUrl)
|
|
354
486
|
process.stdout.write(`Public OAuth URL: ${runtime.config.publicUrl}\n`);
|
|
355
|
-
|
|
487
|
+
const watchArgs = composeCommand("watch", args, profiles);
|
|
488
|
+
const result = runner("docker", composeArgs(runtime, watchArgs), { capture: false });
|
|
489
|
+
if (!commandSucceeded(result)) {
|
|
490
|
+
// `podo dev down` or Ctrl+C ends the watch with SIGTERM or SIGINT.
|
|
491
|
+
if (result.status !== null && STOPPED_STATUSES.has(result.status)) {
|
|
492
|
+
unregisterRoute(runtime);
|
|
493
|
+
stopGatewayWhenUnused(runner);
|
|
494
|
+
process.stdout.write("Stopped watching.\n");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
throw new Error(`docker compose ${watchArgs.join(" ")} exited with status ${String(result.status)}.`);
|
|
498
|
+
}
|
|
356
499
|
}
|
|
357
500
|
catch (error) {
|
|
358
501
|
unregisterRoute(runtime);
|
|
@@ -379,6 +522,7 @@ function runDevCommand(projectRoot, action, args, runner = defaultRunner) {
|
|
|
379
522
|
return;
|
|
380
523
|
}
|
|
381
524
|
if (action === "down") {
|
|
525
|
+
stopWatch(runtime, runner);
|
|
382
526
|
runCompose(runtime, composeCommand("down", args, profiles), runner);
|
|
383
527
|
unregisterRoute(runtime);
|
|
384
528
|
stopGatewayWhenUnused(runner);
|
|
@@ -386,7 +530,5 @@ function runDevCommand(projectRoot, action, args, runner = defaultRunner) {
|
|
|
386
530
|
}
|
|
387
531
|
if (action === "exec" || action === "logs" || action === "ps") {
|
|
388
532
|
runCompose(runtime, composeCommand(action, args, profiles), runner);
|
|
389
|
-
return;
|
|
390
533
|
}
|
|
391
|
-
throw new Error("Usage: podo dev <watch|up|exec|logs|ps|down|url> [docker compose options]");
|
|
392
534
|
}
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const deploy_compose_render_1 = require("./deploy-compose-render");
|
|
|
23
23
|
const deploy_compose_1 = require("./deploy-compose");
|
|
24
24
|
const deploy_compose_sync_1 = require("./deploy-compose-sync");
|
|
25
25
|
const provider_1 = require("./provider");
|
|
26
|
+
const module_api_paths_1 = require("./module-api-paths");
|
|
26
27
|
const HELP = `podo — PodoKit project generator
|
|
27
28
|
|
|
28
29
|
Usage:
|
|
@@ -219,6 +220,11 @@ function parseArgs(argv) {
|
|
|
219
220
|
parsed.positionals = positionals;
|
|
220
221
|
return parsed;
|
|
221
222
|
}
|
|
223
|
+
function writeApiPaths(heading, result, verb) {
|
|
224
|
+
const lines = (0, module_api_paths_1.describeApiPaths)(result, verb);
|
|
225
|
+
if (lines.length)
|
|
226
|
+
process.stdout.write(`\n${heading}\n${lines.join("\n")}\n`);
|
|
227
|
+
}
|
|
222
228
|
function fail(message) {
|
|
223
229
|
process.stderr.write(`error: ${message}\n`);
|
|
224
230
|
process.exit(1);
|
|
@@ -402,6 +408,7 @@ async function main(argv) {
|
|
|
402
408
|
if (result.adopted.length) {
|
|
403
409
|
process.stdout.write(`\nAdopted as module-managed file(s):\n${result.adopted.map((file) => ` ${file}`).join("\n")}\n`);
|
|
404
410
|
}
|
|
411
|
+
writeApiPaths("Direct API paths (the gateway sends them to the API without the web proxy):", result.apiPaths, "add");
|
|
405
412
|
if (result.instructions.length) {
|
|
406
413
|
process.stdout.write(`\nNext steps:\n${result.instructions.map((i) => ` ${i}`).join("\n")}\n`);
|
|
407
414
|
}
|
|
@@ -428,6 +435,7 @@ async function main(argv) {
|
|
|
428
435
|
result.keptEdited.map((f) => ` ${f}`).join("\n") +
|
|
429
436
|
"\n");
|
|
430
437
|
}
|
|
438
|
+
writeApiPaths("Direct API paths:", result.apiPaths, "remove");
|
|
431
439
|
}
|
|
432
440
|
catch (err) {
|
|
433
441
|
fail(err.message);
|
|
@@ -652,9 +660,13 @@ async function main(argv) {
|
|
|
652
660
|
try {
|
|
653
661
|
if (args.apply) {
|
|
654
662
|
const result = (0, update_1.applyUpdate)(process.cwd(), templatesDir, { oldTemplatesDir: args.from });
|
|
663
|
+
if (result.addedModules.length) {
|
|
664
|
+
process.stdout.write(`Added required module(s): ${result.addedModules.join(", ")}\n`);
|
|
665
|
+
}
|
|
655
666
|
process.stdout.write(`Applied: ${result.written.length} written, ${result.moved.length} moved, ` +
|
|
656
667
|
`${result.removed.length} removed, ` +
|
|
657
668
|
`${result.merged.length} merged, ${result.conflicts.length} conflict.\n`);
|
|
669
|
+
writeApiPaths("Direct API paths:", result.apiPaths, "add");
|
|
658
670
|
if (result.conflicts.length) {
|
|
659
671
|
process.stdout.write("\nResolve the following, then commit:\n" +
|
|
660
672
|
result.conflicts.map((f) => ` ${f}`).join("\n") +
|
|
@@ -666,7 +678,16 @@ async function main(argv) {
|
|
|
666
678
|
const counts = (0, update_1.summarize)(plan);
|
|
667
679
|
const shown = plan.changes.filter((c) => c.action !== "up-to-date" && c.action !== "skip");
|
|
668
680
|
process.stdout.write(`podo update ${plan.fromVersion} -> ${plan.toVersion} (template: ${plan.template}; modules: ${plan.modules.join(", ") || "none"})\n\n`);
|
|
669
|
-
if (
|
|
681
|
+
if (plan.addedModules.length) {
|
|
682
|
+
process.stdout.write("Modules to add:\n" +
|
|
683
|
+
plan.addedModules.map((module) => ` ${module.name} (${(0, update_1.addedModuleReason)(module)})`).join("\n") +
|
|
684
|
+
"\n\n");
|
|
685
|
+
}
|
|
686
|
+
const apiPathLines = (0, module_api_paths_1.describeApiPaths)(plan.apiPaths, "add");
|
|
687
|
+
if (apiPathLines.length) {
|
|
688
|
+
process.stdout.write(`Direct API paths to add:\n${apiPathLines.join("\n")}\n\n`);
|
|
689
|
+
}
|
|
690
|
+
if (!shown.length && !plan.apiPaths.changed.length) {
|
|
670
691
|
process.stdout.write("Everything is up to date.\n");
|
|
671
692
|
}
|
|
672
693
|
else {
|
|
@@ -674,7 +695,7 @@ async function main(argv) {
|
|
|
674
695
|
const path = c.fromPath ? `${c.fromPath} -> ${c.path}` : c.path;
|
|
675
696
|
process.stdout.write(` ${c.action.padEnd(9)} ${path} (${c.note})\n`);
|
|
676
697
|
}
|
|
677
|
-
process.stdout.write(
|
|
698
|
+
process.stdout.write(`${shown.length ? "\n" : ""}${counts.update} update, ${counts.add} add, ${counts.move} move, ` +
|
|
678
699
|
`${counts.remove} remove, ${counts.conflict} conflict. ` +
|
|
679
700
|
`Dry-run — nothing was written. Re-run with --apply to write (use --from <dir> for a 3-way merge).\n`);
|
|
680
701
|
}
|
package/dist/lib.d.ts
CHANGED
|
@@ -6,10 +6,12 @@ export { TEMPLATES, DEFAULT_TEMPLATE, TEMPLATE_NAMES, isKnownTemplate } from "./
|
|
|
6
6
|
export type { TemplateInfo } from "./templates";
|
|
7
7
|
export { listModules, addModule } from "./add";
|
|
8
8
|
export type { ModuleManifest, ModuleMigration, ModulePathMove, ModuleTextReplacement, AddOptions, AddResult, } from "./add";
|
|
9
|
+
export type { ApiPathChange, ApiPathResult, ApiPathSkip, ModuleApiPath, } from "./module-api-paths";
|
|
10
|
+
export { describeApiPaths } from "./module-api-paths";
|
|
9
11
|
export { status, diff, doctor, SUPPORTED_FRAMEWORKS } from "./inspect";
|
|
10
12
|
export type { StatusReport, DoctorFinding } from "./inspect";
|
|
11
|
-
export { planUpdate, summarize } from "./update";
|
|
12
|
-
export type { UpdatePlan, FileChange } from "./update";
|
|
13
|
+
export { addedModuleReason, planUpdate, summarize } from "./update";
|
|
14
|
+
export type { AddedModule, UpdatePlan, FileChange } from "./update";
|
|
13
15
|
export { readDevConfig, renderRoute, renderRuntimeCompose, resolveDevRuntime, runDevCommand } from "./dev";
|
|
14
16
|
export type { CommandRunner, DevConfig, DevRuntime } from "./dev";
|
|
15
17
|
export { applyDeployment, doctorDeployment, getDeploymentStatus, inspectClusterFingerprint, planDeployment, planRollback, rollbackDeployment, verifyDeployment, } from "./deploy";
|
package/dist/lib.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.verifyAnyDeployment = exports.planAnyDeployment = exports.getAnyDeploymentStatus = exports.doctorAnyDeployment = exports.describeDeploymentTarget = exports.verifyComposeDeployment = exports.rollbackComposeDeployment = exports.planComposeRollback = exports.planComposeDeployment = exports.inspectComposeEndpointFingerprint = void 0;
|
|
3
|
+
exports.applyComposeDeployment = exports.renderComposeDeployment = exports.parseComposeProfile = exports.initializeComposeProfile = exports.readDeploymentDriver = exports.loadKubernetesProfile = exports.loadComposeProfile = exports.loadAnyDeploymentProfile = exports.listAnyDeploymentProfiles = exports.assertAnyReleaseTag = exports.DEPLOY_DRIVERS = exports.renderDeployment = exports.loadDeploymentProfile = exports.listDeploymentProfiles = exports.initializeDeploymentProfile = exports.deploymentProfileDigest = exports.assertReleaseTag = exports.verifyDeployment = exports.rollbackDeployment = exports.planRollback = exports.planDeployment = exports.inspectClusterFingerprint = exports.getDeploymentStatus = exports.doctorDeployment = exports.applyDeployment = exports.runDevCommand = exports.resolveDevRuntime = exports.renderRuntimeCompose = exports.renderRoute = exports.readDevConfig = exports.summarize = exports.planUpdate = exports.addedModuleReason = exports.SUPPORTED_FRAMEWORKS = exports.doctor = exports.diff = exports.status = exports.describeApiPaths = exports.addModule = exports.listModules = exports.isKnownTemplate = exports.TEMPLATE_NAMES = exports.DEFAULT_TEMPLATE = exports.TEMPLATES = exports.PROVIDERS_SOURCE = exports.providerSource = exports.planProviderChange = exports.listProviders = exports.applyProviderChange = exports.create = void 0;
|
|
4
|
+
exports.verifyAnyDeployment = exports.planAnyDeployment = exports.getAnyDeploymentStatus = exports.doctorAnyDeployment = exports.describeDeploymentTarget = exports.verifyComposeDeployment = exports.rollbackComposeDeployment = exports.planComposeRollback = exports.planComposeDeployment = exports.inspectComposeEndpointFingerprint = exports.getComposeStatus = exports.doctorComposeDeployment = void 0;
|
|
5
5
|
exports.builtinTemplatesDir = builtinTemplatesDir;
|
|
6
6
|
exports.builtinModulesDir = builtinModulesDir;
|
|
7
7
|
// Library entry point: the reusable functions behind the `podo` CLI, for other
|
|
@@ -23,12 +23,15 @@ Object.defineProperty(exports, "isKnownTemplate", { enumerable: true, get: funct
|
|
|
23
23
|
var add_1 = require("./add");
|
|
24
24
|
Object.defineProperty(exports, "listModules", { enumerable: true, get: function () { return add_1.listModules; } });
|
|
25
25
|
Object.defineProperty(exports, "addModule", { enumerable: true, get: function () { return add_1.addModule; } });
|
|
26
|
+
var module_api_paths_1 = require("./module-api-paths");
|
|
27
|
+
Object.defineProperty(exports, "describeApiPaths", { enumerable: true, get: function () { return module_api_paths_1.describeApiPaths; } });
|
|
26
28
|
var inspect_1 = require("./inspect");
|
|
27
29
|
Object.defineProperty(exports, "status", { enumerable: true, get: function () { return inspect_1.status; } });
|
|
28
30
|
Object.defineProperty(exports, "diff", { enumerable: true, get: function () { return inspect_1.diff; } });
|
|
29
31
|
Object.defineProperty(exports, "doctor", { enumerable: true, get: function () { return inspect_1.doctor; } });
|
|
30
32
|
Object.defineProperty(exports, "SUPPORTED_FRAMEWORKS", { enumerable: true, get: function () { return inspect_1.SUPPORTED_FRAMEWORKS; } });
|
|
31
33
|
var update_1 = require("./update");
|
|
34
|
+
Object.defineProperty(exports, "addedModuleReason", { enumerable: true, get: function () { return update_1.addedModuleReason; } });
|
|
32
35
|
Object.defineProperty(exports, "planUpdate", { enumerable: true, get: function () { return update_1.planUpdate; } });
|
|
33
36
|
Object.defineProperty(exports, "summarize", { enumerable: true, get: function () { return update_1.summarize; } });
|
|
34
37
|
var dev_1 = require("./dev");
|
package/dist/lockfile.d.ts
CHANGED
|
@@ -21,12 +21,15 @@ export interface ManifestModule {
|
|
|
21
21
|
moduleVersion?: string;
|
|
22
22
|
/** Module-local update migrations already applied to this project. */
|
|
23
23
|
appliedMigrations?: string[];
|
|
24
|
+
/** Direct API paths of the module already merged into the app's gateway configuration. */
|
|
25
|
+
appliedApiPaths?: string[];
|
|
24
26
|
}
|
|
25
27
|
export interface ManifestModuleInput {
|
|
26
28
|
name: string;
|
|
27
29
|
packageName?: string;
|
|
28
30
|
moduleVersion?: string;
|
|
29
31
|
appliedMigrations?: string[];
|
|
32
|
+
appliedApiPaths?: string[];
|
|
30
33
|
}
|
|
31
34
|
export interface PodokitManifest {
|
|
32
35
|
schemaVersion: number;
|
|
@@ -116,4 +119,6 @@ export declare function mergeOwnedGlobs(existing: string[], add: string[]): stri
|
|
|
116
119
|
* `ownedGlobs` a module declares are merged into the manifest so its owned paths
|
|
117
120
|
* survive every future recompute. No-op on projects without a manifest.
|
|
118
121
|
*/
|
|
119
|
-
export declare function recordModules(projectRoot: string, moduleInputs: (string | ManifestModuleInput)[], version?: string, ownedGlobs?: string[], lockSnapshot?: RecordModulesLockSnapshot, managedOverrides?: string[]
|
|
122
|
+
export declare function recordModules(projectRoot: string, moduleInputs: (string | ManifestModuleInput)[], version?: string, ownedGlobs?: string[], lockSnapshot?: RecordModulesLockSnapshot, managedOverrides?: string[],
|
|
123
|
+
/** Installed module that newly recorded modules must configure before. */
|
|
124
|
+
insertBefore?: string): void;
|