@amerged/ohmyhost-mcp 0.1.15 → 0.1.18

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.
@@ -1,3 +1,4 @@
1
+ import { parseFeedbackSubmissionDetail } from "@ohmyhost/contracts/feedback";
1
2
  import { parseFrameworkConversionDetail } from "@ohmyhost/contracts/framework-admission";
2
3
  import {
3
4
  parseOperationDeploymentProgress,
@@ -21,12 +22,13 @@ import {
21
22
  } from "@ohmyhost/contracts/operation-failure";
22
23
  export type { PublicOperationFailure } from "@ohmyhost/contracts/operation-failure";
23
24
 
24
- const PROBLEM_POLICY = {
25
+ /** Exported for the documentation build, which publishes one page per code. */
26
+ export const PROBLEM_POLICY = {
25
27
  cloudflare_zone_not_bound: {
26
28
  status: 409,
27
29
  retryable: false,
28
30
  action:
29
- "Configure the project domain first with 'ohmyhost domain paid plan' then 'ohmyhost domain paid apply', or configure its sender domain with 'ohmyhost mail domain set'. Then request Cloudflare authorization for that domain's zone.",
31
+ "Configure the project domain first with 'ohmyhost domain paid plan' then 'ohmyhost domain paid apply', or configure its sender domain with 'ohmyhost mail setup'. Then request Cloudflare authorization for that domain's zone.",
30
32
  },
31
33
  github_connection_required: {
32
34
  status: 409,
@@ -197,6 +199,12 @@ const PROBLEM_POLICY = {
197
199
  retryable: false,
198
200
  action: "Select the current succeeded dev deployment and target prod.",
199
201
  },
202
+ mail_capacity_unavailable: {
203
+ status: 503,
204
+ retryable: true,
205
+ action:
206
+ "Report this request ID through feedback. The stored mail setup continues automatically once ohmyho.st has capacity; follow mail status and keep the original idempotency key.",
207
+ },
200
208
  mail_domain_conflict: {
201
209
  status: 409,
202
210
  retryable: false,
@@ -205,7 +213,8 @@ const PROBLEM_POLICY = {
205
213
  mail_domain_required: {
206
214
  status: 409,
207
215
  retryable: false,
208
- action: "Run 'ohmyhost mail domain set' for this project, then plan again.",
216
+ action:
217
+ "This app declares mail.enabled. Configure the project's own sender domain with 'ohmyhost mail setup' using its Prod environment ID, or set mail.enabled to false if the app sends no mail, then plan again.",
209
218
  },
210
219
  storage_jurisdiction_conflict: {
211
220
  status: 409,
@@ -213,6 +222,18 @@ const PROBLEM_POLICY = {
213
222
  action:
214
223
  "Set storage.jurisdiction in ohmyhost.yaml to the project's hosting region shown by project status, then plan again.",
215
224
  },
225
+ shared_data_requires_promotion: {
226
+ status: 409,
227
+ retryable: false,
228
+ action:
229
+ "Plan and deploy this commit to Dev, then run 'ohmyhost deployment promote plan' to reach Prod.",
230
+ },
231
+ production_deployment_required: {
232
+ status: 409,
233
+ retryable: false,
234
+ action:
235
+ "Deploy to Prod first: run 'ohmyhost plan --environment prod' and deploy that plan, or promote the current Dev deployment with 'ohmyhost deployment promote plan'. Then plan the domain again.",
236
+ },
216
237
  framework_conversion_required: {
217
238
  status: 409,
218
239
  retryable: false,
@@ -308,6 +329,8 @@ export class ResponseContractError extends Error {
308
329
 
309
330
  export interface SafeProblem {
310
331
  readonly requestId: string;
332
+ /** The error's documentation page, when the server names one on docs.ohmyho.st. */
333
+ readonly docsUrl?: string;
311
334
  readonly retryAfterSeconds?: number;
312
335
  readonly code: ProblemCode;
313
336
  readonly retryable: boolean;
@@ -690,6 +713,28 @@ export const parseCloudflareDnsAuthorizationStatus = (
690
713
  return input as unknown as PublicCloudflareDnsAuthorizationStatus;
691
714
  };
692
715
 
716
+ export interface PublicDevAccessState {
717
+ readonly project_id: string;
718
+ readonly mode: "protected" | "public";
719
+ readonly share_url: string | null;
720
+ }
721
+
722
+ export const parseDevAccessState = (value: unknown): PublicDevAccessState => {
723
+ const input = exactRecord(value, ["project_id", "mode", "share_url"]);
724
+ if (
725
+ !isUlid(input["project_id"]) ||
726
+ (input["mode"] !== "protected" && input["mode"] !== "public") ||
727
+ (input["mode"] === "public" && input["share_url"] !== null) ||
728
+ (input["share_url"] !== null &&
729
+ (typeof input["share_url"] !== "string" ||
730
+ !/^https:\/\/dev-[a-z0-9]+(?:-[a-z0-9]+){0,4}\.(?:dev\.)?check\.omh\.st\/\.ohmyhost\/dev-access\/redeem\?stage=dev&ticket=[A-Za-z0-9_-]{43}$/u.test(
731
+ input["share_url"],
732
+ )))
733
+ )
734
+ throw new ResponseContractError();
735
+ return input as unknown as PublicDevAccessState;
736
+ };
737
+
693
738
  export const parseDevAccessTicket = (value: unknown): PublicDevAccessTicket => {
694
739
  const input = exactRecord(value, [
695
740
  "ticket_id",
@@ -943,6 +988,7 @@ export const parseProjectStatus = (value: unknown): Readonly<Record<string, unkn
943
988
  "etag",
944
989
  "handle",
945
990
  "region",
991
+ "dev_access_mode",
946
992
  "lifecycle",
947
993
  "source",
948
994
  "default_environment",
@@ -961,6 +1007,7 @@ export const parseProjectStatus = (value: unknown): Readonly<Record<string, unkn
961
1007
  !boundedString(handle, 3, 59) ||
962
1008
  !/^[a-z0-9]+(?:-[a-z0-9]+){0,4}$/u.test(handle) ||
963
1009
  (input["region"] !== "us" && input["region"] !== "eu") ||
1010
+ (input["dev_access_mode"] !== "protected" && input["dev_access_mode"] !== "public") ||
964
1011
  (input["lifecycle"] !== "active" &&
965
1012
  input["lifecycle"] !== "deleting" &&
966
1013
  input["lifecycle"] !== "deleted") ||
@@ -1308,6 +1355,7 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1308
1355
  const input = exactRecord(value, [
1309
1356
  "id",
1310
1357
  "project_id",
1358
+ "environment",
1311
1359
  "commit_sha",
1312
1360
  "source_digest",
1313
1361
  "build_plan_digest",
@@ -1329,6 +1377,7 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1329
1377
  "currency",
1330
1378
  "rate_card_version",
1331
1379
  "credit_micros",
1380
+ "credits",
1332
1381
  "provider_cost_micros",
1333
1382
  "scope",
1334
1383
  "reserved_seconds",
@@ -1336,6 +1385,7 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1336
1385
  if (
1337
1386
  !isUlid(input["id"]) ||
1338
1387
  !isUlid(input["project_id"]) ||
1388
+ (input["environment"] !== "dev" && input["environment"] !== "prod") ||
1339
1389
  !isCommitSha(input["commit_sha"]) ||
1340
1390
  !isDigest(input["source_digest"]) ||
1341
1391
  !isDigest(input["build_plan_digest"]) ||
@@ -1355,6 +1405,8 @@ export const parseDeploymentPlan = (value: unknown): Readonly<Record<string, unk
1355
1405
  !boundedString(cost["currency"], 1, 16) ||
1356
1406
  !boundedString(cost["rate_card_version"], 1, 128) ||
1357
1407
  !decimal(cost["credit_micros"]) ||
1408
+ typeof cost["credits"] !== "string" ||
1409
+ !/^[0-9]+(?:\.[0-9]{1,6})?$/u.test(cost["credits"]) ||
1358
1410
  !decimal(cost["provider_cost_micros"]) ||
1359
1411
  cost["scope"] !== "build_compute" ||
1360
1412
  !decimal(cost["reserved_seconds"]) ||
@@ -1497,14 +1549,18 @@ export const parseSafeProblem = (value: unknown): SafeProblem => {
1497
1549
  retryAfter > 86400)
1498
1550
  )
1499
1551
  throw new ResponseContractError();
1552
+ const docsUrl = String(input["type"]);
1500
1553
  return {
1501
1554
  requestId: input["request_id"],
1555
+ ...(docsUrl.startsWith("https://docs.ohmyho.st/errors/") ? { docsUrl } : {}),
1502
1556
  code: code as ProblemCode,
1503
1557
  retryable: policy.retryable,
1504
1558
  suggestedAction:
1505
1559
  (code === "framework_conversion_required"
1506
1560
  ? (parseFrameworkConversionDetail(input["detail"]) ?? policy.action)
1507
- : policy.action) +
1561
+ : code === "invalid_request"
1562
+ ? (parseFeedbackSubmissionDetail(input["detail"]) ?? policy.action)
1563
+ : policy.action) +
1508
1564
  (retryAfter === undefined ? "" : ` Retry after ${String(retryAfter)} seconds.`),
1509
1565
  ...(retryAfter === undefined ? {} : { retryAfterSeconds: retryAfter as number }),
1510
1566
  };