@amerged/ohmyhost-cli 0.1.11 → 0.1.14
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/bin.js +1 -1
- package/dist/{chunk-WT2RRQUB.js → chunk-U32PJQHH.js} +680 -219
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/apps/product-cli/cli.ts +48 -32
- package/src/apps/product-cli/command.ts +140 -35
- package/src/apps/product-cli/composition.ts +5 -3
- package/src/apps/product-cli/product-api.ts +28 -26
- package/src/apps/product-cli/repository-init.ts +27 -3
- package/src/apps/product-cli/runtime-contract.ts +41 -100
- package/src/packages/contracts/framework-admission.ts +2 -2
- package/src/packages/sdk-ts/generated/index.ts +71 -16
- package/src/packages/sdk-ts/generated/sdk.gen.ts +500 -96
- package/src/packages/sdk-ts/generated/types.gen.ts +741 -162
- package/src/packages/sdk-ts/index.ts +1 -0
- package/src/packages/sdk-ts/managed-mail-response.ts +195 -0
- package/src/packages/sdk-ts/managed-mail.ts +85 -0
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -75,7 +75,6 @@ import {
|
|
|
75
75
|
type PublicEnvironmentSecret,
|
|
76
76
|
parseOperation,
|
|
77
77
|
parseOperationEvent,
|
|
78
|
-
parseProjectMailDomain,
|
|
79
78
|
parseProjectPage,
|
|
80
79
|
parseProjectStatus,
|
|
81
80
|
parseProjectDatabaseQuery,
|
|
@@ -254,6 +253,7 @@ interface CliErrorDocument {
|
|
|
254
253
|
readonly suggested_action: string;
|
|
255
254
|
readonly message?: string;
|
|
256
255
|
readonly retry_after_seconds?: number;
|
|
256
|
+
readonly request_id?: string;
|
|
257
257
|
};
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -312,6 +312,8 @@ const HELP_USAGE = Object.freeze({
|
|
|
312
312
|
"project dev-access create": "ohmyhost project dev-access create --project ULID --json",
|
|
313
313
|
"project handle check":
|
|
314
314
|
"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
|
+
"project handle set":
|
|
316
|
+
"ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
|
|
315
317
|
"database compute set":
|
|
316
318
|
"ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n" +
|
|
317
319
|
" standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
|
|
@@ -367,9 +369,24 @@ const HELP_USAGE = Object.freeze({
|
|
|
367
369
|
"printf '%s' \"$SECRET_VALUE\" | ohmyhost secret set NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY --stdin [--wait] --json",
|
|
368
370
|
"secret delete":
|
|
369
371
|
"ohmyhost secret delete NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY [--wait] --json",
|
|
372
|
+
"mail setup":
|
|
373
|
+
"ohmyhost mail setup --project ULID --environment ULID --domain DOMAIN --sending true --receiving false --idempotency-key KEY --json",
|
|
374
|
+
"mail status": "ohmyhost mail status --project ULID --environment ULID --json",
|
|
375
|
+
"mail webhook set":
|
|
376
|
+
"ohmyhost mail webhook set --project ULID --environment ULID --url HTTPS_URL --idempotency-key KEY --json",
|
|
377
|
+
"mail webhook verify":
|
|
378
|
+
"ohmyhost mail webhook verify --project ULID --environment ULID --idempotency-key KEY --json",
|
|
379
|
+
"mail webhook disable":
|
|
380
|
+
"ohmyhost mail webhook disable --project ULID --environment ULID --idempotency-key KEY --json",
|
|
381
|
+
"mail messages list":
|
|
382
|
+
"ohmyhost mail messages list --project ULID --environment ULID [--after ULID] --json",
|
|
383
|
+
"mail messages get":
|
|
384
|
+
"ohmyhost mail messages get --project ULID --environment ULID --message ULID --json",
|
|
385
|
+
"mail messages retry":
|
|
386
|
+
"ohmyhost mail messages retry --project ULID --environment ULID --message ULID --idempotency-key KEY --json",
|
|
370
387
|
"mail domain set":
|
|
371
|
-
"ohmyhost mail domain set --project ULID --domain DOMAIN --idempotency-key KEY --json",
|
|
372
|
-
"mail domain status": "ohmyhost mail domain status --project ULID --json",
|
|
388
|
+
"ohmyhost mail domain set --project ULID --environment ULID --domain DOMAIN --sending true --receiving false --idempotency-key KEY --json",
|
|
389
|
+
"mail domain status": "ohmyhost mail domain status --project ULID --environment ULID --json",
|
|
373
390
|
} satisfies Readonly<Record<CustomerCommandLabel, string>>);
|
|
374
391
|
|
|
375
392
|
const HELP_COMMANDS = Object.freeze(
|
|
@@ -691,6 +708,8 @@ export class ProductCli {
|
|
|
691
708
|
if (command.kind === "project-list") return await this.listProjects(command, signal);
|
|
692
709
|
if (command.kind === "project-handle-check")
|
|
693
710
|
return await this.checkProjectHandle(command, signal);
|
|
711
|
+
if (command.kind === "project-handle-set")
|
|
712
|
+
return await this.changeProjectHandle(command, signal);
|
|
694
713
|
if (command.kind === "database-compute-set") {
|
|
695
714
|
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
696
715
|
let operation = parseOperation(await api.changeDatabaseCompute(command));
|
|
@@ -802,9 +821,11 @@ export class ProductCli {
|
|
|
802
821
|
if (command.kind === "secret-list") return await this.listEnvironmentSecrets(command, signal);
|
|
803
822
|
if (command.kind === "function-runs") return await this.listFunctionRuns(command, signal);
|
|
804
823
|
if (command.kind === "secret-set") return await this.putEnvironmentSecret(command, signal);
|
|
805
|
-
if (command.kind === "mail
|
|
806
|
-
|
|
807
|
-
|
|
824
|
+
if (command.kind === "managed-mail") {
|
|
825
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
826
|
+
if (!api.managedMail) throw new Error("Mail unavailable");
|
|
827
|
+
return success(command.label, { mail: await api.managedMail(command.request) });
|
|
828
|
+
}
|
|
808
829
|
return await this.deleteEnvironmentSecret(command, signal);
|
|
809
830
|
} catch (error) {
|
|
810
831
|
if (error instanceof UserTokenFileError)
|
|
@@ -1169,6 +1190,22 @@ export class ProductCli {
|
|
|
1169
1190
|
return success("project list", { status: "succeeded", projects });
|
|
1170
1191
|
}
|
|
1171
1192
|
|
|
1193
|
+
private async changeProjectHandle(
|
|
1194
|
+
command: Extract<ProductCliCommand, { kind: "project-handle-set" }>,
|
|
1195
|
+
signal: AbortSignal,
|
|
1196
|
+
): Promise<CliResult> {
|
|
1197
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1198
|
+
const operation = parseOperation(
|
|
1199
|
+
await api.changeProjectHandle({
|
|
1200
|
+
projectId: command.projectId,
|
|
1201
|
+
handle: command.handle,
|
|
1202
|
+
ifMatch: command.ifMatch,
|
|
1203
|
+
idempotencyKey: command.idempotencyKey,
|
|
1204
|
+
}),
|
|
1205
|
+
);
|
|
1206
|
+
return success("project handle set", { status: "accepted", operation });
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1172
1209
|
private async checkProjectHandle(
|
|
1173
1210
|
command: Extract<ProductCliCommand, { kind: "project-handle-check" }>,
|
|
1174
1211
|
signal: AbortSignal,
|
|
@@ -1742,30 +1779,6 @@ export class ProductCli {
|
|
|
1742
1779
|
);
|
|
1743
1780
|
}
|
|
1744
1781
|
|
|
1745
|
-
private async configureMailDomain(
|
|
1746
|
-
command: Extract<ProductCliCommand, { kind: "mail-domain-set" }>,
|
|
1747
|
-
signal: AbortSignal,
|
|
1748
|
-
): Promise<CliResult> {
|
|
1749
|
-
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1750
|
-
const mailDomain = parseProjectMailDomain(
|
|
1751
|
-
await api.configureMailDomain({
|
|
1752
|
-
projectId: command.projectId,
|
|
1753
|
-
domain: command.domain,
|
|
1754
|
-
idempotencyKey: command.idempotencyKey,
|
|
1755
|
-
}),
|
|
1756
|
-
);
|
|
1757
|
-
return success("mail domain set", { status: mailDomain.status, mail_domain: mailDomain });
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
private async getMailDomain(
|
|
1761
|
-
command: Extract<ProductCliCommand, { kind: "mail-domain-status" }>,
|
|
1762
|
-
signal: AbortSignal,
|
|
1763
|
-
): Promise<CliResult> {
|
|
1764
|
-
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1765
|
-
const mailDomain = parseProjectMailDomain(await api.getMailDomain(command.projectId));
|
|
1766
|
-
return success("mail domain status", { status: mailDomain.status, mail_domain: mailDomain });
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
1782
|
private async waitForOperation(
|
|
1770
1783
|
api: ProductApi,
|
|
1771
1784
|
initial: ReturnType<typeof parseOperation>,
|
|
@@ -2030,6 +2043,7 @@ const classifyFailure = (command: CommandLabel, error: unknown, signal: AbortSig
|
|
|
2030
2043
|
problem.suggestedAction,
|
|
2031
2044
|
undefined,
|
|
2032
2045
|
problem.retryAfterSeconds,
|
|
2046
|
+
problem.requestId,
|
|
2033
2047
|
);
|
|
2034
2048
|
} catch {
|
|
2035
2049
|
return failure(
|
|
@@ -2079,6 +2093,7 @@ const failure = (
|
|
|
2079
2093
|
suggestedAction: string,
|
|
2080
2094
|
operation?: ReturnType<typeof parseOperation>,
|
|
2081
2095
|
retryAfterSeconds?: number,
|
|
2096
|
+
requestId?: string,
|
|
2082
2097
|
): CliResult => {
|
|
2083
2098
|
const document: CliErrorDocument = {
|
|
2084
2099
|
version: 1,
|
|
@@ -2090,6 +2105,7 @@ const failure = (
|
|
|
2090
2105
|
retryable,
|
|
2091
2106
|
suggested_action: suggestedAction,
|
|
2092
2107
|
...(retryAfterSeconds === undefined ? {} : { retry_after_seconds: retryAfterSeconds }),
|
|
2108
|
+
...(requestId === undefined ? {} : { request_id: requestId }),
|
|
2093
2109
|
...(operation?.error === undefined ? {} : { message: operation.error.message }),
|
|
2094
2110
|
},
|
|
2095
2111
|
};
|
|
@@ -2129,6 +2145,7 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
|
|
|
2129
2145
|
if (command.kind === "project-status") return "project status";
|
|
2130
2146
|
if (command.kind === "project-dev-access-create") return "project dev-access create";
|
|
2131
2147
|
if (command.kind === "project-handle-check") return "project handle check";
|
|
2148
|
+
if (command.kind === "project-handle-set") return "project handle set";
|
|
2132
2149
|
if (command.kind === "database-write") return "database write";
|
|
2133
2150
|
if (command.kind === "database-query") return "database query";
|
|
2134
2151
|
if (command.kind === "database-access-create") return "database access create";
|
|
@@ -2153,8 +2170,7 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
|
|
|
2153
2170
|
if (command.kind === "function-runs") return "function runs";
|
|
2154
2171
|
if (command.kind === "secret-set") return "secret set";
|
|
2155
2172
|
if (command.kind === "secret-delete") return "secret delete";
|
|
2156
|
-
if (command.kind === "mail
|
|
2157
|
-
if (command.kind === "mail-domain-status") return "mail domain status";
|
|
2173
|
+
if (command.kind === "managed-mail") return command.label;
|
|
2158
2174
|
return command.kind;
|
|
2159
2175
|
};
|
|
2160
2176
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ManagedMailCommand } from "@ohmyhost/sdk-ts";
|
|
1
2
|
import { USER_API_KEY_ID } from "@ohmyhost/contracts/user-api-keys";
|
|
2
3
|
export type ProductCliCommand =
|
|
3
4
|
| {
|
|
@@ -132,6 +133,15 @@ export type ProductCliCommand =
|
|
|
132
133
|
readonly idempotencyKey: string;
|
|
133
134
|
readonly credentialStore: "native";
|
|
134
135
|
}
|
|
136
|
+
| {
|
|
137
|
+
/** Moves a project to a chosen address once its gateways serve it. */
|
|
138
|
+
readonly kind: "project-handle-set";
|
|
139
|
+
readonly projectId: string;
|
|
140
|
+
readonly handle: string;
|
|
141
|
+
readonly ifMatch: string;
|
|
142
|
+
readonly idempotencyKey: string;
|
|
143
|
+
readonly credentialStore: "native";
|
|
144
|
+
}
|
|
135
145
|
| {
|
|
136
146
|
/** Asks whether a project address is free; the candidate is sent exactly as the agent typed it. */
|
|
137
147
|
readonly kind: "project-handle-check";
|
|
@@ -388,15 +398,19 @@ export type ProductCliCommand =
|
|
|
388
398
|
readonly credentialStore: "native";
|
|
389
399
|
}
|
|
390
400
|
| {
|
|
391
|
-
readonly kind: "mail
|
|
392
|
-
readonly
|
|
393
|
-
readonly
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
401
|
+
readonly kind: "managed-mail";
|
|
402
|
+
readonly request: ManagedMailCommand;
|
|
403
|
+
readonly label:
|
|
404
|
+
| "mail domain set"
|
|
405
|
+
| "mail domain status"
|
|
406
|
+
| "mail setup"
|
|
407
|
+
| "mail status"
|
|
408
|
+
| "mail webhook set"
|
|
409
|
+
| "mail webhook verify"
|
|
410
|
+
| "mail webhook disable"
|
|
411
|
+
| "mail messages list"
|
|
412
|
+
| "mail messages get"
|
|
413
|
+
| "mail messages retry";
|
|
400
414
|
readonly credentialStore: "native";
|
|
401
415
|
}
|
|
402
416
|
| {
|
|
@@ -494,6 +508,7 @@ export type RecognizedCommandLabel =
|
|
|
494
508
|
| "project status"
|
|
495
509
|
| "project dev-access create"
|
|
496
510
|
| "project handle check"
|
|
511
|
+
| "project handle set"
|
|
497
512
|
| "database write"
|
|
498
513
|
| "database query"
|
|
499
514
|
| "database access create"
|
|
@@ -524,6 +539,14 @@ export type RecognizedCommandLabel =
|
|
|
524
539
|
| "function runs"
|
|
525
540
|
| "secret set"
|
|
526
541
|
| "secret delete"
|
|
542
|
+
| "mail setup"
|
|
543
|
+
| "mail status"
|
|
544
|
+
| "mail webhook set"
|
|
545
|
+
| "mail webhook verify"
|
|
546
|
+
| "mail webhook disable"
|
|
547
|
+
| "mail messages list"
|
|
548
|
+
| "mail messages get"
|
|
549
|
+
| "mail messages retry"
|
|
527
550
|
| "mail domain set"
|
|
528
551
|
| "mail domain status";
|
|
529
552
|
|
|
@@ -585,6 +608,8 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
585
608
|
if (value === "project" && values[0] === "status") return "project status";
|
|
586
609
|
if (value === "project" && values[0] === "handle" && values[1] === "check")
|
|
587
610
|
return "project handle check";
|
|
611
|
+
if (value === "project" && values[0] === "handle" && values[1] === "set")
|
|
612
|
+
return "project handle set";
|
|
588
613
|
if (value === "project" && values[0] === "dev-access" && values[1] === "create")
|
|
589
614
|
return "project dev-access create";
|
|
590
615
|
if (value === "database" && values[0] === "compute" && values[1] === "set")
|
|
@@ -627,6 +652,20 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
627
652
|
if (values[0] === "delete") return "secret delete";
|
|
628
653
|
}
|
|
629
654
|
if (value === "function" && values[0] === "runs") return "function runs";
|
|
655
|
+
if (value === "mail") {
|
|
656
|
+
if (values[0] === "setup") return "mail setup";
|
|
657
|
+
if (values[0] === "status") return "mail status";
|
|
658
|
+
if (values[0] === "webhook") {
|
|
659
|
+
if (values[1] === "set") return "mail webhook set";
|
|
660
|
+
if (values[1] === "verify") return "mail webhook verify";
|
|
661
|
+
if (values[1] === "disable") return "mail webhook disable";
|
|
662
|
+
}
|
|
663
|
+
if (values[0] === "messages") {
|
|
664
|
+
if (values[1] === "list") return "mail messages list";
|
|
665
|
+
if (values[1] === "get") return "mail messages get";
|
|
666
|
+
if (values[1] === "retry") return "mail messages retry";
|
|
667
|
+
}
|
|
668
|
+
}
|
|
630
669
|
if (value === "mail" && values[0] === "domain") {
|
|
631
670
|
if (values[1] === "set") return "mail domain set";
|
|
632
671
|
if (values[1] === "status") return "mail domain status";
|
|
@@ -1054,6 +1093,24 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1054
1093
|
}
|
|
1055
1094
|
if (first === "project") {
|
|
1056
1095
|
const subcommand = values.shift();
|
|
1096
|
+
if (subcommand === "handle" && values[0] === "set") {
|
|
1097
|
+
values.shift();
|
|
1098
|
+
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1099
|
+
const options = parseOptions(values, [
|
|
1100
|
+
"--project",
|
|
1101
|
+
"--handle",
|
|
1102
|
+
"--if-match",
|
|
1103
|
+
"--idempotency-key",
|
|
1104
|
+
]);
|
|
1105
|
+
return {
|
|
1106
|
+
kind: "project-handle-set",
|
|
1107
|
+
projectId: options["--project"],
|
|
1108
|
+
handle: options["--handle"],
|
|
1109
|
+
ifMatch: options["--if-match"],
|
|
1110
|
+
idempotencyKey: options["--idempotency-key"],
|
|
1111
|
+
credentialStore,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1057
1114
|
if (subcommand === "handle" && values.shift() === "check") {
|
|
1058
1115
|
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1059
1116
|
const options = parseOptions(values, ["--handle"]);
|
|
@@ -1694,34 +1751,82 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
1694
1751
|
}
|
|
1695
1752
|
throw new InvalidCommandError();
|
|
1696
1753
|
}
|
|
1697
|
-
if (first === "mail"
|
|
1754
|
+
if (first === "mail") {
|
|
1698
1755
|
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1699
|
-
values.
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
1718
|
-
values.splice(0, 2);
|
|
1719
|
-
const options = parseOptions(values, ["--project"]);
|
|
1720
|
-
return {
|
|
1721
|
-
kind: "mail-domain-status",
|
|
1722
|
-
projectId: options["--project"],
|
|
1723
|
-
credentialStore,
|
|
1756
|
+
const area = values.shift();
|
|
1757
|
+
const sub =
|
|
1758
|
+
area === "webhook" || area === "messages" || area === "domain" ? values.shift() : undefined;
|
|
1759
|
+
const options = parseOptionalOptions(values, [
|
|
1760
|
+
"--project",
|
|
1761
|
+
"--environment",
|
|
1762
|
+
"--domain",
|
|
1763
|
+
"--sending",
|
|
1764
|
+
"--receiving",
|
|
1765
|
+
"--url",
|
|
1766
|
+
"--idempotency-key",
|
|
1767
|
+
"--message",
|
|
1768
|
+
"--after",
|
|
1769
|
+
]);
|
|
1770
|
+
const required = (key: keyof typeof options) => {
|
|
1771
|
+
const value = options[key];
|
|
1772
|
+
if (!value) throw new InvalidCommandError();
|
|
1773
|
+
return value;
|
|
1724
1774
|
};
|
|
1775
|
+
const scope = { projectId: required("--project"), environmentId: required("--environment") };
|
|
1776
|
+
let request: ManagedMailCommand,
|
|
1777
|
+
label: Extract<ProductCliCommand, { kind: "managed-mail" }>["label"];
|
|
1778
|
+
if (area === "setup" || (area === "domain" && sub === "set")) {
|
|
1779
|
+
const sending = required("--sending"),
|
|
1780
|
+
receiving = required("--receiving");
|
|
1781
|
+
if (!["true", "false"].includes(sending) || !["true", "false"].includes(receiving))
|
|
1782
|
+
throw new InvalidCommandError();
|
|
1783
|
+
request = {
|
|
1784
|
+
...scope,
|
|
1785
|
+
action: "configure",
|
|
1786
|
+
domain: required("--domain"),
|
|
1787
|
+
sending: sending === "true",
|
|
1788
|
+
receiving: receiving === "true",
|
|
1789
|
+
idempotencyKey: required("--idempotency-key"),
|
|
1790
|
+
};
|
|
1791
|
+
label = area === "domain" ? "mail domain set" : "mail setup";
|
|
1792
|
+
} else if (area === "status" || (area === "domain" && sub === "status")) {
|
|
1793
|
+
request = { ...scope, action: "status" };
|
|
1794
|
+
label = area === "domain" ? "mail domain status" : "mail status";
|
|
1795
|
+
} else if (area === "webhook" && sub === "set") {
|
|
1796
|
+
request = {
|
|
1797
|
+
...scope,
|
|
1798
|
+
action: "webhook_set",
|
|
1799
|
+
url: required("--url"),
|
|
1800
|
+
idempotencyKey: required("--idempotency-key"),
|
|
1801
|
+
};
|
|
1802
|
+
label = "mail webhook set";
|
|
1803
|
+
} else if (area === "webhook" && (sub === "verify" || sub === "disable")) {
|
|
1804
|
+
request = {
|
|
1805
|
+
...scope,
|
|
1806
|
+
action: sub === "verify" ? "webhook_verify" : "webhook_disable",
|
|
1807
|
+
idempotencyKey: required("--idempotency-key"),
|
|
1808
|
+
};
|
|
1809
|
+
label = sub === "verify" ? "mail webhook verify" : "mail webhook disable";
|
|
1810
|
+
} else if (area === "messages" && sub === "list") {
|
|
1811
|
+
request = {
|
|
1812
|
+
...scope,
|
|
1813
|
+
action: "messages_list",
|
|
1814
|
+
...(options["--after"] ? { after: options["--after"] } : {}),
|
|
1815
|
+
};
|
|
1816
|
+
label = "mail messages list";
|
|
1817
|
+
} else if (area === "messages" && sub === "get") {
|
|
1818
|
+
request = { ...scope, action: "message_get", messageId: required("--message") };
|
|
1819
|
+
label = "mail messages get";
|
|
1820
|
+
} else if (area === "messages" && sub === "retry") {
|
|
1821
|
+
request = {
|
|
1822
|
+
...scope,
|
|
1823
|
+
action: "message_retry",
|
|
1824
|
+
messageId: required("--message"),
|
|
1825
|
+
idempotencyKey: required("--idempotency-key"),
|
|
1826
|
+
};
|
|
1827
|
+
label = "mail messages retry";
|
|
1828
|
+
} else throw new InvalidCommandError();
|
|
1829
|
+
return { kind: "managed-mail", request, label, credentialStore };
|
|
1725
1830
|
}
|
|
1726
1831
|
throw new InvalidCommandError();
|
|
1727
1832
|
};
|
|
@@ -429,9 +429,11 @@ const runOfflineInit = async (
|
|
|
429
429
|
false,
|
|
430
430
|
error instanceof RepositoryInitConflictError
|
|
431
431
|
? "Keep the existing ohmyhost.yaml or remove it explicitly before retrying."
|
|
432
|
-
: error instanceof RepositoryInitError && error.code === "
|
|
433
|
-
? "
|
|
434
|
-
:
|
|
432
|
+
: error instanceof RepositoryInitError && error.code === "repository_root_required"
|
|
433
|
+
? "Run ohmyhost init from the Git repository root with --root set to the application directory (for example --root website); commit the root ohmyhost.yaml before planning."
|
|
434
|
+
: error instanceof RepositoryInitError && error.code === "project_name_invalid"
|
|
435
|
+
? "Pass a lowercase deployment slug with '--project', then rerun init."
|
|
436
|
+
: "Run 'ohmyhost init --dry-run --json' and resolve every reported blocker.",
|
|
435
437
|
);
|
|
436
438
|
io.writeStdout(result.stdout);
|
|
437
439
|
return result.exitCode;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { executeManagedMail, type ManagedMailCommand } from "@ohmyhost/sdk-ts";
|
|
1
2
|
import {
|
|
2
3
|
assertProjectDatabaseAccess,
|
|
3
4
|
assertProjectDatabaseAccessCredential,
|
|
@@ -53,7 +54,6 @@ import {
|
|
|
53
54
|
type ProjectCreditBudget,
|
|
54
55
|
applyPaidProjectDomain,
|
|
55
56
|
configureProjectDomains,
|
|
56
|
-
configureProjectMailDomain,
|
|
57
57
|
configureProjectSourceAutoDeploy,
|
|
58
58
|
createCloudflareDnsAuthorization,
|
|
59
59
|
createProjectDevAccessTicket,
|
|
@@ -74,7 +74,6 @@ import {
|
|
|
74
74
|
getAccountProfile,
|
|
75
75
|
getCurrentIdentity,
|
|
76
76
|
getDeployment,
|
|
77
|
-
getProjectMailDomain,
|
|
78
77
|
getProjectSourceAutoDeploy,
|
|
79
78
|
getProjectStatus,
|
|
80
79
|
getPaidProjectDomain,
|
|
@@ -83,6 +82,7 @@ import {
|
|
|
83
82
|
listEnvironmentSecrets,
|
|
84
83
|
listFunctionRuns,
|
|
85
84
|
listProjects,
|
|
85
|
+
changeProjectHandle,
|
|
86
86
|
getProjectHandleAvailability,
|
|
87
87
|
planDeployment,
|
|
88
88
|
planPaidProjectDomain,
|
|
@@ -112,7 +112,6 @@ import {
|
|
|
112
112
|
type DeleteEnvironmentSecretResult,
|
|
113
113
|
type OhMyHostClient,
|
|
114
114
|
type ProviderReconciliationAttempt,
|
|
115
|
-
type ProjectMailDomain,
|
|
116
115
|
type CurrentIdentity,
|
|
117
116
|
type DevAccessTicket,
|
|
118
117
|
type ProjectPage,
|
|
@@ -211,6 +210,12 @@ export interface ProductApi {
|
|
|
211
210
|
getAccountProfile(): Promise<readonly PublicWorkspace[]>;
|
|
212
211
|
listProjects(input: { readonly cursor?: string; readonly limit: number }): Promise<ProjectPage>;
|
|
213
212
|
checkProjectHandle(input: { readonly handle: string }): Promise<ProjectHandleAvailability>;
|
|
213
|
+
changeProjectHandle(input: {
|
|
214
|
+
readonly projectId: string;
|
|
215
|
+
readonly handle: string;
|
|
216
|
+
readonly ifMatch: string;
|
|
217
|
+
readonly idempotencyKey: string;
|
|
218
|
+
}): Promise<Operation>;
|
|
214
219
|
getProjectStatus(projectId: string): Promise<ProjectStatus>;
|
|
215
220
|
writeProjectDatabase(
|
|
216
221
|
input: ProjectDatabaseWriteRequest & { projectId: string; idempotencyKey: string },
|
|
@@ -368,12 +373,7 @@ export interface ProductApi {
|
|
|
368
373
|
readonly name: string;
|
|
369
374
|
readonly idempotencyKey: string;
|
|
370
375
|
}): Promise<DeleteEnvironmentSecretResult>;
|
|
371
|
-
|
|
372
|
-
readonly projectId: string;
|
|
373
|
-
readonly domain: string;
|
|
374
|
-
readonly idempotencyKey: string;
|
|
375
|
-
}): Promise<ProjectMailDomain>;
|
|
376
|
-
getMailDomain(projectId: string): Promise<ProjectMailDomain>;
|
|
376
|
+
managedMail?(input: ManagedMailCommand): Promise<unknown>;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
export class GeneratedSdkProductApi implements ProductApi {
|
|
@@ -585,6 +585,23 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
585
585
|
return listProjects(input, { client: this.#client });
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
+
public async changeProjectHandle(input: {
|
|
589
|
+
readonly projectId: string;
|
|
590
|
+
readonly handle: string;
|
|
591
|
+
readonly ifMatch: string;
|
|
592
|
+
readonly idempotencyKey: string;
|
|
593
|
+
}): Promise<Operation> {
|
|
594
|
+
return changeProjectHandle(
|
|
595
|
+
{
|
|
596
|
+
project_id: input.projectId,
|
|
597
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
598
|
+
"If-Match": input.ifMatch,
|
|
599
|
+
handle: input.handle,
|
|
600
|
+
},
|
|
601
|
+
{ client: this.#client },
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
588
605
|
public async checkProjectHandle(input: {
|
|
589
606
|
readonly handle: string;
|
|
590
607
|
}): Promise<ProjectHandleAvailability> {
|
|
@@ -1070,22 +1087,7 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
1070
1087
|
);
|
|
1071
1088
|
}
|
|
1072
1089
|
|
|
1073
|
-
public
|
|
1074
|
-
|
|
1075
|
-
readonly domain: string;
|
|
1076
|
-
readonly idempotencyKey: string;
|
|
1077
|
-
}): Promise<ProjectMailDomain> {
|
|
1078
|
-
return configureProjectMailDomain(
|
|
1079
|
-
{
|
|
1080
|
-
"Idempotency-Key": input.idempotencyKey,
|
|
1081
|
-
project_id: input.projectId,
|
|
1082
|
-
configureProjectMailDomainRequest: { domain: input.domain },
|
|
1083
|
-
},
|
|
1084
|
-
{ client: this.#client },
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
public async getMailDomain(projectId: string): Promise<ProjectMailDomain> {
|
|
1089
|
-
return getProjectMailDomain({ project_id: projectId }, { client: this.#client });
|
|
1090
|
+
public managedMail(input: ManagedMailCommand) {
|
|
1091
|
+
return executeManagedMail(this.#client, input);
|
|
1090
1092
|
}
|
|
1091
1093
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { lstat, open, readFile, readdir, stat } from "node:fs/promises";
|
|
3
|
-
import { posix, resolve } from "node:path";
|
|
3
|
+
import { dirname, posix, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
normalizeApplicationRoot,
|
|
@@ -50,7 +50,7 @@ const MAXIMUM_SOURCE_BYTES = 2 * 1024 * 1024;
|
|
|
50
50
|
const SOURCE_MODULE_PATTERN = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
|
|
51
51
|
const STORAGE_CLIENT_CALL_PATTERN = /\bcreatePrivateStorageClient\s*\(/u;
|
|
52
52
|
// Both database mistakes of 2026-09-18 are visible in the source before anything is built: one read
|
|
53
|
-
// a
|
|
53
|
+
// a retired database binding, the other opened a socket driver. Neither can work in a customer Worker, so
|
|
54
54
|
// naming them here costs a customer minutes instead of a deployment that fails its health check.
|
|
55
55
|
const DATABASE_PRIVATE_BINDING_PATTERN =
|
|
56
56
|
/\bHYPERDRIVE\b|\bconnectionString\b|\bDATABASE_URL\b|postgres(?:ql)?:\/\//u;
|
|
@@ -549,7 +549,7 @@ async function databaseContractBlockers(
|
|
|
549
549
|
if (DATABASE_PRIVATE_BINDING_PATTERN.test(source)) {
|
|
550
550
|
blockers.push({
|
|
551
551
|
code: "database_binding_private",
|
|
552
|
-
message: `${path} reaches for a managed database connection string.
|
|
552
|
+
message: `${path} reaches for a managed database connection string. HYPERDRIVE is retired; DATABASE_URL and postgres:// URLs never reach a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database".`,
|
|
553
553
|
});
|
|
554
554
|
}
|
|
555
555
|
if (DATABASE_SOCKET_DRIVER_PATTERN.test(source)) {
|
|
@@ -876,6 +876,30 @@ async function assertRepositoryRoot(root: string): Promise<void> {
|
|
|
876
876
|
if (!information.isDirectory()) {
|
|
877
877
|
throw new RepositoryInitError("repository_not_directory", "Repository path is not a directory");
|
|
878
878
|
}
|
|
879
|
+
// A Git worktree uses a .git file; never read or follow that file's target.
|
|
880
|
+
for (let ancestor = root; ; ancestor = dirname(ancestor)) {
|
|
881
|
+
let marker;
|
|
882
|
+
try {
|
|
883
|
+
marker = await lstat(resolve(ancestor, ".git"));
|
|
884
|
+
} catch (error) {
|
|
885
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
886
|
+
throw new RepositoryInitError(
|
|
887
|
+
"repository_unreadable",
|
|
888
|
+
"Could not determine the Git repository root",
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (marker !== undefined) {
|
|
893
|
+
if (ancestor !== root) {
|
|
894
|
+
throw new RepositoryInitError(
|
|
895
|
+
"repository_root_required",
|
|
896
|
+
"Run ohmyhost init from the Git repository root and select the application directory with --root; ohmyhost.yaml must be at the repository root.",
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (dirname(ancestor) === ancestor) return;
|
|
902
|
+
}
|
|
879
903
|
}
|
|
880
904
|
|
|
881
905
|
async function collectRepositoryFiles(root: string): Promise<string[]> {
|