@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GENERATED_SKILL_RESOURCES } from "./generated-skill-resources.js";
|
|
2
2
|
|
|
3
|
-
export const OHMYHOST_SKILL_INSTALL_SOURCE = "amerged/docs";
|
|
3
|
+
export const OHMYHOST_SKILL_INSTALL_SOURCE = "amerged-org/docs";
|
|
4
4
|
|
|
5
5
|
export interface OhmyhostSkillResource {
|
|
6
6
|
readonly skillName: string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ohmyhost/contracts",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
"./operation-failure": {
|
|
9
|
+
"source": "./src/operation-failure.ts",
|
|
10
|
+
"types": "./dist/operation-failure.d.ts",
|
|
11
|
+
"import": "./dist/operation-failure.js"
|
|
12
|
+
},
|
|
13
|
+
"./billing": {
|
|
14
|
+
"source": "./src/billing.ts",
|
|
15
|
+
"types": "./dist/billing.d.ts",
|
|
16
|
+
"import": "./dist/billing.js"
|
|
17
|
+
},
|
|
18
|
+
"./credit-pricing": {
|
|
19
|
+
"source": "./src/credit-pricing.ts",
|
|
20
|
+
"types": "./dist/credit-pricing.d.ts",
|
|
21
|
+
"import": "./dist/credit-pricing.js"
|
|
22
|
+
},
|
|
23
|
+
"./platform-origins": {
|
|
24
|
+
"source": "./src/platform-origins.ts",
|
|
25
|
+
"types": "./dist/platform-origins.d.ts",
|
|
26
|
+
"import": "./dist/platform-origins.js"
|
|
27
|
+
},
|
|
28
|
+
".": {
|
|
29
|
+
"source": "./src/index.ts",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./application-root": {
|
|
34
|
+
"source": "./src/application-root.ts",
|
|
35
|
+
"types": "./dist/application-root.d.ts",
|
|
36
|
+
"import": "./dist/application-root.js"
|
|
37
|
+
},
|
|
38
|
+
"./database-migration-admission": {
|
|
39
|
+
"source": "./src/database-migration-admission.ts",
|
|
40
|
+
"types": "./dist/database-migration-admission.d.ts",
|
|
41
|
+
"import": "./dist/database-migration-admission.js"
|
|
42
|
+
},
|
|
43
|
+
"./framework-family": {
|
|
44
|
+
"source": "./src/framework-family.ts",
|
|
45
|
+
"types": "./dist/framework-family.d.ts",
|
|
46
|
+
"import": "./dist/framework-family.js"
|
|
47
|
+
},
|
|
48
|
+
"./operation-deployment-result": {
|
|
49
|
+
"source": "./src/operation-deployment-result.ts",
|
|
50
|
+
"types": "./dist/operation-deployment-result.d.ts",
|
|
51
|
+
"import": "./dist/operation-deployment-result.js"
|
|
52
|
+
},
|
|
53
|
+
"./operation-data-change-result": {
|
|
54
|
+
"source": "./src/operation-data-change-result.ts",
|
|
55
|
+
"types": "./dist/operation-data-change-result.d.ts",
|
|
56
|
+
"import": "./dist/operation-data-change-result.js"
|
|
57
|
+
},
|
|
58
|
+
"./framework-admission": {
|
|
59
|
+
"source": "./src/framework-admission.ts",
|
|
60
|
+
"types": "./dist/framework-admission.d.ts",
|
|
61
|
+
"import": "./dist/framework-admission.js"
|
|
62
|
+
},
|
|
63
|
+
"./framework-build-script": {
|
|
64
|
+
"source": "./src/framework-build-script.ts",
|
|
65
|
+
"types": "./dist/framework-build-script.d.ts",
|
|
66
|
+
"import": "./dist/framework-build-script.js"
|
|
67
|
+
},
|
|
68
|
+
"./browser-capabilities": {
|
|
69
|
+
"source": "./src/browser-capabilities.ts",
|
|
70
|
+
"types": "./dist/browser-capabilities.d.ts",
|
|
71
|
+
"import": "./dist/browser-capabilities.js"
|
|
72
|
+
},
|
|
73
|
+
"./openapi.yaml": "./openapi.yaml",
|
|
74
|
+
"./ohmyhost.schema.json": "./schemas/ohmyhost.schema.json",
|
|
75
|
+
"./feedback": {
|
|
76
|
+
"source": "./src/feedback.ts",
|
|
77
|
+
"types": "./dist/feedback.d.ts",
|
|
78
|
+
"import": "./dist/feedback.js"
|
|
79
|
+
},
|
|
80
|
+
"./user-api-keys": {
|
|
81
|
+
"source": "./src/user-api-keys.ts",
|
|
82
|
+
"types": "./dist/user-api-keys.d.ts",
|
|
83
|
+
"import": "./dist/user-api-keys.js"
|
|
84
|
+
},
|
|
85
|
+
"./project-exports": {
|
|
86
|
+
"source": "./src/project-exports.ts",
|
|
87
|
+
"types": "./dist/project-exports.d.ts",
|
|
88
|
+
"import": "./dist/project-exports.js"
|
|
89
|
+
},
|
|
90
|
+
"./project-context": {
|
|
91
|
+
"source": "./src/project-context.ts",
|
|
92
|
+
"types": "./dist/project-context.d.ts",
|
|
93
|
+
"import": "./dist/project-context.js"
|
|
94
|
+
},
|
|
95
|
+
"./database-compute": {
|
|
96
|
+
"source": "./src/database-compute.ts",
|
|
97
|
+
"types": "./dist/database-compute.d.ts",
|
|
98
|
+
"import": "./dist/database-compute.js"
|
|
99
|
+
},
|
|
100
|
+
"./database-access": {
|
|
101
|
+
"source": "./src/database-access.ts",
|
|
102
|
+
"types": "./dist/database-access.d.ts",
|
|
103
|
+
"import": "./dist/database-access.js"
|
|
104
|
+
},
|
|
105
|
+
"./functions": {
|
|
106
|
+
"source": "./src/functions.ts",
|
|
107
|
+
"types": "./dist/functions.d.ts",
|
|
108
|
+
"import": "./dist/functions.js"
|
|
109
|
+
},
|
|
110
|
+
"./problem-policy": {
|
|
111
|
+
"source": "./src/problem-policy.ts",
|
|
112
|
+
"types": "./dist/problem-policy.d.ts",
|
|
113
|
+
"import": "./dist/problem-policy.js"
|
|
114
|
+
},
|
|
115
|
+
"./secret-names": {
|
|
116
|
+
"source": "./src/secret-names.ts",
|
|
117
|
+
"types": "./dist/secret-names.d.ts",
|
|
118
|
+
"import": "./dist/secret-names.js"
|
|
119
|
+
},
|
|
120
|
+
"./project-data-changes": {
|
|
121
|
+
"source": "./src/project-data-changes.ts",
|
|
122
|
+
"types": "./dist/project-data-changes.d.ts",
|
|
123
|
+
"import": "./dist/project-data-changes.js"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"scripts": {
|
|
127
|
+
"build": "tsc --project tsconfig.build.json",
|
|
128
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
129
|
+
"test": "vitest run --root ../.. packages/contracts/test",
|
|
130
|
+
"validate": "pnpm run typecheck && pnpm run test"
|
|
131
|
+
},
|
|
132
|
+
"dependencies": {
|
|
133
|
+
"ajv": "8.18.0",
|
|
134
|
+
"yaml": "2.8.3"
|
|
135
|
+
},
|
|
136
|
+
"license": "Apache-2.0"
|
|
137
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const RESOURCE_GROUPS = [
|
|
2
|
+
"connect",
|
|
3
|
+
"scripts",
|
|
4
|
+
"styles",
|
|
5
|
+
"images",
|
|
6
|
+
"fonts",
|
|
7
|
+
"frames",
|
|
8
|
+
"frameAncestors",
|
|
9
|
+
] as const;
|
|
10
|
+
type BrowserResourceGroup = (typeof RESOURCE_GROUPS)[number];
|
|
11
|
+
|
|
12
|
+
export type BrowserPolicy = Readonly<
|
|
13
|
+
Record<BrowserResourceGroup, readonly string[]> & {
|
|
14
|
+
workers: Readonly<{ self: boolean; blob: boolean }>;
|
|
15
|
+
cors: Readonly<{ origins: readonly string[]; credentials: boolean }>;
|
|
16
|
+
}
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
const POLICY_KEYS = [...RESOURCE_GROUPS, "workers", "cors"];
|
|
20
|
+
const MAX_ORIGINS = 16;
|
|
21
|
+
|
|
22
|
+
/** Source declarations may omit capabilities; each omission retains the restrictive default. */
|
|
23
|
+
export function normalizeBrowserPolicy(value: unknown): BrowserPolicy {
|
|
24
|
+
const input = value === undefined ? {} : policyRecord(value, POLICY_KEYS);
|
|
25
|
+
const workers =
|
|
26
|
+
input["workers"] === undefined ? {} : policyRecord(input["workers"], ["self", "blob"]);
|
|
27
|
+
const cors =
|
|
28
|
+
input["cors"] === undefined ? {} : policyRecord(input["cors"], ["origins", "credentials"]);
|
|
29
|
+
const resources = Object.fromEntries(
|
|
30
|
+
RESOURCE_GROUPS.map((group) => [group, normalizeOrigins(input[group], group === "connect")]),
|
|
31
|
+
) as Record<BrowserResourceGroup, readonly string[]>;
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
...resources,
|
|
34
|
+
workers: Object.freeze({
|
|
35
|
+
self: normalizeBoolean(workers["self"]),
|
|
36
|
+
blob: normalizeBoolean(workers["blob"]),
|
|
37
|
+
}),
|
|
38
|
+
cors: Object.freeze({
|
|
39
|
+
origins: normalizeOrigins(cors["origins"], false),
|
|
40
|
+
credentials: normalizeBoolean(cors["credentials"]),
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Private routing and artifact custody accept the complete normalized policy only. */
|
|
46
|
+
export function parseBrowserPolicy(value: unknown): BrowserPolicy {
|
|
47
|
+
const input = policyRecord(value, POLICY_KEYS, true);
|
|
48
|
+
const workers = policyRecord(input["workers"], ["self", "blob"], true);
|
|
49
|
+
const cors = policyRecord(input["cors"], ["origins", "credentials"], true);
|
|
50
|
+
if (
|
|
51
|
+
typeof workers["self"] !== "boolean" ||
|
|
52
|
+
typeof workers["blob"] !== "boolean" ||
|
|
53
|
+
typeof cors["credentials"] !== "boolean"
|
|
54
|
+
)
|
|
55
|
+
throw invalidPolicy();
|
|
56
|
+
const normalized = normalizeBrowserPolicy(input);
|
|
57
|
+
for (const group of RESOURCE_GROUPS) {
|
|
58
|
+
if (!sameOrigins(input[group], normalized[group])) throw invalidPolicy();
|
|
59
|
+
}
|
|
60
|
+
if (!sameOrigins(cors["origins"], normalized.cors.origins)) throw invalidPolicy();
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function policyRecord(
|
|
65
|
+
value: unknown,
|
|
66
|
+
keys: readonly string[],
|
|
67
|
+
complete = false,
|
|
68
|
+
): Record<string, unknown> {
|
|
69
|
+
if (
|
|
70
|
+
typeof value !== "object" ||
|
|
71
|
+
value === null ||
|
|
72
|
+
Array.isArray(value) ||
|
|
73
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value)) ||
|
|
74
|
+
Object.keys(value).some((key) => !keys.includes(key)) ||
|
|
75
|
+
(complete && Object.keys(value).length !== keys.length)
|
|
76
|
+
)
|
|
77
|
+
throw invalidPolicy();
|
|
78
|
+
return value as Record<string, unknown>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeBoolean(value: unknown): boolean {
|
|
82
|
+
if (value === undefined) return false;
|
|
83
|
+
if (typeof value !== "boolean") throw invalidPolicy();
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeOrigins(value: unknown, allowWebSocket: boolean): readonly string[] {
|
|
88
|
+
if (value === undefined) return Object.freeze([]);
|
|
89
|
+
if (!Array.isArray(value) || value.length > MAX_ORIGINS) throw invalidPolicy();
|
|
90
|
+
const origins: string[] = [];
|
|
91
|
+
for (const origin of value) {
|
|
92
|
+
if (!browserOrigin(origin, allowWebSocket) || origins.includes(origin)) throw invalidPolicy();
|
|
93
|
+
origins.push(origin);
|
|
94
|
+
}
|
|
95
|
+
return Object.freeze(origins.sort());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function browserOrigin(value: unknown, allowWebSocket: boolean): value is string {
|
|
99
|
+
if (typeof value !== "string" || value.length > 256) return false;
|
|
100
|
+
try {
|
|
101
|
+
const url = new URL(value);
|
|
102
|
+
return (
|
|
103
|
+
(url.protocol === "https:" || (allowWebSocket && url.protocol === "wss:")) &&
|
|
104
|
+
url.origin === value &&
|
|
105
|
+
url.username === "" &&
|
|
106
|
+
url.password === "" &&
|
|
107
|
+
url.pathname === "/" &&
|
|
108
|
+
url.search === "" &&
|
|
109
|
+
url.hash === "" &&
|
|
110
|
+
url.hostname.includes(".") &&
|
|
111
|
+
!url.hostname.endsWith(".local") &&
|
|
112
|
+
!url.hostname.endsWith(".internal") &&
|
|
113
|
+
!/^\d+(?:\.\d+){3}$/u.test(url.hostname) &&
|
|
114
|
+
url.hostname
|
|
115
|
+
.split(".")
|
|
116
|
+
.every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(label))
|
|
117
|
+
);
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sameOrigins(value: unknown, expected: readonly string[]): boolean {
|
|
124
|
+
return (
|
|
125
|
+
Array.isArray(value) &&
|
|
126
|
+
value.length === expected.length &&
|
|
127
|
+
value.every((origin, index) => origin === expected[index])
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function invalidPolicy(): TypeError {
|
|
132
|
+
return new TypeError("Invalid browser capability policy");
|
|
133
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface ManagedCustomerAuthCapabilities {
|
|
2
|
+
readonly auth?: Readonly<{ provider: string }>;
|
|
3
|
+
readonly database?: Readonly<{ enabled: boolean }>;
|
|
4
|
+
readonly mail?: Readonly<{ enabled: boolean }>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function managedCustomerAuthMailMessage(path: string): string {
|
|
8
|
+
return `"${path}" selects the managed Better Auth bridge, which sends verification and password-reset email through ohmyhost Mail. Set mail.enabled: true and set up a verified mail sender, or set auth.provider: none and keep the application-owned Better Auth configuration and sender. Commit and push the change, then plan the new commit.`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function managedCustomerAuthDatabaseMessage(path: string): string {
|
|
12
|
+
return `"${path}" selects the managed Better Auth bridge, which requires the managed PostgreSQL database. Set database.enabled: true with an admitted migrations path, or set auth.provider: none and keep the application-owned authentication configuration. Commit and push the change, then plan the new commit.`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Dependency inventory never selects the managed bridge; only the explicit auth capability does. */
|
|
16
|
+
export function managedCustomerAuthAdmissionIssue(
|
|
17
|
+
capabilities: ManagedCustomerAuthCapabilities | null | undefined,
|
|
18
|
+
): Readonly<{
|
|
19
|
+
code: "managed_auth_database_required" | "managed_auth_mail_required";
|
|
20
|
+
message: string;
|
|
21
|
+
}> | null {
|
|
22
|
+
if (capabilities?.auth?.provider !== "better-auth") {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (capabilities.database?.enabled !== true) {
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
code: "managed_auth_database_required" as const,
|
|
28
|
+
message: managedCustomerAuthDatabaseMessage("ohmyhost.yaml"),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (capabilities.mail?.enabled === true) return null;
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
code: "managed_auth_mail_required" as const,
|
|
34
|
+
message: managedCustomerAuthMailMessage("ohmyhost.yaml"),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -105,7 +105,13 @@ export interface ProjectDatabaseAccess {
|
|
|
105
105
|
readonly issued_at: string;
|
|
106
106
|
readonly expires_at: string;
|
|
107
107
|
readonly revoked_at: string | null;
|
|
108
|
-
readonly revocation_reason:
|
|
108
|
+
readonly revocation_reason:
|
|
109
|
+
| "principal"
|
|
110
|
+
| "expired"
|
|
111
|
+
| "budget"
|
|
112
|
+
| "creation_failed"
|
|
113
|
+
| "data_change"
|
|
114
|
+
| null;
|
|
109
115
|
}
|
|
110
116
|
export interface ProjectDatabaseAccessCredential extends ProjectDatabaseAccess {
|
|
111
117
|
readonly connection_uri: string;
|
|
@@ -219,7 +225,9 @@ function assertAccessFields(value: Record<string, unknown>): void {
|
|
|
219
225
|
(revoked !== null && !timestamp(revoked)) ||
|
|
220
226
|
(revoked === null) !== (reason === null) ||
|
|
221
227
|
(reason !== null &&
|
|
222
|
-
!["principal", "expired", "budget", "creation_failed"].includes(
|
|
228
|
+
!["principal", "expired", "budget", "creation_failed", "data_change"].includes(
|
|
229
|
+
String(reason),
|
|
230
|
+
)) ||
|
|
223
231
|
(value["state"] === "revoked") !== (revoked !== null)
|
|
224
232
|
)
|
|
225
233
|
throw new TypeError("Invalid database access");
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { PINNED_BUN_VERSION } from "./framework-build-script.mjs";
|
|
2
|
+
import {
|
|
3
|
+
managedCustomerAuthMailMessage,
|
|
4
|
+
managedCustomerAuthDatabaseMessage,
|
|
5
|
+
} from "./customer-auth-admission.js";
|
|
6
|
+
export {
|
|
7
|
+
managedCustomerAuthAdmissionIssue,
|
|
8
|
+
managedCustomerAuthMailMessage,
|
|
9
|
+
} from "./customer-auth-admission.js";
|
|
10
|
+
|
|
1
11
|
export type PackageManagerName = "bun" | "npm" | "pnpm" | "yarn";
|
|
2
12
|
|
|
3
13
|
export type FrameworkAdmissionReason =
|
|
@@ -6,7 +16,8 @@ export type FrameworkAdmissionReason =
|
|
|
6
16
|
| "invalid_package_manager"
|
|
7
17
|
| "missing_lockfile"
|
|
8
18
|
| "missing_package_manager"
|
|
9
|
-
| "package_manager_lockfile_mismatch"
|
|
19
|
+
| "package_manager_lockfile_mismatch"
|
|
20
|
+
| "package_manager_version_unsupported";
|
|
10
21
|
|
|
11
22
|
export class FrameworkAdmissionError extends Error {
|
|
12
23
|
public readonly code = "framework_admission_failed";
|
|
@@ -88,6 +99,9 @@ export function resolvePackageManager(input: PackageManagerInput): PackageManage
|
|
|
88
99
|
if (!contract.lockfiles.includes(lockfile)) {
|
|
89
100
|
throw new FrameworkAdmissionError("package_manager_lockfile_mismatch");
|
|
90
101
|
}
|
|
102
|
+
if (manager === "bun" && version !== PINNED_BUN_VERSION) {
|
|
103
|
+
throw new FrameworkAdmissionError("package_manager_version_unsupported");
|
|
104
|
+
}
|
|
91
105
|
return Object.freeze({
|
|
92
106
|
manager,
|
|
93
107
|
version,
|
|
@@ -187,17 +201,25 @@ function versionAdmission(
|
|
|
187
201
|
}
|
|
188
202
|
|
|
189
203
|
export type FrameworkConversionDiagnosticCode =
|
|
204
|
+
| "managed_auth_database_required"
|
|
205
|
+
| "managed_auth_mail_required"
|
|
190
206
|
| "database_binding_private"
|
|
191
207
|
| "database_driver_unsupported"
|
|
192
208
|
| "source_syntax_invalid"
|
|
193
209
|
| "worker_module_default_export_required"
|
|
194
|
-
| "scheduled_handler_required"
|
|
210
|
+
| "scheduled_handler_required"
|
|
211
|
+
| "framework_base_path_unsupported"
|
|
212
|
+
| "tanstack_prerender_required"
|
|
213
|
+
| "tanstack_vite_plugins_required"
|
|
214
|
+
| "next_cache_components_unsupported";
|
|
195
215
|
|
|
196
216
|
export interface FrameworkConversionDiagnostic {
|
|
197
217
|
readonly code: FrameworkConversionDiagnosticCode;
|
|
198
218
|
readonly path: string;
|
|
199
219
|
}
|
|
200
220
|
|
|
221
|
+
const PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
|
|
222
|
+
|
|
201
223
|
// Detail texts are the customer-facing sentence for one diagnostic; parseFrameworkConversionDetail
|
|
202
224
|
// recognizes exactly these sentences so a client can map a detail back to its typed diagnostic.
|
|
203
225
|
const FRAMEWORK_CONVERSION_DETAILS: Readonly<
|
|
@@ -206,38 +228,89 @@ const FRAMEWORK_CONVERSION_DETAILS: Readonly<
|
|
|
206
228
|
Readonly<{ render(path: string): string; pattern: RegExp }>
|
|
207
229
|
>
|
|
208
230
|
> = Object.freeze({
|
|
231
|
+
managed_auth_database_required: Object.freeze({
|
|
232
|
+
render: managedCustomerAuthDatabaseMessage,
|
|
233
|
+
pattern:
|
|
234
|
+
/^"([^"]+)" selects the managed Better Auth bridge, which requires the managed PostgreSQL database\. Set database\.enabled: true with an admitted migrations path, or set auth\.provider: none and keep the application-owned authentication configuration\. Commit and push the change, then plan the new commit\.$/u,
|
|
235
|
+
}),
|
|
236
|
+
managed_auth_mail_required: Object.freeze({
|
|
237
|
+
render: managedCustomerAuthMailMessage,
|
|
238
|
+
pattern:
|
|
239
|
+
/^"([^"]+)" selects the managed Better Auth bridge, which sends verification and password-reset email through ohmyhost Mail\. Set mail\.enabled: true and set up a verified mail sender, or set auth\.provider: none and keep the application-owned Better Auth configuration and sender\. Commit and push the change, then plan the new commit\.$/u,
|
|
240
|
+
}),
|
|
209
241
|
database_binding_private: Object.freeze({
|
|
210
242
|
render: (path: string) =>
|
|
211
|
-
`"${path}" reads a
|
|
243
|
+
`"${path}" reads a database connection string (DATABASE_URL, HYPERDRIVE, connectionString or a postgres:// URL); none reaches a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`,
|
|
212
244
|
pattern:
|
|
213
|
-
/^"([^"]+)" reads a
|
|
245
|
+
/^"([^"]+)" reads a database connection string \(DATABASE_URL, HYPERDRIVE, connectionString or a postgres:\/\/ URL\); none reaches a customer Worker\. Call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit and push the change, then plan the new commit\.$/u,
|
|
214
246
|
}),
|
|
215
247
|
database_driver_unsupported: Object.freeze({
|
|
216
248
|
render: (path: string) =>
|
|
217
|
-
`"${path}" imports a
|
|
249
|
+
`"${path}" imports a database socket driver (pg, postgres, pg-native or mysql2); customer Workers cannot open sockets. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". ${PUSH_AND_PLAN}`,
|
|
218
250
|
pattern:
|
|
219
|
-
/^"([^"]+)" imports a
|
|
251
|
+
/^"([^"]+)" imports a database socket driver \(pg, postgres, pg-native or mysql2\); customer Workers cannot open sockets\. Call the database with createPrivateDatabaseClient from "@ohmyhost\/customer-runtime\/database"\. Commit and push the change, then plan the new commit\.$/u,
|
|
220
252
|
}),
|
|
221
253
|
source_syntax_invalid: Object.freeze({
|
|
222
254
|
render: (path: string) =>
|
|
223
|
-
`Invalid JavaScript or TypeScript in "${path}". Fix the file, commit the change, then plan
|
|
255
|
+
`Invalid JavaScript or TypeScript in "${path}". Fix the file, commit and push the change, then plan the new commit.`,
|
|
224
256
|
pattern:
|
|
225
|
-
/^Invalid JavaScript or TypeScript in "([^"]+)"\. Fix the file, commit the change, then plan
|
|
257
|
+
/^Invalid JavaScript or TypeScript in "([^"]+)"\. Fix the file, commit and push the change, then plan the new commit\.$/u,
|
|
226
258
|
}),
|
|
227
259
|
worker_module_default_export_required: Object.freeze({
|
|
228
260
|
render: (path: string) =>
|
|
229
|
-
`Worker module "${path}" has no default export. Export default { fetch, scheduled } with the handlers ohmyho.st invokes; named exports are never called.
|
|
261
|
+
`Worker module "${path}" has no default export. Export default { fetch, scheduled } with the handlers ohmyho.st invokes; named exports are never called. ${PUSH_AND_PLAN}`,
|
|
230
262
|
pattern:
|
|
231
|
-
/^Worker module "([^"]+)" has no default export\. Export default \{ fetch, scheduled \} with the handlers ohmyho\.st invokes; named exports are never called\. Commit the change, then plan
|
|
263
|
+
/^Worker module "([^"]+)" has no default export\. Export default \{ fetch, scheduled \} with the handlers ohmyho\.st invokes; named exports are never called\. Commit and push the change, then plan the new commit\.$/u,
|
|
232
264
|
}),
|
|
233
265
|
scheduled_handler_required: Object.freeze({
|
|
234
266
|
render: (path: string) =>
|
|
235
|
-
`functions.crons is declared but "${path}" exports no scheduled handler. Add scheduled(controller, env, ctx) to its default export, commit the change, then plan
|
|
267
|
+
`functions.crons is declared but "${path}" exports no scheduled handler. Add scheduled(controller, env, ctx) to its default export, commit and push the change, then plan the new commit.`,
|
|
268
|
+
pattern:
|
|
269
|
+
/^functions\.crons is declared but "([^"]+)" exports no scheduled handler\. Add scheduled\(controller, env, ctx\) to its default export, commit and push the change, then plan the new commit\.$/u,
|
|
270
|
+
}),
|
|
271
|
+
framework_base_path_unsupported: Object.freeze({
|
|
272
|
+
render: (path: string) =>
|
|
273
|
+
`"${path}" sets a Vite base. ohmyho.st serves the app at the root origin: remove base or set base: "/". ${PUSH_AND_PLAN}`,
|
|
236
274
|
pattern:
|
|
237
|
-
/^
|
|
275
|
+
/^"([^"]+)" sets a Vite base\. ohmyho\.st serves the app at the root origin: remove base or set base: "\/"\. Commit and push the change, then plan the new commit\.$/u,
|
|
276
|
+
}),
|
|
277
|
+
tanstack_prerender_required: Object.freeze({
|
|
278
|
+
render: (path: string) =>
|
|
279
|
+
`"${path}" does not call tanstackStart({ prerender: { enabled: true } }), which the static TanStack Start runtime needs; enable prerender, or set runtime.mode: edge with the Cloudflare Vite plugin. ${PUSH_AND_PLAN}`,
|
|
280
|
+
pattern:
|
|
281
|
+
/^"([^"]+)" does not call tanstackStart\(\{ prerender: \{ enabled: true \} \}\), which the static TanStack Start runtime needs; enable prerender, or set runtime\.mode: edge with the Cloudflare Vite plugin\. Commit and push the change, then plan the new commit\.$/u,
|
|
282
|
+
}),
|
|
283
|
+
tanstack_vite_plugins_required: Object.freeze({
|
|
284
|
+
render: (path: string) =>
|
|
285
|
+
`"${path}" does not follow the TanStack Start plugin contract: import tanstackStart from "@tanstack/react-start/plugin/vite"; on runtime edge also import cloudflare from "@cloudflare/vite-plugin" and call cloudflare({ viteEnvironment: { name: "ssr" } }) before tanstackStart(); on runtime static do not import the Cloudflare plugin. ${PUSH_AND_PLAN}`,
|
|
286
|
+
pattern:
|
|
287
|
+
/^"([^"]+)" does not follow the TanStack Start plugin contract: import tanstackStart from "@tanstack\/react-start\/plugin\/vite"; on runtime edge also import cloudflare from "@cloudflare\/vite-plugin" and call cloudflare\(\{ viteEnvironment: \{ name: "ssr" \} \}\) before tanstackStart\(\); on runtime static do not import the Cloudflare plugin\. Commit and push the change, then plan the new commit\.$/u,
|
|
288
|
+
}),
|
|
289
|
+
next_cache_components_unsupported: Object.freeze({
|
|
290
|
+
render: (path: string) =>
|
|
291
|
+
`"${path}" sets cacheComponents: true, which ohmyho.st does not support; remove it or set it to false. ${PUSH_AND_PLAN}`,
|
|
292
|
+
pattern:
|
|
293
|
+
/^"([^"]+)" sets cacheComponents: true, which ohmyho\.st does not support; remove it or set it to false\. Commit and push the change, then plan the new commit\.$/u,
|
|
238
294
|
}),
|
|
239
295
|
});
|
|
240
296
|
|
|
297
|
+
/**
|
|
298
|
+
* An adapter rule that `init` does not check. It stays a TypeError, so every existing caller that
|
|
299
|
+
* treats an adapter refusal as a TypeError keeps working, and carries the typed diagnostic whose
|
|
300
|
+
* detail names the file and its fix.
|
|
301
|
+
*/
|
|
302
|
+
export class FrameworkConversionDiagnosticError extends TypeError {
|
|
303
|
+
public readonly diagnostic: FrameworkConversionDiagnostic;
|
|
304
|
+
|
|
305
|
+
public constructor(diagnostic: unknown) {
|
|
306
|
+
const parsed = parseFrameworkConversionDiagnostic(diagnostic);
|
|
307
|
+
if (parsed === null) throw new TypeError("Framework conversion diagnostic is invalid");
|
|
308
|
+
super(FRAMEWORK_CONVERSION_DETAILS[parsed.code].render(parsed.path));
|
|
309
|
+
this.name = "FrameworkConversionDiagnosticError";
|
|
310
|
+
this.diagnostic = parsed;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
241
314
|
function isFrameworkConversionDiagnosticCode(
|
|
242
315
|
value: unknown,
|
|
243
316
|
): value is FrameworkConversionDiagnosticCode {
|
|
@@ -256,7 +329,9 @@ export function parseFrameworkConversionDiagnostic(
|
|
|
256
329
|
!isFrameworkConversionDiagnosticCode(code) ||
|
|
257
330
|
typeof path !== "string" ||
|
|
258
331
|
path.length > 240 ||
|
|
259
|
-
|
|
332
|
+
(code === "managed_auth_mail_required" || code === "managed_auth_database_required"
|
|
333
|
+
? path !== "ohmyhost.yaml"
|
|
334
|
+
: !/^[A-Za-z0-9_@()[\]./-]+\.(?:[cm]?[jt]sx?)$/u.test(path)) ||
|
|
260
335
|
path.split("/").some((segment) => segment === "" || segment === "." || segment === "..")
|
|
261
336
|
)
|
|
262
337
|
return null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const PINNED_BUN_VERSION = "1.2.22";
|
|
2
|
+
|
|
3
|
+
const TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The same source is consumed by local admission, framework adapters and the build sandbox.
|
|
7
|
+
* @param {"nextjs" | "tanstack-start" | "vite"} framework
|
|
8
|
+
* @param {unknown} script
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function isAdmittedFrameworkBuildScript(framework, script) {
|
|
12
|
+
if (typeof script !== "string") return false;
|
|
13
|
+
if (framework === "nextjs") return script === "next build" || script === "next build --webpack";
|
|
14
|
+
if (framework !== "vite" && framework !== "tanstack-start") return false;
|
|
15
|
+
const stages = script.trim().split(/[ \t]*&&[ \t]*/u);
|
|
16
|
+
if (stages.length === 1) return stages[0] === "vite build";
|
|
17
|
+
if (stages.length !== 2) return false;
|
|
18
|
+
return (
|
|
19
|
+
(stages[0] === "vite build" && TYPESCRIPT_BUILD_STAGE.test(stages[1] ?? "")) ||
|
|
20
|
+
(TYPESCRIPT_BUILD_STAGE.test(stages[0] ?? "") && stages[1] === "vite build")
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { isAdmittedFrameworkBuildScript, PINNED_BUN_VERSION } from "./framework-build-script.mjs";
|
|
@@ -4,12 +4,18 @@ import { parse as parseYaml } from "yaml";
|
|
|
4
4
|
import schema from "../schemas/ohmyhost.schema.json" with { type: "json" };
|
|
5
5
|
import { normalizeApplicationRoot, type ApplicationRoot } from "./application-root.js";
|
|
6
6
|
import { normalizeCrons } from "./functions.js";
|
|
7
|
+
import { normalizeBrowserPolicy, type BrowserPolicy } from "./browser-capabilities.js";
|
|
7
8
|
|
|
8
9
|
export * from "./application-root.js";
|
|
9
10
|
export * from "./framework-admission.js";
|
|
10
11
|
export * from "./operation-deployment-result.js";
|
|
12
|
+
export * from "./operation-data-change-result.js";
|
|
11
13
|
export * from "./operation-failure.js";
|
|
12
14
|
export * from "./functions.js";
|
|
15
|
+
export * from "./browser-capabilities.js";
|
|
16
|
+
export * from "./problem-policy.js";
|
|
17
|
+
export * from "./secret-names.js";
|
|
18
|
+
export * from "./project-data-changes.js";
|
|
13
19
|
|
|
14
20
|
export type OhmyhostRuntimeMode = "static" | "edge" | "functions" | "auto";
|
|
15
21
|
|
|
@@ -30,6 +36,7 @@ export interface OhmyhostConfig {
|
|
|
30
36
|
mode: OhmyhostRuntimeMode;
|
|
31
37
|
healthcheck: string;
|
|
32
38
|
egress: Readonly<{ allow: readonly string[] }>;
|
|
39
|
+
browser?: BrowserPolicy;
|
|
33
40
|
}>;
|
|
34
41
|
readonly database?: Readonly<{
|
|
35
42
|
enabled: boolean;
|
|
@@ -88,6 +95,9 @@ export function parseOhmyhostConfig(value: unknown): OhmyhostConfig {
|
|
|
88
95
|
egress: Object.freeze({
|
|
89
96
|
allow: normalizeEgressOrigins(input.runtime.egress?.allow),
|
|
90
97
|
}),
|
|
98
|
+
...(input.runtime.browser === undefined
|
|
99
|
+
? {}
|
|
100
|
+
: { browser: normalizeBrowserPolicy(input.runtime.browser) }),
|
|
91
101
|
}),
|
|
92
102
|
...(input.database === undefined
|
|
93
103
|
? {}
|
|
@@ -173,6 +183,7 @@ function normalizeEgressOrigins(value: readonly string[] | undefined): readonly
|
|
|
173
183
|
}
|
|
174
184
|
if (
|
|
175
185
|
parsed.protocol !== "https:" ||
|
|
186
|
+
parsed.port !== "" ||
|
|
176
187
|
parsed.username !== "" ||
|
|
177
188
|
parsed.password !== "" ||
|
|
178
189
|
parsed.origin !== origin ||
|
|
@@ -188,28 +199,18 @@ function normalizeEgressOrigins(value: readonly string[] | undefined): readonly
|
|
|
188
199
|
return Object.freeze(origins);
|
|
189
200
|
}
|
|
190
201
|
|
|
202
|
+
// Egress is routed and staged by DNS name on the default HTTPS port: an IP literal (public or
|
|
203
|
+
// private, v4 or v6) can never become a working origin, and a local name never resolves.
|
|
191
204
|
function forbiddenEgressHostname(hostname: string): boolean {
|
|
192
205
|
const normalized = hostname.toLowerCase();
|
|
193
|
-
|
|
206
|
+
return (
|
|
194
207
|
normalized === "localhost" ||
|
|
195
208
|
normalized.endsWith(".localhost") ||
|
|
196
209
|
normalized.endsWith(".local") ||
|
|
197
210
|
normalized.endsWith(".internal") ||
|
|
198
|
-
normalized
|
|
199
|
-
normalized
|
|
200
|
-
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/u.exec(normalized);
|
|
204
|
-
if (ipv4 === null) return normalized.startsWith("[") || normalized.endsWith("]");
|
|
205
|
-
const octets = ipv4.slice(1).map(Number);
|
|
206
|
-
if (octets.some((octet) => octet > 255)) return true;
|
|
207
|
-
return (
|
|
208
|
-
octets[0] === 10 ||
|
|
209
|
-
octets[0] === 127 ||
|
|
210
|
-
(octets[0] === 169 && octets[1] === 254) ||
|
|
211
|
-
(octets[0] === 172 && (octets[1] ?? 0) >= 16 && (octets[1] ?? 0) <= 31) ||
|
|
212
|
-
(octets[0] === 192 && octets[1] === 168)
|
|
211
|
+
/^\d{1,3}(?:\.\d{1,3}){3}$/u.test(normalized) ||
|
|
212
|
+
normalized.startsWith("[") ||
|
|
213
|
+
normalized.endsWith("]")
|
|
213
214
|
);
|
|
214
215
|
}
|
|
215
216
|
|