@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
|
@@ -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,28 @@
|
|
|
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 managedCustomerAuthDatabaseMessage(path: string): string {
|
|
8
|
+
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.`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Managed auth needs its real database; Mail remains an independent optional capability. */
|
|
12
|
+
export function managedCustomerAuthAdmissionIssue(
|
|
13
|
+
capabilities: ManagedCustomerAuthCapabilities | null | undefined,
|
|
14
|
+
): Readonly<{
|
|
15
|
+
code: "managed_auth_database_required";
|
|
16
|
+
message: string;
|
|
17
|
+
}> | null {
|
|
18
|
+
if (capabilities?.auth?.provider !== "better-auth") {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (capabilities.database?.enabled !== true) {
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
code: "managed_auth_database_required" as const,
|
|
24
|
+
message: managedCustomerAuthDatabaseMessage("ohmyhost.yaml"),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
@@ -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,7 @@
|
|
|
1
|
+
import { PINNED_BUN_VERSION } from "./framework-build-script.mjs";
|
|
2
|
+
import { managedCustomerAuthDatabaseMessage } from "./customer-auth-admission.js";
|
|
3
|
+
export { managedCustomerAuthAdmissionIssue } from "./customer-auth-admission.js";
|
|
4
|
+
|
|
1
5
|
export type PackageManagerName = "bun" | "npm" | "pnpm" | "yarn";
|
|
2
6
|
|
|
3
7
|
export type FrameworkAdmissionReason =
|
|
@@ -6,7 +10,8 @@ export type FrameworkAdmissionReason =
|
|
|
6
10
|
| "invalid_package_manager"
|
|
7
11
|
| "missing_lockfile"
|
|
8
12
|
| "missing_package_manager"
|
|
9
|
-
| "package_manager_lockfile_mismatch"
|
|
13
|
+
| "package_manager_lockfile_mismatch"
|
|
14
|
+
| "package_manager_version_unsupported";
|
|
10
15
|
|
|
11
16
|
export class FrameworkAdmissionError extends Error {
|
|
12
17
|
public readonly code = "framework_admission_failed";
|
|
@@ -88,6 +93,9 @@ export function resolvePackageManager(input: PackageManagerInput): PackageManage
|
|
|
88
93
|
if (!contract.lockfiles.includes(lockfile)) {
|
|
89
94
|
throw new FrameworkAdmissionError("package_manager_lockfile_mismatch");
|
|
90
95
|
}
|
|
96
|
+
if (manager === "bun" && version !== PINNED_BUN_VERSION) {
|
|
97
|
+
throw new FrameworkAdmissionError("package_manager_version_unsupported");
|
|
98
|
+
}
|
|
91
99
|
return Object.freeze({
|
|
92
100
|
manager,
|
|
93
101
|
version,
|
|
@@ -187,17 +195,24 @@ function versionAdmission(
|
|
|
187
195
|
}
|
|
188
196
|
|
|
189
197
|
export type FrameworkConversionDiagnosticCode =
|
|
198
|
+
| "managed_auth_database_required"
|
|
190
199
|
| "database_binding_private"
|
|
191
200
|
| "database_driver_unsupported"
|
|
192
201
|
| "source_syntax_invalid"
|
|
193
202
|
| "worker_module_default_export_required"
|
|
194
|
-
| "scheduled_handler_required"
|
|
203
|
+
| "scheduled_handler_required"
|
|
204
|
+
| "framework_base_path_unsupported"
|
|
205
|
+
| "tanstack_prerender_required"
|
|
206
|
+
| "tanstack_vite_plugins_required"
|
|
207
|
+
| "next_cache_components_unsupported";
|
|
195
208
|
|
|
196
209
|
export interface FrameworkConversionDiagnostic {
|
|
197
210
|
readonly code: FrameworkConversionDiagnosticCode;
|
|
198
211
|
readonly path: string;
|
|
199
212
|
}
|
|
200
213
|
|
|
214
|
+
const PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
|
|
215
|
+
|
|
201
216
|
// Detail texts are the customer-facing sentence for one diagnostic; parseFrameworkConversionDetail
|
|
202
217
|
// recognizes exactly these sentences so a client can map a detail back to its typed diagnostic.
|
|
203
218
|
const FRAMEWORK_CONVERSION_DETAILS: Readonly<
|
|
@@ -206,38 +221,84 @@ const FRAMEWORK_CONVERSION_DETAILS: Readonly<
|
|
|
206
221
|
Readonly<{ render(path: string): string; pattern: RegExp }>
|
|
207
222
|
>
|
|
208
223
|
> = Object.freeze({
|
|
224
|
+
managed_auth_database_required: Object.freeze({
|
|
225
|
+
render: managedCustomerAuthDatabaseMessage,
|
|
226
|
+
pattern:
|
|
227
|
+
/^"([^"]+)" 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,
|
|
228
|
+
}),
|
|
209
229
|
database_binding_private: Object.freeze({
|
|
210
230
|
render: (path: string) =>
|
|
211
|
-
`"${path}" reads a
|
|
231
|
+
`"${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
232
|
pattern:
|
|
213
|
-
/^"([^"]+)" reads a
|
|
233
|
+
/^"([^"]+)" 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
234
|
}),
|
|
215
235
|
database_driver_unsupported: Object.freeze({
|
|
216
236
|
render: (path: string) =>
|
|
217
|
-
`"${path}" imports a
|
|
237
|
+
`"${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
238
|
pattern:
|
|
219
|
-
/^"([^"]+)" imports a
|
|
239
|
+
/^"([^"]+)" 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
240
|
}),
|
|
221
241
|
source_syntax_invalid: Object.freeze({
|
|
222
242
|
render: (path: string) =>
|
|
223
|
-
`Invalid JavaScript or TypeScript in "${path}". Fix the file, commit the change, then plan
|
|
243
|
+
`Invalid JavaScript or TypeScript in "${path}". Fix the file, commit and push the change, then plan the new commit.`,
|
|
224
244
|
pattern:
|
|
225
|
-
/^Invalid JavaScript or TypeScript in "([^"]+)"\. Fix the file, commit the change, then plan
|
|
245
|
+
/^Invalid JavaScript or TypeScript in "([^"]+)"\. Fix the file, commit and push the change, then plan the new commit\.$/u,
|
|
226
246
|
}),
|
|
227
247
|
worker_module_default_export_required: Object.freeze({
|
|
228
248
|
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.
|
|
249
|
+
`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
250
|
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
|
|
251
|
+
/^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
252
|
}),
|
|
233
253
|
scheduled_handler_required: Object.freeze({
|
|
234
254
|
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
|
|
255
|
+
`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.`,
|
|
256
|
+
pattern:
|
|
257
|
+
/^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,
|
|
258
|
+
}),
|
|
259
|
+
framework_base_path_unsupported: Object.freeze({
|
|
260
|
+
render: (path: string) =>
|
|
261
|
+
`"${path}" sets a Vite base. ohmyho.st serves the app at the root origin: remove base or set base: "/". ${PUSH_AND_PLAN}`,
|
|
262
|
+
pattern:
|
|
263
|
+
/^"([^"]+)" 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,
|
|
264
|
+
}),
|
|
265
|
+
tanstack_prerender_required: Object.freeze({
|
|
266
|
+
render: (path: string) =>
|
|
267
|
+
`"${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}`,
|
|
236
268
|
pattern:
|
|
237
|
-
/^
|
|
269
|
+
/^"([^"]+)" 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,
|
|
270
|
+
}),
|
|
271
|
+
tanstack_vite_plugins_required: Object.freeze({
|
|
272
|
+
render: (path: string) =>
|
|
273
|
+
`"${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}`,
|
|
274
|
+
pattern:
|
|
275
|
+
/^"([^"]+)" 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,
|
|
276
|
+
}),
|
|
277
|
+
next_cache_components_unsupported: Object.freeze({
|
|
278
|
+
render: (path: string) =>
|
|
279
|
+
`"${path}" sets cacheComponents: true, which ohmyho.st does not support; remove it or set it to false. ${PUSH_AND_PLAN}`,
|
|
280
|
+
pattern:
|
|
281
|
+
/^"([^"]+)" 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
282
|
}),
|
|
239
283
|
});
|
|
240
284
|
|
|
285
|
+
/**
|
|
286
|
+
* An adapter rule that `init` does not check. It stays a TypeError, so every existing caller that
|
|
287
|
+
* treats an adapter refusal as a TypeError keeps working, and carries the typed diagnostic whose
|
|
288
|
+
* detail names the file and its fix.
|
|
289
|
+
*/
|
|
290
|
+
export class FrameworkConversionDiagnosticError extends TypeError {
|
|
291
|
+
public readonly diagnostic: FrameworkConversionDiagnostic;
|
|
292
|
+
|
|
293
|
+
public constructor(diagnostic: unknown) {
|
|
294
|
+
const parsed = parseFrameworkConversionDiagnostic(diagnostic);
|
|
295
|
+
if (parsed === null) throw new TypeError("Framework conversion diagnostic is invalid");
|
|
296
|
+
super(FRAMEWORK_CONVERSION_DETAILS[parsed.code].render(parsed.path));
|
|
297
|
+
this.name = "FrameworkConversionDiagnosticError";
|
|
298
|
+
this.diagnostic = parsed;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
241
302
|
function isFrameworkConversionDiagnosticCode(
|
|
242
303
|
value: unknown,
|
|
243
304
|
): value is FrameworkConversionDiagnosticCode {
|
|
@@ -256,7 +317,9 @@ export function parseFrameworkConversionDiagnostic(
|
|
|
256
317
|
!isFrameworkConversionDiagnosticCode(code) ||
|
|
257
318
|
typeof path !== "string" ||
|
|
258
319
|
path.length > 240 ||
|
|
259
|
-
|
|
320
|
+
(code === "managed_auth_database_required"
|
|
321
|
+
? path !== "ohmyhost.yaml"
|
|
322
|
+
: !/^[A-Za-z0-9_@()[\]./-]+\.(?:[cm]?[jt]sx?)$/u.test(path)) ||
|
|
260
323
|
path.split("/").some((segment) => segment === "" || segment === "." || segment === "..")
|
|
261
324
|
)
|
|
262
325
|
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
|
|