@podosoft/podokit 0.16.4 → 0.17.1
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 +3 -10
- package/dist/deploy-profile.js +82 -291
- 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/server.js +31 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.test.ts +21 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +15 -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/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 +15 -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/lib/i18n/catalogs/admin-dashboard/en.json +5 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/ko.json +5 -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/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +12 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +13 -1
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +27 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +20 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +26 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +10 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/two-factor.ui.spec.ts +3 -3
- 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/server.js +31 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.test.ts +21 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +15 -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/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,148 +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
|
-
function parseCommand(value, field) {
|
|
87
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
88
|
-
throw new Error(`Deployment profile field "${field}" must be a non-empty string array.`);
|
|
89
|
-
}
|
|
90
|
-
return value.map((entry, index) => {
|
|
91
|
-
if (typeof entry !== "string" || entry.trim() === "") {
|
|
92
|
-
throw new Error(`Deployment profile field "${field}[${index}]" must be a non-empty string.`);
|
|
93
|
-
}
|
|
94
|
-
if (/[\u0000-\u001f\u007f]/.test(entry)) {
|
|
95
|
-
throw new Error(`Deployment profile field "${field}[${index}]" must not contain control characters.`);
|
|
96
|
-
}
|
|
97
|
-
return entry;
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
19
|
function parseNamedSecret(value, field) {
|
|
101
|
-
if (!isRecord(value)) {
|
|
20
|
+
if (!(0, deploy_schema_1.isRecord)(value)) {
|
|
102
21
|
throw new Error(`Deployment profile field "${field}" must be an object.`);
|
|
103
22
|
}
|
|
104
|
-
assertOnlyKeys(value, ["name", "requiredKeys"], field);
|
|
23
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["name", "requiredKeys"], field);
|
|
105
24
|
const parsed = {
|
|
106
|
-
name: requiredString(value, "name"),
|
|
107
|
-
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`),
|
|
108
27
|
};
|
|
109
|
-
validateDnsLabel(parsed.name, `${field}.name`);
|
|
28
|
+
(0, deploy_schema_1.validateDnsLabel)(parsed.name, `${field}.name`);
|
|
110
29
|
return parsed;
|
|
111
30
|
}
|
|
112
|
-
function validateDnsLabel(value, field) {
|
|
113
|
-
if (!DNS_LABEL.test(value)) {
|
|
114
|
-
throw new Error(`Deployment profile field "${field}" must be a Kubernetes DNS label.`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
function validateDnsSubdomain(value, field) {
|
|
118
|
-
if (value.length > 253 ||
|
|
119
|
-
!value.split(".").every((label) => DNS_LABEL.test(label))) {
|
|
120
|
-
throw new Error(`Deployment profile field "${field}" must be a Kubernetes DNS subdomain.`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
function parseImageReference(value) {
|
|
124
|
-
if (value.length > 512 || value.includes("://") || /[\s\u0000-\u001f\u007f]/.test(value)) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
const at = value.indexOf("@");
|
|
128
|
-
if (at !== -1 && at !== value.lastIndexOf("@"))
|
|
129
|
-
return null;
|
|
130
|
-
const digest = at === -1 ? null : value.slice(at + 1);
|
|
131
|
-
const tagged = at === -1 ? value : value.slice(0, at);
|
|
132
|
-
const lastSlash = tagged.lastIndexOf("/");
|
|
133
|
-
const lastColon = tagged.lastIndexOf(":");
|
|
134
|
-
const hasTag = lastColon > lastSlash;
|
|
135
|
-
const name = hasTag ? tagged.slice(0, lastColon) : tagged;
|
|
136
|
-
const tag = hasTag ? tagged.slice(lastColon + 1) : null;
|
|
137
|
-
if (!IMAGE_NAME.test(name) ||
|
|
138
|
-
(tag !== null && !IMAGE_TAG.test(tag)) ||
|
|
139
|
-
(digest !== null && !/^sha256:[a-f0-9]{64}$/.test(digest))) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
return { name, tag, digest };
|
|
143
|
-
}
|
|
144
|
-
function validateImage(value, field) {
|
|
145
|
-
const parsed = parseImageReference(value);
|
|
146
|
-
if (!parsed || parsed.tag === "latest" || (!parsed.tag && !parsed.digest)) {
|
|
147
|
-
throw new Error(`Deployment profile field "${field}" must use an explicit non-latest image tag or digest.`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function validateImageRepository(value, field) {
|
|
151
|
-
const parsed = parseImageReference(value);
|
|
152
|
-
if (!parsed || parsed.tag || parsed.digest) {
|
|
153
|
-
throw new Error(`Deployment profile field "${field}" must be an image repository without a tag or digest.`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
31
|
function validateResource(value, field) {
|
|
157
32
|
if (!RESOURCE_VALUE.test(value)) {
|
|
158
33
|
throw new Error(`Deployment profile field "${field}" is not a supported resource quantity.`);
|
|
@@ -164,22 +39,22 @@ function validateStorage(value, field) {
|
|
|
164
39
|
}
|
|
165
40
|
}
|
|
166
41
|
function parseResources(value, field) {
|
|
167
|
-
assertOnlyKeys(value, ["cpuRequest", "cpuLimit", "memoryRequest", "memoryLimit"], field);
|
|
42
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["cpuRequest", "cpuLimit", "memoryRequest", "memoryLimit"], field);
|
|
168
43
|
const parsed = {
|
|
169
|
-
cpuRequest: requiredString(value, "cpuRequest"),
|
|
170
|
-
cpuLimit: requiredString(value, "cpuLimit"),
|
|
171
|
-
memoryRequest: requiredString(value, "memoryRequest"),
|
|
172
|
-
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"),
|
|
173
48
|
};
|
|
174
49
|
for (const [key, quantity] of Object.entries(parsed))
|
|
175
50
|
validateResource(quantity, `${field}.${key}`);
|
|
176
51
|
return parsed;
|
|
177
52
|
}
|
|
178
53
|
function parseWorkload(value, field) {
|
|
179
|
-
assertOnlyKeys(value, ["replicas", "resources"], field);
|
|
54
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["replicas", "resources"], field);
|
|
180
55
|
return {
|
|
181
|
-
replicas: requiredNumber(value, "replicas"),
|
|
182
|
-
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`),
|
|
183
58
|
};
|
|
184
59
|
}
|
|
185
60
|
function parseMode(value, field) {
|
|
@@ -189,16 +64,16 @@ function parseMode(value, field) {
|
|
|
189
64
|
return value;
|
|
190
65
|
}
|
|
191
66
|
function parseStatefulDependency(value, field, extraKeys = []) {
|
|
192
|
-
assertOnlyKeys(value, ["mode", "image", "secretName", "storageClassName", "storageSize", ...extraKeys], field);
|
|
67
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, ["mode", "image", "secretName", "storageClassName", "storageSize", ...extraKeys], field);
|
|
193
68
|
const parsed = {
|
|
194
69
|
mode: parseMode(value.mode, `${field}.mode`),
|
|
195
|
-
image: requiredString(value, "image"),
|
|
196
|
-
secretName: requiredString(value, "secretName"),
|
|
70
|
+
image: (0, deploy_schema_1.requiredString)(value, "image"),
|
|
71
|
+
secretName: (0, deploy_schema_1.requiredString)(value, "secretName"),
|
|
197
72
|
storageClassName: typeof value.storageClassName === "string" ? value.storageClassName : "",
|
|
198
|
-
storageSize: requiredString(value, "storageSize"),
|
|
73
|
+
storageSize: (0, deploy_schema_1.requiredString)(value, "storageSize"),
|
|
199
74
|
};
|
|
200
|
-
validateImage(parsed.image, `${field}.image`);
|
|
201
|
-
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`);
|
|
202
77
|
validateStorage(parsed.storageSize, `${field}.storageSize`);
|
|
203
78
|
if (parsed.storageClassName && parsed.storageClassName.length > 253) {
|
|
204
79
|
throw new Error(`Deployment profile field "${field}.storageClassName" is too long.`);
|
|
@@ -208,25 +83,10 @@ function parseStatefulDependency(value, field, extraKeys = []) {
|
|
|
208
83
|
function parseRedisDependency(value, field) {
|
|
209
84
|
return parseStatefulDependency(value, field);
|
|
210
85
|
}
|
|
211
|
-
function validateHttpsOrigin(value) {
|
|
212
|
-
let parsed;
|
|
213
|
-
try {
|
|
214
|
-
parsed = new URL(value);
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
throw new Error(`Deployment verification baseUrl "${value}" is invalid.`);
|
|
218
|
-
}
|
|
219
|
-
if (parsed.username || parsed.password) {
|
|
220
|
-
throw new Error("Deployment verification baseUrl must not contain URL credentials; use a credential-free HTTPS origin.");
|
|
221
|
-
}
|
|
222
|
-
if (parsed.protocol !== "https:" || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
223
|
-
throw new Error("Deployment verification baseUrl must be an HTTPS origin without a path.");
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
86
|
function parseProfile(value) {
|
|
227
|
-
if (!isRecord(value))
|
|
87
|
+
if (!(0, deploy_schema_1.isRecord)(value))
|
|
228
88
|
throw new Error("Deployment profile must be a JSON object.");
|
|
229
|
-
assertOnlyKeys(value, [
|
|
89
|
+
(0, deploy_schema_1.assertOnlyKeys)(value, [
|
|
230
90
|
"schemaVersion",
|
|
231
91
|
"driver",
|
|
232
92
|
"target",
|
|
@@ -245,67 +105,67 @@ function parseProfile(value) {
|
|
|
245
105
|
if (value.driver !== "kubernetes-helm") {
|
|
246
106
|
throw new Error(`Unsupported deployment driver: ${String(value.driver)}`);
|
|
247
107
|
}
|
|
248
|
-
const target = requiredRecord(value, "target");
|
|
249
|
-
const release = requiredRecord(value, "release");
|
|
250
|
-
const exposure = requiredRecord(value, "exposure");
|
|
251
|
-
const workloads = requiredRecord(value, "workloads");
|
|
252
|
-
const dependencies = requiredRecord(value, "dependencies");
|
|
253
|
-
const secrets = requiredRecord(value, "secrets");
|
|
254
|
-
const migration = value.migration === undefined ? undefined : requiredRecord(value, "migration");
|
|
255
|
-
const verification = requiredRecord(value, "verification");
|
|
256
|
-
const runtimeConfig = requiredRecord(value, "runtimeConfig");
|
|
257
|
-
assertOnlyKeys(target, ["context", "clusterFingerprint", "namespace", "release", "createNamespace"], "target");
|
|
258
|
-
assertOnlyKeys(release, ["strategy", "tagPattern", "apiRepository", "webRepository"], "release");
|
|
259
|
-
assertOnlyKeys(exposure, ["mode", "host", "ingressClassName", "tlsSecretName"], "exposure");
|
|
260
|
-
assertOnlyKeys(workloads, ["api", "web", "worker"], "workloads");
|
|
261
|
-
assertOnlyKeys(dependencies, ["postgres", "redis", "objectStorage"], "dependencies");
|
|
262
|
-
assertOnlyKeys(secrets, ["api", "web", "imagePull"], "secrets");
|
|
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");
|
|
263
123
|
if (migration)
|
|
264
|
-
assertOnlyKeys(migration, ["command"], "migration");
|
|
265
|
-
assertOnlyKeys(verification, ["baseUrl", "checks"], "verification");
|
|
266
|
-
const context = requiredString(target, "context");
|
|
267
|
-
const clusterFingerprint = requiredString(target, "clusterFingerprint");
|
|
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");
|
|
268
128
|
if (!/^sha256:[a-f0-9]{64}$/.test(clusterFingerprint)) {
|
|
269
129
|
throw new Error("Deployment target.clusterFingerprint must be a sha256 fingerprint.");
|
|
270
130
|
}
|
|
271
|
-
const namespace = requiredString(target, "namespace");
|
|
272
|
-
const releaseName = requiredString(target, "release");
|
|
273
|
-
validateDnsLabel(namespace, "target.namespace");
|
|
274
|
-
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");
|
|
275
135
|
if (releaseName.length > 40) {
|
|
276
136
|
throw new Error("Deployment target.release must be at most 40 characters.");
|
|
277
137
|
}
|
|
278
138
|
if (target.createNamespace !== false) {
|
|
279
139
|
throw new Error("Deployment target.createNamespace must be false in schema version 1.");
|
|
280
140
|
}
|
|
281
|
-
const tagPattern = requiredString(release, "tagPattern");
|
|
282
|
-
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) {
|
|
283
143
|
throw new Error("Deployment release.tagPattern must require stable vMAJOR.MINOR.PATCH tags.");
|
|
284
144
|
}
|
|
285
145
|
if (release.strategy !== "shared-tag") {
|
|
286
146
|
throw new Error("Deployment release.strategy must be shared-tag.");
|
|
287
147
|
}
|
|
288
|
-
const apiRepository = requiredString(release, "apiRepository");
|
|
289
|
-
const webRepository = requiredString(release, "webRepository");
|
|
290
|
-
validateImageRepository(apiRepository, "release.apiRepository");
|
|
291
|
-
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");
|
|
292
152
|
const exposureMode = exposure.mode;
|
|
293
153
|
if (exposureMode !== "ingress" && exposureMode !== "nodePort") {
|
|
294
154
|
throw new Error("Deployment exposure.mode must be ingress or nodePort.");
|
|
295
155
|
}
|
|
296
|
-
const host = requiredString(exposure, "host");
|
|
156
|
+
const host = (0, deploy_schema_1.requiredString)(exposure, "host");
|
|
297
157
|
if (exposureMode === "ingress") {
|
|
298
|
-
validateDnsSubdomain(host, "exposure.host");
|
|
158
|
+
(0, deploy_schema_1.validateDnsSubdomain)(host, "exposure.host");
|
|
299
159
|
}
|
|
300
|
-
const ingressClassName = requiredString(exposure, "ingressClassName");
|
|
301
|
-
validateDnsSubdomain(ingressClassName, "exposure.ingressClassName");
|
|
302
|
-
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");
|
|
303
163
|
if (tlsSecretName)
|
|
304
|
-
validateDnsSubdomain(tlsSecretName, "exposure.tlsSecretName");
|
|
305
|
-
const postgres = parseStatefulDependency(requiredRecord(dependencies, "postgres"), "dependencies.postgres");
|
|
306
|
-
const redis = parseRedisDependency(requiredRecord(dependencies, "redis"), "dependencies.redis");
|
|
307
|
-
const objectStorageRecord = requiredRecord(dependencies, "objectStorage");
|
|
308
|
-
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, [
|
|
309
169
|
"mode",
|
|
310
170
|
"image",
|
|
311
171
|
"secretName",
|
|
@@ -316,73 +176,13 @@ function parseProfile(value) {
|
|
|
316
176
|
], "dependencies.objectStorage");
|
|
317
177
|
const objectStorage = {
|
|
318
178
|
...parseStatefulDependency(objectStorageRecord, "dependencies.objectStorage", ["bucket", "clientImage"]),
|
|
319
|
-
bucket: requiredString(objectStorageRecord, "bucket"),
|
|
320
|
-
clientImage: requiredString(objectStorageRecord, "clientImage"),
|
|
179
|
+
bucket: (0, deploy_schema_1.requiredString)(objectStorageRecord, "bucket"),
|
|
180
|
+
clientImage: (0, deploy_schema_1.requiredString)(objectStorageRecord, "clientImage"),
|
|
321
181
|
};
|
|
322
|
-
validateDnsLabel(objectStorage.bucket, "dependencies.objectStorage.bucket");
|
|
323
|
-
validateImage(objectStorage.clientImage, "dependencies.objectStorage.clientImage");
|
|
324
|
-
const parsedConfig =
|
|
325
|
-
|
|
326
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
|
|
327
|
-
throw new Error(`Deployment runtimeConfig key "${key}" must be an uppercase environment name.`);
|
|
328
|
-
}
|
|
329
|
-
if (SENSITIVE_KEY.test(key)) {
|
|
330
|
-
throw new Error(`Deployment runtimeConfig key "${key}" may contain a secret; use a Kubernetes Secret.`);
|
|
331
|
-
}
|
|
332
|
-
if (["PORT", "BACKEND_INTERNAL_URL"].includes(key)) {
|
|
333
|
-
throw new Error(`Deployment runtimeConfig key "${key}" is managed by PodoKit.`);
|
|
334
|
-
}
|
|
335
|
-
if (typeof entry !== "string") {
|
|
336
|
-
throw new Error(`Deployment runtimeConfig value "${key}" must be a string.`);
|
|
337
|
-
}
|
|
338
|
-
if (containsUrlCredentials(entry)) {
|
|
339
|
-
throw new Error(`Deployment runtimeConfig value "${key}" contains URL credentials; use a Kubernetes Secret.`);
|
|
340
|
-
}
|
|
341
|
-
parsedConfig[key] = entry;
|
|
342
|
-
}
|
|
343
|
-
const baseUrl = requiredString(verification, "baseUrl");
|
|
344
|
-
validateHttpsOrigin(baseUrl);
|
|
345
|
-
const baseOrigin = new URL(baseUrl).origin;
|
|
346
|
-
const checksValue = verification.checks;
|
|
347
|
-
if (!Array.isArray(checksValue) || checksValue.length === 0) {
|
|
348
|
-
throw new Error("Deployment verification.checks must be a non-empty array.");
|
|
349
|
-
}
|
|
350
|
-
const checks = checksValue.map((entry, index) => {
|
|
351
|
-
if (!isRecord(entry)) {
|
|
352
|
-
throw new Error(`Deployment verification.checks[${index}] must be an object.`);
|
|
353
|
-
}
|
|
354
|
-
assertOnlyKeys(entry, ["path", "expectedStatus", "expectedJson"], `verification.checks[${index}]`);
|
|
355
|
-
const path = requiredString(entry, "path");
|
|
356
|
-
if (!path.startsWith("/") ||
|
|
357
|
-
path.startsWith("//") ||
|
|
358
|
-
path.includes("\\") ||
|
|
359
|
-
/[\u0000-\u001f\u007f]/.test(path) ||
|
|
360
|
-
new URL(path, baseUrl).origin !== baseOrigin) {
|
|
361
|
-
throw new Error("Every deployment verification path must be an absolute URL path.");
|
|
362
|
-
}
|
|
363
|
-
const expectedStatus = requiredNumber(entry, "expectedStatus");
|
|
364
|
-
if (expectedStatus < 100 || expectedStatus > 599) {
|
|
365
|
-
throw new Error("Deployment verification expectedStatus must be an HTTP status.");
|
|
366
|
-
}
|
|
367
|
-
const expectedJsonValue = entry.expectedJson;
|
|
368
|
-
let expectedJson;
|
|
369
|
-
if (expectedJsonValue !== undefined) {
|
|
370
|
-
if (!isRecord(expectedJsonValue)) {
|
|
371
|
-
throw new Error("Deployment verification expectedJson must be an object.");
|
|
372
|
-
}
|
|
373
|
-
expectedJson = {};
|
|
374
|
-
for (const [key, value] of Object.entries(expectedJsonValue)) {
|
|
375
|
-
if (value !== null &&
|
|
376
|
-
typeof value !== "string" &&
|
|
377
|
-
typeof value !== "number" &&
|
|
378
|
-
typeof value !== "boolean") {
|
|
379
|
-
throw new Error("Deployment verification expectedJson values must be scalar.");
|
|
380
|
-
}
|
|
381
|
-
expectedJson[key] = value;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return { path, expectedStatus, ...(expectedJson ? { expectedJson } : {}) };
|
|
385
|
-
});
|
|
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);
|
|
386
186
|
const profile = {
|
|
387
187
|
schemaVersion: 1,
|
|
388
188
|
driver: "kubernetes-helm",
|
|
@@ -406,11 +206,11 @@ function parseProfile(value) {
|
|
|
406
206
|
tlsSecretName,
|
|
407
207
|
},
|
|
408
208
|
workloads: {
|
|
409
|
-
api: parseWorkload(requiredRecord(workloads, "api"), "workloads.api"),
|
|
410
|
-
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"),
|
|
411
211
|
worker: workloads.worker === null
|
|
412
212
|
? null
|
|
413
|
-
: parseWorkload(requiredRecord(workloads, "worker"), "workloads.worker"),
|
|
213
|
+
: parseWorkload((0, deploy_schema_1.requiredRecord)(workloads, "worker"), "workloads.worker"),
|
|
414
214
|
},
|
|
415
215
|
dependencies: { postgres, redis, objectStorage },
|
|
416
216
|
secrets: {
|
|
@@ -418,28 +218,19 @@ function parseProfile(value) {
|
|
|
418
218
|
web: secrets.web === null
|
|
419
219
|
? null
|
|
420
220
|
: parseNamedSecret(secrets.web, "secrets.web"),
|
|
421
|
-
imagePull: requiredString(secrets, "imagePull"),
|
|
221
|
+
imagePull: (0, deploy_schema_1.requiredString)(secrets, "imagePull"),
|
|
422
222
|
},
|
|
423
223
|
...(migration
|
|
424
|
-
? { migration: { command: parseCommand(migration.command, "migration.command") } }
|
|
224
|
+
? { migration: { command: (0, deploy_schema_1.parseCommand)(migration.command, "migration.command") } }
|
|
425
225
|
: {}),
|
|
426
226
|
runtimeConfig: parsedConfig,
|
|
427
227
|
verification: { baseUrl, checks },
|
|
428
228
|
};
|
|
429
|
-
validateDnsLabel(profile.secrets.imagePull, "secrets.imagePull");
|
|
229
|
+
(0, deploy_schema_1.validateDnsLabel)(profile.secrets.imagePull, "secrets.imagePull");
|
|
430
230
|
return profile;
|
|
431
231
|
}
|
|
432
|
-
function profileDirectory(projectRoot) {
|
|
433
|
-
return (0, node_path_1.join)((0, node_path_1.resolve)(projectRoot), ".podokit", "deploy");
|
|
434
|
-
}
|
|
435
|
-
function profilePath(projectRoot, name) {
|
|
436
|
-
if (!PROFILE_NAME.test(name)) {
|
|
437
|
-
throw new Error(`Invalid deployment profile name "${name}".`);
|
|
438
|
-
}
|
|
439
|
-
return (0, node_path_1.join)(profileDirectory(projectRoot), `${name}.json`);
|
|
440
|
-
}
|
|
441
232
|
function loadDeploymentProfile(projectRoot, name) {
|
|
442
|
-
const path = profilePath(projectRoot, name);
|
|
233
|
+
const path = (0, deploy_schema_1.profilePath)(projectRoot, name);
|
|
443
234
|
if (!(0, node_fs_1.existsSync)(path))
|
|
444
235
|
throw new Error(`Deployment profile "${name}" does not exist at ${path}.`);
|
|
445
236
|
let parsed;
|
|
@@ -452,7 +243,7 @@ function loadDeploymentProfile(projectRoot, name) {
|
|
|
452
243
|
return parseProfile(parsed);
|
|
453
244
|
}
|
|
454
245
|
function listDeploymentProfiles(projectRoot) {
|
|
455
|
-
const directory = profileDirectory(projectRoot);
|
|
246
|
+
const directory = (0, deploy_schema_1.profileDirectory)(projectRoot);
|
|
456
247
|
if (!(0, node_fs_1.existsSync)(directory))
|
|
457
248
|
return [];
|
|
458
249
|
return (0, node_fs_1.readdirSync)(directory)
|
|
@@ -470,7 +261,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
470
261
|
const projectName = manifest.answers.projectName;
|
|
471
262
|
if (!projectName)
|
|
472
263
|
throw new Error("PodoKit manifest is missing answers.projectName.");
|
|
473
|
-
const path = profilePath(projectRoot, name);
|
|
264
|
+
const path = (0, deploy_schema_1.profilePath)(projectRoot, name);
|
|
474
265
|
if ((0, node_fs_1.existsSync)(path))
|
|
475
266
|
throw new Error(`Deployment profile "${name}" already exists.`);
|
|
476
267
|
const releaseName = projectName
|
|
@@ -501,7 +292,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
501
292
|
},
|
|
502
293
|
release: {
|
|
503
294
|
strategy: "shared-tag",
|
|
504
|
-
tagPattern: STABLE_SEMVER_TAG_PATTERN,
|
|
295
|
+
tagPattern: deploy_schema_1.STABLE_SEMVER_TAG_PATTERN,
|
|
505
296
|
apiRepository: `ghcr.io/example/${releaseName}-api`,
|
|
506
297
|
webRepository: `ghcr.io/example/${releaseName}-web`,
|
|
507
298
|
},
|
|
@@ -610,7 +401,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
610
401
|
},
|
|
611
402
|
};
|
|
612
403
|
const validated = parseProfile(defaultProfile);
|
|
613
|
-
(0, node_fs_1.mkdirSync)(profileDirectory(projectRoot), { recursive: true });
|
|
404
|
+
(0, node_fs_1.mkdirSync)((0, deploy_schema_1.profileDirectory)(projectRoot), { recursive: true });
|
|
614
405
|
(0, node_fs_1.writeFileSync)(path, `${JSON.stringify(validated, null, 2)}\n`, { flag: "wx" });
|
|
615
406
|
return { name, path, profile: validated };
|
|
616
407
|
}
|
|
@@ -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;
|