@amerged/ohmyhost-mcp 0.1.15 → 0.1.17
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/README.md +1 -1
- package/dist/{chunk-LGXYRWBD.js → chunk-SVJRXROG.js} +10 -10
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +216 -16
- package/package.json +1 -1
- package/src/apps/mcp/local-client.ts +56 -1
- package/src/apps/mcp/local-server.ts +110 -9
- package/src/apps/product-cli/cli.ts +89 -10
- package/src/apps/product-cli/command.ts +135 -10
- package/src/apps/product-cli/composition.ts +47 -3
- package/src/apps/product-cli/index.ts +1 -0
- package/src/apps/product-cli/product-api.ts +55 -1
- package/src/apps/product-cli/project-link-store.ts +45 -2
- package/src/apps/product-cli/repository-init.ts +2 -1
- package/src/apps/product-cli/runtime-contract.ts +56 -3
- package/src/packages/agent-skills/generated-skill-resources.ts +9 -9
- package/src/packages/sdk-ts/generated/index.ts +33 -0
- package/src/packages/sdk-ts/generated/sdk.gen.ts +241 -3
- package/src/packages/sdk-ts/generated/types.gen.ts +337 -10
- package/src/packages/sdk-ts/managed-mail-response.ts +7 -0
- package/src/packages/sdk-ts/managed-mail.ts +7 -1
|
@@ -53,6 +53,20 @@ export interface LocalMcpProductClient {
|
|
|
53
53
|
}): Promise<CloudflareDnsAuthorization>;
|
|
54
54
|
getCloudflareDnsStatus(projectId: string): Promise<CloudflareDnsAuthorizationStatus>;
|
|
55
55
|
createDevAccessTicket(projectId: string): Promise<DevAccessTicket>;
|
|
56
|
+
ensureDevShareLink(projectId: string): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
57
|
+
setDevAccessMode(input: {
|
|
58
|
+
projectId: string;
|
|
59
|
+
mode: "protected" | "public";
|
|
60
|
+
idempotencyKey: string;
|
|
61
|
+
}): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
62
|
+
rotateDevShareLink(input: {
|
|
63
|
+
projectId: string;
|
|
64
|
+
idempotencyKey: string;
|
|
65
|
+
}): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
66
|
+
revokeDevShareLink(input: {
|
|
67
|
+
projectId: string;
|
|
68
|
+
idempotencyKey: string;
|
|
69
|
+
}): Promise<import("@ohmyhost/sdk-ts").DevAccessState>;
|
|
56
70
|
changeDatabaseCompute(input: {
|
|
57
71
|
projectId: string;
|
|
58
72
|
environment: "dev" | "prod";
|
|
@@ -264,6 +278,7 @@ interface ProjectCreateInput {
|
|
|
264
278
|
readonly organizationId: string;
|
|
265
279
|
readonly name: string;
|
|
266
280
|
readonly dataMode?: "shared" | "isolated";
|
|
281
|
+
readonly devAccessMode?: "protected" | "public";
|
|
267
282
|
readonly region?: "us" | "eu";
|
|
268
283
|
readonly idempotencyKey: string;
|
|
269
284
|
}
|
|
@@ -278,6 +293,7 @@ interface SourceLinkInput {
|
|
|
278
293
|
interface DeploymentPlanInput {
|
|
279
294
|
readonly projectId: string;
|
|
280
295
|
readonly commitSha: string;
|
|
296
|
+
readonly environment: "dev" | "prod";
|
|
281
297
|
}
|
|
282
298
|
|
|
283
299
|
interface DeploymentCreateInput {
|
|
@@ -461,7 +477,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
461
477
|
server,
|
|
462
478
|
dependencies,
|
|
463
479
|
"domain_paid_plan",
|
|
464
|
-
"Plan a customer-owned production hostname and return the manual CNAME/validation instructions. This does not activate or change DNS. Free projects use their platform domain without customer DNS authorization.",
|
|
480
|
+
"Plan a customer-owned production hostname and return the manual CNAME/validation instructions. This does not activate or change DNS. Free projects use their platform domain without customer DNS authorization. production_deployment_required means Prod has no active deployment: deploy to Prod or promote the current Dev deployment, then plan again.",
|
|
465
481
|
z.object({ project_id: identifier, hostname: domainName }),
|
|
466
482
|
(client, input) =>
|
|
467
483
|
client.planPaidDomain({ projectId: input.project_id, hostname: input.hostname }),
|
|
@@ -470,7 +486,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
470
486
|
server,
|
|
471
487
|
dependencies,
|
|
472
488
|
"domain_paid_apply",
|
|
473
|
-
"Activate the explicitly requested customer hostname. Requires Paid; the backend may reuse only its scoped customer OAuth grant. After domain_cloudflare_status is authorized, replay the original hostname/key to reconcile its exact DNS records. Otherwise return the manual DNS records to the human. Preserve the original hostname/key after uncertainty; no arbitrary DNS or provider credentials are accepted.",
|
|
489
|
+
"Activate the explicitly requested customer hostname. Requires Paid; the backend may reuse only its scoped customer OAuth grant. After domain_cloudflare_status is authorized, replay the original hostname/key to reconcile its exact DNS records. Otherwise return the manual DNS records to the human. Preserve the original hostname/key after uncertainty; no arbitrary DNS or provider credentials are accepted. production_deployment_required changed nothing: deploy to Prod or promote Dev first, then repeat the same hostname/key.",
|
|
474
490
|
z.object({
|
|
475
491
|
project_id: identifier,
|
|
476
492
|
hostname: domainName,
|
|
@@ -1001,11 +1017,12 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1001
1017
|
server,
|
|
1002
1018
|
dependencies,
|
|
1003
1019
|
"project_create",
|
|
1004
|
-
"Create an ohmyho.st project. Recommend isolated Dev/Prod data, but respect the customer's choice and added database consumption. shared is the default and uses one database/file namespace; isolated separates database/Auth records and files. Choose before provisioning; changing the established mode requires a data migration. region defaults to us; eu places the project's database, files and builds in the EU at the same prices. The region cannot be changed later, and ohmyhost.yaml storage.jurisdiction must equal it.",
|
|
1020
|
+
"Create an ohmyho.st project. Ask whether Dev should be protected (default) or public before creation; public means anyone with the Dev URL can open it, including when Dev and Prod share data. Recommend isolated Dev/Prod data, but respect the customer's choice and added database consumption. shared is the default and uses one database/file namespace; isolated separates database/Auth records and files. Choose before provisioning; changing the established mode requires a data migration. region defaults to us; eu places the project's database, files and builds in the EU at the same prices. The region cannot be changed later, and ohmyhost.yaml storage.jurisdiction must equal it.",
|
|
1005
1021
|
z.object({
|
|
1006
1022
|
organization_id: identifier,
|
|
1007
1023
|
name: z.string().min(1).max(128),
|
|
1008
1024
|
data_mode: z.enum(["shared", "isolated"]).optional(),
|
|
1025
|
+
dev_access_mode: z.enum(["protected", "public"]).optional(),
|
|
1009
1026
|
region: z.enum(["us", "eu"]).optional(),
|
|
1010
1027
|
idempotency_key: idempotencyKey,
|
|
1011
1028
|
}),
|
|
@@ -1014,6 +1031,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1014
1031
|
organizationId: input.organization_id,
|
|
1015
1032
|
name: input.name,
|
|
1016
1033
|
...(input.data_mode === undefined ? {} : { dataMode: input.data_mode }),
|
|
1034
|
+
...(input.dev_access_mode === undefined ? {} : { devAccessMode: input.dev_access_mode }),
|
|
1017
1035
|
...(input.region === undefined ? {} : { region: input.region }),
|
|
1018
1036
|
idempotencyKey: input.idempotency_key,
|
|
1019
1037
|
}),
|
|
@@ -1031,15 +1049,68 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1031
1049
|
server,
|
|
1032
1050
|
dependencies,
|
|
1033
1051
|
"project_status",
|
|
1034
|
-
"Get source, both Dev/Prod environment IDs, deployment URLs, latest operation and cleanup status. Dev
|
|
1052
|
+
"Get source, both Dev/Prod environment IDs, deployment URLs, Dev access mode, latest operation and cleanup status. Protected Dev needs project_dev_share_link_get for a persistent share link. Select environments by name when configuring secrets; never infer a Prod ID from the default Dev environment.",
|
|
1035
1053
|
z.object({ project_id: identifier }),
|
|
1036
1054
|
(client, input) => client.getProjectStatus(input.project_id),
|
|
1037
1055
|
);
|
|
1056
|
+
registerProductTool(
|
|
1057
|
+
server,
|
|
1058
|
+
dependencies,
|
|
1059
|
+
"project_dev_share_link_get",
|
|
1060
|
+
"Owner only: get or create the persistent protected Dev link. The same link works for multiple visitors and has no automatic expiry. Keep it out of notes and logs.",
|
|
1061
|
+
z.object({ project_id: identifier }),
|
|
1062
|
+
(client, input) => client.ensureDevShareLink(input.project_id),
|
|
1063
|
+
mutationAnnotations(),
|
|
1064
|
+
);
|
|
1065
|
+
registerProductTool(
|
|
1066
|
+
server,
|
|
1067
|
+
dependencies,
|
|
1068
|
+
"project_dev_access_mode_set",
|
|
1069
|
+
"Owner only: choose public Dev (no platform token) or protected Dev (share link required). A switch takes effect on the next request; switching back creates a new link.",
|
|
1070
|
+
z.object({
|
|
1071
|
+
project_id: identifier,
|
|
1072
|
+
mode: z.enum(["protected", "public"]),
|
|
1073
|
+
idempotency_key: idempotencyKey,
|
|
1074
|
+
}),
|
|
1075
|
+
(client, input) =>
|
|
1076
|
+
client.setDevAccessMode({
|
|
1077
|
+
projectId: input.project_id,
|
|
1078
|
+
mode: input.mode,
|
|
1079
|
+
idempotencyKey: input.idempotency_key,
|
|
1080
|
+
}),
|
|
1081
|
+
destructiveAnnotations(),
|
|
1082
|
+
);
|
|
1083
|
+
registerProductTool(
|
|
1084
|
+
server,
|
|
1085
|
+
dependencies,
|
|
1086
|
+
"project_dev_share_link_rotate",
|
|
1087
|
+
"Owner only: replace the persistent Dev link and immediately revoke old links and sessions. Repeat the same key after uncertainty.",
|
|
1088
|
+
z.object({ project_id: identifier, idempotency_key: idempotencyKey }),
|
|
1089
|
+
(client, input) =>
|
|
1090
|
+
client.rotateDevShareLink({
|
|
1091
|
+
projectId: input.project_id,
|
|
1092
|
+
idempotencyKey: input.idempotency_key,
|
|
1093
|
+
}),
|
|
1094
|
+
destructiveAnnotations(),
|
|
1095
|
+
);
|
|
1096
|
+
registerProductTool(
|
|
1097
|
+
server,
|
|
1098
|
+
dependencies,
|
|
1099
|
+
"project_dev_share_link_revoke",
|
|
1100
|
+
"Owner only: revoke the persistent Dev link and active sessions immediately; Dev stays protected until a new link is obtained.",
|
|
1101
|
+
z.object({ project_id: identifier, idempotency_key: idempotencyKey }),
|
|
1102
|
+
(client, input) =>
|
|
1103
|
+
client.revokeDevShareLink({
|
|
1104
|
+
projectId: input.project_id,
|
|
1105
|
+
idempotencyKey: input.idempotency_key,
|
|
1106
|
+
}),
|
|
1107
|
+
destructiveAnnotations(),
|
|
1108
|
+
);
|
|
1038
1109
|
registerProductTool(
|
|
1039
1110
|
server,
|
|
1040
1111
|
dependencies,
|
|
1041
1112
|
"project_dev_access_create",
|
|
1042
|
-
"Create an owner-only
|
|
1113
|
+
"Create an owner-only one-hour single-use access link for the protected Dev app. Anonymous Dev HTTP 404 is expected; it does not prove deployment failure. Open redeem_url once in the intended browser or isolated cookie jar, then use the clean origin with its session cookie. Keep the link and cookie private, out of reports, logs and project notes. A new ticket invalidates earlier one-time ticket sessions and unused owner tickets but not the persistent share link; do not blindly retry an uncertain request. This grants no customer application login or Prod access.",
|
|
1043
1114
|
z.object({ project_id: identifier }),
|
|
1044
1115
|
(client, input) => client.createDevAccessTicket(input.project_id),
|
|
1045
1116
|
{ ...mutationAnnotations(), idempotentHint: false },
|
|
@@ -1097,10 +1168,18 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1097
1168
|
server,
|
|
1098
1169
|
dependencies,
|
|
1099
1170
|
"deployment_plan",
|
|
1100
|
-
"Plan an immutable deployment. Preserve the customer's existing authentication integration, such as Better Auth or WorkOS, and use the same generic Next.js/Vite/TanStack runtime contract. ohmyho.st login is separate. Read the app Skill's auth guidance and report actual plan blockers, required secret names/callback configuration and unverified runtime compatibility. Never assume a working platform login proves application login or silently replace its auth provider.",
|
|
1101
|
-
z.object({
|
|
1171
|
+
"Plan an immutable deployment. Preserve the customer's existing authentication integration, such as Better Auth or WorkOS, and use the same generic Next.js/Vite/TanStack runtime contract. ohmyho.st login is separate. Read the app Skill's auth guidance and report actual plan blockers, required secret names/callback configuration and unverified runtime compatibility. Hosting, including Better Auth, needs no mail domain unless the app declares mail.enabled. Never assume a working platform login proves application login or silently replace its auth provider. The plan builds into Dev unless environment is prod; prod builds straight into Prod without a Dev deployment (a project with shared data and a database must deploy to Dev and promote instead). The plan stays valid for 24 hours.",
|
|
1172
|
+
z.object({
|
|
1173
|
+
project_id: identifier,
|
|
1174
|
+
commit_sha: commitSha,
|
|
1175
|
+
environment: z.enum(["dev", "prod"]).optional(),
|
|
1176
|
+
}),
|
|
1102
1177
|
(client, input) =>
|
|
1103
|
-
client.planDeployment({
|
|
1178
|
+
client.planDeployment({
|
|
1179
|
+
projectId: input.project_id,
|
|
1180
|
+
commitSha: input.commit_sha,
|
|
1181
|
+
environment: input.environment ?? "dev",
|
|
1182
|
+
}),
|
|
1104
1183
|
);
|
|
1105
1184
|
registerProductTool(
|
|
1106
1185
|
server,
|
|
@@ -1219,7 +1298,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1219
1298
|
server,
|
|
1220
1299
|
dependencies,
|
|
1221
1300
|
"mail_setup",
|
|
1222
|
-
"Configure the customer's one production mail domain using the project Prod environment ID. The Dev and Prod application URLs do not create separate mail domains. Ask whether they want sending and receiving. Configure sending first; receiving requires mail_webhook_set, application signature verification and mail_webhook_verify. Preserve existing mailbox MX records. No Resend account or key is needed.",
|
|
1301
|
+
"Configure the customer's one production mail domain using the project Prod environment ID, only when the customer wants mail or the app declares mail.enabled; hosting needs no mail domain and none is registered automatically. The Dev and Prod application URLs do not create separate mail domains. Ask whether they want sending and receiving. Configure sending first; receiving requires mail_webhook_set, application signature verification and mail_webhook_verify. Preserve existing mailbox MX records. No Resend account or key is needed.",
|
|
1223
1302
|
z.object({
|
|
1224
1303
|
project_id: identifier,
|
|
1225
1304
|
environment_id: identifier,
|
|
@@ -1397,6 +1476,28 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1397
1476
|
});
|
|
1398
1477
|
},
|
|
1399
1478
|
);
|
|
1479
|
+
registerProductTool(
|
|
1480
|
+
server,
|
|
1481
|
+
dependencies,
|
|
1482
|
+
"mail_domain_delete",
|
|
1483
|
+
"Retire the project's mail domain while the project stays active; use its Prod environment ID. Sending and receiving stop at once, then its provider domain and key are removed. DNS is not changed: remove the returned dns_records from the domain's DNS. Only when the customer no longer wants mail. Requires explicit confirmation; repeat the same key after uncertainty.",
|
|
1484
|
+
z.object({
|
|
1485
|
+
project_id: identifier,
|
|
1486
|
+
environment_id: identifier,
|
|
1487
|
+
idempotency_key: idempotencyKey,
|
|
1488
|
+
confirm: z.literal(true),
|
|
1489
|
+
}),
|
|
1490
|
+
async (client, input) => {
|
|
1491
|
+
if (!client.managedMail) throw new Error("Mail unavailable");
|
|
1492
|
+
return client.managedMail({
|
|
1493
|
+
action: "domain_delete",
|
|
1494
|
+
projectId: input.project_id,
|
|
1495
|
+
environmentId: input.environment_id,
|
|
1496
|
+
idempotencyKey: input.idempotency_key,
|
|
1497
|
+
});
|
|
1498
|
+
},
|
|
1499
|
+
destructiveAnnotations(),
|
|
1500
|
+
);
|
|
1400
1501
|
registerProductTool(
|
|
1401
1502
|
server,
|
|
1402
1503
|
dependencies,
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
parseOrganizationCredits,
|
|
68
68
|
parseProjectCreditBudget,
|
|
69
69
|
parseDevAccessTicket,
|
|
70
|
+
parseDevAccessState,
|
|
70
71
|
parseGuardedActionPlan,
|
|
71
72
|
parseEnvironmentSecret,
|
|
72
73
|
parseEnvironmentSecretDeletion,
|
|
@@ -254,6 +255,7 @@ interface CliErrorDocument {
|
|
|
254
255
|
readonly message?: string;
|
|
255
256
|
readonly retry_after_seconds?: number;
|
|
256
257
|
readonly request_id?: string;
|
|
258
|
+
readonly docs_url?: string;
|
|
257
259
|
};
|
|
258
260
|
}
|
|
259
261
|
|
|
@@ -303,13 +305,20 @@ const HELP_USAGE = Object.freeze({
|
|
|
303
305
|
"feedback submit":
|
|
304
306
|
"ohmyhost feedback submit --organization ULID --kind bug|issue|feature_request --title TITLE --description REDACTED_REPORT [--project ULID] [--environment ULID] [--operation ULID] [--error-code CODE] [--client-version VERSION] --idempotency-key KEY --json",
|
|
305
307
|
"project create":
|
|
306
|
-
"ohmyhost project create --organization ULID --name NAME [--data-mode shared|isolated] [--region us|eu] --idempotency-key KEY --json (the region is chosen once: us is the default, eu places the database, files and builds in the EU; it cannot be changed later)",
|
|
308
|
+
"ohmyhost project create --organization ULID --name NAME [--data-mode shared|isolated] [--dev-access-mode protected|public] [--region us|eu] --idempotency-key KEY --json (the region is chosen once: us is the default, eu places the database, files and builds in the EU; it cannot be changed later)",
|
|
307
309
|
"project list": "ohmyhost project list [--cursor ULID] [--limit LIMIT] --json",
|
|
308
310
|
"project context": "ohmyhost project context --project ULID --json",
|
|
309
311
|
"project notes set":
|
|
310
312
|
"ohmyhost project notes set --project ULID --version NUMBER --markdown TEXT --idempotency-key KEY --json (no credentials or signed URLs)",
|
|
311
313
|
"project status": "ohmyhost project status --project ULID --json",
|
|
312
314
|
"project dev-access create": "ohmyhost project dev-access create --project ULID --json",
|
|
315
|
+
"project dev-share link": "ohmyhost project dev-share link --project ULID --json",
|
|
316
|
+
"project dev-share rotate":
|
|
317
|
+
"ohmyhost project dev-share rotate --project ULID --idempotency-key KEY --yes --json",
|
|
318
|
+
"project dev-share revoke":
|
|
319
|
+
"ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
|
|
320
|
+
"project dev-access mode":
|
|
321
|
+
"ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
|
|
313
322
|
"project handle check":
|
|
314
323
|
"ohmyhost project handle check --handle HANDLE --json (is this address free? answers with a reason and free alternatives; the address becomes HANDLE.check.omh.st)",
|
|
315
324
|
"project handle set":
|
|
@@ -347,9 +356,9 @@ const HELP_USAGE = Object.freeze({
|
|
|
347
356
|
"domain paid status": "ohmyhost domain paid status --project ULID --json",
|
|
348
357
|
"domain paid delete":
|
|
349
358
|
"ohmyhost domain paid delete --project ULID --hostname HOST --idempotency-key KEY --yes --json",
|
|
350
|
-
plan: "ohmyhost plan --project ULID --commit SHA --json",
|
|
359
|
+
plan: "ohmyhost plan --project ULID --commit SHA [--environment dev|prod] --json",
|
|
351
360
|
deploy:
|
|
352
|
-
"ohmyhost deploy --project ULID --plan-id ULID --idempotency-key KEY --yes [--wait] --json",
|
|
361
|
+
"ohmyhost deploy --project ULID (--plan-id ULID | --commit SHA [--environment dev|prod]) --idempotency-key KEY --yes [--wait] --json",
|
|
353
362
|
logs: "ohmyhost logs OPERATION_ULID --follow --json",
|
|
354
363
|
"deployment logs": "ohmyhost deployment logs --project ULID --deployment ULID --follow --json",
|
|
355
364
|
"rollback plan": "ohmyhost rollback plan --project ULID --deployment DEPLOYMENT_ULID --json",
|
|
@@ -387,6 +396,8 @@ const HELP_USAGE = Object.freeze({
|
|
|
387
396
|
"mail domain set":
|
|
388
397
|
"ohmyhost mail domain set --project ULID --environment ULID --domain DOMAIN --sending true --receiving false --idempotency-key KEY --json",
|
|
389
398
|
"mail domain status": "ohmyhost mail domain status --project ULID --environment ULID --json",
|
|
399
|
+
"mail domain delete":
|
|
400
|
+
"ohmyhost mail domain delete --project ULID --environment ULID --idempotency-key KEY --yes --json",
|
|
390
401
|
} satisfies Readonly<Record<CustomerCommandLabel, string>>);
|
|
391
402
|
|
|
392
403
|
const HELP_COMMANDS = Object.freeze(
|
|
@@ -740,6 +751,37 @@ export class ProductCli {
|
|
|
740
751
|
if (command.kind === "project-status") return await this.getProjectStatus(command, signal);
|
|
741
752
|
if (command.kind === "project-dev-access-create")
|
|
742
753
|
return await this.createDevAccessTicket(command, signal);
|
|
754
|
+
if (
|
|
755
|
+
command.kind === "project-dev-share-link" ||
|
|
756
|
+
command.kind === "project-dev-share-rotate" ||
|
|
757
|
+
command.kind === "project-dev-share-revoke" ||
|
|
758
|
+
command.kind === "project-dev-access-mode-set"
|
|
759
|
+
) {
|
|
760
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
761
|
+
const state = parseDevAccessState(
|
|
762
|
+
command.kind === "project-dev-share-link"
|
|
763
|
+
? await api.ensureDevShareLink(command.projectId)
|
|
764
|
+
: command.kind === "project-dev-share-rotate"
|
|
765
|
+
? await api.rotateDevShareLink(command.projectId, command.idempotencyKey)
|
|
766
|
+
: command.kind === "project-dev-share-revoke"
|
|
767
|
+
? await api.revokeDevShareLink(command.projectId, command.idempotencyKey)
|
|
768
|
+
: await api.setDevAccessMode(
|
|
769
|
+
command.projectId,
|
|
770
|
+
command.mode,
|
|
771
|
+
command.idempotencyKey,
|
|
772
|
+
),
|
|
773
|
+
);
|
|
774
|
+
return success(
|
|
775
|
+
command.kind === "project-dev-share-link"
|
|
776
|
+
? "project dev-share link"
|
|
777
|
+
: command.kind === "project-dev-share-rotate"
|
|
778
|
+
? "project dev-share rotate"
|
|
779
|
+
: command.kind === "project-dev-share-revoke"
|
|
780
|
+
? "project dev-share revoke"
|
|
781
|
+
: "project dev-access mode",
|
|
782
|
+
{ status: "succeeded", state },
|
|
783
|
+
);
|
|
784
|
+
}
|
|
743
785
|
if (command.kind === "database-write") {
|
|
744
786
|
const file = await open(command.statementFile, "r"),
|
|
745
787
|
buffer = Buffer.alloc(65_537);
|
|
@@ -1169,6 +1211,7 @@ export class ProductCli {
|
|
|
1169
1211
|
organizationId: command.organizationId,
|
|
1170
1212
|
name: command.name,
|
|
1171
1213
|
...(command.dataMode === undefined ? {} : { dataMode: command.dataMode }),
|
|
1214
|
+
...(command.devAccessMode === undefined ? {} : { devAccessMode: command.devAccessMode }),
|
|
1172
1215
|
...(command.region === undefined ? {} : { region: command.region }),
|
|
1173
1216
|
idempotencyKey: command.idempotencyKey,
|
|
1174
1217
|
}),
|
|
@@ -1451,7 +1494,10 @@ export class ProductCli {
|
|
|
1451
1494
|
): Promise<CliResult> {
|
|
1452
1495
|
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1453
1496
|
const autoDeploy = await api.getAutoDeploy(command.projectId);
|
|
1454
|
-
return success("source auto-deploy status", {
|
|
1497
|
+
return success("source auto-deploy status", {
|
|
1498
|
+
status: autoDeploy.generation === 0 ? "not_configured" : "ready",
|
|
1499
|
+
auto_deploy: autoDeploy,
|
|
1500
|
+
});
|
|
1455
1501
|
}
|
|
1456
1502
|
|
|
1457
1503
|
private async authorizeCloudflareDns(
|
|
@@ -1507,7 +1553,16 @@ export class ProductCli {
|
|
|
1507
1553
|
): Promise<CliResult> {
|
|
1508
1554
|
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1509
1555
|
const plan = parseDeploymentPlan(await api.planDeployment(command));
|
|
1510
|
-
|
|
1556
|
+
// Say which environment this plan changes and how the change reaches the live site.
|
|
1557
|
+
const notes =
|
|
1558
|
+
plan["environment"] === "prod"
|
|
1559
|
+
? [
|
|
1560
|
+
`This plan builds straight into Prod (${String(plan["route"])}); Prod traffic switches once it is activated.`,
|
|
1561
|
+
]
|
|
1562
|
+
: [
|
|
1563
|
+
`This plan updates Dev only (${String(plan["route"])}). To go live, deploy with --environment prod or promote the Dev deployment with '${this.dependencies.commandPrefix} deployment promote plan --project ULID --deployment DEV_DEPLOYMENT_ULID --json'.`,
|
|
1564
|
+
];
|
|
1565
|
+
return success("plan", { status: "planned", plan, notes });
|
|
1511
1566
|
}
|
|
1512
1567
|
|
|
1513
1568
|
private async deploy(
|
|
@@ -1515,20 +1570,37 @@ export class ProductCli {
|
|
|
1515
1570
|
signal: AbortSignal,
|
|
1516
1571
|
): Promise<CliResult> {
|
|
1517
1572
|
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1518
|
-
|
|
1573
|
+
// With --commit the deploy plans first, so an approved deploy needs no separate plan step.
|
|
1574
|
+
const plan =
|
|
1575
|
+
command.commitSha === null
|
|
1576
|
+
? null
|
|
1577
|
+
: parseDeploymentPlan(
|
|
1578
|
+
await api.planDeployment({
|
|
1579
|
+
projectId: command.projectId,
|
|
1580
|
+
commitSha: command.commitSha,
|
|
1581
|
+
environment: command.environment,
|
|
1582
|
+
}),
|
|
1583
|
+
);
|
|
1584
|
+
let operation = parseOperation(
|
|
1585
|
+
await api.createDeployment({
|
|
1586
|
+
projectId: command.projectId,
|
|
1587
|
+
planId: plan === null ? String(command.planId) : String(plan["id"]),
|
|
1588
|
+
idempotencyKey: command.idempotencyKey,
|
|
1589
|
+
}),
|
|
1590
|
+
);
|
|
1519
1591
|
if (command.wait) operation = await this.waitForOperation(api, operation, signal);
|
|
1520
|
-
//
|
|
1521
|
-
// this sentence the first thing a customer sees after a successful deploy looks like a failure.
|
|
1592
|
+
// Dev access follows the project-selected mode; the clean URL alone is insufficient when protected.
|
|
1522
1593
|
const notes =
|
|
1523
1594
|
operation.state === "succeeded"
|
|
1524
1595
|
? [
|
|
1525
|
-
`
|
|
1596
|
+
`Check project status for dev_access_mode. For protected Dev, run '${this.dependencies.commandPrefix} project dev-share link --project ULID --json' and open or share its persistent share_url; public Dev opens directly.`,
|
|
1526
1597
|
]
|
|
1527
1598
|
: [];
|
|
1528
1599
|
return success("deploy", {
|
|
1529
1600
|
status: command.wait ? "completed" : "accepted",
|
|
1530
1601
|
operation,
|
|
1531
|
-
...(
|
|
1602
|
+
...(plan === null ? {} : { plan }),
|
|
1603
|
+
...(notes.length > 0 && plan?.["environment"] !== "prod" ? { notes } : {}),
|
|
1532
1604
|
});
|
|
1533
1605
|
}
|
|
1534
1606
|
|
|
@@ -2044,6 +2116,7 @@ const classifyFailure = (command: CommandLabel, error: unknown, signal: AbortSig
|
|
|
2044
2116
|
undefined,
|
|
2045
2117
|
problem.retryAfterSeconds,
|
|
2046
2118
|
problem.requestId,
|
|
2119
|
+
problem.docsUrl,
|
|
2047
2120
|
);
|
|
2048
2121
|
} catch {
|
|
2049
2122
|
return failure(
|
|
@@ -2094,6 +2167,7 @@ const failure = (
|
|
|
2094
2167
|
operation?: ReturnType<typeof parseOperation>,
|
|
2095
2168
|
retryAfterSeconds?: number,
|
|
2096
2169
|
requestId?: string,
|
|
2170
|
+
docsUrl?: string,
|
|
2097
2171
|
): CliResult => {
|
|
2098
2172
|
const document: CliErrorDocument = {
|
|
2099
2173
|
version: 1,
|
|
@@ -2106,6 +2180,7 @@ const failure = (
|
|
|
2106
2180
|
suggested_action: suggestedAction,
|
|
2107
2181
|
...(retryAfterSeconds === undefined ? {} : { retry_after_seconds: retryAfterSeconds }),
|
|
2108
2182
|
...(requestId === undefined ? {} : { request_id: requestId }),
|
|
2183
|
+
...(docsUrl === undefined ? {} : { docs_url: docsUrl }),
|
|
2109
2184
|
...(operation?.error === undefined ? {} : { message: operation.error.message }),
|
|
2110
2185
|
},
|
|
2111
2186
|
};
|
|
@@ -2144,6 +2219,10 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
|
|
|
2144
2219
|
if (command.kind === "project-notes-set") return "project notes set";
|
|
2145
2220
|
if (command.kind === "project-status") return "project status";
|
|
2146
2221
|
if (command.kind === "project-dev-access-create") return "project dev-access create";
|
|
2222
|
+
if (command.kind === "project-dev-share-link") return "project dev-share link";
|
|
2223
|
+
if (command.kind === "project-dev-share-rotate") return "project dev-share rotate";
|
|
2224
|
+
if (command.kind === "project-dev-share-revoke") return "project dev-share revoke";
|
|
2225
|
+
if (command.kind === "project-dev-access-mode-set") return "project dev-access mode";
|
|
2147
2226
|
if (command.kind === "project-handle-check") return "project handle check";
|
|
2148
2227
|
if (command.kind === "project-handle-set") return "project handle set";
|
|
2149
2228
|
if (command.kind === "database-write") return "database write";
|