@podosoft/podokit 0.16.3 → 0.17.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/deploy-compose-exec.d.ts +93 -0
- package/dist/deploy-compose-exec.js +198 -0
- package/dist/deploy-compose-profile.d.ts +107 -0
- package/dist/deploy-compose-profile.js +361 -0
- package/dist/deploy-compose-render.d.ts +48 -0
- package/dist/deploy-compose-render.js +333 -0
- package/dist/deploy-compose-sync.d.ts +144 -0
- package/dist/deploy-compose-sync.js +412 -0
- package/dist/deploy-compose.d.ts +98 -0
- package/dist/deploy-compose.js +667 -0
- package/dist/deploy-dispatch.d.ts +33 -0
- package/dist/deploy-dispatch.js +58 -0
- package/dist/deploy-driver.d.ts +25 -0
- package/dist/deploy-driver.js +78 -0
- package/dist/deploy-profile.d.ts +4 -7
- package/dist/deploy-profile.js +86 -274
- package/dist/deploy-render.js +10 -1
- package/dist/deploy-schema.d.ts +56 -0
- package/dist/deploy-schema.js +261 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +178 -6
- package/dist/lib.d.ts +9 -0
- package/dist/lib.js +30 -1
- package/dist/templates/fullstack-nest-svelte/AGENTS.md +2 -1
- package/dist/templates/fullstack-nest-svelte/Dockerfile.dev +34 -3
- package/dist/templates/fullstack-nest-svelte/apps/api/Dockerfile +68 -7
- package/dist/templates/fullstack-nest-svelte/apps/web/Dockerfile +60 -8
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +2 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/server.js +31 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.test.ts +79 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +24 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/ws-upgrade.js +267 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/ws-upgrade.test.js +134 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +6 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +6 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/vite.config.ts +17 -7
- package/dist/templates/fullstack-nest-svelte/dot-agents/skills/podokit-deploy/SKILL.md +55 -10
- package/dist/templates/fullstack-nest-svelte/dot-agents/skills/podokit-deploy/references/profile.md +50 -1
- package/dist/templates/fullstack-nest-svelte/dot-agents/skills/podokit-deploy/references/release.md +64 -0
- package/dist/templates/fullstack-nest-svelte/dot-agents/skills/podokit-deploy-fast/SKILL.md +92 -0
- package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-deploy/SKILL.md +1 -1
- package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-deploy-fast/SKILL.md +8 -0
- package/dist/templates/fullstack-nest-svelte/dot-dockerignore +7 -0
- package/dist/templates/fullstack-nest-svelte/dot-env.example +7 -0
- package/dist/templates/fullstack-nest-svelte/dot-github/workflows/release.yml +142 -0
- package/dist/templates/fullstack-nest-svelte/infra/k3s/README.md +18 -0
- package/dist/templates/fullstack-nest-svelte/infra/k3s/api-deployment.yaml +6 -1
- package/dist/templates/fullstack-nest-svelte/infra/k3s/ingress.yaml +8 -0
- package/dist/templates/fullstack-nest-svelte/infra/k3s/web-deployment.yaml +11 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/account-page.svelte +3 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/device-label.test.ts +62 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/device-label.ts +50 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/sessions/+page.svelte +3 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/users/+page.svelte +3 -2
- package/dist/templates/modules/bullmq/files/infra/k3s/worker-deployment.yaml +2 -1
- package/dist/templates/todo/AGENTS.md +2 -1
- package/dist/templates/todo/Dockerfile.dev +34 -3
- package/dist/templates/todo/apps/api/Dockerfile +68 -7
- package/dist/templates/todo/apps/web/Dockerfile +60 -8
- package/dist/templates/todo/apps/web/package.json +2 -1
- package/dist/templates/todo/apps/web/server.js +31 -0
- package/dist/templates/todo/apps/web/src/lib/server/api.ts +2 -2
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.test.ts +79 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +24 -1
- package/dist/templates/todo/apps/web/src/lib/server/ws-upgrade.js +267 -0
- package/dist/templates/todo/apps/web/src/lib/server/ws-upgrade.test.js +134 -0
- package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +6 -2
- package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +6 -2
- package/dist/templates/todo/apps/web/vite.config.ts +17 -7
- package/dist/templates/todo/dot-agents/skills/podokit-deploy/SKILL.md +55 -10
- package/dist/templates/todo/dot-agents/skills/podokit-deploy/references/profile.md +47 -0
- package/dist/templates/todo/dot-agents/skills/podokit-deploy/references/release.md +64 -0
- package/dist/templates/todo/dot-agents/skills/podokit-deploy-fast/SKILL.md +92 -0
- package/dist/templates/todo/dot-claude/skills/podokit-deploy/SKILL.md +1 -1
- package/dist/templates/todo/dot-claude/skills/podokit-deploy-fast/SKILL.md +8 -0
- package/dist/templates/todo/dot-dockerignore +7 -0
- package/dist/templates/todo/dot-env.example +7 -0
- package/dist/templates/todo/dot-github/workflows/release.yml +142 -0
- package/dist/templates/todo/infra/k3s/README.md +18 -0
- package/dist/templates/todo/infra/k3s/api-deployment.yaml +6 -1
- package/dist/templates/todo/infra/k3s/ingress.yaml +8 -0
- package/dist/templates/todo/infra/k3s/web-deployment.yaml +11 -1
- package/package.json +1 -1
package/dist/deploy-profile.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.profileDirectory =
|
|
4
|
-
exports.profilePath = profilePath;
|
|
3
|
+
exports.profilePath = exports.profileDirectory = void 0;
|
|
5
4
|
exports.loadDeploymentProfile = loadDeploymentProfile;
|
|
6
5
|
exports.listDeploymentProfiles = listDeploymentProfiles;
|
|
7
6
|
exports.initializeDeploymentProfile = initializeDeploymentProfile;
|
|
@@ -11,134 +10,24 @@ const node_crypto_1 = require("node:crypto");
|
|
|
11
10
|
const node_fs_1 = require("node:fs");
|
|
12
11
|
const node_path_1 = require("node:path");
|
|
13
12
|
const lockfile_1 = require("./lockfile");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const deploy_schema_1 = require("./deploy-schema");
|
|
14
|
+
var deploy_schema_2 = require("./deploy-schema");
|
|
15
|
+
Object.defineProperty(exports, "profileDirectory", { enumerable: true, get: function () { return deploy_schema_2.profileDirectory; } });
|
|
16
|
+
Object.defineProperty(exports, "profilePath", { enumerable: true, get: function () { return deploy_schema_2.profilePath; } });
|
|
18
17
|
const RESOURCE_VALUE = /^[1-9][0-9]*(?:m|Mi|Gi)?$/;
|
|
19
18
|
const STORAGE_VALUE = /^[1-9][0-9]*(?:Mi|Gi|Ti)$/;
|
|
20
|
-
const SENSITIVE_KEY = /(password|passphrase|secret|token|private.?key|credential|access.?key|api.?keys?|signing.?key|encryption.?key)/i;
|
|
21
|
-
const STABLE_SEMVER_TAG_PATTERN = "^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$";
|
|
22
|
-
function containsUrlCredentials(value) {
|
|
23
|
-
if (!value.includes("://"))
|
|
24
|
-
return false;
|
|
25
|
-
try {
|
|
26
|
-
const parsed = new URL(value);
|
|
27
|
-
return parsed.username !== "" || parsed.password !== "";
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function isRecord(value) {
|
|
34
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
-
}
|
|
36
|
-
function assertOnlyKeys(value, allowed, field) {
|
|
37
|
-
const unexpected = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
38
|
-
if (unexpected.length) {
|
|
39
|
-
throw new Error(`Deployment profile field "${field}" contains unknown key(s): ${unexpected.join(", ")}.`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function requiredRecord(parent, key) {
|
|
43
|
-
const value = parent[key];
|
|
44
|
-
if (!isRecord(value))
|
|
45
|
-
throw new Error(`Deployment profile field "${key}" must be an object.`);
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
function requiredString(parent, key) {
|
|
49
|
-
const value = parent[key];
|
|
50
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
51
|
-
throw new Error(`Deployment profile field "${key}" must be a non-empty string.`);
|
|
52
|
-
}
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
function optionalString(parent, key) {
|
|
56
|
-
const value = parent[key];
|
|
57
|
-
if (value === null || value === undefined)
|
|
58
|
-
return null;
|
|
59
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
60
|
-
throw new Error(`Deployment profile field "${key}" must be a string or null.`);
|
|
61
|
-
}
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
function requiredNumber(parent, key) {
|
|
65
|
-
const value = parent[key];
|
|
66
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
67
|
-
throw new Error(`Deployment profile field "${key}" must be a positive integer.`);
|
|
68
|
-
}
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
function parseRequiredKeys(value, field) {
|
|
72
|
-
if (!Array.isArray(value)) {
|
|
73
|
-
throw new Error(`Deployment profile field "${field}" must be a string array.`);
|
|
74
|
-
}
|
|
75
|
-
const keys = value.map((entry) => {
|
|
76
|
-
if (typeof entry !== "string" || !/^[A-Z][A-Z0-9_]*$/.test(entry)) {
|
|
77
|
-
throw new Error(`Deployment profile field "${field}" contains an invalid environment key.`);
|
|
78
|
-
}
|
|
79
|
-
return entry;
|
|
80
|
-
});
|
|
81
|
-
if (new Set(keys).size !== keys.length) {
|
|
82
|
-
throw new Error(`Deployment profile field "${field}" contains duplicate keys.`);
|
|
83
|
-
}
|
|
84
|
-
return keys.sort();
|
|
85
|
-
}
|
|
86
19
|
function parseNamedSecret(value, field) {
|
|
87
|
-
if (!isRecord(value)) {
|
|
20
|
+
if (!(0, deploy_schema_1.isRecord)(value)) {
|
|
88
21
|
throw new Error(`Deployment profile field "${field}" must be an object.`);
|
|
89
22
|
}
|
|
90
|
-
assertOnlyKeys(value, ["name", "requiredKeys"], field);
|
|
23
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["name", "requiredKeys"], field);
|
|
91
24
|
const parsed = {
|
|
92
|
-
name: requiredString(value, "name"),
|
|
93
|
-
requiredKeys: parseRequiredKeys(value.requiredKeys, `${field}.requiredKeys`),
|
|
25
|
+
name: (0, deploy_schema_1.requiredString)(value, "name"),
|
|
26
|
+
requiredKeys: (0, deploy_schema_1.parseRequiredKeys)(value.requiredKeys, `${field}.requiredKeys`),
|
|
94
27
|
};
|
|
95
|
-
validateDnsLabel(parsed.name, `${field}.name`);
|
|
28
|
+
(0, deploy_schema_1.validateDnsLabel)(parsed.name, `${field}.name`);
|
|
96
29
|
return parsed;
|
|
97
30
|
}
|
|
98
|
-
function validateDnsLabel(value, field) {
|
|
99
|
-
if (!DNS_LABEL.test(value)) {
|
|
100
|
-
throw new Error(`Deployment profile field "${field}" must be a Kubernetes DNS label.`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function validateDnsSubdomain(value, field) {
|
|
104
|
-
if (value.length > 253 ||
|
|
105
|
-
!value.split(".").every((label) => DNS_LABEL.test(label))) {
|
|
106
|
-
throw new Error(`Deployment profile field "${field}" must be a Kubernetes DNS subdomain.`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function parseImageReference(value) {
|
|
110
|
-
if (value.length > 512 || value.includes("://") || /[\s\u0000-\u001f\u007f]/.test(value)) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
const at = value.indexOf("@");
|
|
114
|
-
if (at !== -1 && at !== value.lastIndexOf("@"))
|
|
115
|
-
return null;
|
|
116
|
-
const digest = at === -1 ? null : value.slice(at + 1);
|
|
117
|
-
const tagged = at === -1 ? value : value.slice(0, at);
|
|
118
|
-
const lastSlash = tagged.lastIndexOf("/");
|
|
119
|
-
const lastColon = tagged.lastIndexOf(":");
|
|
120
|
-
const hasTag = lastColon > lastSlash;
|
|
121
|
-
const name = hasTag ? tagged.slice(0, lastColon) : tagged;
|
|
122
|
-
const tag = hasTag ? tagged.slice(lastColon + 1) : null;
|
|
123
|
-
if (!IMAGE_NAME.test(name) ||
|
|
124
|
-
(tag !== null && !IMAGE_TAG.test(tag)) ||
|
|
125
|
-
(digest !== null && !/^sha256:[a-f0-9]{64}$/.test(digest))) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
return { name, tag, digest };
|
|
129
|
-
}
|
|
130
|
-
function validateImage(value, field) {
|
|
131
|
-
const parsed = parseImageReference(value);
|
|
132
|
-
if (!parsed || parsed.tag === "latest" || (!parsed.tag && !parsed.digest)) {
|
|
133
|
-
throw new Error(`Deployment profile field "${field}" must use an explicit non-latest image tag or digest.`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
function validateImageRepository(value, field) {
|
|
137
|
-
const parsed = parseImageReference(value);
|
|
138
|
-
if (!parsed || parsed.tag || parsed.digest) {
|
|
139
|
-
throw new Error(`Deployment profile field "${field}" must be an image repository without a tag or digest.`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
31
|
function validateResource(value, field) {
|
|
143
32
|
if (!RESOURCE_VALUE.test(value)) {
|
|
144
33
|
throw new Error(`Deployment profile field "${field}" is not a supported resource quantity.`);
|
|
@@ -150,22 +39,22 @@ function validateStorage(value, field) {
|
|
|
150
39
|
}
|
|
151
40
|
}
|
|
152
41
|
function parseResources(value, field) {
|
|
153
|
-
assertOnlyKeys(value, ["cpuRequest", "cpuLimit", "memoryRequest", "memoryLimit"], field);
|
|
42
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["cpuRequest", "cpuLimit", "memoryRequest", "memoryLimit"], field);
|
|
154
43
|
const parsed = {
|
|
155
|
-
cpuRequest: requiredString(value, "cpuRequest"),
|
|
156
|
-
cpuLimit: requiredString(value, "cpuLimit"),
|
|
157
|
-
memoryRequest: requiredString(value, "memoryRequest"),
|
|
158
|
-
memoryLimit: requiredString(value, "memoryLimit"),
|
|
44
|
+
cpuRequest: (0, deploy_schema_1.requiredString)(value, "cpuRequest"),
|
|
45
|
+
cpuLimit: (0, deploy_schema_1.requiredString)(value, "cpuLimit"),
|
|
46
|
+
memoryRequest: (0, deploy_schema_1.requiredString)(value, "memoryRequest"),
|
|
47
|
+
memoryLimit: (0, deploy_schema_1.requiredString)(value, "memoryLimit"),
|
|
159
48
|
};
|
|
160
49
|
for (const [key, quantity] of Object.entries(parsed))
|
|
161
50
|
validateResource(quantity, `${field}.${key}`);
|
|
162
51
|
return parsed;
|
|
163
52
|
}
|
|
164
53
|
function parseWorkload(value, field) {
|
|
165
|
-
assertOnlyKeys(value, ["replicas", "resources"], field);
|
|
54
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["replicas", "resources"], field);
|
|
166
55
|
return {
|
|
167
|
-
replicas: requiredNumber(value, "replicas"),
|
|
168
|
-
resources: parseResources(requiredRecord(value, "resources"), `${field}.resources`),
|
|
56
|
+
replicas: (0, deploy_schema_1.requiredNumber)(value, "replicas"),
|
|
57
|
+
resources: parseResources((0, deploy_schema_1.requiredRecord)(value, "resources"), `${field}.resources`),
|
|
169
58
|
};
|
|
170
59
|
}
|
|
171
60
|
function parseMode(value, field) {
|
|
@@ -175,16 +64,16 @@ function parseMode(value, field) {
|
|
|
175
64
|
return value;
|
|
176
65
|
}
|
|
177
66
|
function parseStatefulDependency(value, field, extraKeys = []) {
|
|
178
|
-
assertOnlyKeys(value, ["mode", "image", "secretName", "storageClassName", "storageSize", ...extraKeys], field);
|
|
67
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["mode", "image", "secretName", "storageClassName", "storageSize", ...extraKeys], field);
|
|
179
68
|
const parsed = {
|
|
180
69
|
mode: parseMode(value.mode, `${field}.mode`),
|
|
181
|
-
image: requiredString(value, "image"),
|
|
182
|
-
secretName: requiredString(value, "secretName"),
|
|
70
|
+
image: (0, deploy_schema_1.requiredString)(value, "image"),
|
|
71
|
+
secretName: (0, deploy_schema_1.requiredString)(value, "secretName"),
|
|
183
72
|
storageClassName: typeof value.storageClassName === "string" ? value.storageClassName : "",
|
|
184
|
-
storageSize: requiredString(value, "storageSize"),
|
|
73
|
+
storageSize: (0, deploy_schema_1.requiredString)(value, "storageSize"),
|
|
185
74
|
};
|
|
186
|
-
validateImage(parsed.image, `${field}.image`);
|
|
187
|
-
validateDnsLabel(parsed.secretName, `${field}.secretName`);
|
|
75
|
+
(0, deploy_schema_1.validateImage)(parsed.image, `${field}.image`);
|
|
76
|
+
(0, deploy_schema_1.validateDnsLabel)(parsed.secretName, `${field}.secretName`);
|
|
188
77
|
validateStorage(parsed.storageSize, `${field}.storageSize`);
|
|
189
78
|
if (parsed.storageClassName && parsed.storageClassName.length > 253) {
|
|
190
79
|
throw new Error(`Deployment profile field "${field}.storageClassName" is too long.`);
|
|
@@ -194,25 +83,10 @@ function parseStatefulDependency(value, field, extraKeys = []) {
|
|
|
194
83
|
function parseRedisDependency(value, field) {
|
|
195
84
|
return parseStatefulDependency(value, field);
|
|
196
85
|
}
|
|
197
|
-
function validateHttpsOrigin(value) {
|
|
198
|
-
let parsed;
|
|
199
|
-
try {
|
|
200
|
-
parsed = new URL(value);
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
throw new Error(`Deployment verification baseUrl "${value}" is invalid.`);
|
|
204
|
-
}
|
|
205
|
-
if (parsed.username || parsed.password) {
|
|
206
|
-
throw new Error("Deployment verification baseUrl must not contain URL credentials; use a credential-free HTTPS origin.");
|
|
207
|
-
}
|
|
208
|
-
if (parsed.protocol !== "https:" || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
209
|
-
throw new Error("Deployment verification baseUrl must be an HTTPS origin without a path.");
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
86
|
function parseProfile(value) {
|
|
213
|
-
if (!isRecord(value))
|
|
87
|
+
if (!(0, deploy_schema_1.isRecord)(value))
|
|
214
88
|
throw new Error("Deployment profile must be a JSON object.");
|
|
215
|
-
assertOnlyKeys(value, [
|
|
89
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, [
|
|
216
90
|
"schemaVersion",
|
|
217
91
|
"driver",
|
|
218
92
|
"target",
|
|
@@ -221,6 +95,7 @@ function parseProfile(value) {
|
|
|
221
95
|
"workloads",
|
|
222
96
|
"dependencies",
|
|
223
97
|
"secrets",
|
|
98
|
+
"migration",
|
|
224
99
|
"runtimeConfig",
|
|
225
100
|
"verification",
|
|
226
101
|
], "root");
|
|
@@ -230,64 +105,67 @@ function parseProfile(value) {
|
|
|
230
105
|
if (value.driver !== "kubernetes-helm") {
|
|
231
106
|
throw new Error(`Unsupported deployment driver: ${String(value.driver)}`);
|
|
232
107
|
}
|
|
233
|
-
const target = requiredRecord(value, "target");
|
|
234
|
-
const release = requiredRecord(value, "release");
|
|
235
|
-
const exposure = requiredRecord(value, "exposure");
|
|
236
|
-
const workloads = requiredRecord(value, "workloads");
|
|
237
|
-
const dependencies = requiredRecord(value, "dependencies");
|
|
238
|
-
const secrets = requiredRecord(value, "secrets");
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
assertOnlyKeys(
|
|
243
|
-
assertOnlyKeys(
|
|
244
|
-
assertOnlyKeys(
|
|
245
|
-
assertOnlyKeys(
|
|
246
|
-
assertOnlyKeys(
|
|
247
|
-
assertOnlyKeys(
|
|
248
|
-
|
|
249
|
-
|
|
108
|
+
const target = (0, deploy_schema_1.requiredRecord)(value, "target");
|
|
109
|
+
const release = (0, deploy_schema_1.requiredRecord)(value, "release");
|
|
110
|
+
const exposure = (0, deploy_schema_1.requiredRecord)(value, "exposure");
|
|
111
|
+
const workloads = (0, deploy_schema_1.requiredRecord)(value, "workloads");
|
|
112
|
+
const dependencies = (0, deploy_schema_1.requiredRecord)(value, "dependencies");
|
|
113
|
+
const secrets = (0, deploy_schema_1.requiredRecord)(value, "secrets");
|
|
114
|
+
const migration = value.migration === undefined ? undefined : (0, deploy_schema_1.requiredRecord)(value, "migration");
|
|
115
|
+
const verification = (0, deploy_schema_1.requiredRecord)(value, "verification");
|
|
116
|
+
const runtimeConfig = (0, deploy_schema_1.requiredRecord)(value, "runtimeConfig");
|
|
117
|
+
(0, deploy_schema_1.assertOnlyKeys)(target, ["context", "clusterFingerprint", "namespace", "release", "createNamespace"], "target");
|
|
118
|
+
(0, deploy_schema_1.assertOnlyKeys)(release, ["strategy", "tagPattern", "apiRepository", "webRepository"], "release");
|
|
119
|
+
(0, deploy_schema_1.assertOnlyKeys)(exposure, ["mode", "host", "ingressClassName", "tlsSecretName"], "exposure");
|
|
120
|
+
(0, deploy_schema_1.assertOnlyKeys)(workloads, ["api", "web", "worker"], "workloads");
|
|
121
|
+
(0, deploy_schema_1.assertOnlyKeys)(dependencies, ["postgres", "redis", "objectStorage"], "dependencies");
|
|
122
|
+
(0, deploy_schema_1.assertOnlyKeys)(secrets, ["api", "web", "imagePull"], "secrets");
|
|
123
|
+
if (migration)
|
|
124
|
+
(0, deploy_schema_1.assertOnlyKeys)(migration, ["command"], "migration");
|
|
125
|
+
(0, deploy_schema_1.assertOnlyKeys)(verification, ["baseUrl", "checks"], "verification");
|
|
126
|
+
const context = (0, deploy_schema_1.requiredString)(target, "context");
|
|
127
|
+
const clusterFingerprint = (0, deploy_schema_1.requiredString)(target, "clusterFingerprint");
|
|
250
128
|
if (!/^sha256:[a-f0-9]{64}$/.test(clusterFingerprint)) {
|
|
251
129
|
throw new Error("Deployment target.clusterFingerprint must be a sha256 fingerprint.");
|
|
252
130
|
}
|
|
253
|
-
const namespace = requiredString(target, "namespace");
|
|
254
|
-
const releaseName = requiredString(target, "release");
|
|
255
|
-
validateDnsLabel(namespace, "target.namespace");
|
|
256
|
-
validateDnsLabel(releaseName, "target.release");
|
|
131
|
+
const namespace = (0, deploy_schema_1.requiredString)(target, "namespace");
|
|
132
|
+
const releaseName = (0, deploy_schema_1.requiredString)(target, "release");
|
|
133
|
+
(0, deploy_schema_1.validateDnsLabel)(namespace, "target.namespace");
|
|
134
|
+
(0, deploy_schema_1.validateDnsLabel)(releaseName, "target.release");
|
|
257
135
|
if (releaseName.length > 40) {
|
|
258
136
|
throw new Error("Deployment target.release must be at most 40 characters.");
|
|
259
137
|
}
|
|
260
138
|
if (target.createNamespace !== false) {
|
|
261
139
|
throw new Error("Deployment target.createNamespace must be false in schema version 1.");
|
|
262
140
|
}
|
|
263
|
-
const tagPattern = requiredString(release, "tagPattern");
|
|
264
|
-
if (tagPattern !== STABLE_SEMVER_TAG_PATTERN) {
|
|
141
|
+
const tagPattern = (0, deploy_schema_1.requiredString)(release, "tagPattern");
|
|
142
|
+
if (tagPattern !== deploy_schema_1.STABLE_SEMVER_TAG_PATTERN) {
|
|
265
143
|
throw new Error("Deployment release.tagPattern must require stable vMAJOR.MINOR.PATCH tags.");
|
|
266
144
|
}
|
|
267
145
|
if (release.strategy !== "shared-tag") {
|
|
268
146
|
throw new Error("Deployment release.strategy must be shared-tag.");
|
|
269
147
|
}
|
|
270
|
-
const apiRepository = requiredString(release, "apiRepository");
|
|
271
|
-
const webRepository = requiredString(release, "webRepository");
|
|
272
|
-
validateImageRepository(apiRepository, "release.apiRepository");
|
|
273
|
-
validateImageRepository(webRepository, "release.webRepository");
|
|
148
|
+
const apiRepository = (0, deploy_schema_1.requiredString)(release, "apiRepository");
|
|
149
|
+
const webRepository = (0, deploy_schema_1.requiredString)(release, "webRepository");
|
|
150
|
+
(0, deploy_schema_1.validateImageRepository)(apiRepository, "release.apiRepository");
|
|
151
|
+
(0, deploy_schema_1.validateImageRepository)(webRepository, "release.webRepository");
|
|
274
152
|
const exposureMode = exposure.mode;
|
|
275
153
|
if (exposureMode !== "ingress" && exposureMode !== "nodePort") {
|
|
276
154
|
throw new Error("Deployment exposure.mode must be ingress or nodePort.");
|
|
277
155
|
}
|
|
278
|
-
const host = requiredString(exposure, "host");
|
|
156
|
+
const host = (0, deploy_schema_1.requiredString)(exposure, "host");
|
|
279
157
|
if (exposureMode === "ingress") {
|
|
280
|
-
validateDnsSubdomain(host, "exposure.host");
|
|
158
|
+
(0, deploy_schema_1.validateDnsSubdomain)(host, "exposure.host");
|
|
281
159
|
}
|
|
282
|
-
const ingressClassName = requiredString(exposure, "ingressClassName");
|
|
283
|
-
validateDnsSubdomain(ingressClassName, "exposure.ingressClassName");
|
|
284
|
-
const tlsSecretName = optionalString(exposure, "tlsSecretName");
|
|
160
|
+
const ingressClassName = (0, deploy_schema_1.requiredString)(exposure, "ingressClassName");
|
|
161
|
+
(0, deploy_schema_1.validateDnsSubdomain)(ingressClassName, "exposure.ingressClassName");
|
|
162
|
+
const tlsSecretName = (0, deploy_schema_1.optionalString)(exposure, "tlsSecretName");
|
|
285
163
|
if (tlsSecretName)
|
|
286
|
-
validateDnsSubdomain(tlsSecretName, "exposure.tlsSecretName");
|
|
287
|
-
const postgres = parseStatefulDependency(requiredRecord(dependencies, "postgres"), "dependencies.postgres");
|
|
288
|
-
const redis = parseRedisDependency(requiredRecord(dependencies, "redis"), "dependencies.redis");
|
|
289
|
-
const objectStorageRecord = requiredRecord(dependencies, "objectStorage");
|
|
290
|
-
assertOnlyKeys(objectStorageRecord, [
|
|
164
|
+
(0, deploy_schema_1.validateDnsSubdomain)(tlsSecretName, "exposure.tlsSecretName");
|
|
165
|
+
const postgres = parseStatefulDependency((0, deploy_schema_1.requiredRecord)(dependencies, "postgres"), "dependencies.postgres");
|
|
166
|
+
const redis = parseRedisDependency((0, deploy_schema_1.requiredRecord)(dependencies, "redis"), "dependencies.redis");
|
|
167
|
+
const objectStorageRecord = (0, deploy_schema_1.requiredRecord)(dependencies, "objectStorage");
|
|
168
|
+
(0, deploy_schema_1.assertOnlyKeys)(objectStorageRecord, [
|
|
291
169
|
"mode",
|
|
292
170
|
"image",
|
|
293
171
|
"secretName",
|
|
@@ -298,73 +176,13 @@ function parseProfile(value) {
|
|
|
298
176
|
], "dependencies.objectStorage");
|
|
299
177
|
const objectStorage = {
|
|
300
178
|
...parseStatefulDependency(objectStorageRecord, "dependencies.objectStorage", ["bucket", "clientImage"]),
|
|
301
|
-
bucket: requiredString(objectStorageRecord, "bucket"),
|
|
302
|
-
clientImage: requiredString(objectStorageRecord, "clientImage"),
|
|
179
|
+
bucket: (0, deploy_schema_1.requiredString)(objectStorageRecord, "bucket"),
|
|
180
|
+
clientImage: (0, deploy_schema_1.requiredString)(objectStorageRecord, "clientImage"),
|
|
303
181
|
};
|
|
304
|
-
validateDnsLabel(objectStorage.bucket, "dependencies.objectStorage.bucket");
|
|
305
|
-
validateImage(objectStorage.clientImage, "dependencies.objectStorage.clientImage");
|
|
306
|
-
const parsedConfig =
|
|
307
|
-
|
|
308
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
|
|
309
|
-
throw new Error(`Deployment runtimeConfig key "${key}" must be an uppercase environment name.`);
|
|
310
|
-
}
|
|
311
|
-
if (SENSITIVE_KEY.test(key)) {
|
|
312
|
-
throw new Error(`Deployment runtimeConfig key "${key}" may contain a secret; use a Kubernetes Secret.`);
|
|
313
|
-
}
|
|
314
|
-
if (["PORT", "BACKEND_INTERNAL_URL"].includes(key)) {
|
|
315
|
-
throw new Error(`Deployment runtimeConfig key "${key}" is managed by PodoKit.`);
|
|
316
|
-
}
|
|
317
|
-
if (typeof entry !== "string") {
|
|
318
|
-
throw new Error(`Deployment runtimeConfig value "${key}" must be a string.`);
|
|
319
|
-
}
|
|
320
|
-
if (containsUrlCredentials(entry)) {
|
|
321
|
-
throw new Error(`Deployment runtimeConfig value "${key}" contains URL credentials; use a Kubernetes Secret.`);
|
|
322
|
-
}
|
|
323
|
-
parsedConfig[key] = entry;
|
|
324
|
-
}
|
|
325
|
-
const baseUrl = requiredString(verification, "baseUrl");
|
|
326
|
-
validateHttpsOrigin(baseUrl);
|
|
327
|
-
const baseOrigin = new URL(baseUrl).origin;
|
|
328
|
-
const checksValue = verification.checks;
|
|
329
|
-
if (!Array.isArray(checksValue) || checksValue.length === 0) {
|
|
330
|
-
throw new Error("Deployment verification.checks must be a non-empty array.");
|
|
331
|
-
}
|
|
332
|
-
const checks = checksValue.map((entry, index) => {
|
|
333
|
-
if (!isRecord(entry)) {
|
|
334
|
-
throw new Error(`Deployment verification.checks[${index}] must be an object.`);
|
|
335
|
-
}
|
|
336
|
-
assertOnlyKeys(entry, ["path", "expectedStatus", "expectedJson"], `verification.checks[${index}]`);
|
|
337
|
-
const path = requiredString(entry, "path");
|
|
338
|
-
if (!path.startsWith("/") ||
|
|
339
|
-
path.startsWith("//") ||
|
|
340
|
-
path.includes("\\") ||
|
|
341
|
-
/[\u0000-\u001f\u007f]/.test(path) ||
|
|
342
|
-
new URL(path, baseUrl).origin !== baseOrigin) {
|
|
343
|
-
throw new Error("Every deployment verification path must be an absolute URL path.");
|
|
344
|
-
}
|
|
345
|
-
const expectedStatus = requiredNumber(entry, "expectedStatus");
|
|
346
|
-
if (expectedStatus < 100 || expectedStatus > 599) {
|
|
347
|
-
throw new Error("Deployment verification expectedStatus must be an HTTP status.");
|
|
348
|
-
}
|
|
349
|
-
const expectedJsonValue = entry.expectedJson;
|
|
350
|
-
let expectedJson;
|
|
351
|
-
if (expectedJsonValue !== undefined) {
|
|
352
|
-
if (!isRecord(expectedJsonValue)) {
|
|
353
|
-
throw new Error("Deployment verification expectedJson must be an object.");
|
|
354
|
-
}
|
|
355
|
-
expectedJson = {};
|
|
356
|
-
for (const [key, value] of Object.entries(expectedJsonValue)) {
|
|
357
|
-
if (value !== null &&
|
|
358
|
-
typeof value !== "string" &&
|
|
359
|
-
typeof value !== "number" &&
|
|
360
|
-
typeof value !== "boolean") {
|
|
361
|
-
throw new Error("Deployment verification expectedJson values must be scalar.");
|
|
362
|
-
}
|
|
363
|
-
expectedJson[key] = value;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return { path, expectedStatus, ...(expectedJson ? { expectedJson } : {}) };
|
|
367
|
-
});
|
|
182
|
+
(0, deploy_schema_1.validateDnsLabel)(objectStorage.bucket, "dependencies.objectStorage.bucket");
|
|
183
|
+
(0, deploy_schema_1.validateImage)(objectStorage.clientImage, "dependencies.objectStorage.clientImage");
|
|
184
|
+
const parsedConfig = (0, deploy_schema_1.parseRuntimeConfig)(runtimeConfig);
|
|
185
|
+
const { baseUrl, checks } = (0, deploy_schema_1.parseVerification)(verification);
|
|
368
186
|
const profile = {
|
|
369
187
|
schemaVersion: 1,
|
|
370
188
|
driver: "kubernetes-helm",
|
|
@@ -388,11 +206,11 @@ function parseProfile(value) {
|
|
|
388
206
|
tlsSecretName,
|
|
389
207
|
},
|
|
390
208
|
workloads: {
|
|
391
|
-
api: parseWorkload(requiredRecord(workloads, "api"), "workloads.api"),
|
|
392
|
-
web: parseWorkload(requiredRecord(workloads, "web"), "workloads.web"),
|
|
209
|
+
api: parseWorkload((0, deploy_schema_1.requiredRecord)(workloads, "api"), "workloads.api"),
|
|
210
|
+
web: parseWorkload((0, deploy_schema_1.requiredRecord)(workloads, "web"), "workloads.web"),
|
|
393
211
|
worker: workloads.worker === null
|
|
394
212
|
? null
|
|
395
|
-
: parseWorkload(requiredRecord(workloads, "worker"), "workloads.worker"),
|
|
213
|
+
: parseWorkload((0, deploy_schema_1.requiredRecord)(workloads, "worker"), "workloads.worker"),
|
|
396
214
|
},
|
|
397
215
|
dependencies: { postgres, redis, objectStorage },
|
|
398
216
|
secrets: {
|
|
@@ -400,25 +218,19 @@ function parseProfile(value) {
|
|
|
400
218
|
web: secrets.web === null
|
|
401
219
|
? null
|
|
402
220
|
: parseNamedSecret(secrets.web, "secrets.web"),
|
|
403
|
-
imagePull: requiredString(secrets, "imagePull"),
|
|
221
|
+
imagePull: (0, deploy_schema_1.requiredString)(secrets, "imagePull"),
|
|
404
222
|
},
|
|
223
|
+
...(migration
|
|
224
|
+
? { migration: { command: (0, deploy_schema_1.parseCommand)(migration.command, "migration.command") } }
|
|
225
|
+
: {}),
|
|
405
226
|
runtimeConfig: parsedConfig,
|
|
406
227
|
verification: { baseUrl, checks },
|
|
407
228
|
};
|
|
408
|
-
validateDnsLabel(profile.secrets.imagePull, "secrets.imagePull");
|
|
229
|
+
(0, deploy_schema_1.validateDnsLabel)(profile.secrets.imagePull, "secrets.imagePull");
|
|
409
230
|
return profile;
|
|
410
231
|
}
|
|
411
|
-
function profileDirectory(projectRoot) {
|
|
412
|
-
return (0, node_path_1.join)((0, node_path_1.resolve)(projectRoot), ".podokit", "deploy");
|
|
413
|
-
}
|
|
414
|
-
function profilePath(projectRoot, name) {
|
|
415
|
-
if (!PROFILE_NAME.test(name)) {
|
|
416
|
-
throw new Error(`Invalid deployment profile name "${name}".`);
|
|
417
|
-
}
|
|
418
|
-
return (0, node_path_1.join)(profileDirectory(projectRoot), `${name}.json`);
|
|
419
|
-
}
|
|
420
232
|
function loadDeploymentProfile(projectRoot, name) {
|
|
421
|
-
const path = profilePath(projectRoot, name);
|
|
233
|
+
const path = (0, deploy_schema_1.profilePath)(projectRoot, name);
|
|
422
234
|
if (!(0, node_fs_1.existsSync)(path))
|
|
423
235
|
throw new Error(`Deployment profile "${name}" does not exist at ${path}.`);
|
|
424
236
|
let parsed;
|
|
@@ -431,7 +243,7 @@ function loadDeploymentProfile(projectRoot, name) {
|
|
|
431
243
|
return parseProfile(parsed);
|
|
432
244
|
}
|
|
433
245
|
function listDeploymentProfiles(projectRoot) {
|
|
434
|
-
const directory = profileDirectory(projectRoot);
|
|
246
|
+
const directory = (0, deploy_schema_1.profileDirectory)(projectRoot);
|
|
435
247
|
if (!(0, node_fs_1.existsSync)(directory))
|
|
436
248
|
return [];
|
|
437
249
|
return (0, node_fs_1.readdirSync)(directory)
|
|
@@ -449,7 +261,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
449
261
|
const projectName = manifest.answers.projectName;
|
|
450
262
|
if (!projectName)
|
|
451
263
|
throw new Error("PodoKit manifest is missing answers.projectName.");
|
|
452
|
-
const path = profilePath(projectRoot, name);
|
|
264
|
+
const path = (0, deploy_schema_1.profilePath)(projectRoot, name);
|
|
453
265
|
if ((0, node_fs_1.existsSync)(path))
|
|
454
266
|
throw new Error(`Deployment profile "${name}" already exists.`);
|
|
455
267
|
const releaseName = projectName
|
|
@@ -480,7 +292,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
480
292
|
},
|
|
481
293
|
release: {
|
|
482
294
|
strategy: "shared-tag",
|
|
483
|
-
tagPattern: STABLE_SEMVER_TAG_PATTERN,
|
|
295
|
+
tagPattern: deploy_schema_1.STABLE_SEMVER_TAG_PATTERN,
|
|
484
296
|
apiRepository: `ghcr.io/example/${releaseName}-api`,
|
|
485
297
|
webRepository: `ghcr.io/example/${releaseName}-web`,
|
|
486
298
|
},
|
|
@@ -589,7 +401,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
589
401
|
},
|
|
590
402
|
};
|
|
591
403
|
const validated = parseProfile(defaultProfile);
|
|
592
|
-
(0, node_fs_1.mkdirSync)(profileDirectory(projectRoot), { recursive: true });
|
|
404
|
+
(0, node_fs_1.mkdirSync)((0, deploy_schema_1.profileDirectory)(projectRoot), { recursive: true });
|
|
593
405
|
(0, node_fs_1.writeFileSync)(path, `${JSON.stringify(validated, null, 2)}\n`, { flag: "wx" });
|
|
594
406
|
return { name, path, profile: validated };
|
|
595
407
|
}
|
package/dist/deploy-render.js
CHANGED
|
@@ -8,6 +8,14 @@ const deploy_profile_1 = require("./deploy-profile");
|
|
|
8
8
|
function quote(value) {
|
|
9
9
|
return JSON.stringify(value);
|
|
10
10
|
}
|
|
11
|
+
function yamlScalar(value) {
|
|
12
|
+
return /^(?!null$|true$|false$|~$)[A-Za-z_][A-Za-z0-9_./:-]*$/i.test(value)
|
|
13
|
+
? value
|
|
14
|
+
: quote(value);
|
|
15
|
+
}
|
|
16
|
+
function yamlStringSequence(values) {
|
|
17
|
+
return `[${values.map((value) => yamlScalar(value)).join(", ")}]`;
|
|
18
|
+
}
|
|
11
19
|
function digest(parts) {
|
|
12
20
|
const hash = (0, node_crypto_1.createHash)("sha256");
|
|
13
21
|
for (const part of parts)
|
|
@@ -571,6 +579,7 @@ function migrationRuntimeEnv(profile) {
|
|
|
571
579
|
}
|
|
572
580
|
function migrationManifest(profile, release, images) {
|
|
573
581
|
const releaseSlug = release.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
582
|
+
const command = profile.migration?.command ?? ["npm", "run", "migrate:all"];
|
|
574
583
|
return `apiVersion: batch/v1
|
|
575
584
|
kind: Job
|
|
576
585
|
metadata:
|
|
@@ -596,7 +605,7 @@ ${labels(`${profile.target.release}-migration`, 8)} spec:
|
|
|
596
605
|
- name: migrate
|
|
597
606
|
image: ${quote(images.api)}
|
|
598
607
|
imagePullPolicy: IfNotPresent
|
|
599
|
-
command:
|
|
608
|
+
command: ${yamlStringSequence(command)}
|
|
600
609
|
env:
|
|
601
610
|
${migrationRuntimeEnv(profile)}
|
|
602
611
|
envFrom:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation primitives shared by every deployment driver.
|
|
3
|
+
*
|
|
4
|
+
* A driver profile decides its own target, exposure, and dependency shape, but the
|
|
5
|
+
* pieces that describe *what is being released* — image repositories, the stable tag
|
|
6
|
+
* pattern, the migration command, non-secret runtime configuration, and the public
|
|
7
|
+
* verification contract — mean the same thing everywhere and are parsed here once.
|
|
8
|
+
*/
|
|
9
|
+
export interface VerificationCheckProfile {
|
|
10
|
+
path: string;
|
|
11
|
+
expectedStatus: number;
|
|
12
|
+
expectedJson?: Record<string, string | number | boolean | null>;
|
|
13
|
+
}
|
|
14
|
+
export interface VerificationProfile {
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
checks: VerificationCheckProfile[];
|
|
17
|
+
}
|
|
18
|
+
export interface MigrationProfile {
|
|
19
|
+
command: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface ReleaseProfile {
|
|
22
|
+
strategy: "shared-tag";
|
|
23
|
+
tagPattern: string;
|
|
24
|
+
apiRepository: string;
|
|
25
|
+
webRepository: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const PROFILE_NAME: RegExp;
|
|
28
|
+
export declare const DNS_LABEL: RegExp;
|
|
29
|
+
export declare const SENSITIVE_KEY: RegExp;
|
|
30
|
+
export declare const STABLE_SEMVER_TAG_PATTERN = "^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$";
|
|
31
|
+
/** Keys PodoKit derives itself, so a profile may not set them. */
|
|
32
|
+
export declare const MANAGED_RUNTIME_CONFIG_KEYS: string[];
|
|
33
|
+
export declare function containsUrlCredentials(value: string): boolean;
|
|
34
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
35
|
+
export declare function assertOnlyKeys(value: Record<string, unknown>, allowed: string[], field: string): void;
|
|
36
|
+
export declare function requiredRecord(parent: Record<string, unknown>, key: string): Record<string, unknown>;
|
|
37
|
+
export declare function requiredString(parent: Record<string, unknown>, key: string): string;
|
|
38
|
+
export declare function optionalString(parent: Record<string, unknown>, key: string): string | null;
|
|
39
|
+
export declare function requiredNumber(parent: Record<string, unknown>, key: string): number;
|
|
40
|
+
export declare function parseRequiredKeys(value: unknown, field: string): string[];
|
|
41
|
+
export declare function parseCommand(value: unknown, field: string): string[];
|
|
42
|
+
export declare function validateDnsLabel(value: string, field: string): void;
|
|
43
|
+
export declare function validateDnsSubdomain(value: string, field: string): void;
|
|
44
|
+
export declare function parseImageReference(value: string): {
|
|
45
|
+
name: string;
|
|
46
|
+
tag: string | null;
|
|
47
|
+
digest: string | null;
|
|
48
|
+
} | null;
|
|
49
|
+
export declare function validateImage(value: string, field: string): void;
|
|
50
|
+
export declare function validateImageRepository(value: string, field: string): void;
|
|
51
|
+
export declare function parseReleaseProfile(release: Record<string, unknown>): ReleaseProfile;
|
|
52
|
+
export declare function parseRuntimeConfig(runtimeConfig: Record<string, unknown>): Record<string, string>;
|
|
53
|
+
export declare function validateHttpsOrigin(value: string): void;
|
|
54
|
+
export declare function parseVerification(verification: Record<string, unknown>): VerificationProfile;
|
|
55
|
+
export declare function profileDirectory(projectRoot: string): string;
|
|
56
|
+
export declare function profilePath(projectRoot: string, name: string): string;
|