@hexclave/shared 1.0.61 → 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-ChuyYSXj.d.ts → admin-interface-D63SFHX_.d.ts} +94 -12
- package/dist/admin-interface-D63SFHX_.d.ts.map +1 -0
- package/dist/{admin-metrics-1yiWGq9z.d.ts → admin-metrics-bsOkw4YQ.d.ts} +15 -15
- package/dist/{admin-metrics-1yiWGq9z.d.ts.map → admin-metrics-bsOkw4YQ.d.ts.map} +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +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.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-C4H4gn_G.d.ts → client-interface-DvkciaL0.d.ts} +10 -10
- package/dist/{client-interface-C4H4gn_G.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-BCkMYzHx.d.ts → current-user-BBoN3hsn.d.ts} +17 -17
- package/dist/{current-user-BCkMYzHx.d.ts.map → current-user-BBoN3hsn.d.ts.map} +1 -1
- package/dist/{email-outbox-BRZiK0ZG.d.ts → email-outbox-CqzwInBL.d.ts} +313 -313
- package/dist/{email-outbox-BRZiK0ZG.d.ts.map → email-outbox-CqzwInBL.d.ts.map} +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +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.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 +249 -119
- 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 +14 -14
- package/dist/esm/interface/conversations.d.ts +37 -37
- package/dist/esm/interface/crud/current-user.d.ts +16 -16
- package/dist/esm/interface/crud/email-outbox.d.ts +312 -312
- 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 +32 -32
- package/dist/esm/interface/crud/transactions.d.ts +29 -29
- package/dist/esm/interface/crud/transactions.d.ts.map +1 -1
- package/dist/esm/interface/crud/users.d.ts +22 -22
- package/dist/esm/interface/plan-usage.d.ts +1 -1
- package/dist/esm/interface/webhooks.d.ts +4 -4
- package/dist/esm/known-errors.d.ts +4 -4
- package/dist/esm/schema-fields.d.ts +12 -12
- package/dist/esm/sessions.d.ts +9 -9
- 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 +37 -37
- 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-Bhd0JAgh.d.ts → known-errors-Bm4a61wL.d.ts} +5 -5
- package/dist/{known-errors-Bhd0JAgh.d.ts.map → known-errors-Bm4a61wL.d.ts.map} +1 -1
- package/dist/known-errors.d.ts +1 -1
- package/dist/{plan-usage-COq9I00z.d.ts → plan-usage-BaDxcLZ1.d.ts} +2 -2
- package/dist/{plan-usage-COq9I00z.d.ts.map → plan-usage-BaDxcLZ1.d.ts.map} +1 -1
- package/dist/{products-BkPf1X78.d.ts → products-D4o8A2W0.d.ts} +24 -24
- package/dist/{products-BkPf1X78.d.ts.map → products-D4o8A2W0.d.ts.map} +1 -1
- package/dist/{project-api-keys-CK9SIo0q.d.ts → project-api-keys--7V5X_UW.d.ts} +5 -5
- package/dist/{project-api-keys-CK9SIo0q.d.ts.map → project-api-keys--7V5X_UW.d.ts.map} +1 -1
- package/dist/{projects-CVqfljhv.d.ts → projects-BX5Y50kJ.d.ts} +60 -60
- package/dist/{projects-CVqfljhv.d.ts.map → projects-BX5Y50kJ.d.ts.map} +1 -1
- package/dist/{schema-BPMGhu_7.d.ts → schema-dJ_7rUCO.d.ts} +250 -120
- package/dist/schema-dJ_7rUCO.d.ts.map +1 -0
- package/dist/{schema-fields-C8sIQVTq.d.ts → schema-fields-DZtDQEEO.d.ts} +13 -13
- package/dist/{schema-fields-C8sIQVTq.d.ts.map → schema-fields-DZtDQEEO.d.ts.map} +1 -1
- package/dist/schema-fields.d.ts +1 -1
- package/dist/{server-interface-CRp4VVZh.d.ts → server-interface-Ra_vIHP4.d.ts} +8 -8
- package/dist/{server-interface-CRp4VVZh.d.ts.map → server-interface-Ra_vIHP4.d.ts.map} +1 -1
- package/dist/{sessions-COluKK6_.d.ts → sessions-CI-0TBQb.d.ts} +11 -11
- package/dist/{sessions-COluKK6_.d.ts.map → sessions-CI-0TBQb.d.ts.map} +1 -1
- package/dist/sessions.d.ts +1 -1
- package/dist/{team-member-profiles-CgYXR-Rx.d.ts → team-member-profiles-BoDEqx-0.d.ts} +33 -33
- package/dist/{team-member-profiles-CgYXR-Rx.d.ts.map → team-member-profiles-BoDEqx-0.d.ts.map} +1 -1
- package/dist/{transactions-7q13mxhG.d.ts → transactions-CHNUJ42J.d.ts} +30 -30
- package/dist/transactions-CHNUJ42J.d.ts.map +1 -0
- package/dist/{users-7MKfEyyq.d.ts → users-DHNRwoSz.d.ts} +24 -24
- package/dist/{users-7MKfEyyq.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-C5gG85Ce.d.ts → webhooks-8Ts1Clcb.d.ts} +5 -5
- package/dist/{webhooks-C5gG85Ce.d.ts.map → webhooks-8Ts1Clcb.d.ts.map} +1 -1
- package/package.json +1 -1
- package/src/ai/unified-prompts/skill-site-prompt-parts/deployments-skill.ts +128 -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-ChuyYSXj.d.ts.map +0 -1
- package/dist/schema-BPMGhu_7.d.ts.map +0 -1
- package/dist/transactions-7q13mxhG.d.ts.map +0 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { deindent } from "../../../utils/strings";
|
|
2
|
+
|
|
3
|
+
// The Deployments-specific addendum served at https://skill.hexclave.com/deployments.
|
|
4
|
+
// It is appended verbatim after the full base skill, so this section assumes the
|
|
5
|
+
// reader already has the general Hexclave skill (CLI auth, config format, SDKs) in
|
|
6
|
+
// context and only teaches what is specific to the Deployments app.
|
|
7
|
+
export const deploymentsSkillSection = deindent`
|
|
8
|
+
# Hexclave Deployments
|
|
9
|
+
|
|
10
|
+
The Deployments app makes deploying a service very easy. You select a type of service to deploy and provide your code, build instructions, run instructions, environment variables, and the code framework. You can set up multiple deployments per Hexclave project — for example, a backend service and a frontend service. Once a service is deployed, it is publicly accessible.
|
|
11
|
+
|
|
12
|
+
For this iteration of the Deployments app, only the \`vercel\` service type is available. It can be used to set up serverless deployments.
|
|
13
|
+
|
|
14
|
+
To use Hexclave Deployments, you must do a few things. First, the Deployments app must be enabled, by adding \`deployments-alpha\` under \`apps.installed\`. Second, you must edit your \`hexclave.config.ts\` file to add the relevant configuration, as specified below. Both keys are \`deployments-alpha\`, not \`deployments\` — the app is in alpha and its id says so. Because of the hyphen, quote it.
|
|
15
|
+
|
|
16
|
+
## Config
|
|
17
|
+
|
|
18
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
19
|
+
import type { HexclaveConfig } from "@hexclave/js/config"; // replace \`js\` with the correct framework SDK package
|
|
20
|
+
|
|
21
|
+
export const config: HexclaveConfig = {
|
|
22
|
+
apps: {
|
|
23
|
+
installed: {
|
|
24
|
+
authentication: { enabled: true },
|
|
25
|
+
"deployments-alpha": { enabled: true },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
"deployments-alpha": {
|
|
29
|
+
services: {
|
|
30
|
+
web: {
|
|
31
|
+
type: "vercel",
|
|
32
|
+
rootDirectory: "./",
|
|
33
|
+
framework: "nextjs",
|
|
34
|
+
installCommand: "pnpm install",
|
|
35
|
+
buildCommand: "pnpm build",
|
|
36
|
+
outputDirectory: ".next",
|
|
37
|
+
env: {
|
|
38
|
+
"MY_ENV_VAR": { value: "true" },
|
|
39
|
+
"DATABASE_CONNECTION_STRING": { type: "secret", key: "db_connection" },
|
|
40
|
+
"NEXT_PUBLIC_HEXCLAVE_PROJECT_ID": { type: "connection", value: "hexclave.projectId" },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
Here, \`web\` is the name of your service. \`type\` refers to the type of service you want to set up; for now, as mentioned earlier, only \`vercel\` is supported. \`rootDirectory\` is the directory in which the install and build commands are run, and where the code for your service is found. \`installCommand\` is the command run to install your application's dependencies, and \`buildCommand\` is what is run to build the necessary packages and files. \`framework\` is the framework used by your service; from it and \`outputDirectory\`, the run command is inferred.
|
|
49
|
+
|
|
50
|
+
The \`HexclaveConfig\` annotation requires the SDK package it is imported from to be a dependency of the project. If it isn't, either install it or drop the annotation (the config is plain TypeScript — the CLI does not type-check it).
|
|
51
|
+
|
|
52
|
+
Note also that the config file usually sits inside \`rootDirectory\`, which means it is uploaded with your source and compiled by the remote build like any other file. If the deployed app doesn't depend on the Hexclave SDK, add \`hexclave.config.ts\` to \`.vercelignore\`: the CLI reads the config from disk before packaging, so the build itself never needs it, and excluding it keeps an SDK import in the config from breaking a build that is otherwise unrelated to Hexclave.
|
|
53
|
+
|
|
54
|
+
## Domains
|
|
55
|
+
|
|
56
|
+
Every Vercel service gets a Vercel domain autoprovisioned. If you want to attach a custom domain, there are two ways to do so.
|
|
57
|
+
|
|
58
|
+
**Option 1 — the dashboard.** Visit \`https://app.hexclave.com/projects/<project-id>/deployments\`, click the service of choice, go to the Domains tab, add your domain, and then create the returned DNS records at your DNS provider.
|
|
59
|
+
|
|
60
|
+
**Option 2 — the CLI.** Here is an example of adding \`app.example.com\` to the service \`api\` and receiving the DNS records that then need to be set with your DNS provider:
|
|
61
|
+
|
|
62
|
+
\`\`\`sh title="Terminal"
|
|
63
|
+
npx @hexclave/cli@latest exec --cloud-project-id <project-id> \\
|
|
64
|
+
"const p = await hexclaveServerApp.getProject(); \\
|
|
65
|
+
await p.addDeploymentServiceDomain('api', 'app.example.com'); \\
|
|
66
|
+
return p.getDeploymentServiceDomain('api', 'app.example.com');"
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
## Deploying
|
|
70
|
+
|
|
71
|
+
Before deploying, run the service's \`installCommand\` and \`buildCommand\` locally and confirm they succeed. The remote build runs the same commands, but \`deploy\` exits before the build finishes, so a broken build does not fail the command — it surfaces only when you check the run afterwards.
|
|
72
|
+
|
|
73
|
+
From the directory containing your config file:
|
|
74
|
+
|
|
75
|
+
\`\`\`sh title="Terminal"
|
|
76
|
+
npx @hexclave/cli@latest deploy web --secret db_connection="$DATABASE_URL"
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
The CLI packages the service's root directory (respecting \`.gitignore\` and \`.vercelignore\`, and always excluding \`node_modules\` and \`.git\`), uploads it, and starts a remote build. Without a config file, the configuration stored in Hexclave governs the deploy, with the root directory resolved against the current directory. It always deploys to production, never prompts, and exits as soon as the build is queued, printing the run id. Follow the build in the dashboard's Deployments tab.
|
|
80
|
+
|
|
81
|
+
Every secret defined in the service's \`env\` must be passed via \`--secret\` on every deploy — a missing (or misspelled) one fails the deploy before anything is uploaded.
|
|
82
|
+
|
|
83
|
+
Options: \`--config <path>\` (default: auto-discover \`hexclave.config.ts\` in the current directory, dashboard configuration otherwise), \`--cloud-project-id <id>\` (default: the \`HEXCLAVE_PROJECT_ID\` env var), and \`--secret KEY=VALUE\` (repeatable, see above).
|
|
84
|
+
|
|
85
|
+
You can also deploy through GitHub Actions, like so:
|
|
86
|
+
|
|
87
|
+
\`\`\`yaml title=".github/workflows/deploy.yaml"
|
|
88
|
+
- run: npm i -g @hexclave/cli
|
|
89
|
+
- run: hexclave deploy web --secret db_connection="$DATABASE_URL"
|
|
90
|
+
env:
|
|
91
|
+
HEXCLAVE_PROJECT_ID: \${{ secrets.HEXCLAVE_PROJECT_ID }}
|
|
92
|
+
HEXCLAVE_SECRET_SERVER_KEY: \${{ secrets.HEXCLAVE_SECRET_SERVER_KEY }}
|
|
93
|
+
DATABASE_URL: \${{ secrets.DATABASE_URL }}
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
## Checking status and debugging failures
|
|
97
|
+
|
|
98
|
+
Because \`deploy\` returns as soon as the build is queued, a successful exit does not mean the deploy succeeded. Check the service to find out:
|
|
99
|
+
|
|
100
|
+
\`\`\`sh title="Terminal"
|
|
101
|
+
npx @hexclave/cli@latest exec --cloud-project-id <project-id> \\
|
|
102
|
+
"const p = await hexclaveServerApp.getProject(); \\
|
|
103
|
+
const svc = (await p.listDeploymentServices()).find(s => s.id === 'web'); \\
|
|
104
|
+
return { status: svc.status, url: svc.url, run: svc.latest_run };"
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
A service's \`status\` is one of \`not_deployed\`, \`queued\`, \`building\`, \`deployed\`, \`failed\`, or \`canceled\`; a run's is \`queued\`, \`building\`, \`ready\`, \`error\`, or \`canceled\`. Poll until the run leaves \`queued\`/\`building\`.
|
|
108
|
+
|
|
109
|
+
A failed run's \`error\` field is only a one-line summary (for example \`Command "npm run build" exited with 1\`). Do not try to guess the cause from it, and do not reproduce the build locally to find out — fetch the actual build output by run id:
|
|
110
|
+
|
|
111
|
+
\`\`\`sh title="Terminal"
|
|
112
|
+
npx @hexclave/cli@latest exec --cloud-project-id <project-id> \\
|
|
113
|
+
"const p = await hexclaveServerApp.getProject(); \\
|
|
114
|
+
return p.getDeploymentRunLogs('<run-id>');"
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
\`deploy\` prints the run id it started. If you don't have one, \`listDeploymentRuns('web', { limit: 5 })\` returns the most recent runs, newest first — call it through \`exec\` like the snippets above.
|
|
118
|
+
|
|
119
|
+
## Deleting a service
|
|
120
|
+
|
|
121
|
+
If you want to delete a deployed service, you can do so through the dashboard, or through the CLI like so:
|
|
122
|
+
|
|
123
|
+
\`\`\`sh title="Terminal"
|
|
124
|
+
npx @hexclave/cli@latest exec --cloud-project-id <project-id> \\
|
|
125
|
+
"const p = await hexclaveServerApp.getProject(); \\
|
|
126
|
+
await p.deleteDeploymentService('api');"
|
|
127
|
+
\`\`\`
|
|
128
|
+
`;
|
|
@@ -6,6 +6,7 @@ import { cliHelpPrompt } from "./skill-site-prompt-parts/cli-help";
|
|
|
6
6
|
import { configDocsPrompt } from "./skill-site-prompt-parts/config-docs";
|
|
7
7
|
import { customComponentsInstructionsPrompt } from "./skill-site-prompt-parts/custom-components-instructions";
|
|
8
8
|
import { dashboardInstructionsPrompt } from "./skill-site-prompt-parts/dashboard-instructions";
|
|
9
|
+
import { deploymentsSkillSection } from "./skill-site-prompt-parts/deployments-skill";
|
|
9
10
|
import { docsIndexPrompt } from "./skill-site-prompt-parts/docs-index";
|
|
10
11
|
import { sdkInterfaceSourcePrompt } from "./skill-site-prompt-parts/sdk-interface-source";
|
|
11
12
|
|
|
@@ -73,3 +74,17 @@ export function buildSkillSitePrompt(docsIndexPromptValue = docsIndexPrompt) {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
export const skillSitePrompt = buildSkillSitePrompt();
|
|
77
|
+
|
|
78
|
+
// The /deployments variant of the skill: the entire base skill, followed by the
|
|
79
|
+
// Deployments-specific addendum. Served at https://skill.hexclave.com/deployments
|
|
80
|
+
// so an agent pointed at that URL gets the full Hexclave skill plus the deeper
|
|
81
|
+
// Deployments material in one document.
|
|
82
|
+
export function buildDeploymentsSkillSitePrompt(baseSkillPrompt = skillSitePrompt) {
|
|
83
|
+
return deindent`
|
|
84
|
+
${baseSkillPrompt}
|
|
85
|
+
|
|
86
|
+
${deploymentsSkillSection}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const deploymentsSkillSitePrompt = buildDeploymentsSkillSitePrompt();
|
package/src/apps/apps-config.ts
CHANGED
|
@@ -192,6 +192,15 @@ export const ALL_APPS = {
|
|
|
192
192
|
tags: ["auth", "developers"],
|
|
193
193
|
stage: "alpha",
|
|
194
194
|
},
|
|
195
|
+
// The app id carries the `-alpha` suffix deliberately: it is the key users
|
|
196
|
+
// write in `apps.installed` and under the top-level config, so the stage is
|
|
197
|
+
// visible in every hexclave.config.ts. The display name stays "Deployments".
|
|
198
|
+
"deployments-alpha": {
|
|
199
|
+
displayName: "Deployments",
|
|
200
|
+
subtitle: "Configure and connect the services that run your app",
|
|
201
|
+
tags: ["developers", "operations"],
|
|
202
|
+
stage: "alpha",
|
|
203
|
+
},
|
|
195
204
|
} as const satisfies Record<string, App>;
|
|
196
205
|
|
|
197
206
|
export function getParentAppId(appId: AppId): AppId | null {
|
|
@@ -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
|
}
|