@amerged/ohmyhost-mcp 0.1.23 → 0.1.25
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-P44YTAKK.js → chunk-L5A5FEGY.js} +25 -24
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +724 -266
- package/package.json +2 -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} +324 -142
- 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} +90 -36
- 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 +28 -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} +76 -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 +113 -4
- package/src/packages/sdk-ts/{generated → src/generated}/types.gen.ts +201 -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,7 @@ export interface RepositoryInitBlocker {
|
|
|
96
102
|
| "database_driver_unsupported"
|
|
97
103
|
| "build_script_missing"
|
|
98
104
|
| "better_auth_version_unsupported"
|
|
105
|
+
| "managed_auth_database_required"
|
|
99
106
|
| "framework_ambiguous"
|
|
100
107
|
| "framework_unsupported"
|
|
101
108
|
| "framework_version_unsupported"
|
|
@@ -184,6 +191,11 @@ export interface RepositoryInitResult {
|
|
|
184
191
|
bindings: readonly string[];
|
|
185
192
|
packages: RepositoryInitRuntimePackages;
|
|
186
193
|
}> | null;
|
|
194
|
+
/** What server code of any framework installs and reads once a capability is enabled. */
|
|
195
|
+
readonly runtime: Readonly<{
|
|
196
|
+
bindings: readonly string[];
|
|
197
|
+
packages: RepositoryInitRuntimePackages;
|
|
198
|
+
}> | null;
|
|
187
199
|
}
|
|
188
200
|
|
|
189
201
|
export class RepositoryInitError extends Error {
|
|
@@ -302,6 +314,9 @@ export async function initializeRepository(
|
|
|
302
314
|
: "none";
|
|
303
315
|
const migrationCount =
|
|
304
316
|
databaseProvider === "supabase" ? supabaseMigrationCount : postgresMigrationCount;
|
|
317
|
+
// Inventory remains evidence; an existing configuration selects the managed capabilities.
|
|
318
|
+
const selectedDatabaseProvider =
|
|
319
|
+
existingConfiguration === null ? databaseProvider : configuredDatabase ? "postgresql" : "none";
|
|
305
320
|
const betterAuth =
|
|
306
321
|
dependencies.has("better-auth") || existingConfiguration?.auth?.provider === "better-auth";
|
|
307
322
|
const workos = [
|
|
@@ -377,7 +392,7 @@ export async function initializeRepository(
|
|
|
377
392
|
});
|
|
378
393
|
const configurationYaml = existingConfigurationYaml ?? generatedConfigurationYaml;
|
|
379
394
|
const capabilities: RuntimeCapabilities = Object.freeze({
|
|
380
|
-
database:
|
|
395
|
+
database: selectedDatabaseProvider !== "none",
|
|
381
396
|
auth: targetBetterAuth,
|
|
382
397
|
mail: targetMail,
|
|
383
398
|
storage: targetStorage,
|
|
@@ -391,7 +406,7 @@ export async function initializeRepository(
|
|
|
391
406
|
applicationDirectory,
|
|
392
407
|
applicationFiles,
|
|
393
408
|
existingConfiguration?.database?.migrations,
|
|
394
|
-
|
|
409
|
+
selectedDatabaseProvider,
|
|
395
410
|
);
|
|
396
411
|
const blockers = [
|
|
397
412
|
...repositoryAdmissionBlockers,
|
|
@@ -402,11 +417,13 @@ export async function initializeRepository(
|
|
|
402
417
|
applicationDirectory,
|
|
403
418
|
applicationFiles,
|
|
404
419
|
)),
|
|
405
|
-
...
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
420
|
+
...(selectedDatabaseProvider === "none"
|
|
421
|
+
? []
|
|
422
|
+
: migrationFilenameBlockers(
|
|
423
|
+
applicationFiles,
|
|
424
|
+
existingConfiguration?.database?.migrations,
|
|
425
|
+
selectedDatabaseProvider,
|
|
426
|
+
)),
|
|
410
427
|
...migrationSqlAdmissionBlockers,
|
|
411
428
|
...(await storageClientBlockers(existingConfiguration, applicationDirectory, applicationFiles)),
|
|
412
429
|
...(await databaseContractBlockers(
|
|
@@ -447,6 +464,8 @@ export async function initializeRepository(
|
|
|
447
464
|
...runtimeContract(capabilities),
|
|
448
465
|
})
|
|
449
466
|
: null,
|
|
467
|
+
// Next.js and TanStack Start have neither a companion nor a Worker module, and read it here.
|
|
468
|
+
runtime: Object.values(capabilities).some(Boolean) ? runtimeContract(capabilities) : null,
|
|
450
469
|
});
|
|
451
470
|
}
|
|
452
471
|
|
|
@@ -480,7 +499,7 @@ async function cronBlockers(
|
|
|
480
499
|
{
|
|
481
500
|
code: "scheduled_functions_runtime_unsupported",
|
|
482
501
|
message:
|
|
483
|
-
"
|
|
502
|
+
"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
503
|
},
|
|
485
504
|
];
|
|
486
505
|
}
|
|
@@ -585,20 +604,31 @@ function migrationFilenameBlockers(
|
|
|
585
604
|
const invalid = files
|
|
586
605
|
.filter((path) => path.startsWith(prefix))
|
|
587
606
|
.map((path) => path.slice(prefix.length))
|
|
588
|
-
.
|
|
607
|
+
.find(
|
|
589
608
|
(path) => !/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*\d{14}_[a-z0-9][a-z0-9_-]*\.sql$/u.test(path),
|
|
590
609
|
);
|
|
591
|
-
return invalid
|
|
592
|
-
? [
|
|
610
|
+
return invalid === undefined
|
|
611
|
+
? []
|
|
612
|
+
: [
|
|
593
613
|
{
|
|
594
614
|
code: "migration_filename_noncanonical",
|
|
595
|
-
message:
|
|
596
|
-
"Rename every PostgreSQL migration to YYYYMMDDHHMMSS_name.sql before initialization.",
|
|
615
|
+
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
616
|
},
|
|
598
|
-
]
|
|
599
|
-
: [];
|
|
617
|
+
];
|
|
600
618
|
}
|
|
601
619
|
|
|
620
|
+
/** What each rejection reason of the expand-only migration gate means, and how to satisfy it. */
|
|
621
|
+
const MIGRATION_REJECTION: Readonly<Record<DatabaseMigrationAdmissionReason, string>> = {
|
|
622
|
+
destructive:
|
|
623
|
+
"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'.",
|
|
624
|
+
provider_specific:
|
|
625
|
+
"It mentions supabase, CREATE POLICY, ROW LEVEL SECURITY, the auth. or storage. schema, or the anon, authenticated or service_role role; comments count.",
|
|
626
|
+
unsupported:
|
|
627
|
+
"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.",
|
|
628
|
+
invalid:
|
|
629
|
+
"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).",
|
|
630
|
+
};
|
|
631
|
+
|
|
602
632
|
async function migrationSqlBlockers(
|
|
603
633
|
applicationDirectory: string,
|
|
604
634
|
files: readonly string[],
|
|
@@ -634,7 +664,7 @@ async function migrationSqlBlockers(
|
|
|
634
664
|
if (!(error instanceof DatabaseMigrationAdmissionError)) throw error;
|
|
635
665
|
blockers.push({
|
|
636
666
|
code: "migration_sql_not_admitted",
|
|
637
|
-
message: `PostgreSQL migration '${migration.path}' is not admitted (${error.reason}).`,
|
|
667
|
+
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
668
|
});
|
|
639
669
|
}
|
|
640
670
|
}
|
|
@@ -647,7 +677,7 @@ async function migrationSqlBlockers(
|
|
|
647
677
|
return [
|
|
648
678
|
{
|
|
649
679
|
code: "migration_sql_not_admitted",
|
|
650
|
-
message: `
|
|
680
|
+
message: `The migration set is not admitted (${error.reason}): ${MIGRATION_REJECTION[error.reason]}`,
|
|
651
681
|
},
|
|
652
682
|
];
|
|
653
683
|
}
|
|
@@ -664,23 +694,24 @@ async function readExistingConfiguration(
|
|
|
664
694
|
if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES) {
|
|
665
695
|
throw new RepositoryInitError(
|
|
666
696
|
"repository_configuration_invalid",
|
|
667
|
-
"
|
|
697
|
+
"ohmyhost.yaml must be one regular file of at most 64 KiB",
|
|
668
698
|
);
|
|
669
699
|
}
|
|
670
700
|
const configuration = await readFile(configurationPath, "utf8");
|
|
671
701
|
let parsed;
|
|
672
702
|
try {
|
|
673
703
|
parsed = parseOhmyhostConfigYaml(configuration);
|
|
674
|
-
} catch {
|
|
704
|
+
} catch (error) {
|
|
705
|
+
// The parser names the offending key; dropping it left the agent nothing to fix.
|
|
675
706
|
throw new RepositoryInitError(
|
|
676
707
|
"repository_configuration_invalid",
|
|
677
|
-
"
|
|
708
|
+
error instanceof TypeError ? error.message : "ohmyhost.yaml is invalid",
|
|
678
709
|
);
|
|
679
710
|
}
|
|
680
711
|
if (parsed.applicationRoot !== applicationRoot) {
|
|
681
712
|
throw new RepositoryInitError(
|
|
682
713
|
"repository_configuration_root_mismatch",
|
|
683
|
-
|
|
714
|
+
`ohmyhost.yaml sets applicationRoot to '${parsed.applicationRoot}', but init selected '${applicationRoot}'. Run 'ohmyhost init --root ${parsed.applicationRoot} --dry-run --json', or correct applicationRoot.`,
|
|
684
715
|
);
|
|
685
716
|
}
|
|
686
717
|
return configuration;
|
|
@@ -858,10 +889,18 @@ function hasDirectSafeBuildCommand(
|
|
|
858
889
|
): boolean {
|
|
859
890
|
const scripts = packageJson["scripts"];
|
|
860
891
|
if (!isRecord(scripts) || typeof scripts["build"] !== "string") return false;
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
892
|
+
return (
|
|
893
|
+
(framework === "nextjs" || framework === "vite" || framework === "tanstack-start") &&
|
|
894
|
+
isAdmittedFrameworkBuildScript(framework, scripts["build"])
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** The package.json build-script blocker, quoting at most 200 characters of the rejected script. */
|
|
899
|
+
function buildScriptMessage(framework: RepositoryFramework, command: string): string {
|
|
900
|
+
const quoted = command.slice(0, 200);
|
|
901
|
+
if (framework === "nextjs")
|
|
902
|
+
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.`;
|
|
903
|
+
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
904
|
}
|
|
866
905
|
|
|
867
906
|
async function assertRepositoryRoot(root: string): Promise<void> {
|
|
@@ -1049,6 +1088,13 @@ function frameworkConfigurations(files: readonly string[]): FrameworkConfigurati
|
|
|
1049
1088
|
});
|
|
1050
1089
|
}
|
|
1051
1090
|
|
|
1091
|
+
/** The package.json dependency that carries each admitted framework's version. */
|
|
1092
|
+
const FRAMEWORK_PACKAGE: Readonly<Record<AdmittedFramework, string>> = {
|
|
1093
|
+
nextjs: "next",
|
|
1094
|
+
"tanstack-start": "@tanstack/react-start",
|
|
1095
|
+
vite: "vite",
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1052
1098
|
function frameworkCompatibility(
|
|
1053
1099
|
packageJson: Record<string, unknown>,
|
|
1054
1100
|
framework: RepositoryFramework,
|
|
@@ -1056,13 +1102,13 @@ function frameworkCompatibility(
|
|
|
1056
1102
|
const targets: readonly Readonly<{ framework: AdmittedFramework; packageName: string }>[] =
|
|
1057
1103
|
framework === "tanstack-start"
|
|
1058
1104
|
? [
|
|
1059
|
-
{ framework: "tanstack-start", packageName: "
|
|
1060
|
-
{ framework: "vite", packageName:
|
|
1105
|
+
{ framework: "tanstack-start", packageName: FRAMEWORK_PACKAGE["tanstack-start"] },
|
|
1106
|
+
{ framework: "vite", packageName: FRAMEWORK_PACKAGE.vite },
|
|
1061
1107
|
]
|
|
1062
1108
|
: framework === "nextjs"
|
|
1063
|
-
? [{ framework: "nextjs", packageName:
|
|
1109
|
+
? [{ framework: "nextjs", packageName: FRAMEWORK_PACKAGE.nextjs }]
|
|
1064
1110
|
: framework === "vite"
|
|
1065
|
-
? [{ framework: "vite", packageName:
|
|
1111
|
+
? [{ framework: "vite", packageName: FRAMEWORK_PACKAGE.vite }]
|
|
1066
1112
|
: [];
|
|
1067
1113
|
const frameworks = targets.map(({ framework: targetFramework, packageName }) => {
|
|
1068
1114
|
const version = dependencyVersion(packageJson, packageName) ?? "";
|
|
@@ -1132,7 +1178,7 @@ function repositoryBlockers(
|
|
|
1132
1178
|
blockers.push({
|
|
1133
1179
|
code: "framework_ambiguous",
|
|
1134
1180
|
message:
|
|
1135
|
-
|
|
1181
|
+
'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
1182
|
});
|
|
1137
1183
|
}
|
|
1138
1184
|
const relevantAmbiguousConfigs = frameworkConfigs.ambiguous.filter((config) =>
|
|
@@ -1156,7 +1202,9 @@ function repositoryBlockers(
|
|
|
1156
1202
|
blockers.push({
|
|
1157
1203
|
code: "package_manager_unpinned",
|
|
1158
1204
|
message:
|
|
1159
|
-
|
|
1205
|
+
packageManagerAdmission.reason === "package_manager_version_unsupported"
|
|
1206
|
+
? `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.`
|
|
1207
|
+
: "Declare one exact npm, pnpm, Yarn, or Bun packageManager version and commit its matching lockfile.",
|
|
1160
1208
|
});
|
|
1161
1209
|
} else if (
|
|
1162
1210
|
existingConfiguration !== null &&
|
|
@@ -1166,17 +1214,21 @@ function repositoryBlockers(
|
|
|
1166
1214
|
) {
|
|
1167
1215
|
blockers.push({
|
|
1168
1216
|
code: "build_command_unsupported",
|
|
1169
|
-
message:
|
|
1217
|
+
message: `ohmyhost.yaml build.install must be exactly "${packageManagerAdmission.installCommand}"${
|
|
1218
|
+
"command" in existingConfiguration.build
|
|
1219
|
+
? ` and build.command "${packageManagerAdmission.buildCommand}"`
|
|
1220
|
+
: ""
|
|
1221
|
+
} for the packageManager in package.json. Set them, then commit and push.`,
|
|
1170
1222
|
});
|
|
1171
1223
|
}
|
|
1172
1224
|
if (compatibility.classification === "unsupported" && compatibility.frameworks.length > 0) {
|
|
1173
1225
|
const unsupported = compatibility.frameworks
|
|
1174
1226
|
.filter((entry) => entry.classification === "unsupported")
|
|
1175
|
-
.map((entry) => `${entry.framework}@${entry.version || "missing"}`)
|
|
1227
|
+
.map((entry) => `${FRAMEWORK_PACKAGE[entry.framework]}@${entry.version || "missing"}`)
|
|
1176
1228
|
.join(", ");
|
|
1177
1229
|
blockers.push({
|
|
1178
1230
|
code: "framework_version_unsupported",
|
|
1179
|
-
message:
|
|
1231
|
+
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
1232
|
});
|
|
1181
1233
|
}
|
|
1182
1234
|
const scripts = packageJson["scripts"];
|
|
@@ -1192,10 +1244,12 @@ function repositoryBlockers(
|
|
|
1192
1244
|
} else if (!hasDirectSafeBuildCommand(packageJson, framework)) {
|
|
1193
1245
|
blockers.push({
|
|
1194
1246
|
code: "build_command_unsupported",
|
|
1195
|
-
message:
|
|
1247
|
+
message: buildScriptMessage(framework, scripts["build"]),
|
|
1196
1248
|
});
|
|
1197
1249
|
}
|
|
1198
1250
|
const betterAuthVersion = dependencyVersion(packageJson, "better-auth");
|
|
1251
|
+
const managedAuthIssue = managedCustomerAuthAdmissionIssue(existingConfiguration);
|
|
1252
|
+
if (managedAuthIssue !== null) blockers.push(managedAuthIssue);
|
|
1199
1253
|
if (
|
|
1200
1254
|
existingConfiguration?.auth?.provider === "better-auth" &&
|
|
1201
1255
|
betterAuthVersion !== null &&
|
|
@@ -1229,7 +1283,7 @@ function repositoryBlockers(
|
|
|
1229
1283
|
blockers.push({
|
|
1230
1284
|
code: "next_adapter_unconfigured",
|
|
1231
1285
|
message:
|
|
1232
|
-
"
|
|
1286
|
+
'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
1287
|
});
|
|
1234
1288
|
}
|
|
1235
1289
|
}
|
|
@@ -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,
|