@amerged/ohmyhost-mcp 0.1.22 → 0.1.24
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 +5 -2
- package/dist/{chunk-NLBFEH6H.js → chunk-EEFUMPKG.js} +25 -24
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +734 -269
- package/package.json +1 -1
- package/src/apps/mcp/package.json +47 -0
- package/src/apps/mcp/{local-client.ts → src/local-client.ts} +69 -12
- package/src/apps/mcp/{local-server.ts → src/local-server.ts} +327 -145
- package/src/apps/product-cli/package.json +45 -0
- package/src/apps/product-cli/{bin.ts → src/bin.ts} +3 -0
- package/src/apps/product-cli/{cli.ts → src/cli.ts} +363 -87
- package/src/apps/product-cli/{command.ts → src/command.ts} +58 -11
- package/src/apps/product-cli/{composition.ts → src/composition.ts} +98 -21
- package/src/apps/product-cli/{index.ts → src/index.ts} +1 -0
- package/src/apps/product-cli/{product-api.ts → src/product-api.ts} +65 -19
- package/src/apps/product-cli/{repository-init.ts → src/repository-init.ts} +79 -29
- package/src/apps/product-cli/{runtime-contract.ts → src/runtime-contract.ts} +16 -309
- package/src/apps/product-cli/{user-token-file.ts → src/user-token-file.ts} +46 -35
- package/src/apps/product-cli/{workspace.ts → src/workspace.ts} +6 -0
- package/src/packages/agent-skills/package.json +24 -0
- package/src/packages/agent-skills/src/generated-skill-resources.ts +165 -0
- package/src/packages/agent-skills/{index.ts → src/index.ts} +1 -1
- package/src/packages/contracts/package.json +137 -0
- package/src/packages/contracts/src/browser-capabilities.ts +133 -0
- package/src/packages/contracts/src/customer-auth-admission.ts +36 -0
- package/src/packages/contracts/{database-access.ts → src/database-access.ts} +10 -2
- package/src/packages/contracts/{framework-admission.ts → src/framework-admission.ts} +88 -13
- package/src/packages/contracts/src/framework-build-script.mjs +22 -0
- package/src/packages/contracts/src/framework-build-script.ts +1 -0
- package/src/packages/contracts/{index.ts → src/index.ts} +17 -16
- package/src/packages/contracts/src/operation-data-change-result.ts +86 -0
- package/src/packages/contracts/{operation-deployment-result.ts → src/operation-deployment-result.ts} +2 -2
- package/src/packages/contracts/{operation-failure.ts → src/operation-failure.ts} +24 -12
- package/src/packages/contracts/src/problem-policy.ts +498 -0
- package/src/packages/contracts/src/project-data-changes.ts +90 -0
- package/src/packages/contracts/src/secret-names.ts +27 -0
- package/src/packages/sdk-ts/package.json +21 -0
- package/src/packages/sdk-ts/{generated → src/generated}/index.ts +15 -0
- package/src/packages/sdk-ts/{generated → src/generated}/sdk.gen.ts +114 -5
- package/src/packages/sdk-ts/{generated → src/generated}/types.gen.ts +209 -10
- package/src/packages/workos-auth-contracts/package.json +39 -0
- package/src/packages/agent-skills/generated-skill-resources.ts +0 -165
- /package/src/apps/mcp/{index.ts → src/index.ts} +0 -0
- /package/src/apps/mcp/{stdio-main.ts → src/stdio-main.ts} +0 -0
- /package/src/apps/product-cli/{credential-store.ts → src/credential-store.ts} +0 -0
- /package/src/apps/product-cli/{device-token-expiry.ts → src/device-token-expiry.ts} +0 -0
- /package/src/apps/product-cli/{file-credential-store.ts → src/file-credential-store.ts} +0 -0
- /package/src/apps/product-cli/{profile-store.ts → src/profile-store.ts} +0 -0
- /package/src/apps/product-cli/{project-link-store.ts → src/project-link-store.ts} +0 -0
- /package/src/apps/product-cli/{psql-session.ts → src/psql-session.ts} +0 -0
- /package/src/packages/contracts/{application-root.ts → src/application-root.ts} +0 -0
- /package/src/packages/contracts/{billing.ts → src/billing.ts} +0 -0
- /package/src/packages/contracts/{credit-pricing.ts → src/credit-pricing.ts} +0 -0
- /package/src/packages/contracts/{database-compute.ts → src/database-compute.ts} +0 -0
- /package/src/packages/contracts/{database-migration-admission.ts → src/database-migration-admission.ts} +0 -0
- /package/src/packages/contracts/{feedback.ts → src/feedback.ts} +0 -0
- /package/src/packages/contracts/{framework-family.ts → src/framework-family.ts} +0 -0
- /package/src/packages/contracts/{functions.ts → src/functions.ts} +0 -0
- /package/src/packages/contracts/{platform-origins.ts → src/platform-origins.ts} +0 -0
- /package/src/packages/contracts/{project-context.ts → src/project-context.ts} +0 -0
- /package/src/packages/contracts/{project-exports.ts → src/project-exports.ts} +0 -0
- /package/src/packages/contracts/{user-api-keys.ts → src/user-api-keys.ts} +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/client/client.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/client/index.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/client/types.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/client/utils.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/client.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/auth.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/bodySerializer.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/params.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/pathSerializer.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/queryKeySerializer.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/serverSentEvents.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/types.gen.ts +0 -0
- /package/src/packages/sdk-ts/{generated → src/generated}/core/utils.gen.ts +0 -0
- /package/src/packages/sdk-ts/{index.ts → src/index.ts} +0 -0
- /package/src/packages/sdk-ts/{managed-mail-response.ts → src/managed-mail-response.ts} +0 -0
- /package/src/packages/sdk-ts/{managed-mail.ts → src/managed-mail.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{device-flow.ts → src/device-flow.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{index.ts → src/index.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{management-rpc.ts → src/management-rpc.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{organizations.ts → src/organizations.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{parsing.ts → src/parsing.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{session-status.ts → src/session-status.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{tokens.ts → src/tokens.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{user-api-keys.ts → src/user-api-keys.ts} +0 -0
- /package/src/packages/workos-auth-contracts/{webhooks.ts → src/webhooks.ts} +0 -0
|
@@ -7,9 +7,14 @@ import {
|
|
|
7
7
|
type ApplicationRoot,
|
|
8
8
|
} from "@ohmyhost/contracts/application-root";
|
|
9
9
|
import { parseOhmyhostConfigYaml, type OhmyhostConfig } from "@ohmyhost/contracts";
|
|
10
|
+
import {
|
|
11
|
+
isAdmittedFrameworkBuildScript,
|
|
12
|
+
PINNED_BUN_VERSION,
|
|
13
|
+
} from "@ohmyhost/contracts/framework-build-script";
|
|
10
14
|
import {
|
|
11
15
|
classifyFrameworkVersion,
|
|
12
16
|
FrameworkAdmissionError,
|
|
17
|
+
managedCustomerAuthAdmissionIssue,
|
|
13
18
|
resolveFrameworkConfig,
|
|
14
19
|
resolvePackageManager,
|
|
15
20
|
type AdmittedFramework,
|
|
@@ -24,6 +29,7 @@ import {
|
|
|
24
29
|
import {
|
|
25
30
|
admitExpandOnlyMigrations,
|
|
26
31
|
DatabaseMigrationAdmissionError,
|
|
32
|
+
type DatabaseMigrationAdmissionReason,
|
|
27
33
|
} from "@ohmyhost/contracts/database-migration-admission";
|
|
28
34
|
|
|
29
35
|
import cliPackage from "../package.json" with { type: "json" };
|
|
@@ -96,6 +102,8 @@ export interface RepositoryInitBlocker {
|
|
|
96
102
|
| "database_driver_unsupported"
|
|
97
103
|
| "build_script_missing"
|
|
98
104
|
| "better_auth_version_unsupported"
|
|
105
|
+
| "managed_auth_mail_required"
|
|
106
|
+
| "managed_auth_database_required"
|
|
99
107
|
| "framework_ambiguous"
|
|
100
108
|
| "framework_unsupported"
|
|
101
109
|
| "framework_version_unsupported"
|
|
@@ -184,6 +192,11 @@ export interface RepositoryInitResult {
|
|
|
184
192
|
bindings: readonly string[];
|
|
185
193
|
packages: RepositoryInitRuntimePackages;
|
|
186
194
|
}> | null;
|
|
195
|
+
/** What server code of any framework installs and reads once a capability is enabled. */
|
|
196
|
+
readonly runtime: Readonly<{
|
|
197
|
+
bindings: readonly string[];
|
|
198
|
+
packages: RepositoryInitRuntimePackages;
|
|
199
|
+
}> | null;
|
|
187
200
|
}
|
|
188
201
|
|
|
189
202
|
export class RepositoryInitError extends Error {
|
|
@@ -447,6 +460,8 @@ export async function initializeRepository(
|
|
|
447
460
|
...runtimeContract(capabilities),
|
|
448
461
|
})
|
|
449
462
|
: null,
|
|
463
|
+
// Next.js and TanStack Start have neither a companion nor a Worker module, and read it here.
|
|
464
|
+
runtime: Object.values(capabilities).some(Boolean) ? runtimeContract(capabilities) : null,
|
|
450
465
|
});
|
|
451
466
|
}
|
|
452
467
|
|
|
@@ -480,7 +495,7 @@ async function cronBlockers(
|
|
|
480
495
|
{
|
|
481
496
|
code: "scheduled_functions_runtime_unsupported",
|
|
482
497
|
message:
|
|
483
|
-
"
|
|
498
|
+
"functions.crons needs runtime.mode edge (Next.js, TanStack Start, or Vite with src/ohmyhost/companion.ts) or functions (src/ohmyhost/worker.ts) in ohmyhost.yaml. Change runtime.mode, or remove functions.crons.",
|
|
484
499
|
},
|
|
485
500
|
];
|
|
486
501
|
}
|
|
@@ -585,20 +600,31 @@ function migrationFilenameBlockers(
|
|
|
585
600
|
const invalid = files
|
|
586
601
|
.filter((path) => path.startsWith(prefix))
|
|
587
602
|
.map((path) => path.slice(prefix.length))
|
|
588
|
-
.
|
|
603
|
+
.find(
|
|
589
604
|
(path) => !/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*\d{14}_[a-z0-9][a-z0-9_-]*\.sql$/u.test(path),
|
|
590
605
|
);
|
|
591
|
-
return invalid
|
|
592
|
-
? [
|
|
606
|
+
return invalid === undefined
|
|
607
|
+
? []
|
|
608
|
+
: [
|
|
593
609
|
{
|
|
594
610
|
code: "migration_filename_noncanonical",
|
|
595
|
-
message:
|
|
596
|
-
"Rename every PostgreSQL migration to YYYYMMDDHHMMSS_name.sql before initialization.",
|
|
611
|
+
message: `Migration file '${invalid}' in ${root} is not named YYYYMMDDHHMMSS_name.sql (14 digits, an underscore, then lowercase letters, digits, _ or -). Every file in that directory must match, README and .gitkeep included: rename or move it, keeping the run order.`,
|
|
597
612
|
},
|
|
598
|
-
]
|
|
599
|
-
: [];
|
|
613
|
+
];
|
|
600
614
|
}
|
|
601
615
|
|
|
616
|
+
/** What each rejection reason of the expand-only migration gate means, and how to satisfy it. */
|
|
617
|
+
const MIGRATION_REJECTION: Readonly<Record<DatabaseMigrationAdmissionReason, string>> = {
|
|
618
|
+
destructive:
|
|
619
|
+
"It contains DROP, TRUNCATE, INSERT, UPDATE, DELETE, BEGIN/COMMIT/ROLLBACK, CREATE OR REPLACE, or ALTER TABLE other than ADD; move row changes to 'ohmyhost database write'.",
|
|
620
|
+
provider_specific:
|
|
621
|
+
"It mentions supabase, CREATE POLICY, ROW LEVEL SECURITY, the auth. or storage. schema, or the anon, authenticated or service_role role; comments count.",
|
|
622
|
+
unsupported:
|
|
623
|
+
"It has a statement other than CREATE TABLE, TYPE, DOMAIN, SEQUENCE, VIEW, MATERIALIZED VIEW, TRIGGER, INDEX or FUNCTION, ALTER TABLE ... ADD, COMMENT ON, CREATE SCHEMA IF NOT EXISTS auth|extensions|private or CREATE EXTENSION IF NOT EXISTS pgcrypto|btree_gist|unaccent; or a function not in LANGUAGE sql or plpgsql, SECURITY DEFINER without SET search_path, or the word ohmyhost, comments included.",
|
|
624
|
+
invalid:
|
|
625
|
+
"It is empty, not UTF-8 with LF line endings, has a BOM or NUL byte or an unterminated quote or comment, or exceeds 256 KiB (at most 128 files and 2 MiB in total).",
|
|
626
|
+
};
|
|
627
|
+
|
|
602
628
|
async function migrationSqlBlockers(
|
|
603
629
|
applicationDirectory: string,
|
|
604
630
|
files: readonly string[],
|
|
@@ -634,7 +660,7 @@ async function migrationSqlBlockers(
|
|
|
634
660
|
if (!(error instanceof DatabaseMigrationAdmissionError)) throw error;
|
|
635
661
|
blockers.push({
|
|
636
662
|
code: "migration_sql_not_admitted",
|
|
637
|
-
message: `PostgreSQL migration '${migration.path}' is not admitted (${error.reason}).`,
|
|
663
|
+
message: `PostgreSQL migration '${migration.path}' is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]} Migrations are expand-only. Fix the file before its first deployment; never edit a migration a deployment already applied.`,
|
|
638
664
|
});
|
|
639
665
|
}
|
|
640
666
|
}
|
|
@@ -647,7 +673,7 @@ async function migrationSqlBlockers(
|
|
|
647
673
|
return [
|
|
648
674
|
{
|
|
649
675
|
code: "migration_sql_not_admitted",
|
|
650
|
-
message: `
|
|
676
|
+
message: `The migration set is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]}`,
|
|
651
677
|
},
|
|
652
678
|
];
|
|
653
679
|
}
|
|
@@ -664,23 +690,24 @@ async function readExistingConfiguration(
|
|
|
664
690
|
if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES) {
|
|
665
691
|
throw new RepositoryInitError(
|
|
666
692
|
"repository_configuration_invalid",
|
|
667
|
-
"
|
|
693
|
+
"ohmyhost.yaml must be one regular file of at most 64 KiB",
|
|
668
694
|
);
|
|
669
695
|
}
|
|
670
696
|
const configuration = await readFile(configurationPath, "utf8");
|
|
671
697
|
let parsed;
|
|
672
698
|
try {
|
|
673
699
|
parsed = parseOhmyhostConfigYaml(configuration);
|
|
674
|
-
} catch {
|
|
700
|
+
} catch (error) {
|
|
701
|
+
// The parser names the offending key; dropping it left the agent nothing to fix.
|
|
675
702
|
throw new RepositoryInitError(
|
|
676
703
|
"repository_configuration_invalid",
|
|
677
|
-
"
|
|
704
|
+
error instanceof TypeError ? error.message : "ohmyhost.yaml is invalid",
|
|
678
705
|
);
|
|
679
706
|
}
|
|
680
707
|
if (parsed.applicationRoot !== applicationRoot) {
|
|
681
708
|
throw new RepositoryInitError(
|
|
682
709
|
"repository_configuration_root_mismatch",
|
|
683
|
-
|
|
710
|
+
`ohmyhost.yaml sets applicationRoot to '${parsed.applicationRoot}', but init selected '${applicationRoot}'. Run 'ohmyhost init --root ${parsed.applicationRoot} --dry-run --json', or correct applicationRoot.`,
|
|
684
711
|
);
|
|
685
712
|
}
|
|
686
713
|
return configuration;
|
|
@@ -858,10 +885,18 @@ function hasDirectSafeBuildCommand(
|
|
|
858
885
|
): boolean {
|
|
859
886
|
const scripts = packageJson["scripts"];
|
|
860
887
|
if (!isRecord(scripts) || typeof scripts["build"] !== "string") return false;
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
888
|
+
return (
|
|
889
|
+
(framework === "nextjs" || framework === "vite" || framework === "tanstack-start") &&
|
|
890
|
+
isAdmittedFrameworkBuildScript(framework, scripts["build"])
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/** The package.json build-script blocker, quoting at most 200 characters of the rejected script. */
|
|
895
|
+
function buildScriptMessage(framework: RepositoryFramework, command: string): string {
|
|
896
|
+
const quoted = command.slice(0, 200);
|
|
897
|
+
if (framework === "nextjs")
|
|
898
|
+
return `package.json scripts.build must be exactly "next build" or "next build --webpack", not "${quoted}". Move other steps into their own scripts, then commit and push.`;
|
|
899
|
+
return `package.json scripts.build must be "vite build", optionally joined with && to exactly one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage before or after it, not "${quoted}". Move other steps into their own scripts, then commit and push.`;
|
|
865
900
|
}
|
|
866
901
|
|
|
867
902
|
async function assertRepositoryRoot(root: string): Promise<void> {
|
|
@@ -1049,6 +1084,13 @@ function frameworkConfigurations(files: readonly string[]): FrameworkConfigurati
|
|
|
1049
1084
|
});
|
|
1050
1085
|
}
|
|
1051
1086
|
|
|
1087
|
+
/** The package.json dependency that carries each admitted framework's version. */
|
|
1088
|
+
const FRAMEWORK_PACKAGE: Readonly<Record<AdmittedFramework, string>> = {
|
|
1089
|
+
nextjs: "next",
|
|
1090
|
+
"tanstack-start": "@tanstack/react-start",
|
|
1091
|
+
vite: "vite",
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1052
1094
|
function frameworkCompatibility(
|
|
1053
1095
|
packageJson: Record<string, unknown>,
|
|
1054
1096
|
framework: RepositoryFramework,
|
|
@@ -1056,13 +1098,13 @@ function frameworkCompatibility(
|
|
|
1056
1098
|
const targets: readonly Readonly<{ framework: AdmittedFramework; packageName: string }>[] =
|
|
1057
1099
|
framework === "tanstack-start"
|
|
1058
1100
|
? [
|
|
1059
|
-
{ framework: "tanstack-start", packageName: "
|
|
1060
|
-
{ framework: "vite", packageName:
|
|
1101
|
+
{ framework: "tanstack-start", packageName: FRAMEWORK_PACKAGE["tanstack-start"] },
|
|
1102
|
+
{ framework: "vite", packageName: FRAMEWORK_PACKAGE.vite },
|
|
1061
1103
|
]
|
|
1062
1104
|
: framework === "nextjs"
|
|
1063
|
-
? [{ framework: "nextjs", packageName:
|
|
1105
|
+
? [{ framework: "nextjs", packageName: FRAMEWORK_PACKAGE.nextjs }]
|
|
1064
1106
|
: framework === "vite"
|
|
1065
|
-
? [{ framework: "vite", packageName:
|
|
1107
|
+
? [{ framework: "vite", packageName: FRAMEWORK_PACKAGE.vite }]
|
|
1066
1108
|
: [];
|
|
1067
1109
|
const frameworks = targets.map(({ framework: targetFramework, packageName }) => {
|
|
1068
1110
|
const version = dependencyVersion(packageJson, packageName) ?? "";
|
|
@@ -1132,7 +1174,7 @@ function repositoryBlockers(
|
|
|
1132
1174
|
blockers.push({
|
|
1133
1175
|
code: "framework_ambiguous",
|
|
1134
1176
|
message:
|
|
1135
|
-
|
|
1177
|
+
'TanStack Start needs one vite.config.js, .mjs or .ts that selects a mode. Static: tanstackStart({ prerender: { enabled: true } }) from "@tanstack/react-start/plugin/vite" and no "@cloudflare/vite-plugin". Server: cloudflare({ viteEnvironment: { name: "ssr" } }) from "@cloudflare/vite-plugin" listed before tanstackStart() in plugins, with @cloudflare/vite-plugin, @tanstack/react-router, @tanstack/react-start, react, react-dom, @vitejs/plugin-react and vite in package.json.',
|
|
1136
1178
|
});
|
|
1137
1179
|
}
|
|
1138
1180
|
const relevantAmbiguousConfigs = frameworkConfigs.ambiguous.filter((config) =>
|
|
@@ -1156,7 +1198,9 @@ function repositoryBlockers(
|
|
|
1156
1198
|
blockers.push({
|
|
1157
1199
|
code: "package_manager_unpinned",
|
|
1158
1200
|
message:
|
|
1159
|
-
|
|
1201
|
+
packageManagerAdmission.reason === "package_manager_version_unsupported"
|
|
1202
|
+
? `This build image runs Bun ${PINNED_BUN_VERSION}; pin packageManager to bun@${PINNED_BUN_VERSION} and commit its matching lockfile. Repeating the unchanged version cannot work.`
|
|
1203
|
+
: "Declare one exact npm, pnpm, Yarn, or Bun packageManager version and commit its matching lockfile.",
|
|
1160
1204
|
});
|
|
1161
1205
|
} else if (
|
|
1162
1206
|
existingConfiguration !== null &&
|
|
@@ -1166,17 +1210,21 @@ function repositoryBlockers(
|
|
|
1166
1210
|
) {
|
|
1167
1211
|
blockers.push({
|
|
1168
1212
|
code: "build_command_unsupported",
|
|
1169
|
-
message:
|
|
1213
|
+
message: `ohmyhost.yaml build.install must be exactly "${packageManagerAdmission.installCommand}"${
|
|
1214
|
+
"command" in existingConfiguration.build
|
|
1215
|
+
? ` and build.command "${packageManagerAdmission.buildCommand}"`
|
|
1216
|
+
: ""
|
|
1217
|
+
} for the packageManager in package.json. Set them, then commit and push.`,
|
|
1170
1218
|
});
|
|
1171
1219
|
}
|
|
1172
1220
|
if (compatibility.classification === "unsupported" && compatibility.frameworks.length > 0) {
|
|
1173
1221
|
const unsupported = compatibility.frameworks
|
|
1174
1222
|
.filter((entry) => entry.classification === "unsupported")
|
|
1175
|
-
.map((entry) => `${entry.framework}@${entry.version || "missing"}`)
|
|
1223
|
+
.map((entry) => `${FRAMEWORK_PACKAGE[entry.framework]}@${entry.version || "missing"}`)
|
|
1176
1224
|
.join(", ");
|
|
1177
1225
|
blockers.push({
|
|
1178
1226
|
code: "framework_version_unsupported",
|
|
1179
|
-
message:
|
|
1227
|
+
message: `${unsupported} is outside the versions ohmyho.st builds: next 15.5.x or 16.0.0–16.3.4, vite 5.4.0–8.2.2, @tanstack/react-start 1.168.26–1.168.49, as an exact version or a ^ or ~ range whose base version is inside. Pin a version in that range in package.json, update the lockfile, commit and push.`,
|
|
1180
1228
|
});
|
|
1181
1229
|
}
|
|
1182
1230
|
const scripts = packageJson["scripts"];
|
|
@@ -1192,10 +1240,12 @@ function repositoryBlockers(
|
|
|
1192
1240
|
} else if (!hasDirectSafeBuildCommand(packageJson, framework)) {
|
|
1193
1241
|
blockers.push({
|
|
1194
1242
|
code: "build_command_unsupported",
|
|
1195
|
-
message:
|
|
1243
|
+
message: buildScriptMessage(framework, scripts["build"]),
|
|
1196
1244
|
});
|
|
1197
1245
|
}
|
|
1198
1246
|
const betterAuthVersion = dependencyVersion(packageJson, "better-auth");
|
|
1247
|
+
const managedMailIssue = managedCustomerAuthAdmissionIssue(existingConfiguration);
|
|
1248
|
+
if (managedMailIssue !== null) blockers.push(managedMailIssue);
|
|
1199
1249
|
if (
|
|
1200
1250
|
existingConfiguration?.auth?.provider === "better-auth" &&
|
|
1201
1251
|
betterAuthVersion !== null &&
|
|
@@ -1229,7 +1279,7 @@ function repositoryBlockers(
|
|
|
1229
1279
|
blockers.push({
|
|
1230
1280
|
code: "next_adapter_unconfigured",
|
|
1231
1281
|
message:
|
|
1232
|
-
"
|
|
1282
|
+
'Next.js needs scripts.build exactly "next build" or "next build --webpack", react and react-dom at exact versions such as "19.1.0" (no ^ or ~), next within 15.5.x or 16.0.0–16.3.4, and exactly one next.config.js, .mjs or .ts, with at most one open-next.config file. ohmyho.st adds the OpenNext build itself; do not make it the build script.',
|
|
1233
1283
|
});
|
|
1234
1284
|
}
|
|
1235
1285
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { parseFeedbackSubmissionDetail } from "@ohmyhost/contracts/feedback";
|
|
2
|
+
import {
|
|
3
|
+
parseProjectDataChangePlan as readProjectDataChangePlan,
|
|
4
|
+
type ProjectDataChangePlan,
|
|
5
|
+
} from "@ohmyhost/contracts/project-data-changes";
|
|
2
6
|
import { parseFrameworkConversionDetail } from "@ohmyhost/contracts/framework-admission";
|
|
3
7
|
import {
|
|
4
8
|
parseOperationDeploymentProgress,
|
|
@@ -8,7 +12,6 @@ import type {
|
|
|
8
12
|
GithubConnectionAuthorization,
|
|
9
13
|
GithubOrganizationConnection,
|
|
10
14
|
GithubOrganizationConnectionStatus,
|
|
11
|
-
ProblemDetails,
|
|
12
15
|
OrganizationCredits,
|
|
13
16
|
ProjectCreditBudget,
|
|
14
17
|
} from "@ohmyhost/sdk-ts";
|
|
@@ -22,314 +25,8 @@ import {
|
|
|
22
25
|
} from "@ohmyhost/contracts/operation-failure";
|
|
23
26
|
export type { PublicOperationFailure } from "@ohmyhost/contracts/operation-failure";
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
export
|
|
27
|
-
cloudflare_zone_not_bound: {
|
|
28
|
-
status: 409,
|
|
29
|
-
retryable: false,
|
|
30
|
-
action:
|
|
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.",
|
|
32
|
-
},
|
|
33
|
-
github_connection_required: {
|
|
34
|
-
status: 409,
|
|
35
|
-
retryable: false,
|
|
36
|
-
action:
|
|
37
|
-
"This workspace has no GitHub connection yet. Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' for it, complete its browser link, then repeat the original source link. If the project belongs to another account, use that account's login instead; error.acting_as names the login this ran as.",
|
|
38
|
-
},
|
|
39
|
-
github_connection_revoked: {
|
|
40
|
-
status: 409,
|
|
41
|
-
retryable: false,
|
|
42
|
-
action:
|
|
43
|
-
"Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' to restore this workspace's GitHub connection, then repeat the original source link.",
|
|
44
|
-
},
|
|
45
|
-
repository_not_installed: {
|
|
46
|
-
status: 403,
|
|
47
|
-
retryable: false,
|
|
48
|
-
action:
|
|
49
|
-
"Read 'ohmyhost github status --organization ULID --json', open connection.settings_url and add this repository to the installation. Then repeat the original source link and key.",
|
|
50
|
-
},
|
|
51
|
-
database_write_pending: {
|
|
52
|
-
status: 409,
|
|
53
|
-
retryable: false,
|
|
54
|
-
action: "Poll the original database-write operation before transfer or deletion.",
|
|
55
|
-
},
|
|
56
|
-
cloudflare_authorization_closed: {
|
|
57
|
-
status: 409,
|
|
58
|
-
retryable: false,
|
|
59
|
-
action:
|
|
60
|
-
"Read Cloudflare DNS status. Reuse a valid grant for the requested zone; otherwise request fresh authorization with a new idempotency key. Do not replay the callback.",
|
|
61
|
-
},
|
|
62
|
-
billing_recharge_conflict: {
|
|
63
|
-
status: 409,
|
|
64
|
-
retryable: false,
|
|
65
|
-
action:
|
|
66
|
-
"Read billing recharge get and the current revision. Preserve the original key after uncertainty; do not start a checkout or automatically re-enable charges.",
|
|
67
|
-
},
|
|
68
|
-
billing_purchase_conflict: {
|
|
69
|
-
status: 409,
|
|
70
|
-
retryable: false,
|
|
71
|
-
action:
|
|
72
|
-
"Read the original checkout and use billing portal to manage the existing subscription. Do not create another purchase after uncertainty.",
|
|
73
|
-
},
|
|
74
|
-
compute_performance_paid_required: {
|
|
75
|
-
status: 403,
|
|
76
|
-
retryable: false,
|
|
77
|
-
action: "Check Paid service and usable credits or grace, or choose standard compute.",
|
|
78
|
-
},
|
|
79
|
-
compute_performance_unavailable: {
|
|
80
|
-
status: 503,
|
|
81
|
-
retryable: false,
|
|
82
|
-
action:
|
|
83
|
-
"Choose standard compute or report inactive performance pricing through feedback. Do not repeatedly resubmit.",
|
|
84
|
-
},
|
|
85
|
-
compute_change_pending: {
|
|
86
|
-
status: 409,
|
|
87
|
-
retryable: false,
|
|
88
|
-
action:
|
|
89
|
-
"Poll the existing operation after 60 seconds. Do not submit another size change, deletion or transfer while it runs.",
|
|
90
|
-
},
|
|
91
|
-
compute_change_conflict: {
|
|
92
|
-
status: 409,
|
|
93
|
-
retryable: false,
|
|
94
|
-
action:
|
|
95
|
-
"Read the original operation and current database compute. Preserve data and report unresolved conflicts through feedback with the operation ID.",
|
|
96
|
-
},
|
|
97
|
-
database_access_limit: {
|
|
98
|
-
status: 409,
|
|
99
|
-
retryable: false,
|
|
100
|
-
action:
|
|
101
|
-
"List the project's database credentials and revoke one you no longer need, then request access again.",
|
|
102
|
-
},
|
|
103
|
-
project_notes_conflict: {
|
|
104
|
-
status: 409,
|
|
105
|
-
retryable: false,
|
|
106
|
-
action:
|
|
107
|
-
"Read project context, merge the current notes and retry with its version and a new idempotency key.",
|
|
108
|
-
},
|
|
109
|
-
invalid_request: { status: 400, retryable: false, action: "Check the request and retry." },
|
|
110
|
-
unauthenticated: {
|
|
111
|
-
status: 401,
|
|
112
|
-
retryable: false,
|
|
113
|
-
action:
|
|
114
|
-
"Check OHMYHOST_TOKEN and the selected environment. Replace invalid tokens, or unset OHMYHOST_TOKEN and run 'ohmyhost login'.",
|
|
115
|
-
},
|
|
116
|
-
forbidden: { status: 403, retryable: false, action: "Check organization access." },
|
|
117
|
-
organization_required: {
|
|
118
|
-
status: 409,
|
|
119
|
-
retryable: false,
|
|
120
|
-
action:
|
|
121
|
-
"Run 'ohmyhost organization list --json' and select one with 'ohmyhost organization use --organization ULID --json', or create the first workspace.",
|
|
122
|
-
},
|
|
123
|
-
resource_not_found: {
|
|
124
|
-
status: 404,
|
|
125
|
-
retryable: false,
|
|
126
|
-
action:
|
|
127
|
-
"Check the resource identifier and the account: a project is visible only to its own organization, and error.acting_as names the login this ran as. Use the login of the account that owns it.",
|
|
128
|
-
},
|
|
129
|
-
idempotency_key_reused: {
|
|
130
|
-
status: 409,
|
|
131
|
-
retryable: false,
|
|
132
|
-
action: "Use a new idempotency key for a different request.",
|
|
133
|
-
},
|
|
134
|
-
project_handle_unavailable: {
|
|
135
|
-
status: 503,
|
|
136
|
-
retryable: true,
|
|
137
|
-
action: "Retry project creation with the same idempotency key.",
|
|
138
|
-
},
|
|
139
|
-
project_handle_taken: {
|
|
140
|
-
status: 409,
|
|
141
|
-
retryable: false,
|
|
142
|
-
action:
|
|
143
|
-
"Run 'ohmyhost project handle check' for a free address, then rename to one of the alternatives it returns.",
|
|
144
|
-
},
|
|
145
|
-
project_handle_invalid: {
|
|
146
|
-
status: 409,
|
|
147
|
-
retryable: false,
|
|
148
|
-
action:
|
|
149
|
-
"Choose one to five lowercase words of letters and digits, three to fifty-nine characters, avoiding reserved words.",
|
|
150
|
-
},
|
|
151
|
-
project_handle_unchanged: {
|
|
152
|
-
status: 409,
|
|
153
|
-
retryable: false,
|
|
154
|
-
action: "The project already answers on this address; no rename is needed.",
|
|
155
|
-
},
|
|
156
|
-
project_rename_blocked: {
|
|
157
|
-
status: 409,
|
|
158
|
-
retryable: true,
|
|
159
|
-
action:
|
|
160
|
-
"Wait for the running operation to finish, read the project again, then rename with the new ETag.",
|
|
161
|
-
},
|
|
162
|
-
project_identity_unavailable: {
|
|
163
|
-
status: 503,
|
|
164
|
-
retryable: true,
|
|
165
|
-
action: "Retry project creation with the same idempotency key.",
|
|
166
|
-
},
|
|
167
|
-
deployment_plan_expired: {
|
|
168
|
-
status: 409,
|
|
169
|
-
retryable: false,
|
|
170
|
-
action: "Run 'ohmyhost plan' again, then deploy the new plan.",
|
|
171
|
-
},
|
|
172
|
-
deployment_plan_incompatible: {
|
|
173
|
-
status: 409,
|
|
174
|
-
retryable: false,
|
|
175
|
-
action: "Run 'ohmyhost plan' again with the current source revision.",
|
|
176
|
-
},
|
|
177
|
-
confirmation_expired: {
|
|
178
|
-
status: 409,
|
|
179
|
-
retryable: false,
|
|
180
|
-
action: "Plan the destructive action again before confirming it.",
|
|
181
|
-
},
|
|
182
|
-
confirmation_invalid: {
|
|
183
|
-
status: 409,
|
|
184
|
-
retryable: false,
|
|
185
|
-
action: "Use the confirmation token from a fresh matching plan.",
|
|
186
|
-
},
|
|
187
|
-
etag_mismatch: {
|
|
188
|
-
status: 409,
|
|
189
|
-
retryable: false,
|
|
190
|
-
action: "Refresh the resource, plan the action again, and retry.",
|
|
191
|
-
},
|
|
192
|
-
promotion_source_stale: {
|
|
193
|
-
status: 409,
|
|
194
|
-
retryable: false,
|
|
195
|
-
action: "Read the current dev deployment, then plan promotion again.",
|
|
196
|
-
},
|
|
197
|
-
promotion_target_stale: {
|
|
198
|
-
status: 409,
|
|
199
|
-
retryable: false,
|
|
200
|
-
action: "Read the current prod status, then plan promotion again.",
|
|
201
|
-
},
|
|
202
|
-
promotion_invalid_target: {
|
|
203
|
-
status: 409,
|
|
204
|
-
retryable: false,
|
|
205
|
-
action: "Select the current succeeded dev deployment and target prod.",
|
|
206
|
-
},
|
|
207
|
-
mail_capacity_unavailable: {
|
|
208
|
-
status: 503,
|
|
209
|
-
retryable: true,
|
|
210
|
-
action:
|
|
211
|
-
"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.",
|
|
212
|
-
},
|
|
213
|
-
mail_domain_conflict: {
|
|
214
|
-
status: 409,
|
|
215
|
-
retryable: false,
|
|
216
|
-
action: "Read and use the sender domain already configured for this project.",
|
|
217
|
-
},
|
|
218
|
-
mail_domain_required: {
|
|
219
|
-
status: 409,
|
|
220
|
-
retryable: false,
|
|
221
|
-
action:
|
|
222
|
-
"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.",
|
|
223
|
-
},
|
|
224
|
-
storage_jurisdiction_conflict: {
|
|
225
|
-
status: 409,
|
|
226
|
-
retryable: false,
|
|
227
|
-
action:
|
|
228
|
-
"Set storage.jurisdiction in ohmyhost.yaml to the project's hosting region shown by project status, then plan again.",
|
|
229
|
-
},
|
|
230
|
-
shared_data_requires_promotion: {
|
|
231
|
-
status: 409,
|
|
232
|
-
retryable: false,
|
|
233
|
-
action:
|
|
234
|
-
"Plan and deploy this commit to Dev, then run 'ohmyhost deployment promote plan' to reach Prod.",
|
|
235
|
-
},
|
|
236
|
-
production_deployment_required: {
|
|
237
|
-
status: 409,
|
|
238
|
-
retryable: false,
|
|
239
|
-
action:
|
|
240
|
-
"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.",
|
|
241
|
-
},
|
|
242
|
-
framework_conversion_required: {
|
|
243
|
-
status: 409,
|
|
244
|
-
retryable: false,
|
|
245
|
-
action:
|
|
246
|
-
"Run 'ohmyhost init', apply the matching ohmyhost framework skill, then plan the new commit.",
|
|
247
|
-
},
|
|
248
|
-
repository_configuration_missing: {
|
|
249
|
-
status: 409,
|
|
250
|
-
retryable: false,
|
|
251
|
-
action:
|
|
252
|
-
"Place ohmyhost.yaml at the Git repository root and set applicationRoot to the application directory (for example website). Commit and push the change, then plan the new commit.",
|
|
253
|
-
},
|
|
254
|
-
migration_filename_noncanonical: {
|
|
255
|
-
status: 409,
|
|
256
|
-
retryable: false,
|
|
257
|
-
action:
|
|
258
|
-
"Rename every migration to YYYYMMDDHHMMSS_name.sql, commit the change, then plan again.",
|
|
259
|
-
},
|
|
260
|
-
environment_secret_mutation_blocked: {
|
|
261
|
-
status: 409,
|
|
262
|
-
retryable: true,
|
|
263
|
-
action: "Wait for the active deployment or rollback to finish, then retry unchanged.",
|
|
264
|
-
},
|
|
265
|
-
workers_runtime_incompatible: {
|
|
266
|
-
status: 409,
|
|
267
|
-
retryable: false,
|
|
268
|
-
action: "Remove or replace the reported module, commit the change, then create a new plan.",
|
|
269
|
-
},
|
|
270
|
-
payload_too_large: { status: 413, retryable: false, action: "Reduce the request size." },
|
|
271
|
-
project_export_not_ready: {
|
|
272
|
-
status: 409,
|
|
273
|
-
retryable: false,
|
|
274
|
-
action: "Finish database provisioning, then request the export again. No backup was accepted.",
|
|
275
|
-
},
|
|
276
|
-
powered_by_flag_required: {
|
|
277
|
-
status: 409,
|
|
278
|
-
retryable: false,
|
|
279
|
-
action:
|
|
280
|
-
"Keep the flag on, remove the custom domain with 'ohmyhost domain paid delete', or buy Paid, then switch the flag off.",
|
|
281
|
-
},
|
|
282
|
-
rate_limited: { status: 429, retryable: true, action: "Retry after the server delay." },
|
|
283
|
-
insufficient_organization_credits: {
|
|
284
|
-
status: 402,
|
|
285
|
-
retryable: false,
|
|
286
|
-
action:
|
|
287
|
-
"Add organization credits or wait for reservations to settle, then retry the same request.",
|
|
288
|
-
},
|
|
289
|
-
project_budget_exceeded: {
|
|
290
|
-
status: 402,
|
|
291
|
-
retryable: false,
|
|
292
|
-
action: "Raise the project's stop budget or select continue, then retry the same request.",
|
|
293
|
-
},
|
|
294
|
-
paid_plan_required: {
|
|
295
|
-
status: 402,
|
|
296
|
-
retryable: false,
|
|
297
|
-
action:
|
|
298
|
-
"Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid. For a website domain alone, a Free workspace may instead show the powered-by flag on the project.",
|
|
299
|
-
},
|
|
300
|
-
interactive_login_required: {
|
|
301
|
-
status: 403,
|
|
302
|
-
retryable: false,
|
|
303
|
-
action:
|
|
304
|
-
"Unset OHMYHOST_TOKEN and run ohmyhost login before managing tokens. Existing token files remain unchanged.",
|
|
305
|
-
},
|
|
306
|
-
api_key_creation_uncertain: {
|
|
307
|
-
status: 503,
|
|
308
|
-
retryable: true,
|
|
309
|
-
action:
|
|
310
|
-
"Repeat the original token name and Idempotency-Key to observe its result; do not blindly create another key.",
|
|
311
|
-
},
|
|
312
|
-
api_key_permissions_unavailable: {
|
|
313
|
-
status: 503,
|
|
314
|
-
retryable: false,
|
|
315
|
-
action:
|
|
316
|
-
"Report the platform user-key permission configuration through feedback. Repeated login or creation will not fix it.",
|
|
317
|
-
},
|
|
318
|
-
reconciliation_exhausted: {
|
|
319
|
-
status: 409,
|
|
320
|
-
retryable: false,
|
|
321
|
-
action:
|
|
322
|
-
"Stop retries and submit feedback with this operation ID. A platform recovery review is required.",
|
|
323
|
-
},
|
|
324
|
-
service_unavailable: { status: 503, retryable: true, action: "Retry later." },
|
|
325
|
-
} as const satisfies Readonly<
|
|
326
|
-
Record<
|
|
327
|
-
ProblemDetails["code"],
|
|
328
|
-
{ readonly status: number; readonly retryable: boolean; readonly action: string }
|
|
329
|
-
>
|
|
330
|
-
>;
|
|
331
|
-
|
|
332
|
-
type ProblemCode = keyof typeof PROBLEM_POLICY;
|
|
28
|
+
import { PROBLEM_POLICY, type ProblemCode } from "@ohmyhost/contracts/problem-policy";
|
|
29
|
+
export { PROBLEM_POLICY } from "@ohmyhost/contracts/problem-policy";
|
|
333
30
|
|
|
334
31
|
export class ResponseContractError extends Error {
|
|
335
32
|
public constructor() {
|
|
@@ -338,8 +35,17 @@ export class ResponseContractError extends Error {
|
|
|
338
35
|
}
|
|
339
36
|
}
|
|
340
37
|
|
|
38
|
+
export function parseProjectDataChangePlan(value: unknown): ProjectDataChangePlan {
|
|
39
|
+
try {
|
|
40
|
+
return readProjectDataChangePlan(value);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new ResponseContractError();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
341
46
|
export interface SafeProblem {
|
|
342
47
|
readonly requestId: string;
|
|
48
|
+
readonly message?: string;
|
|
343
49
|
/** The error's documentation page, when the server names one on docs.ohmyho.st. */
|
|
344
50
|
readonly docsUrl?: string;
|
|
345
51
|
readonly retryAfterSeconds?: number;
|
|
@@ -1604,6 +1310,7 @@ export const parseSafeProblem = (value: unknown): SafeProblem => {
|
|
|
1604
1310
|
const docsUrl = String(input["type"]);
|
|
1605
1311
|
return {
|
|
1606
1312
|
requestId: input["request_id"],
|
|
1313
|
+
...(input["detail"] === undefined ? {} : { message: String(input["detail"]) }),
|
|
1607
1314
|
...(docsUrl.startsWith("https://docs.ohmyho.st/errors/") ? { docsUrl } : {}),
|
|
1608
1315
|
code: code as ProblemCode,
|
|
1609
1316
|
retryable: policy.retryable,
|