@hexclave/shared 1.0.60 → 1.0.62
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/{admin-interface-BxUHaniY.d.ts → admin-interface-D63SFHX_.d.ts} +94 -12
- package/dist/admin-interface-D63SFHX_.d.ts.map +1 -0
- package/dist/{admin-metrics-DQ7vvSym.d.ts → admin-metrics-bsOkw4YQ.d.ts} +16 -16
- package/dist/{admin-metrics-DQ7vvSym.d.ts.map → admin-metrics-bsOkw4YQ.d.ts.map} +1 -1
- package/dist/ai/unified-prompts/reminders.js +1 -1
- package/dist/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +12 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.d.ts +5 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.d.ts.map +1 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.js +128 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.js.map +1 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +1 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt.d.ts +3 -1
- package/dist/ai/unified-prompts/skill-site-prompt.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt.js +11 -0
- package/dist/ai/unified-prompts/skill-site-prompt.js.map +1 -1
- package/dist/apps/apps-config.d.ts +6 -0
- package/dist/apps/apps-config.d.ts.map +1 -1
- package/dist/apps/apps-config.js +6 -0
- package/dist/apps/apps-config.js.map +1 -1
- package/dist/{client-interface-D-cO5E3C.d.ts → client-interface-DvkciaL0.d.ts} +10 -10
- package/dist/{client-interface-D-cO5E3C.d.ts.map → client-interface-DvkciaL0.d.ts.map} +1 -1
- package/dist/config/schema-fuzzer.test.js +38 -1
- package/dist/config/schema-fuzzer.test.js.map +1 -1
- package/dist/config/schema.d.ts +2 -2
- package/dist/config/schema.js +45 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config-authoring.d.ts +1 -1
- package/dist/{current-user-Cg6by3KK.d.ts → current-user-BBoN3hsn.d.ts} +13 -13
- package/dist/{current-user-Cg6by3KK.d.ts.map → current-user-BBoN3hsn.d.ts.map} +1 -1
- package/dist/{email-outbox-B3lyx4Eg.d.ts → email-outbox-CqzwInBL.d.ts} +207 -207
- package/dist/{email-outbox-B3lyx4Eg.d.ts.map → email-outbox-CqzwInBL.d.ts.map} +1 -1
- package/dist/esm/ai/unified-prompts/reminders.js +1 -1
- package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +12 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.d.ts +5 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.d.ts.map +1 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.js +128 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.js.map +1 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +1 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt.d.ts +3 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt.js +10 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt.js.map +1 -1
- package/dist/esm/apps/apps-config.d.ts +6 -0
- package/dist/esm/apps/apps-config.d.ts.map +1 -1
- package/dist/esm/apps/apps-config.js +6 -0
- package/dist/esm/apps/apps-config.js.map +1 -1
- package/dist/esm/config/schema-fuzzer.test.js +38 -1
- package/dist/esm/config/schema-fuzzer.test.js.map +1 -1
- package/dist/esm/config/schema.d.ts +220 -90
- package/dist/esm/config/schema.d.ts.map +1 -1
- package/dist/esm/config/schema.js +42 -1
- package/dist/esm/config/schema.js.map +1 -1
- package/dist/esm/interface/admin-interface.d.ts +83 -1
- package/dist/esm/interface/admin-interface.d.ts.map +1 -1
- package/dist/esm/interface/admin-interface.js +48 -0
- package/dist/esm/interface/admin-interface.js.map +1 -1
- package/dist/esm/interface/admin-metrics.d.ts +15 -15
- package/dist/esm/interface/conversations.d.ts +36 -36
- package/dist/esm/interface/crud/current-user.d.ts +12 -12
- package/dist/esm/interface/crud/email-outbox.d.ts +206 -206
- package/dist/esm/interface/crud/invoices.d.ts +2 -2
- package/dist/esm/interface/crud/products.d.ts +23 -23
- package/dist/esm/interface/crud/project-api-keys.d.ts +4 -4
- package/dist/esm/interface/crud/projects.d.ts +59 -59
- package/dist/esm/interface/crud/team-member-profiles.d.ts +26 -26
- package/dist/esm/interface/crud/transactions.d.ts +20 -20
- package/dist/esm/interface/crud/transactions.d.ts.map +1 -1
- package/dist/esm/interface/crud/users.d.ts +16 -16
- package/dist/esm/interface/plan-usage.d.ts +2 -2
- package/dist/esm/interface/webhooks.d.ts +2 -2
- package/dist/esm/known-errors.d.ts +1 -1
- package/dist/esm/schema-fields.d.ts +4 -4
- package/dist/esm/sessions.d.ts +11 -11
- package/dist/esm/utils/tar.d.ts +26 -0
- package/dist/esm/utils/tar.d.ts.map +1 -0
- package/dist/esm/utils/tar.js +136 -0
- package/dist/esm/utils/tar.js.map +1 -0
- package/dist/helpers/password.d.ts +1 -1
- package/dist/helpers/production-mode.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/interface/admin-interface.d.ts +3 -3
- package/dist/interface/admin-interface.js +48 -0
- package/dist/interface/admin-interface.js.map +1 -1
- package/dist/interface/admin-metrics.d.ts +1 -1
- package/dist/interface/client-interface.d.ts +1 -1
- package/dist/interface/conversations.d.ts +36 -36
- package/dist/interface/crud/current-user.d.ts +1 -1
- package/dist/interface/crud/email-outbox.d.ts +1 -1
- package/dist/interface/crud/invoices.d.ts +1 -1
- package/dist/interface/crud/products.d.ts +1 -1
- package/dist/interface/crud/project-api-keys.d.ts +1 -1
- package/dist/interface/crud/projects.d.ts +1 -1
- package/dist/interface/crud/team-member-profiles.d.ts +1 -1
- package/dist/interface/crud/transactions.d.ts +1 -1
- package/dist/interface/crud/users.d.ts +1 -1
- package/dist/interface/plan-usage.d.ts +1 -1
- package/dist/interface/server-interface.d.ts +1 -1
- package/dist/interface/webhooks.d.ts +1 -1
- package/dist/{invoices-BGLLnTlR.d.ts → invoices-CHo9YKbI.d.ts} +3 -3
- package/dist/{invoices-BGLLnTlR.d.ts.map → invoices-CHo9YKbI.d.ts.map} +1 -1
- package/dist/{known-errors-BFf8SVm8.d.ts → known-errors-Bm4a61wL.d.ts} +2 -2
- package/dist/{known-errors-BFf8SVm8.d.ts.map → known-errors-Bm4a61wL.d.ts.map} +1 -1
- package/dist/known-errors.d.ts +1 -1
- package/dist/{plan-usage-Dw09F0sh.d.ts → plan-usage-BaDxcLZ1.d.ts} +3 -3
- package/dist/{plan-usage-Dw09F0sh.d.ts.map → plan-usage-BaDxcLZ1.d.ts.map} +1 -1
- package/dist/{products-Cwn7_4uD.d.ts → products-D4o8A2W0.d.ts} +24 -24
- package/dist/{products-Cwn7_4uD.d.ts.map → products-D4o8A2W0.d.ts.map} +1 -1
- package/dist/{project-api-keys-CXTBQ2IZ.d.ts → project-api-keys--7V5X_UW.d.ts} +5 -5
- package/dist/{project-api-keys-CXTBQ2IZ.d.ts.map → project-api-keys--7V5X_UW.d.ts.map} +1 -1
- package/dist/{projects-Cw80wrT7.d.ts → projects-BX5Y50kJ.d.ts} +60 -60
- package/dist/{projects-Cw80wrT7.d.ts.map → projects-BX5Y50kJ.d.ts.map} +1 -1
- package/dist/{schema-DGZfskAR.d.ts → schema-dJ_7rUCO.d.ts} +221 -91
- package/dist/schema-dJ_7rUCO.d.ts.map +1 -0
- package/dist/{schema-fields-DPaAnhmc.d.ts → schema-fields-DZtDQEEO.d.ts} +5 -5
- package/dist/{schema-fields-DPaAnhmc.d.ts.map → schema-fields-DZtDQEEO.d.ts.map} +1 -1
- package/dist/schema-fields.d.ts +1 -1
- package/dist/{server-interface-BXeSO8Wj.d.ts → server-interface-Ra_vIHP4.d.ts} +8 -8
- package/dist/{server-interface-BXeSO8Wj.d.ts.map → server-interface-Ra_vIHP4.d.ts.map} +1 -1
- package/dist/{sessions-B7755rIu.d.ts → sessions-CI-0TBQb.d.ts} +13 -13
- package/dist/{sessions-B7755rIu.d.ts.map → sessions-CI-0TBQb.d.ts.map} +1 -1
- package/dist/sessions.d.ts +1 -1
- package/dist/{team-member-profiles-B9sAsj4g.d.ts → team-member-profiles-BoDEqx-0.d.ts} +27 -27
- package/dist/{team-member-profiles-B9sAsj4g.d.ts.map → team-member-profiles-BoDEqx-0.d.ts.map} +1 -1
- package/dist/{transactions-Brdmr3jE.d.ts → transactions-CHNUJ42J.d.ts} +21 -21
- package/dist/transactions-CHNUJ42J.d.ts.map +1 -0
- package/dist/{users-ydVQVHD_.d.ts → users-DHNRwoSz.d.ts} +18 -18
- package/dist/{users-ydVQVHD_.d.ts.map → users-DHNRwoSz.d.ts.map} +1 -1
- package/dist/utils/tar.d.ts +26 -0
- package/dist/utils/tar.d.ts.map +1 -0
- package/dist/utils/tar.js +138 -0
- package/dist/utils/tar.js.map +1 -0
- package/dist/{webhooks-ByXrdSOx.d.ts → webhooks-8Ts1Clcb.d.ts} +3 -3
- package/dist/{webhooks-ByXrdSOx.d.ts.map → webhooks-8Ts1Clcb.d.ts.map} +1 -1
- package/package.json +1 -1
- package/src/ai/unified-prompts/reminders.ts +1 -1
- package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +7 -7
- package/src/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.ts +128 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts +1 -0
- package/src/ai/unified-prompts/skill-site-prompt.ts +15 -0
- package/src/apps/apps-config.ts +9 -0
- package/src/config/schema-fuzzer.test.ts +37 -0
- package/src/config/schema.ts +193 -0
- package/src/interface/admin-interface.ts +173 -0
- package/src/utils/tar.tsx +261 -0
- package/dist/admin-interface-BxUHaniY.d.ts.map +0 -1
- package/dist/schema-DGZfskAR.d.ts.map +0 -1
- package/dist/transactions-Brdmr3jE.d.ts.map +0 -1
|
@@ -185,6 +185,43 @@ const branchSchemaFuzzerConfig = [{
|
|
|
185
185
|
onboarding: [{
|
|
186
186
|
requireEmailVerification: [true, false],
|
|
187
187
|
}],
|
|
188
|
+
"deployments-alpha": [{
|
|
189
|
+
services: [{
|
|
190
|
+
"some-service-id": [{
|
|
191
|
+
type: ["vercel"] as const,
|
|
192
|
+
framework: ["nextjs", "vite", ""],
|
|
193
|
+
installCommand: ["pnpm install", ""],
|
|
194
|
+
buildCommand: ["pnpm build", ""],
|
|
195
|
+
outputDirectory: [".next", "dist", ""],
|
|
196
|
+
rootDirectory: ["./", "./apps/web", ""],
|
|
197
|
+
env: [{
|
|
198
|
+
"SOME_PLAIN_VAR": [{
|
|
199
|
+
type: [undefined] as ("secret" | "connection" | undefined)[],
|
|
200
|
+
value: ["some-value", ""],
|
|
201
|
+
key: [undefined] as (string | undefined)[],
|
|
202
|
+
}],
|
|
203
|
+
"SOME_CONNECTION_VAR": [{
|
|
204
|
+
type: ["connection"] as const,
|
|
205
|
+
// Stays valid even when the fuzzer drops `type`: without it, the
|
|
206
|
+
// entry reads as a plain env var whose literal value happens to be
|
|
207
|
+
// "hexclave.projectId".
|
|
208
|
+
value: ["hexclave.projectId", "some-other-service.url"],
|
|
209
|
+
key: [undefined] as (string | undefined)[],
|
|
210
|
+
}],
|
|
211
|
+
"SOME_SECRET_VAR": [{
|
|
212
|
+
type: ["secret"] as const,
|
|
213
|
+
value: [undefined] as (string | undefined)[],
|
|
214
|
+
// Kept undefined: `key` is only allowed when type is "secret", and
|
|
215
|
+
// the fuzzer randomizes each field independently so it cannot honor
|
|
216
|
+
// that coupling (same situation as oauth's customCallbackUrl). The
|
|
217
|
+
// accept path is covered by the schema unit tests and the
|
|
218
|
+
// deployments e2e tests.
|
|
219
|
+
key: [undefined] as (string | undefined)[],
|
|
220
|
+
}],
|
|
221
|
+
}],
|
|
222
|
+
}],
|
|
223
|
+
}],
|
|
224
|
+
}],
|
|
188
225
|
}] satisfies FuzzerConfig<BranchConfigNormalizedOverride>;
|
|
189
226
|
|
|
190
227
|
const environmentSchemaFuzzerConfig = [{
|
package/src/config/schema.ts
CHANGED
|
@@ -293,6 +293,127 @@ const branchOnboardingSchema = yupObject({
|
|
|
293
293
|
requireEmailVerification: yupBoolean(),
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
// --- Deployments Schema ---
|
|
297
|
+
// Service DEFINITIONS (type, framework, build config, env vars) live in the
|
|
298
|
+
// branch config so they follow the project's config source: pushable from
|
|
299
|
+
// hexclave.config.ts / GitHub, or editable in the dashboard when the config is
|
|
300
|
+
// unlinked. Operational state (Vercel project id, deployment runs, custom
|
|
301
|
+
// domains and their verification) lives in the backend database instead, keyed
|
|
302
|
+
// by the service id.
|
|
303
|
+
export const DEPLOYMENT_ENV_VAR_KEY_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
304
|
+
export const DEPLOYMENT_SECRET_KEY_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
305
|
+
// A connection value is `<serviceId>.<outputKey>` — a typed pointer to another
|
|
306
|
+
// service's output (e.g. "hexclave.projectId") that the backend resolves at
|
|
307
|
+
// deploy time. This is deliberately its own env var TYPE rather than a
|
|
308
|
+
// `{serviceId.outputKey}` interpolation syntax inside plain values: with
|
|
309
|
+
// interpolation, a literal value that happens to contain `{...}` would be
|
|
310
|
+
// misinterpreted as a reference, so plain values must stay entirely literal.
|
|
311
|
+
export const DEPLOYMENT_CONNECTION_VALUE_REGEX = /^[a-zA-Z0-9_-]+\.[A-Za-z0-9_]+$/;
|
|
312
|
+
|
|
313
|
+
// One env var, discriminated by `type`:
|
|
314
|
+
// - absent (plain): a literal `value`, committed to the config.
|
|
315
|
+
// - "secret": only the secret's name (`key`) is in the config; its value must
|
|
316
|
+
// be supplied at deploy time via `hexclave deploy --secret <key>=<value>`
|
|
317
|
+
// and is never persisted by Hexclave.
|
|
318
|
+
// - "connection": `value` names another service's output (see the regex
|
|
319
|
+
// comment above); resolved server-side at deploy time.
|
|
320
|
+
// Exported so the backend's deployments API routes validate request bodies
|
|
321
|
+
// with the exact same rules as pushed configs.
|
|
322
|
+
export const deploymentEnvVarSchema = yupObject({
|
|
323
|
+
type: yupString().oneOf(["secret", "connection"]).optional(),
|
|
324
|
+
value: yupString().when("type", ([type], schema) => {
|
|
325
|
+
switch (type) {
|
|
326
|
+
case "secret": {
|
|
327
|
+
return schema.oneOf([undefined], 'deployment env vars with type "secret" must not have a value — the value is supplied at deploy time via `hexclave deploy --secret <key>=<value>`');
|
|
328
|
+
}
|
|
329
|
+
case "connection": {
|
|
330
|
+
return schema.defined().matches(DEPLOYMENT_CONNECTION_VALUE_REGEX, 'deployment env vars with type "connection" must reference a service output like "hexclave.projectId"');
|
|
331
|
+
}
|
|
332
|
+
default: {
|
|
333
|
+
return schema.defined();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}),
|
|
337
|
+
key: yupString().when("type", ([type], schema) => type === "secret"
|
|
338
|
+
? schema.defined().matches(DEPLOYMENT_SECRET_KEY_REGEX, "deployment secret keys must contain only letters, numbers, underscores, and hyphens")
|
|
339
|
+
: schema.oneOf([undefined], 'deployment env vars may only have a key when their type is "secret"')),
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const branchDeploymentsSchema = yupObject({
|
|
343
|
+
services: yupRecord(
|
|
344
|
+
// "hexclave" is the managed backend's slot on the deployments board; a
|
|
345
|
+
// config entry must never shadow it, so it's rejected at the schema level
|
|
346
|
+
// (not just in the dashboard-create route — pushed configs go through
|
|
347
|
+
// validation too).
|
|
348
|
+
userSpecifiedIdSchema("serviceId").notOneOf(["hexclave"], 'The service id "hexclave" is reserved for the managed Hexclave service'),
|
|
349
|
+
yupObject({
|
|
350
|
+
// Which platform runs the service. Only Vercel-backed services exist
|
|
351
|
+
// today, but the field is required so configs stay unambiguous once more
|
|
352
|
+
// types are added (every write path must state what it's creating).
|
|
353
|
+
type: yupString().oneOf(["vercel"]).defined(),
|
|
354
|
+
framework: yupString().optional(),
|
|
355
|
+
installCommand: yupString().optional(),
|
|
356
|
+
buildCommand: yupString().optional(),
|
|
357
|
+
outputDirectory: yupString().optional(),
|
|
358
|
+
rootDirectory: yupString().optional(),
|
|
359
|
+
env: yupRecord(
|
|
360
|
+
yupString().matches(DEPLOYMENT_ENV_VAR_KEY_REGEX, "deployment env var keys must start with a letter or underscore and contain only letters, digits, and underscores"),
|
|
361
|
+
deploymentEnvVarSchema,
|
|
362
|
+
),
|
|
363
|
+
}),
|
|
364
|
+
),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
import.meta.vitest?.test("branchDeploymentsSchema accepts all three env var types", async ({ expect }) => {
|
|
368
|
+
await expect(branchDeploymentsSchema.validate({
|
|
369
|
+
services: {
|
|
370
|
+
web: {
|
|
371
|
+
type: "vercel",
|
|
372
|
+
rootDirectory: "./",
|
|
373
|
+
framework: "nextjs",
|
|
374
|
+
env: {
|
|
375
|
+
MY_ENV_VAR: { value: "true" },
|
|
376
|
+
DATABASE_CONNECTION_STRING: { type: "secret", key: "db_connection" },
|
|
377
|
+
NEXT_PUBLIC_HEXCLAVE_PROJECT_ID: { type: "connection", value: "hexclave.projectId" },
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
}, { abortEarly: false })).resolves.toBeDefined();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
import.meta.vitest?.test("branchDeploymentsSchema rejects services without a type", async ({ expect }) => {
|
|
385
|
+
await expect(branchDeploymentsSchema.validate({
|
|
386
|
+
services: {
|
|
387
|
+
web: { framework: "nextjs" },
|
|
388
|
+
},
|
|
389
|
+
}, { abortEarly: false })).rejects.toThrow(/type/);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
import.meta.vitest?.test("branchDeploymentsSchema rejects invalid env var shapes", async ({ expect }) => {
|
|
393
|
+
// A secret with an inline value would defeat the whole point of secrets.
|
|
394
|
+
await expect(branchDeploymentsSchema.validate({
|
|
395
|
+
services: { web: { type: "vercel", env: { A: { type: "secret", key: "a", value: "leaked" } } } },
|
|
396
|
+
}, { abortEarly: false })).rejects.toThrow(/must not have a value/);
|
|
397
|
+
// A secret without a key can never be filled at deploy time.
|
|
398
|
+
await expect(branchDeploymentsSchema.validate({
|
|
399
|
+
services: { web: { type: "vercel", env: { A: { type: "secret" } } } },
|
|
400
|
+
}, { abortEarly: false })).rejects.toThrow(/key/);
|
|
401
|
+
// Plain values may not carry a secret key.
|
|
402
|
+
await expect(branchDeploymentsSchema.validate({
|
|
403
|
+
services: { web: { type: "vercel", env: { A: { value: "x", key: "a" } } } },
|
|
404
|
+
}, { abortEarly: false })).rejects.toThrow(/only have a key/);
|
|
405
|
+
// Connections must point at `<serviceId>.<outputKey>` — braces in particular
|
|
406
|
+
// are the OLD interpolation syntax and must not validate.
|
|
407
|
+
await expect(branchDeploymentsSchema.validate({
|
|
408
|
+
services: { web: { type: "vercel", env: { A: { type: "connection", value: "{hexclave.projectId}" } } } },
|
|
409
|
+
}, { abortEarly: false })).rejects.toThrow(/service output/);
|
|
410
|
+
// Env var keys must be valid POSIX-ish env var names.
|
|
411
|
+
await expect(branchDeploymentsSchema.validate({
|
|
412
|
+
services: { web: { type: "vercel", env: { "1BAD": { value: "x" } } } },
|
|
413
|
+
}, { abortEarly: false })).rejects.toThrow(/env var keys/);
|
|
414
|
+
});
|
|
415
|
+
// --- END Deployments Schema ---
|
|
416
|
+
|
|
296
417
|
|
|
297
418
|
export const branchConfigSchema = canNoLongerBeOverridden(projectConfigSchema, [
|
|
298
419
|
"sourceOfTruth",
|
|
@@ -327,6 +448,7 @@ export const branchConfigSchema = canNoLongerBeOverridden(projectConfigSchema, [
|
|
|
327
448
|
|
|
328
449
|
payments: branchPaymentsSchema,
|
|
329
450
|
|
|
451
|
+
"deployments-alpha": branchDeploymentsSchema,
|
|
330
452
|
|
|
331
453
|
dataVault: yupObject({
|
|
332
454
|
stores: yupRecord(
|
|
@@ -574,6 +696,16 @@ export function migrateConfigOverride(type: "project" | "branch" | "environment"
|
|
|
574
696
|
}
|
|
575
697
|
// END
|
|
576
698
|
|
|
699
|
+
// BEGIN 2026-07-24: the deployments app is now `deployments-alpha`, both as its
|
|
700
|
+
// `apps.installed` key and as its top-level config section, so the alpha stage is
|
|
701
|
+
// visible in every hexclave.config.ts that opts into it. Both renames are needed:
|
|
702
|
+
// an installed entry without the config section (or vice versa) is a valid state.
|
|
703
|
+
if (isBranchOrHigher) {
|
|
704
|
+
res = renameProperty(res, "deployments", "deployments-alpha");
|
|
705
|
+
res = renameProperty(res, "apps.installed.deployments", "deployments-alpha");
|
|
706
|
+
}
|
|
707
|
+
// END
|
|
708
|
+
|
|
577
709
|
// return the result
|
|
578
710
|
return res;
|
|
579
711
|
};
|
|
@@ -608,6 +740,51 @@ import.meta.vitest?.test("migrateConfigOverride removes legacy branch-level dbSy
|
|
|
608
740
|
expect(migrateConfigOverride("environment", { dbSync })).toEqual({ dbSync });
|
|
609
741
|
});
|
|
610
742
|
|
|
743
|
+
import.meta.vitest?.test("migrateConfigOverride renames the deployments app to deployments-alpha", ({ expect }) => {
|
|
744
|
+
const services = { web: { type: "vercel", rootDirectory: "./" } };
|
|
745
|
+
|
|
746
|
+
// Fully nested overrides.
|
|
747
|
+
expect(migrateConfigOverride("branch", {
|
|
748
|
+
deployments: { services },
|
|
749
|
+
apps: { installed: { deployments: { enabled: true } } },
|
|
750
|
+
})).toEqual({
|
|
751
|
+
"deployments-alpha": { services },
|
|
752
|
+
apps: { installed: { "deployments-alpha": { enabled: true } } },
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// Dot-notation overrides, which is how the dashboard and the Deployments app
|
|
756
|
+
// itself write single keys. The flat key form is preserved — only the renamed
|
|
757
|
+
// segment changes — so the override keeps overriding exactly one leaf rather
|
|
758
|
+
// than being widened into a whole subtree.
|
|
759
|
+
expect(migrateConfigOverride("branch", {
|
|
760
|
+
"deployments.services.web.buildCommand": "pnpm build",
|
|
761
|
+
"apps.installed.deployments.enabled": true,
|
|
762
|
+
})).toEqual({
|
|
763
|
+
"deployments-alpha.services.web.buildCommand": "pnpm build",
|
|
764
|
+
"apps.installed.deployments-alpha.enabled": true,
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// A service literally named "deployments" sits below the renamed section and
|
|
768
|
+
// must survive untouched — only the top-level section and the installed-apps
|
|
769
|
+
// entry are renamed.
|
|
770
|
+
expect(migrateConfigOverride("branch", {
|
|
771
|
+
deployments: { services: { deployments: { type: "vercel" } } },
|
|
772
|
+
})).toEqual({
|
|
773
|
+
"deployments-alpha": { services: { deployments: { type: "vercel" } } },
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
// Already-migrated configs are left alone (the migration must be idempotent,
|
|
777
|
+
// since it re-runs on every read).
|
|
778
|
+
expect(migrateConfigOverride("branch", {
|
|
779
|
+
"deployments-alpha": { services },
|
|
780
|
+
})).toEqual({
|
|
781
|
+
"deployments-alpha": { services },
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// Project level is not branch-or-higher, so nothing is renamed there.
|
|
785
|
+
expect(migrateConfigOverride("project", { deployments: { services } })).toEqual({ deployments: { services } });
|
|
786
|
+
});
|
|
787
|
+
|
|
611
788
|
function removeProperty(obj: Record<string, any>, pathCond: (path: (string | symbol)[]) => boolean): any {
|
|
612
789
|
return mapProperty(obj, pathCond, () => undefined);
|
|
613
790
|
}
|
|
@@ -875,6 +1052,22 @@ const organizationConfigDefaults = {
|
|
|
875
1052
|
}),
|
|
876
1053
|
},
|
|
877
1054
|
|
|
1055
|
+
"deployments-alpha": {
|
|
1056
|
+
services: (key: string) => ({
|
|
1057
|
+
type: undefined,
|
|
1058
|
+
framework: undefined,
|
|
1059
|
+
installCommand: undefined,
|
|
1060
|
+
buildCommand: undefined,
|
|
1061
|
+
outputDirectory: undefined,
|
|
1062
|
+
rootDirectory: undefined,
|
|
1063
|
+
env: (envVarKey: string) => ({
|
|
1064
|
+
type: undefined,
|
|
1065
|
+
value: undefined,
|
|
1066
|
+
key: undefined,
|
|
1067
|
+
}),
|
|
1068
|
+
}),
|
|
1069
|
+
},
|
|
1070
|
+
|
|
878
1071
|
dataVault: {
|
|
879
1072
|
stores: (key: string) => ({
|
|
880
1073
|
displayName: "Unnamed Vault",
|
|
@@ -36,6 +36,74 @@ export type ChatContent = Array<
|
|
|
36
36
|
| { type: "tool-call", toolName: string, toolCallId: string, args: any, argsText: string, result: any }
|
|
37
37
|
>;
|
|
38
38
|
|
|
39
|
+
export type AdminDeploymentRunJson = {
|
|
40
|
+
id: string,
|
|
41
|
+
service_id: string,
|
|
42
|
+
status: "queued" | "building" | "ready" | "error" | "canceled",
|
|
43
|
+
target: string,
|
|
44
|
+
triggered_by: string,
|
|
45
|
+
url: string | null,
|
|
46
|
+
error: string | null,
|
|
47
|
+
created_at_millis: number,
|
|
48
|
+
finished_at_millis: number | null,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// One env var of a deployment service, normalized from the config-side
|
|
52
|
+
// definition: "plain" vars carry their literal `value`, "connection" vars
|
|
53
|
+
// carry the "serviceId.outputKey" reference they resolve to at deploy time,
|
|
54
|
+
// and "secret" vars carry only the `secret_key` whose value is supplied via
|
|
55
|
+
// `hexclave deploy --secret <key>=<value>` (never stored).
|
|
56
|
+
export type AdminDeploymentEnvVarJson = {
|
|
57
|
+
key: string,
|
|
58
|
+
type: "plain" | "secret" | "connection",
|
|
59
|
+
value: string | null,
|
|
60
|
+
secret_key: string | null,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// The config-side shape of one env var, mirroring
|
|
64
|
+
// `deployments-alpha.services.<id>.env.<KEY>` in hexclave.config.ts: no type means a
|
|
65
|
+
// plain value, "secret" requires `key`, "connection" requires a
|
|
66
|
+
// "serviceId.outputKey" `value`.
|
|
67
|
+
export type AdminDeploymentEnvVarOptions =
|
|
68
|
+
| { type?: undefined, value: string }
|
|
69
|
+
| { type: "secret", key: string }
|
|
70
|
+
| { type: "connection", value: string };
|
|
71
|
+
|
|
72
|
+
export type AdminDeploymentServiceJson = {
|
|
73
|
+
id: string,
|
|
74
|
+
type: "vercel",
|
|
75
|
+
framework: string | null,
|
|
76
|
+
install_command: string | null,
|
|
77
|
+
build_command: string | null,
|
|
78
|
+
output_directory: string | null,
|
|
79
|
+
root_directory: string | null,
|
|
80
|
+
provisioned: boolean,
|
|
81
|
+
status: "not_deployed" | "queued" | "building" | "deployed" | "failed" | "canceled",
|
|
82
|
+
has_successful_deploy: boolean,
|
|
83
|
+
url: string | null,
|
|
84
|
+
env: AdminDeploymentEnvVarJson[],
|
|
85
|
+
domains: { hostname: string, is_primary: boolean, verified: boolean }[],
|
|
86
|
+
latest_run: AdminDeploymentRunJson | null,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// null means "unset this field" (falls back to platform auto-detection);
|
|
90
|
+
// undefined means "leave unchanged".
|
|
91
|
+
export type AdminDeploymentServiceBuildOptions = {
|
|
92
|
+
framework?: string | null,
|
|
93
|
+
install_command?: string | null,
|
|
94
|
+
build_command?: string | null,
|
|
95
|
+
output_directory?: string | null,
|
|
96
|
+
root_directory?: string | null,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type AdminDeploymentDomainJson = {
|
|
100
|
+
hostname: string,
|
|
101
|
+
is_primary: boolean,
|
|
102
|
+
verified: boolean,
|
|
103
|
+
pending_first_deploy: boolean,
|
|
104
|
+
dns_records: { type: string, name: string, value: string }[],
|
|
105
|
+
};
|
|
106
|
+
|
|
39
107
|
export type AdminAuthApplicationOptions = ServerAuthApplicationOptions &(
|
|
40
108
|
| {
|
|
41
109
|
superSecretAdminKey: string,
|
|
@@ -1204,4 +1272,109 @@ export class HexclaveAdminInterface extends HexclaveServerInterface {
|
|
|
1204
1272
|
return await response.json();
|
|
1205
1273
|
}
|
|
1206
1274
|
|
|
1275
|
+
// ---- Deployments app ------------------------------------------------------
|
|
1276
|
+
|
|
1277
|
+
async listDeploymentServices(): Promise<AdminDeploymentServiceJson[]> {
|
|
1278
|
+
const response = await this.sendAdminRequest(
|
|
1279
|
+
"/deployments/services",
|
|
1280
|
+
{ method: "GET" },
|
|
1281
|
+
null,
|
|
1282
|
+
);
|
|
1283
|
+
return (await response.json()).items;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async createDeploymentService(id: string, build: AdminDeploymentServiceBuildOptions): Promise<AdminDeploymentServiceJson> {
|
|
1287
|
+
const response = await this.sendAdminRequest(
|
|
1288
|
+
"/deployments/services",
|
|
1289
|
+
{
|
|
1290
|
+
method: "POST",
|
|
1291
|
+
headers: {
|
|
1292
|
+
"content-type": "application/json",
|
|
1293
|
+
},
|
|
1294
|
+
body: JSON.stringify({ id, ...build }),
|
|
1295
|
+
},
|
|
1296
|
+
null,
|
|
1297
|
+
);
|
|
1298
|
+
return await response.json();
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
async updateDeploymentService(serviceId: string, update: AdminDeploymentServiceBuildOptions & {
|
|
1302
|
+
// Replaces the service's whole env var set (config-side definition shape).
|
|
1303
|
+
env?: Record<string, AdminDeploymentEnvVarOptions>,
|
|
1304
|
+
}): Promise<AdminDeploymentServiceJson> {
|
|
1305
|
+
const response = await this.sendAdminRequest(
|
|
1306
|
+
urlString`/deployments/services/${serviceId}`,
|
|
1307
|
+
{
|
|
1308
|
+
method: "PATCH",
|
|
1309
|
+
headers: {
|
|
1310
|
+
"content-type": "application/json",
|
|
1311
|
+
},
|
|
1312
|
+
body: JSON.stringify(update),
|
|
1313
|
+
},
|
|
1314
|
+
null,
|
|
1315
|
+
);
|
|
1316
|
+
return await response.json();
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
async deleteDeploymentService(serviceId: string): Promise<void> {
|
|
1320
|
+
await this.sendAdminRequest(
|
|
1321
|
+
urlString`/deployments/services/${serviceId}`,
|
|
1322
|
+
{ method: "DELETE" },
|
|
1323
|
+
null,
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
async listDeploymentRuns(serviceId: string, options?: { limit?: number }): Promise<AdminDeploymentRunJson[]> {
|
|
1328
|
+
const response = await this.sendAdminRequest(
|
|
1329
|
+
urlString`/deployments/services/${serviceId}/runs` + (options?.limit !== undefined ? `?limit=${options.limit}` : ""),
|
|
1330
|
+
{ method: "GET" },
|
|
1331
|
+
null,
|
|
1332
|
+
);
|
|
1333
|
+
return (await response.json()).items;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
async getDeploymentRunLogs(runId: string, options?: { signal?: AbortSignal }): Promise<string> {
|
|
1337
|
+
// The endpoint streams chunked plain text until the run is terminal (or a
|
|
1338
|
+
// server-side cap); reading the full body gives "the logs so far". Pass a
|
|
1339
|
+
// signal so an abandoned view can abort — otherwise the server keeps
|
|
1340
|
+
// following the build for minutes.
|
|
1341
|
+
const response = await this.sendAdminRequest(
|
|
1342
|
+
urlString`/deployments/runs/${runId}/logs`,
|
|
1343
|
+
{ method: "GET", signal: options?.signal },
|
|
1344
|
+
null,
|
|
1345
|
+
);
|
|
1346
|
+
return await response.text();
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
async addDeploymentServiceDomain(serviceId: string, hostname: string, options?: { isPrimary?: boolean }): Promise<void> {
|
|
1350
|
+
await this.sendAdminRequest(
|
|
1351
|
+
urlString`/deployments/services/${serviceId}/domains`,
|
|
1352
|
+
{
|
|
1353
|
+
method: "POST",
|
|
1354
|
+
headers: {
|
|
1355
|
+
"content-type": "application/json",
|
|
1356
|
+
},
|
|
1357
|
+
body: JSON.stringify({ hostname, ...(options?.isPrimary ? { is_primary: true } : {}) }),
|
|
1358
|
+
},
|
|
1359
|
+
null,
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
async getDeploymentServiceDomain(serviceId: string, hostname: string): Promise<AdminDeploymentDomainJson> {
|
|
1364
|
+
const response = await this.sendAdminRequest(
|
|
1365
|
+
urlString`/deployments/services/${serviceId}/domains/${hostname}`,
|
|
1366
|
+
{ method: "GET" },
|
|
1367
|
+
null,
|
|
1368
|
+
);
|
|
1369
|
+
return await response.json();
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
async deleteDeploymentServiceDomain(serviceId: string, hostname: string): Promise<void> {
|
|
1373
|
+
await this.sendAdminRequest(
|
|
1374
|
+
urlString`/deployments/services/${serviceId}/domains/${hostname}`,
|
|
1375
|
+
{ method: "DELETE" },
|
|
1376
|
+
null,
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1207
1380
|
}
|