everything-dev 2.0.0-rc.7 → 2.0.0-rc.9
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/dist/api/auth-middleware.cjs +14 -1
- package/dist/api/auth-middleware.cjs.map +1 -1
- package/dist/api/auth-middleware.d.cts +9 -0
- package/dist/api/auth-middleware.d.mts +9 -0
- package/dist/api/auth-middleware.mjs +14 -1
- package/dist/api/auth-middleware.mjs.map +1 -1
- package/dist/apps/upload/src/client.cjs +7 -1
- package/dist/apps/upload/src/client.cjs.map +1 -1
- package/dist/apps/upload/src/client.mjs +7 -1
- package/dist/apps/upload/src/client.mjs.map +1 -1
- package/dist/auth-login.cjs +24 -1
- package/dist/auth-login.cjs.map +1 -1
- package/dist/auth-login.mjs +24 -1
- package/dist/auth-login.mjs.map +1 -1
- package/dist/auth-session.cjs +4 -2
- package/dist/auth-session.cjs.map +1 -1
- package/dist/auth-session.mjs +4 -2
- package/dist/auth-session.mjs.map +1 -1
- package/dist/cdn-deploy.cjs +11 -7
- package/dist/cdn-deploy.cjs.map +1 -1
- package/dist/cdn-deploy.mjs +11 -7
- package/dist/cdn-deploy.mjs.map +1 -1
- package/dist/cli/import.cjs +1140 -0
- package/dist/cli/import.cjs.map +1 -0
- package/dist/cli/import.mjs +1126 -0
- package/dist/cli/import.mjs.map +1 -0
- package/dist/cli/init.cjs +33 -14
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +8 -2
- package/dist/cli/init.d.mts +8 -2
- package/dist/cli/init.mjs +33 -15
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli.cjs +59 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +59 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/import.cjs +49 -0
- package/dist/commands/import.cjs.map +1 -0
- package/dist/commands/import.mjs +46 -0
- package/dist/commands/import.mjs.map +1 -0
- package/dist/commands/keys.cjs +25 -4
- package/dist/commands/keys.cjs.map +1 -1
- package/dist/commands/keys.mjs +25 -4
- package/dist/commands/keys.mjs.map +1 -1
- package/dist/config.d.cts +1 -0
- package/dist/config.d.mts +1 -0
- package/dist/contract.cjs +89 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +190 -1
- package/dist/contract.d.mts +190 -1
- package/dist/contract.meta.cjs +17 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +27 -1
- package/dist/contract.meta.d.mts +27 -1
- package/dist/contract.meta.mjs +17 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +84 -1
- package/dist/contract.mjs.map +1 -1
- package/dist/custody-signer.cjs +70 -0
- package/dist/custody-signer.cjs.map +1 -0
- package/dist/custody-signer.mjs +68 -0
- package/dist/custody-signer.mjs.map +1 -0
- package/dist/db/core.cjs +22 -4
- package/dist/db/core.cjs.map +1 -1
- package/dist/db/core.mjs +22 -4
- package/dist/db/core.mjs.map +1 -1
- package/dist/descriptor/schema.cjs +8 -5
- package/dist/descriptor/schema.cjs.map +1 -1
- package/dist/descriptor/schema.d.cts +1 -0
- package/dist/descriptor/schema.d.mts +1 -0
- package/dist/descriptor/schema.mjs +8 -5
- package/dist/descriptor/schema.mjs.map +1 -1
- package/dist/descriptor/serialize.cjs +1 -1
- package/dist/descriptor/serialize.cjs.map +1 -1
- package/dist/descriptor/serialize.mjs +1 -1
- package/dist/descriptor/serialize.mjs.map +1 -1
- package/dist/http-client.cjs +14 -4
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.mjs +14 -4
- package/dist/http-client.mjs.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/infra/planner.cjs +6 -2
- package/dist/infra/planner.cjs.map +1 -1
- package/dist/infra/planner.mjs +6 -2
- package/dist/infra/planner.mjs.map +1 -1
- package/dist/outlayer-login.cjs +54 -0
- package/dist/outlayer-login.cjs.map +1 -0
- package/dist/outlayer-login.mjs +54 -0
- package/dist/outlayer-login.mjs.map +1 -0
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +74 -0
- package/dist/plugin.d.mts +74 -0
- package/dist/plugin.mjs +2 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/publish.cjs +145 -22
- package/dist/publish.cjs.map +1 -1
- package/dist/publish.mjs +147 -24
- package/dist/publish.mjs.map +1 -1
- package/dist/source-archive.cjs +51 -0
- package/dist/source-archive.cjs.map +1 -0
- package/dist/source-archive.mjs +50 -0
- package/dist/source-archive.mjs.map +1 -0
- package/dist/storage-upload.cjs +6 -1
- package/dist/storage-upload.cjs.map +1 -1
- package/dist/storage-upload.mjs +6 -1
- package/dist/storage-upload.mjs.map +1 -1
- package/dist/types.cjs +5 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +5 -1
- package/dist/types.mjs.map +1 -1
- package/dist/ui/auth.d.cts +217 -123
- package/dist/ui/auth.d.mts +217 -123
- package/dist/ui/router-client.cjs +2 -1
- package/dist/ui/router-client.cjs.map +1 -1
- package/dist/ui/router-client.mjs +2 -1
- package/dist/ui/router-client.mjs.map +1 -1
- package/dist/utils/workspace-subtree.cjs +37 -0
- package/dist/utils/workspace-subtree.cjs.map +1 -0
- package/dist/utils/workspace-subtree.mjs +36 -0
- package/dist/utils/workspace-subtree.mjs.map +1 -0
- package/dist/version-manifest-deploy.cjs +4 -2
- package/dist/version-manifest-deploy.cjs.map +1 -1
- package/dist/version-manifest-deploy.mjs +4 -2
- package/dist/version-manifest-deploy.mjs.map +1 -1
- package/dist/version-manifest-resolve.cjs +2 -1
- package/dist/version-manifest-resolve.cjs.map +1 -1
- package/dist/version-manifest-resolve.d.cts +3 -0
- package/dist/version-manifest-resolve.d.mts +3 -0
- package/dist/version-manifest-resolve.mjs +2 -1
- package/dist/version-manifest-resolve.mjs.map +1 -1
- package/package.json +11 -10
- package/skills/api-and-auth/SKILL.md +12 -9
- package/skills/api-and-auth/references/middleware.md +38 -0
- package/skills/cli-reference/SKILL.md +38 -6
- package/skills/init-upgrade/SKILL.md +76 -3
- package/skills/talk-to-the-app/SKILL.md +32 -5
package/dist/auth-login.mjs
CHANGED
|
@@ -92,6 +92,10 @@ function buildVerificationUrl(siteUrl, code, opts) {
|
|
|
92
92
|
params.set("contract", opts.delegate.contract);
|
|
93
93
|
params.set("network", opts.delegate.network);
|
|
94
94
|
}
|
|
95
|
+
if (opts.custody) {
|
|
96
|
+
params.set("custody", "1");
|
|
97
|
+
params.set("network", opts.custody.network);
|
|
98
|
+
}
|
|
95
99
|
return url.toString();
|
|
96
100
|
}
|
|
97
101
|
async function completeLogin(siteUrl, sessionToken, opts) {
|
|
@@ -109,7 +113,26 @@ async function completeLogin(siteUrl, sessionToken, opts) {
|
|
|
109
113
|
return {
|
|
110
114
|
sessionToken,
|
|
111
115
|
accountId: await resolveNearAccount(siteUrl, cookiePair),
|
|
112
|
-
apiKey
|
|
116
|
+
apiKey,
|
|
117
|
+
custodyWallet: opts.custody ? await fetchCustodyCredential(siteUrl, cookiePair, opts.custody.network).catch(() => null) : null
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function fetchCustodyCredential(siteUrl, cookiePair, network) {
|
|
121
|
+
const { status, json } = await authFetch(siteUrl, "/custody/credential", { network }, cookiePair);
|
|
122
|
+
if (status !== 200) {
|
|
123
|
+
const detail = json.message;
|
|
124
|
+
throw new Error(`Custody wallet credential could not be fetched${detail ? `: ${detail}` : ` (${status})`}`);
|
|
125
|
+
}
|
|
126
|
+
const data = json;
|
|
127
|
+
if (!data.apiKey?.startsWith("wk_")) throw new Error("Custody wallet credential returned no API key");
|
|
128
|
+
if (data.network !== "mainnet" && data.network !== "testnet") throw new Error(`Custody wallet credential returned unknown network "${data.network}"`);
|
|
129
|
+
return {
|
|
130
|
+
apiKey: data.apiKey,
|
|
131
|
+
walletId: data.walletId,
|
|
132
|
+
nearAccountId: data.nearAccountId,
|
|
133
|
+
network: data.network,
|
|
134
|
+
handoffUrl: data.handoffUrl ?? null,
|
|
135
|
+
registeredAt: data.registeredAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
113
136
|
};
|
|
114
137
|
}
|
|
115
138
|
async function mintApiKey(siteUrl, cookiePair, opts) {
|
package/dist/auth-login.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-login.mjs","names":[],"sources":["../src/auth-login.ts"],"sourcesContent":["import { platform } from \"node:os\";\nimport { execa } from \"execa\";\n\nconst DEVICE_CODE_GRANT_TYPE = \"urn:ietf:params:oauth:grant-type:device_code\";\nconst DEFAULT_CLIENT_ID = \"bos-cli\";\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\nconst SLOW_DOWN_PENALTY_SECONDS = 5;\n\nexport interface DeviceLoginOptions {\n siteUrl: string;\n /** label for the approving device, shown on the approval page */\n device?: string;\n /** the NEAR account the CLI expects to authenticate as (approval-page copy only) */\n account?: string;\n /** delegate mode — the approval page adds this scoped function-call key to the user's account */\n delegate?: { pubKey: string; contract: string; network: string };\n /** seconds the minted API key stays valid (login mode) */\n expiresIn?: number;\n}\n\nexport interface DeviceApproval {\n sessionToken: string;\n /** the NEAR account signed in on the approving browser (login mode) */\n accountId: string | null;\n /** the minted CLI credential (login mode) */\n apiKey: { key: string; id: string } | null;\n}\n\nexport interface DeviceLoginHandle {\n userCode: string;\n verificationUrl: string;\n waitForApproval: (timeoutMs?: number) => Promise<DeviceApproval>;\n abort: (reason?: string) => void;\n}\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri?: string;\n verification_uri_complete?: string;\n expires_in?: number;\n interval?: number;\n}\n\ninterface DeviceTokenSuccess {\n access_token: string;\n token_type: \"Bearer\";\n}\n\ninterface DeviceTokenError {\n error:\n | \"authorization_pending\"\n | \"slow_down\"\n | \"expired_token\"\n | \"access_denied\"\n | \"invalid_grant\"\n | \"invalid_request\"\n | \"invalid_client\";\n error_description?: string;\n}\n\ninterface ApiKeyCreated {\n id: string;\n key: string;\n}\n\ninterface NearAccountsResponse {\n accounts: { accountId: string }[];\n activeAccount: { accountId: string } | null;\n}\n\nasync function authFetch(\n siteUrl: string,\n path: string,\n body: unknown,\n cookiePair?: string,\n): Promise<{ status: number; json: unknown; setCookie: string | null }> {\n // Better Auth's origin check demands an Origin on cookie-bearing\n // requests — present the site's own origin, exactly as a browser would.\n const origin = new URL(siteUrl).origin;\n const headers: Record<string, string> = { \"content-type\": \"application/json\", origin };\n if (cookiePair) headers.cookie = cookiePair;\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n return {\n status: response.status,\n json: await response.json().catch(() => ({})),\n setCookie: response.headers.get(\"set-cookie\"),\n };\n}\n\n/**\n * CLI login via the OAuth 2.0 Device Flow (RFC 8628) — the same flow the\n * site's QR pairing uses. The CLI requests a device code, the user approves\n * at `/login/device` in any browser (same machine or not), and the CLI polls\n * the token endpoint until the grant completes.\n */\nexport async function startDeviceLogin(opts: DeviceLoginOptions): Promise<DeviceLoginHandle> {\n const siteUrl = opts.siteUrl.replace(/\\/$/, \"\");\n const clientId = DEFAULT_CLIENT_ID;\n\n const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ client_id: clientId }),\n });\n if (!codeResponse.ok) {\n throw new Error(\n `Device login could not start (${codeResponse.status}) — is ${siteUrl} running the platform auth plugin?`,\n );\n }\n const code = (await codeResponse.json()) as DeviceCodeResponse;\n\n const verificationUrl = buildVerificationUrl(siteUrl, code, opts);\n\n let abortReason: string | null = null;\n let rejectPending!: (reason?: Error) => void;\n const pendingReject = new Promise<never>((_, reject) => {\n rejectPending = reject;\n });\n\n const waitForApproval = (timeoutMs = 10 * 60_000): Promise<DeviceApproval> => {\n const deadline = Date.now() + timeoutMs;\n let intervalSeconds = code.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;\n\n const poll = async (): Promise<DeviceApproval> => {\n if (abortReason) throw new Error(abortReason);\n if (Date.now() > deadline) {\n throw new Error(\"Login timed out — the device code expired before approval\");\n }\n\n const { status, json } = await authFetch(siteUrl, \"/device/token\", {\n grant_type: DEVICE_CODE_GRANT_TYPE,\n device_code: code.device_code,\n client_id: clientId,\n });\n\n if (status === 200) {\n const { access_token: sessionToken } = json as DeviceTokenSuccess;\n return await completeLogin(siteUrl, sessionToken, opts);\n }\n\n const error = (json as DeviceTokenError).error;\n if (error === \"authorization_pending\") {\n // keep polling\n } else if (error === \"slow_down\") {\n intervalSeconds += SLOW_DOWN_PENALTY_SECONDS;\n } else if (error === \"expired_token\") {\n throw new Error(\"Login timed out — the device code expired before approval\");\n } else if (error === \"access_denied\") {\n throw new Error(\"Login request was denied on the approval page\");\n } else {\n throw new Error(\n (json as DeviceTokenError).error_description ?? `Login failed (${error ?? status})`,\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1000));\n return poll();\n };\n\n return Promise.race([pendingReject, poll()]);\n };\n\n return {\n userCode: code.user_code,\n verificationUrl,\n waitForApproval,\n abort: (reason) => {\n abortReason = reason ?? \"Login aborted\";\n rejectPending(new Error(abortReason));\n },\n };\n}\n\nfunction buildVerificationUrl(\n siteUrl: string,\n code: DeviceCodeResponse,\n opts: DeviceLoginOptions,\n): string {\n const base = code.verification_uri_complete ?? code.verification_uri ?? \"/login/device\";\n const url = new URL(base, siteUrl);\n const params = url.searchParams;\n params.set(\"user_code\", code.user_code);\n if (opts.account) params.set(\"account\", opts.account);\n if (opts.device) params.set(\"device\", opts.device);\n if (opts.delegate) {\n params.set(\"pubKey\", opts.delegate.pubKey);\n params.set(\"contract\", opts.delegate.contract);\n params.set(\"network\", opts.delegate.network);\n }\n return url.toString();\n}\n\nasync function completeLogin(\n siteUrl: string,\n sessionToken: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval> {\n // The token endpoint returns the raw session token; Better Auth session\n // cookies carry an HMAC signature, so exchange the token for a signed\n // session cookie via the device-link claim endpoint (the QR pairing path).\n const claim = await authFetch(siteUrl, \"/device-link/claim\", {\n token: sessionToken,\n client_id: DEFAULT_CLIENT_ID,\n });\n if (claim.status !== 200) {\n const detail = (claim.json as { message?: string }).message;\n throw new Error(\n `Approved session could not be claimed${detail ? `: ${detail}` : ` (${claim.status})`}`,\n );\n }\n const cookiePair = claim.setCookie?.split(\";\")[0];\n if (!cookiePair?.includes(\"=\")) {\n throw new Error(\"Session claim returned no session cookie\");\n }\n\n const apiKey = await mintApiKey(siteUrl, cookiePair, opts);\n const accountId = await resolveNearAccount(siteUrl, cookiePair);\n return { sessionToken, accountId, apiKey };\n}\n\nasync function mintApiKey(\n siteUrl: string,\n cookiePair: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval[\"apiKey\"]> {\n const { status, json } = await authFetch(\n siteUrl,\n \"/api-key/create\",\n {\n configId: \"user-keys\",\n name: `bos login — ${opts.device ?? \"cli\"} — ${new Date().toISOString().slice(0, 16).replace(\"T\", \" \")}`,\n ...(opts.expiresIn ? { expiresIn: opts.expiresIn } : {}),\n },\n cookiePair,\n );\n if (status !== 200) {\n const detail = (json as { message?: string }).message;\n throw new Error(\n `CLI credential could not be created${detail ? `: ${detail}` : ` (${status})`}`,\n );\n }\n const created = json as ApiKeyCreated;\n if (!created.key) throw new Error(\"API key creation returned no key\");\n return { key: created.key, id: created.id };\n}\n\nasync function resolveNearAccount(siteUrl: string, cookiePair: string): Promise<string | null> {\n try {\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth/near/list-accounts`, {\n headers: { cookie: cookiePair },\n });\n if (!response.ok) return null;\n const data = (await response.json()) as NearAccountsResponse;\n return data.activeAccount?.accountId ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function openInBrowser(url: string): Promise<void> {\n const os = platform();\n try {\n if (os === \"darwin\") {\n await execa(\"open\", [url], { stdio: \"ignore\" });\n } else if (os === \"win32\") {\n await execa(\"cmd\", [\"/c\", \"start\", \"\", url], {\n stdio: \"ignore\",\n windowsVerbatimArguments: true,\n });\n } else {\n await execa(\"xdg-open\", [url], { stdio: \"ignore\" });\n }\n } catch {\n throw new Error(`Failed to open browser. Open this URL manually:\\n ${url}`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth-login.mjs","names":[],"sources":["../src/auth-login.ts"],"sourcesContent":["import { platform } from \"node:os\";\nimport { execa } from \"execa\";\nimport type { CustodyWalletRecord } from \"./auth-session\";\n\nconst DEVICE_CODE_GRANT_TYPE = \"urn:ietf:params:oauth:grant-type:device_code\";\nconst DEFAULT_CLIENT_ID = \"bos-cli\";\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\nconst SLOW_DOWN_PENALTY_SECONDS = 5;\n\nexport interface DeviceLoginOptions {\n siteUrl: string;\n /** label for the approving device, shown on the approval page */\n device?: string;\n /** the NEAR account the CLI expects to authenticate as (approval-page copy only) */\n account?: string;\n /** delegate mode — the approval page adds this scoped function-call key to the user's account */\n delegate?: { pubKey: string; contract: string; network: string };\n /** custody mode — the approval page also registers an OutLayer custody wallet under the user's account */\n custody?: { network: \"mainnet\" | \"testnet\" };\n /** seconds the minted API key stays valid (login mode) */\n expiresIn?: number;\n}\n\nexport interface DeviceApproval {\n sessionToken: string;\n /** the NEAR account signed in on the approving browser (login mode) */\n accountId: string | null;\n /** the minted CLI credential (login mode) */\n apiKey: { key: string; id: string } | null;\n /** the custody wallet registered during approval (custody mode) — null when it could not be fetched */\n custodyWallet: CustodyWalletRecord | null;\n}\n\nexport interface DeviceLoginHandle {\n userCode: string;\n verificationUrl: string;\n waitForApproval: (timeoutMs?: number) => Promise<DeviceApproval>;\n abort: (reason?: string) => void;\n}\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri?: string;\n verification_uri_complete?: string;\n expires_in?: number;\n interval?: number;\n}\n\ninterface DeviceTokenSuccess {\n access_token: string;\n token_type: \"Bearer\";\n}\n\ninterface DeviceTokenError {\n error:\n | \"authorization_pending\"\n | \"slow_down\"\n | \"expired_token\"\n | \"access_denied\"\n | \"invalid_grant\"\n | \"invalid_request\"\n | \"invalid_client\";\n error_description?: string;\n}\n\ninterface ApiKeyCreated {\n id: string;\n key: string;\n}\n\ninterface NearAccountsResponse {\n accounts: { accountId: string }[];\n activeAccount: { accountId: string } | null;\n}\n\nasync function authFetch(\n siteUrl: string,\n path: string,\n body: unknown,\n cookiePair?: string,\n): Promise<{ status: number; json: unknown; setCookie: string | null }> {\n // Better Auth's origin check demands an Origin on cookie-bearing\n // requests — present the site's own origin, exactly as a browser would.\n const origin = new URL(siteUrl).origin;\n const headers: Record<string, string> = { \"content-type\": \"application/json\", origin };\n if (cookiePair) headers.cookie = cookiePair;\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n return {\n status: response.status,\n json: await response.json().catch(() => ({})),\n setCookie: response.headers.get(\"set-cookie\"),\n };\n}\n\n/**\n * CLI login via the OAuth 2.0 Device Flow (RFC 8628) — the same flow the\n * site's QR pairing uses. The CLI requests a device code, the user approves\n * at `/login/device` in any browser (same machine or not), and the CLI polls\n * the token endpoint until the grant completes.\n */\nexport async function startDeviceLogin(opts: DeviceLoginOptions): Promise<DeviceLoginHandle> {\n const siteUrl = opts.siteUrl.replace(/\\/$/, \"\");\n const clientId = DEFAULT_CLIENT_ID;\n\n const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ client_id: clientId }),\n });\n if (!codeResponse.ok) {\n throw new Error(\n `Device login could not start (${codeResponse.status}) — is ${siteUrl} running the platform auth plugin?`,\n );\n }\n const code = (await codeResponse.json()) as DeviceCodeResponse;\n\n const verificationUrl = buildVerificationUrl(siteUrl, code, opts);\n\n let abortReason: string | null = null;\n let rejectPending!: (reason?: Error) => void;\n const pendingReject = new Promise<never>((_, reject) => {\n rejectPending = reject;\n });\n\n const waitForApproval = (timeoutMs = 10 * 60_000): Promise<DeviceApproval> => {\n const deadline = Date.now() + timeoutMs;\n let intervalSeconds = code.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;\n\n const poll = async (): Promise<DeviceApproval> => {\n if (abortReason) throw new Error(abortReason);\n if (Date.now() > deadline) {\n throw new Error(\"Login timed out — the device code expired before approval\");\n }\n\n const { status, json } = await authFetch(siteUrl, \"/device/token\", {\n grant_type: DEVICE_CODE_GRANT_TYPE,\n device_code: code.device_code,\n client_id: clientId,\n });\n\n if (status === 200) {\n const { access_token: sessionToken } = json as DeviceTokenSuccess;\n return await completeLogin(siteUrl, sessionToken, opts);\n }\n\n const error = (json as DeviceTokenError).error;\n if (error === \"authorization_pending\") {\n // keep polling\n } else if (error === \"slow_down\") {\n intervalSeconds += SLOW_DOWN_PENALTY_SECONDS;\n } else if (error === \"expired_token\") {\n throw new Error(\"Login timed out — the device code expired before approval\");\n } else if (error === \"access_denied\") {\n throw new Error(\"Login request was denied on the approval page\");\n } else {\n throw new Error(\n (json as DeviceTokenError).error_description ?? `Login failed (${error ?? status})`,\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1000));\n return poll();\n };\n\n return Promise.race([pendingReject, poll()]);\n };\n\n return {\n userCode: code.user_code,\n verificationUrl,\n waitForApproval,\n abort: (reason) => {\n abortReason = reason ?? \"Login aborted\";\n rejectPending(new Error(abortReason));\n },\n };\n}\n\nfunction buildVerificationUrl(\n siteUrl: string,\n code: DeviceCodeResponse,\n opts: DeviceLoginOptions,\n): string {\n const base = code.verification_uri_complete ?? code.verification_uri ?? \"/login/device\";\n const url = new URL(base, siteUrl);\n const params = url.searchParams;\n params.set(\"user_code\", code.user_code);\n if (opts.account) params.set(\"account\", opts.account);\n if (opts.device) params.set(\"device\", opts.device);\n if (opts.delegate) {\n params.set(\"pubKey\", opts.delegate.pubKey);\n params.set(\"contract\", opts.delegate.contract);\n params.set(\"network\", opts.delegate.network);\n }\n if (opts.custody) {\n params.set(\"custody\", \"1\");\n params.set(\"network\", opts.custody.network);\n }\n return url.toString();\n}\n\nasync function completeLogin(\n siteUrl: string,\n sessionToken: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval> {\n // The token endpoint returns the raw session token; Better Auth session\n // cookies carry an HMAC signature, so exchange the token for a signed\n // session cookie via the device-link claim endpoint (the QR pairing path).\n const claim = await authFetch(siteUrl, \"/device-link/claim\", {\n token: sessionToken,\n client_id: DEFAULT_CLIENT_ID,\n });\n if (claim.status !== 200) {\n const detail = (claim.json as { message?: string }).message;\n throw new Error(\n `Approved session could not be claimed${detail ? `: ${detail}` : ` (${claim.status})`}`,\n );\n }\n const cookiePair = claim.setCookie?.split(\";\")[0];\n if (!cookiePair?.includes(\"=\")) {\n throw new Error(\"Session claim returned no session cookie\");\n }\n\n const apiKey = await mintApiKey(siteUrl, cookiePair, opts);\n const accountId = await resolveNearAccount(siteUrl, cookiePair);\n const custodyWallet = opts.custody\n ? await fetchCustodyCredential(siteUrl, cookiePair, opts.custody.network).catch(() => null)\n : null;\n return { sessionToken, accountId, apiKey, custodyWallet };\n}\n\ninterface CustodyCredentialResponse {\n apiKey: string;\n walletId: string;\n nearAccountId: string;\n network: string;\n handoffUrl: string | null;\n registeredAt?: string;\n}\n\nasync function fetchCustodyCredential(\n siteUrl: string,\n cookiePair: string,\n network: \"mainnet\" | \"testnet\",\n): Promise<CustodyWalletRecord> {\n const { status, json } = await authFetch(siteUrl, \"/custody/credential\", { network }, cookiePair);\n if (status !== 200) {\n const detail = (json as { message?: string }).message;\n throw new Error(\n `Custody wallet credential could not be fetched${detail ? `: ${detail}` : ` (${status})`}`,\n );\n }\n const data = json as CustodyCredentialResponse;\n if (!data.apiKey?.startsWith(\"wk_\")) {\n throw new Error(\"Custody wallet credential returned no API key\");\n }\n if (data.network !== \"mainnet\" && data.network !== \"testnet\") {\n throw new Error(`Custody wallet credential returned unknown network \"${data.network}\"`);\n }\n return {\n apiKey: data.apiKey,\n walletId: data.walletId,\n nearAccountId: data.nearAccountId,\n network: data.network,\n handoffUrl: data.handoffUrl ?? null,\n registeredAt: data.registeredAt ?? new Date().toISOString(),\n };\n}\n\nasync function mintApiKey(\n siteUrl: string,\n cookiePair: string,\n opts: DeviceLoginOptions,\n): Promise<DeviceApproval[\"apiKey\"]> {\n const { status, json } = await authFetch(\n siteUrl,\n \"/api-key/create\",\n {\n configId: \"user-keys\",\n name: `bos login — ${opts.device ?? \"cli\"} — ${new Date().toISOString().slice(0, 16).replace(\"T\", \" \")}`,\n ...(opts.expiresIn ? { expiresIn: opts.expiresIn } : {}),\n },\n cookiePair,\n );\n if (status !== 200) {\n const detail = (json as { message?: string }).message;\n throw new Error(\n `CLI credential could not be created${detail ? `: ${detail}` : ` (${status})`}`,\n );\n }\n const created = json as ApiKeyCreated;\n if (!created.key) throw new Error(\"API key creation returned no key\");\n return { key: created.key, id: created.id };\n}\n\nasync function resolveNearAccount(siteUrl: string, cookiePair: string): Promise<string | null> {\n try {\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth/near/list-accounts`, {\n headers: { cookie: cookiePair },\n });\n if (!response.ok) return null;\n const data = (await response.json()) as NearAccountsResponse;\n return data.activeAccount?.accountId ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function openInBrowser(url: string): Promise<void> {\n const os = platform();\n try {\n if (os === \"darwin\") {\n await execa(\"open\", [url], { stdio: \"ignore\" });\n } else if (os === \"win32\") {\n await execa(\"cmd\", [\"/c\", \"start\", \"\", url], {\n stdio: \"ignore\",\n windowsVerbatimArguments: true,\n });\n } else {\n await execa(\"xdg-open\", [url], { stdio: \"ignore\" });\n }\n } catch {\n throw new Error(`Failed to open browser. Open this URL manually:\\n ${url}`);\n }\n}\n"],"mappings":";;;;AAIA,MAAM,yBAAyB;AAC/B,MAAM,oBAAoB;AAC1B,MAAM,gCAAgC;AACtC,MAAM,4BAA4B;AAqElC,eAAe,UACb,SACA,MACA,MACA,YACsE;CAItE,MAAM,UAAkC;EAAE,gBAAgB;EAAoB,QAD/D,IAAI,IAAI,OAAO,CAAC,CAAC;CACqD;CACrF,IAAI,YAAY,QAAQ,SAAS;CACjC,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,EAAE,WAAW,QAAQ;EAC5E,QAAQ;EACR;EACA,MAAM,KAAK,UAAU,IAAI;CAC3B,CAAC;CACD,OAAO;EACL,QAAQ,SAAS;EACjB,MAAM,MAAM,SAAS,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;EAC5C,WAAW,SAAS,QAAQ,IAAI,YAAY;CAC9C;AACF;;;;;;;AAQA,eAAsB,iBAAiB,MAAsD;CAC3F,MAAM,UAAU,KAAK,QAAQ,QAAQ,OAAO,EAAE;CAC9C,MAAM,WAAW;CAEjB,MAAM,eAAe,MAAM,MAAM,GAAG,QAAQ,wBAAwB;EAClE,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU,EAAE,WAAW,SAAS,CAAC;CAC9C,CAAC;CACD,IAAI,CAAC,aAAa,IAChB,MAAM,IAAI,MACR,iCAAiC,aAAa,OAAO,SAAS,QAAQ,mCACxE;CAEF,MAAM,OAAQ,MAAM,aAAa,KAAK;CAEtC,MAAM,kBAAkB,qBAAqB,SAAS,MAAM,IAAI;CAEhE,IAAI,cAA6B;CACjC,IAAI;CACJ,MAAM,gBAAgB,IAAI,SAAgB,GAAG,WAAW;EACtD,gBAAgB;CAClB,CAAC;CAED,MAAM,mBAAmB,YAAY,QAAyC;EAC5E,MAAM,WAAW,KAAK,IAAI,IAAI;EAC9B,IAAI,kBAAkB,KAAK,YAAY;EAEvC,MAAM,OAAO,YAAqC;GAChD,IAAI,aAAa,MAAM,IAAI,MAAM,WAAW;GAC5C,IAAI,KAAK,IAAI,IAAI,UACf,MAAM,IAAI,MAAM,2DAA2D;GAG7E,MAAM,EAAE,QAAQ,SAAS,MAAM,UAAU,SAAS,iBAAiB;IACjE,YAAY;IACZ,aAAa,KAAK;IAClB,WAAW;GACb,CAAC;GAED,IAAI,WAAW,KAAK;IAClB,MAAM,EAAE,cAAc,iBAAiB;IACvC,OAAO,MAAM,cAAc,SAAS,cAAc,IAAI;GACxD;GAEA,MAAM,QAAS,KAA0B;GACzC,IAAI,UAAU,yBAAyB,CAEvC,OAAO,IAAI,UAAU,aACnB,mBAAmB;QACd,IAAI,UAAU,iBACnB,MAAM,IAAI,MAAM,2DAA2D;QACtE,IAAI,UAAU,iBACnB,MAAM,IAAI,MAAM,+CAA+C;QAE/D,MAAM,IAAI,MACP,KAA0B,qBAAqB,iBAAiB,SAAS,OAAO,EACnF;GAGF,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,kBAAkB,GAAI,CAAC;GAC1E,OAAO,KAAK;EACd;EAEA,OAAO,QAAQ,KAAK,CAAC,eAAe,KAAK,CAAC,CAAC;CAC7C;CAEA,OAAO;EACL,UAAU,KAAK;EACf;EACA;EACA,QAAQ,WAAW;GACjB,cAAc,UAAU;GACxB,cAAc,IAAI,MAAM,WAAW,CAAC;EACtC;CACF;AACF;AAEA,SAAS,qBACP,SACA,MACA,MACQ;CACR,MAAM,OAAO,KAAK,6BAA6B,KAAK,oBAAoB;CACxE,MAAM,MAAM,IAAI,IAAI,MAAM,OAAO;CACjC,MAAM,SAAS,IAAI;CACnB,OAAO,IAAI,aAAa,KAAK,SAAS;CACtC,IAAI,KAAK,SAAS,OAAO,IAAI,WAAW,KAAK,OAAO;CACpD,IAAI,KAAK,QAAQ,OAAO,IAAI,UAAU,KAAK,MAAM;CACjD,IAAI,KAAK,UAAU;EACjB,OAAO,IAAI,UAAU,KAAK,SAAS,MAAM;EACzC,OAAO,IAAI,YAAY,KAAK,SAAS,QAAQ;EAC7C,OAAO,IAAI,WAAW,KAAK,SAAS,OAAO;CAC7C;CACA,IAAI,KAAK,SAAS;EAChB,OAAO,IAAI,WAAW,GAAG;EACzB,OAAO,IAAI,WAAW,KAAK,QAAQ,OAAO;CAC5C;CACA,OAAO,IAAI,SAAS;AACtB;AAEA,eAAe,cACb,SACA,cACA,MACyB;CAIzB,MAAM,QAAQ,MAAM,UAAU,SAAS,sBAAsB;EAC3D,OAAO;EACP,WAAW;CACb,CAAC;CACD,IAAI,MAAM,WAAW,KAAK;EACxB,MAAM,SAAU,MAAM,KAA8B;EACpD,MAAM,IAAI,MACR,wCAAwC,SAAS,KAAK,WAAW,KAAK,MAAM,OAAO,IACrF;CACF;CACA,MAAM,aAAa,MAAM,WAAW,MAAM,GAAG,CAAC,CAAC;CAC/C,IAAI,CAAC,YAAY,SAAS,GAAG,GAC3B,MAAM,IAAI,MAAM,0CAA0C;CAG5D,MAAM,SAAS,MAAM,WAAW,SAAS,YAAY,IAAI;CAKzD,OAAO;EAAE;EAAc,iBAJC,mBAAmB,SAAS,UAAU;EAI5B;EAAQ,eAHpB,KAAK,UACvB,MAAM,uBAAuB,SAAS,YAAY,KAAK,QAAQ,OAAO,CAAC,CAAC,YAAY,IAAI,IACxF;CACoD;AAC1D;AAWA,eAAe,uBACb,SACA,YACA,SAC8B;CAC9B,MAAM,EAAE,QAAQ,SAAS,MAAM,UAAU,SAAS,uBAAuB,EAAE,QAAQ,GAAG,UAAU;CAChG,IAAI,WAAW,KAAK;EAClB,MAAM,SAAU,KAA8B;EAC9C,MAAM,IAAI,MACR,iDAAiD,SAAS,KAAK,WAAW,KAAK,OAAO,IACxF;CACF;CACA,MAAM,OAAO;CACb,IAAI,CAAC,KAAK,QAAQ,WAAW,KAAK,GAChC,MAAM,IAAI,MAAM,+CAA+C;CAEjE,IAAI,KAAK,YAAY,aAAa,KAAK,YAAY,WACjD,MAAM,IAAI,MAAM,uDAAuD,KAAK,QAAQ,EAAE;CAExF,OAAO;EACL,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,eAAe,KAAK;EACpB,SAAS,KAAK;EACd,YAAY,KAAK,cAAc;EAC/B,cAAc,KAAK,iCAAgB,IAAI,KAAK,EAAC,CAAC,YAAY;CAC5D;AACF;AAEA,eAAe,WACb,SACA,YACA,MACmC;CACnC,MAAM,EAAE,QAAQ,SAAS,MAAM,UAC7B,SACA,mBACA;EACE,UAAU;EACV,MAAM,eAAe,KAAK,UAAU,MAAM,sBAAK,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,QAAQ,KAAK,GAAG;EACrG,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;CACxD,GACA,UACF;CACA,IAAI,WAAW,KAAK;EAClB,MAAM,SAAU,KAA8B;EAC9C,MAAM,IAAI,MACR,sCAAsC,SAAS,KAAK,WAAW,KAAK,OAAO,IAC7E;CACF;CACA,MAAM,UAAU;CAChB,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAI,MAAM,kCAAkC;CACpE,OAAO;EAAE,KAAK,QAAQ;EAAK,IAAI,QAAQ;CAAG;AAC5C;AAEA,eAAe,mBAAmB,SAAiB,YAA4C;CAC7F,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,EAAE,+BAA+B,EACxF,SAAS,EAAE,QAAQ,WAAW,EAChC,CAAC;EACD,IAAI,CAAC,SAAS,IAAI,OAAO;EAEzB,QAAO,MADa,SAAS,KAAK,EACvB,CAAC,eAAe,aAAa;CAC1C,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAsB,cAAc,KAA4B;CAC9D,MAAM,KAAK,SAAS;CACpB,IAAI;EACF,IAAI,OAAO,UACT,MAAM,MAAM,QAAQ,CAAC,GAAG,GAAG,EAAE,OAAO,SAAS,CAAC;OACzC,IAAI,OAAO,SAChB,MAAM,MAAM,OAAO;GAAC;GAAM;GAAS;GAAI;EAAG,GAAG;GAC3C,OAAO;GACP,0BAA0B;EAC5B,CAAC;OAED,MAAM,MAAM,YAAY,CAAC,GAAG,GAAG,EAAE,OAAO,SAAS,CAAC;CAEtD,QAAQ;EACN,MAAM,IAAI,MAAM,sDAAsD,KAAK;CAC7E;AACF"}
|
package/dist/auth-session.cjs
CHANGED
|
@@ -17,7 +17,8 @@ function readSessionHandle(configDir) {
|
|
|
17
17
|
version: 1,
|
|
18
18
|
credential: parsed.credential ?? null,
|
|
19
19
|
publishKey: parsed.publishKey ?? null,
|
|
20
|
-
delegateKey: parsed.delegateKey ?? null
|
|
20
|
+
delegateKey: parsed.delegateKey ?? null,
|
|
21
|
+
custodyWallet: parsed.custodyWallet ?? null
|
|
21
22
|
};
|
|
22
23
|
} catch {
|
|
23
24
|
return null;
|
|
@@ -33,7 +34,8 @@ function updateSessionHandle(configDir, update) {
|
|
|
33
34
|
version: 1,
|
|
34
35
|
credential: null,
|
|
35
36
|
publishKey: null,
|
|
36
|
-
delegateKey: null
|
|
37
|
+
delegateKey: null,
|
|
38
|
+
custodyWallet: null
|
|
37
39
|
});
|
|
38
40
|
writeSessionHandle(configDir, next);
|
|
39
41
|
return next;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-session.cjs","names":["join","existsSync","readFileSync","homedir"],"sources":["../src/auth-session.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface SessionCredential {\n kind: \"session\";\n apiKey: string;\n apiKeyId: string;\n accountId: string | null;\n label: string;\n siteUrl: string;\n createdAt: string;\n expiresAt: string | null;\n}\n\nexport interface PublishKeyRecord {\n publicKey: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n exportedTo: string;\n}\n\nexport interface DelegateKeyRecord {\n publicKey: string;\n privateKey: string;\n accountId: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n mintedAt: string;\n}\n\nexport interface BosSessionFile {\n version: 1;\n credential: SessionCredential | null;\n publishKey: PublishKeyRecord | null;\n delegateKey: DelegateKeyRecord | null;\n}\n\nconst SESSION_FILE_NAME = \"session.json\";\n\nexport function getSessionFilePath(configDir: string): string {\n return join(configDir, \".bos\", SESSION_FILE_NAME);\n}\n\nexport function readSessionHandle(configDir: string): BosSessionFile | null {\n const filePath = getSessionFilePath(configDir);\n if (!existsSync(filePath)) return null;\n try {\n const parsed = JSON.parse(readFileSync(filePath, \"utf-8\")) as Partial<BosSessionFile>;\n if (parsed.version !== 1) return null;\n return {\n version: 1,\n credential: parsed.credential ?? null,\n publishKey: parsed.publishKey ?? null,\n delegateKey: parsed.delegateKey ?? null,\n };\n } catch {\n return null;\n }\n}\n\nexport function writeSessionHandle(configDir: string, session: BosSessionFile): void {\n const bosDir = join(configDir, \".bos\");\n mkdirSync(bosDir, { recursive: true });\n writeFileSync(getSessionFilePath(configDir), `${JSON.stringify(session, null, 2)}\\n`, {\n mode: 0o600,\n });\n}\n\nexport function updateSessionHandle(\n configDir: string,\n update: (session: BosSessionFile) => BosSessionFile,\n): BosSessionFile {\n const current = readSessionHandle(configDir) ?? {\n version: 1 as const,\n credential: null,\n publishKey: null,\n delegateKey: null,\n };\n const next = update(current);\n writeSessionHandle(configDir, next);\n return next;\n}\n\nexport function deleteSessionHandle(configDir: string): void {\n const filePath = getSessionFilePath(configDir);\n if (existsSync(filePath)) {\n rmSync(filePath);\n }\n}\n\nexport function nearCredentialsPath(network: \"mainnet\" | \"testnet\", account: string): string {\n return join(homedir(), \".near-credentials\", network, `${account}.json`);\n}\n\nexport function removePublishedKeyFile(\n network: \"mainnet\" | \"testnet\",\n account: string,\n publicKey: string,\n): void {\n const filePath = nearCredentialsPath(network, account);\n if (!existsSync(filePath)) return;\n try {\n const credential = JSON.parse(readFileSync(filePath, \"utf-8\")) as { public_key?: string };\n if (credential.public_key === publicKey) {\n rmSync(filePath);\n }\n } catch {\n // leave malformed files alone\n }\n}\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"auth-session.cjs","names":["join","existsSync","readFileSync","homedir"],"sources":["../src/auth-session.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface SessionCredential {\n kind: \"session\";\n apiKey: string;\n apiKeyId: string;\n accountId: string | null;\n label: string;\n siteUrl: string;\n createdAt: string;\n expiresAt: string | null;\n}\n\nexport interface PublishKeyRecord {\n publicKey: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n exportedTo: string;\n}\n\nexport interface DelegateKeyRecord {\n publicKey: string;\n privateKey: string;\n accountId: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n mintedAt: string;\n}\n\nexport interface CustodyWalletRecord {\n apiKey: string;\n walletId: string;\n nearAccountId: string;\n network: \"mainnet\" | \"testnet\";\n handoffUrl: string | null;\n registeredAt: string;\n}\n\nexport interface BosSessionFile {\n version: 1;\n credential: SessionCredential | null;\n publishKey: PublishKeyRecord | null;\n delegateKey: DelegateKeyRecord | null;\n custodyWallet: CustodyWalletRecord | null;\n}\n\nconst SESSION_FILE_NAME = \"session.json\";\n\nexport function getSessionFilePath(configDir: string): string {\n return join(configDir, \".bos\", SESSION_FILE_NAME);\n}\n\nexport function readSessionHandle(configDir: string): BosSessionFile | null {\n const filePath = getSessionFilePath(configDir);\n if (!existsSync(filePath)) return null;\n try {\n const parsed = JSON.parse(readFileSync(filePath, \"utf-8\")) as Partial<BosSessionFile>;\n if (parsed.version !== 1) return null;\n return {\n version: 1,\n credential: parsed.credential ?? null,\n publishKey: parsed.publishKey ?? null,\n delegateKey: parsed.delegateKey ?? null,\n custodyWallet: parsed.custodyWallet ?? null,\n };\n } catch {\n return null;\n }\n}\n\nexport function writeSessionHandle(configDir: string, session: BosSessionFile): void {\n const bosDir = join(configDir, \".bos\");\n mkdirSync(bosDir, { recursive: true });\n writeFileSync(getSessionFilePath(configDir), `${JSON.stringify(session, null, 2)}\\n`, {\n mode: 0o600,\n });\n}\n\nexport function updateSessionHandle(\n configDir: string,\n update: (session: BosSessionFile) => BosSessionFile,\n): BosSessionFile {\n const current = readSessionHandle(configDir) ?? {\n version: 1 as const,\n credential: null,\n publishKey: null,\n delegateKey: null,\n custodyWallet: null,\n };\n const next = update(current);\n writeSessionHandle(configDir, next);\n return next;\n}\n\nexport function deleteSessionHandle(configDir: string): void {\n const filePath = getSessionFilePath(configDir);\n if (existsSync(filePath)) {\n rmSync(filePath);\n }\n}\n\nexport function nearCredentialsPath(network: \"mainnet\" | \"testnet\", account: string): string {\n return join(homedir(), \".near-credentials\", network, `${account}.json`);\n}\n\nexport function removePublishedKeyFile(\n network: \"mainnet\" | \"testnet\",\n account: string,\n publicKey: string,\n): void {\n const filePath = nearCredentialsPath(network, account);\n if (!existsSync(filePath)) return;\n try {\n const credential = JSON.parse(readFileSync(filePath, \"utf-8\")) as { public_key?: string };\n if (credential.public_key === publicKey) {\n rmSync(filePath);\n }\n } catch {\n // leave malformed files alone\n }\n}\n"],"mappings":";;;;;AAgDA,MAAM,oBAAoB;AAE1B,SAAgB,mBAAmB,WAA2B;CAC5D,WAAOA,gBAAK,WAAW,QAAQ,iBAAiB;AAClD;AAEA,SAAgB,kBAAkB,WAA0C;CAC1E,MAAM,WAAW,mBAAmB,SAAS;CAC7C,IAAI,KAACC,oBAAW,QAAQ,GAAG,OAAO;CAClC,IAAI;EACF,MAAM,SAAS,KAAK,UAAMC,sBAAa,UAAU,OAAO,CAAC;EACzD,IAAI,OAAO,YAAY,GAAG,OAAO;EACjC,OAAO;GACL,SAAS;GACT,YAAY,OAAO,cAAc;GACjC,YAAY,OAAO,cAAc;GACjC,aAAa,OAAO,eAAe;GACnC,eAAe,OAAO,iBAAiB;EACzC;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,mBAAmB,WAAmB,SAA+B;CACnF,MAAM,aAASF,gBAAK,WAAW,MAAM;CACrC,uBAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;CACrC,2BAAc,mBAAmB,SAAS,GAAG,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK,EACpF,MAAM,IACR,CAAC;AACH;AAEA,SAAgB,oBACd,WACA,QACgB;CAQhB,MAAM,OAAO,OAPG,kBAAkB,SAAS,KAAK;EAC9C,SAAS;EACT,YAAY;EACZ,YAAY;EACZ,aAAa;EACb,eAAe;CACjB,CAC2B;CAC3B,mBAAmB,WAAW,IAAI;CAClC,OAAO;AACT;AAEA,SAAgB,oBAAoB,WAAyB;CAC3D,MAAM,WAAW,mBAAmB,SAAS;CAC7C,QAAIC,oBAAW,QAAQ,GACrB,oBAAO,QAAQ;AAEnB;AAEA,SAAgB,oBAAoB,SAAgC,SAAyB;CAC3F,WAAOD,oBAAKG,iBAAQ,GAAG,qBAAqB,SAAS,GAAG,QAAQ,MAAM;AACxE;AAEA,SAAgB,uBACd,SACA,SACA,WACM;CACN,MAAM,WAAW,oBAAoB,SAAS,OAAO;CACrD,IAAI,KAACF,oBAAW,QAAQ,GAAG;CAC3B,IAAI;EAEF,IADmB,KAAK,UAAMC,sBAAa,UAAU,OAAO,CAC/C,CAAC,CAAC,eAAe,WAC5B,oBAAO,QAAQ;CAEnB,QAAQ,CAER;AACF"}
|
package/dist/auth-session.mjs
CHANGED
|
@@ -17,7 +17,8 @@ function readSessionHandle(configDir) {
|
|
|
17
17
|
version: 1,
|
|
18
18
|
credential: parsed.credential ?? null,
|
|
19
19
|
publishKey: parsed.publishKey ?? null,
|
|
20
|
-
delegateKey: parsed.delegateKey ?? null
|
|
20
|
+
delegateKey: parsed.delegateKey ?? null,
|
|
21
|
+
custodyWallet: parsed.custodyWallet ?? null
|
|
21
22
|
};
|
|
22
23
|
} catch {
|
|
23
24
|
return null;
|
|
@@ -33,7 +34,8 @@ function updateSessionHandle(configDir, update) {
|
|
|
33
34
|
version: 1,
|
|
34
35
|
credential: null,
|
|
35
36
|
publishKey: null,
|
|
36
|
-
delegateKey: null
|
|
37
|
+
delegateKey: null,
|
|
38
|
+
custodyWallet: null
|
|
37
39
|
});
|
|
38
40
|
writeSessionHandle(configDir, next);
|
|
39
41
|
return next;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-session.mjs","names":[],"sources":["../src/auth-session.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface SessionCredential {\n kind: \"session\";\n apiKey: string;\n apiKeyId: string;\n accountId: string | null;\n label: string;\n siteUrl: string;\n createdAt: string;\n expiresAt: string | null;\n}\n\nexport interface PublishKeyRecord {\n publicKey: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n exportedTo: string;\n}\n\nexport interface DelegateKeyRecord {\n publicKey: string;\n privateKey: string;\n accountId: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n mintedAt: string;\n}\n\nexport interface BosSessionFile {\n version: 1;\n credential: SessionCredential | null;\n publishKey: PublishKeyRecord | null;\n delegateKey: DelegateKeyRecord | null;\n}\n\nconst SESSION_FILE_NAME = \"session.json\";\n\nexport function getSessionFilePath(configDir: string): string {\n return join(configDir, \".bos\", SESSION_FILE_NAME);\n}\n\nexport function readSessionHandle(configDir: string): BosSessionFile | null {\n const filePath = getSessionFilePath(configDir);\n if (!existsSync(filePath)) return null;\n try {\n const parsed = JSON.parse(readFileSync(filePath, \"utf-8\")) as Partial<BosSessionFile>;\n if (parsed.version !== 1) return null;\n return {\n version: 1,\n credential: parsed.credential ?? null,\n publishKey: parsed.publishKey ?? null,\n delegateKey: parsed.delegateKey ?? null,\n };\n } catch {\n return null;\n }\n}\n\nexport function writeSessionHandle(configDir: string, session: BosSessionFile): void {\n const bosDir = join(configDir, \".bos\");\n mkdirSync(bosDir, { recursive: true });\n writeFileSync(getSessionFilePath(configDir), `${JSON.stringify(session, null, 2)}\\n`, {\n mode: 0o600,\n });\n}\n\nexport function updateSessionHandle(\n configDir: string,\n update: (session: BosSessionFile) => BosSessionFile,\n): BosSessionFile {\n const current = readSessionHandle(configDir) ?? {\n version: 1 as const,\n credential: null,\n publishKey: null,\n delegateKey: null,\n };\n const next = update(current);\n writeSessionHandle(configDir, next);\n return next;\n}\n\nexport function deleteSessionHandle(configDir: string): void {\n const filePath = getSessionFilePath(configDir);\n if (existsSync(filePath)) {\n rmSync(filePath);\n }\n}\n\nexport function nearCredentialsPath(network: \"mainnet\" | \"testnet\", account: string): string {\n return join(homedir(), \".near-credentials\", network, `${account}.json`);\n}\n\nexport function removePublishedKeyFile(\n network: \"mainnet\" | \"testnet\",\n account: string,\n publicKey: string,\n): void {\n const filePath = nearCredentialsPath(network, account);\n if (!existsSync(filePath)) return;\n try {\n const credential = JSON.parse(readFileSync(filePath, \"utf-8\")) as { public_key?: string };\n if (credential.public_key === publicKey) {\n rmSync(filePath);\n }\n } catch {\n // leave malformed files alone\n }\n}\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"auth-session.mjs","names":[],"sources":["../src/auth-session.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface SessionCredential {\n kind: \"session\";\n apiKey: string;\n apiKeyId: string;\n accountId: string | null;\n label: string;\n siteUrl: string;\n createdAt: string;\n expiresAt: string | null;\n}\n\nexport interface PublishKeyRecord {\n publicKey: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n exportedTo: string;\n}\n\nexport interface DelegateKeyRecord {\n publicKey: string;\n privateKey: string;\n accountId: string;\n network: \"mainnet\" | \"testnet\";\n contract: string;\n mintedAt: string;\n}\n\nexport interface CustodyWalletRecord {\n apiKey: string;\n walletId: string;\n nearAccountId: string;\n network: \"mainnet\" | \"testnet\";\n handoffUrl: string | null;\n registeredAt: string;\n}\n\nexport interface BosSessionFile {\n version: 1;\n credential: SessionCredential | null;\n publishKey: PublishKeyRecord | null;\n delegateKey: DelegateKeyRecord | null;\n custodyWallet: CustodyWalletRecord | null;\n}\n\nconst SESSION_FILE_NAME = \"session.json\";\n\nexport function getSessionFilePath(configDir: string): string {\n return join(configDir, \".bos\", SESSION_FILE_NAME);\n}\n\nexport function readSessionHandle(configDir: string): BosSessionFile | null {\n const filePath = getSessionFilePath(configDir);\n if (!existsSync(filePath)) return null;\n try {\n const parsed = JSON.parse(readFileSync(filePath, \"utf-8\")) as Partial<BosSessionFile>;\n if (parsed.version !== 1) return null;\n return {\n version: 1,\n credential: parsed.credential ?? null,\n publishKey: parsed.publishKey ?? null,\n delegateKey: parsed.delegateKey ?? null,\n custodyWallet: parsed.custodyWallet ?? null,\n };\n } catch {\n return null;\n }\n}\n\nexport function writeSessionHandle(configDir: string, session: BosSessionFile): void {\n const bosDir = join(configDir, \".bos\");\n mkdirSync(bosDir, { recursive: true });\n writeFileSync(getSessionFilePath(configDir), `${JSON.stringify(session, null, 2)}\\n`, {\n mode: 0o600,\n });\n}\n\nexport function updateSessionHandle(\n configDir: string,\n update: (session: BosSessionFile) => BosSessionFile,\n): BosSessionFile {\n const current = readSessionHandle(configDir) ?? {\n version: 1 as const,\n credential: null,\n publishKey: null,\n delegateKey: null,\n custodyWallet: null,\n };\n const next = update(current);\n writeSessionHandle(configDir, next);\n return next;\n}\n\nexport function deleteSessionHandle(configDir: string): void {\n const filePath = getSessionFilePath(configDir);\n if (existsSync(filePath)) {\n rmSync(filePath);\n }\n}\n\nexport function nearCredentialsPath(network: \"mainnet\" | \"testnet\", account: string): string {\n return join(homedir(), \".near-credentials\", network, `${account}.json`);\n}\n\nexport function removePublishedKeyFile(\n network: \"mainnet\" | \"testnet\",\n account: string,\n publicKey: string,\n): void {\n const filePath = nearCredentialsPath(network, account);\n if (!existsSync(filePath)) return;\n try {\n const credential = JSON.parse(readFileSync(filePath, \"utf-8\")) as { public_key?: string };\n if (credential.public_key === publicKey) {\n rmSync(filePath);\n }\n } catch {\n // leave malformed files alone\n }\n}\n"],"mappings":";;;;;AAgDA,MAAM,oBAAoB;AAE1B,SAAgB,mBAAmB,WAA2B;CAC5D,OAAO,KAAK,WAAW,QAAQ,iBAAiB;AAClD;AAEA,SAAgB,kBAAkB,WAA0C;CAC1E,MAAM,WAAW,mBAAmB,SAAS;CAC7C,IAAI,CAAC,WAAW,QAAQ,GAAG,OAAO;CAClC,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;EACzD,IAAI,OAAO,YAAY,GAAG,OAAO;EACjC,OAAO;GACL,SAAS;GACT,YAAY,OAAO,cAAc;GACjC,YAAY,OAAO,cAAc;GACjC,aAAa,OAAO,eAAe;GACnC,eAAe,OAAO,iBAAiB;EACzC;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,mBAAmB,WAAmB,SAA+B;CACnF,MAAM,SAAS,KAAK,WAAW,MAAM;CACrC,UAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;CACrC,cAAc,mBAAmB,SAAS,GAAG,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK,EACpF,MAAM,IACR,CAAC;AACH;AAEA,SAAgB,oBACd,WACA,QACgB;CAQhB,MAAM,OAAO,OAPG,kBAAkB,SAAS,KAAK;EAC9C,SAAS;EACT,YAAY;EACZ,YAAY;EACZ,aAAa;EACb,eAAe;CACjB,CAC2B;CAC3B,mBAAmB,WAAW,IAAI;CAClC,OAAO;AACT;AAEA,SAAgB,oBAAoB,WAAyB;CAC3D,MAAM,WAAW,mBAAmB,SAAS;CAC7C,IAAI,WAAW,QAAQ,GACrB,OAAO,QAAQ;AAEnB;AAEA,SAAgB,oBAAoB,SAAgC,SAAyB;CAC3F,OAAO,KAAK,QAAQ,GAAG,qBAAqB,SAAS,GAAG,QAAQ,MAAM;AACxE;AAEA,SAAgB,uBACd,SACA,SACA,WACM;CACN,MAAM,WAAW,oBAAoB,SAAS,OAAO;CACrD,IAAI,CAAC,WAAW,QAAQ,GAAG;CAC3B,IAAI;EAEF,IADmB,KAAK,MAAM,aAAa,UAAU,OAAO,CAC/C,CAAC,CAAC,eAAe,WAC5B,OAAO,QAAQ;CAEnB,QAAQ,CAER;AACF"}
|
package/dist/cdn-deploy.cjs
CHANGED
|
@@ -87,13 +87,17 @@ function resolveCdnDeployInputs(input) {
|
|
|
87
87
|
warning,
|
|
88
88
|
error: "CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)"
|
|
89
89
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const uploadIdentities = new Set([input.session?.accountId, input.custodyAccount].filter((id) => !!id));
|
|
91
|
+
if (uploadIdentities.size > 0 && !uploadIdentities.has(input.account)) {
|
|
92
|
+
const sessionAccount = input.session?.accountId;
|
|
93
|
+
return {
|
|
94
|
+
cdnOrigin,
|
|
95
|
+
storageOrigin,
|
|
96
|
+
apiKey,
|
|
97
|
+
warning,
|
|
98
|
+
error: sessionAccount ? `The CLI session was created for ${sessionAccount}, but the configured account is ${input.account}. Re-run bos login under the matching account.` : `The configured account is ${input.account}, but the custody publish identity is ${input.custodyAccount}. Set the authored config's account to the custody wallet account.`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
97
101
|
return {
|
|
98
102
|
cdnOrigin,
|
|
99
103
|
storageOrigin,
|
package/dist/cdn-deploy.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdn-deploy.cjs","names":[],"sources":["../src/cdn-deploy.ts"],"sourcesContent":["interface SessionCredentialLike {\n apiKey?: string;\n siteUrl?: string;\n accountId?: string | null;\n}\n\nexport interface CdnDeployInputs {\n cdnOrigin: string | undefined;\n storageOrigin: string;\n apiKey: string | undefined;\n warning?: string;\n error?: string;\n}\n\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\", \"local\"]);\n\n/** Local/private destinations can never serve production bundles. */\nexport function isLocalOrigin(url: string): boolean {\n try {\n const { hostname, protocol } = new URL(url);\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n return (\n LOCAL_HOSTNAMES.has(host) ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^10\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^192\\.168\\.\\d+\\.\\d+$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d+\\.\\d+$/.test(host) ||\n host.startsWith(\"169.254.\") ||\n (host.includes(\":\") &&\n (host.startsWith(\"fe80:\") || host.startsWith(\"fc\") || host.startsWith(\"fd\")))\n );\n } catch {\n return true;\n }\n}\n\n/**\n * Deploy origin resolution (ADR 0020, as amended): the CDN origin comes from\n * the env or the authored `cdn.origin` (inherited via extends) — a hard\n * requirement when uploads are planned, because the host never serves\n * `/bundles` itself. The upload origin is the env, the `bos login` session's\n * siteUrl, or the runtime's own gateway. A session pinned to a local site is\n * a hard error (the origin can only ever be a squatter's port); an env-pinned\n * local origin is deliberate local testing and only warns. Config-only\n * publishes (`uploadsPlanned: false`) ship the committed config verbatim —\n * no origins are resolved and nothing is required.\n */\nexport function resolveCdnDeployInputs(input: {\n env: Record<string, string | undefined>;\n bosConfig: { cdn?: { origin?: string } } | null;\n session: SessionCredentialLike | null;\n account: string;\n gateway: string;\n uploadsPlanned: boolean;\n}): CdnDeployInputs {\n const envCdnOrigin = input.env.BOS_BUNDLE_CDN_ORIGIN?.replace(/\\/$/, \"\");\n const configCdnOrigin = input.bosConfig?.cdn?.origin?.replace(/\\/$/, \"\");\n const storageEnvOrigin = input.env.BOS_STORAGE_ORIGIN?.replace(/\\/$/, \"\");\n const sessionSite = input.session?.siteUrl?.replace(/\\/$/, \"\");\n const apiKey = input.env.BOS_STORAGE_API_KEY ?? input.session?.apiKey;\n const uploadsPlanned = input.uploadsPlanned;\n\n const storageOriginDefault = storageEnvOrigin ?? sessionSite ?? `https://${input.gateway}`;\n\n if (!uploadsPlanned) {\n return { cdnOrigin: undefined, storageOrigin: storageOriginDefault, apiKey };\n }\n\n let cdnOrigin: string | undefined;\n let warning: string | undefined;\n if (envCdnOrigin) {\n cdnOrigin = envCdnOrigin;\n if (isLocalOrigin(envCdnOrigin)) {\n warning = `BOS_BUNDLE_CDN_ORIGIN is a local URL (${envCdnOrigin}) — deploying to a local stack, published bundle URLs will not resolve publicly.`;\n }\n } else if (configCdnOrigin) {\n if (isLocalOrigin(configCdnOrigin)) {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n `the authored config sets cdn.origin to the local URL ${configCdnOrigin} — deploy bundle ` +\n \"origins must be publicly reachable. Set BOS_BUNDLE_CDN_ORIGIN for a local deploy, \" +\n \"or fix cdn.origin (inherited via extends).\",\n };\n }\n cdnOrigin = configCdnOrigin;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n \"CDN deploy requires a bundle origin — set `cdn.origin` in bos.config.json \" +\n \"(children inherit it from the base runtime via extends) or BOS_BUNDLE_CDN_ORIGIN \" +\n \"in the environment.\",\n };\n }\n\n let storageOrigin: string;\n if (storageEnvOrigin) {\n storageOrigin = storageEnvOrigin;\n if (isLocalOrigin(storageEnvOrigin) && !warning) {\n warning = `BOS_STORAGE_ORIGIN is a local URL (${storageEnvOrigin}) — uploading to a local stack.`;\n }\n } else if (sessionSite) {\n if (isLocalOrigin(sessionSite)) {\n return {\n cdnOrigin,\n storageOrigin: sessionSite,\n apiKey,\n error:\n `The CLI session pins uploads to ${sessionSite} — a local site can never receive ` +\n \"production bundles. Re-run `bos login` against the production site, or set \" +\n \"BOS_STORAGE_ORIGIN explicitly.\",\n };\n }\n storageOrigin = sessionSite;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n warning,\n error:\n `Bundle uploads require an upload-service origin — the gateway (${input.gateway}) no longer serves ` +\n \"uploads (ADR 0027: the upload service is standalone at upload.everything.dev). Set BOS_STORAGE_ORIGIN \" +\n \"in the environment (CI) or re-run `bos login` against the production site so the session pins it.\",\n };\n }\n\n if (!apiKey) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error:\n \"CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key \" +\n \"to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)\",\n };\n }\n if (input.session?.accountId && input.session.accountId !== input.account) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error: `The CLI session was created for ${input.session.accountId}, but the configured account is ${input.account}. Re-run bos login under the matching account.`,\n };\n }\n return { cdnOrigin, storageOrigin, apiKey, warning };\n}\n\n/**\n * Pre-upload probe: the storage origin must actually be the bundle-upload\n * service.\n * `/health` is the primary readiness check, with MCP kept as a compatibility\n * fallback for older fixtures and legacy hosts.\n */\nexport async function probeStorageOrigin(\n storageOrigin: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<string | undefined> {\n const base = storageOrigin.replace(/\\/$/, \"\");\n const healthUrl = `${base}/health`;\n const mcpUrl = `${base}/.well-known/mcp.json`;\n let response: Response;\n try {\n response = await fetchImpl(healthUrl, { headers: { accept: \"text/plain\" } });\n } catch (error) {\n return `unreachable (${String(error instanceof Error ? error.message : error)})`;\n }\n if (response.ok) {\n const body = await response.text().catch(() => \"\");\n if (body.trim() === \"OK\" || body.trim().length === 0) return undefined;\n }\n let mcpResponse: Response;\n try {\n mcpResponse = await fetchImpl(mcpUrl, { headers: { accept: \"application/json\" } });\n } catch {\n return `GET /health responded ${response.status}; fallback /.well-known/mcp.json unreachable`;\n }\n if (!mcpResponse.ok) {\n return `GET /health responded ${response.status}; GET /.well-known/mcp.json responded ${mcpResponse.status}`;\n }\n try {\n const body = await mcpResponse.json();\n if (!body || typeof body !== \"object\") {\n return \"did not return the platform MCP descriptor\";\n }\n return undefined;\n } catch {\n return \"did not return the platform MCP descriptor (non-JSON response)\";\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cdn-deploy.cjs","names":[],"sources":["../src/cdn-deploy.ts"],"sourcesContent":["interface SessionCredentialLike {\n apiKey?: string;\n siteUrl?: string;\n accountId?: string | null;\n}\n\nexport interface CdnDeployInputs {\n cdnOrigin: string | undefined;\n storageOrigin: string;\n apiKey: string | undefined;\n warning?: string;\n error?: string;\n}\n\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\", \"local\"]);\n\n/** Local/private destinations can never serve production bundles. */\nexport function isLocalOrigin(url: string): boolean {\n try {\n const { hostname, protocol } = new URL(url);\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n return (\n LOCAL_HOSTNAMES.has(host) ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^10\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^192\\.168\\.\\d+\\.\\d+$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d+\\.\\d+$/.test(host) ||\n host.startsWith(\"169.254.\") ||\n (host.includes(\":\") &&\n (host.startsWith(\"fe80:\") || host.startsWith(\"fc\") || host.startsWith(\"fd\")))\n );\n } catch {\n return true;\n }\n}\n\n/**\n * Deploy origin resolution (ADR 0020, as amended): the CDN origin comes from\n * the env or the authored `cdn.origin` (inherited via extends) — a hard\n * requirement when uploads are planned, because the host never serves\n * `/bundles` itself. The upload origin is the env, the `bos login` session's\n * siteUrl, or the runtime's own gateway. A session pinned to a local site is\n * a hard error (the origin can only ever be a squatter's port); an env-pinned\n * local origin is deliberate local testing and only warns. Config-only\n * publishes (`uploadsPlanned: false`) ship the committed config verbatim —\n * no origins are resolved and nothing is required.\n */\nexport function resolveCdnDeployInputs(input: {\n env: Record<string, string | undefined>;\n bosConfig: { cdn?: { origin?: string } } | null;\n session: SessionCredentialLike | null;\n custodyAccount?: string;\n account: string;\n gateway: string;\n uploadsPlanned: boolean;\n}): CdnDeployInputs {\n const envCdnOrigin = input.env.BOS_BUNDLE_CDN_ORIGIN?.replace(/\\/$/, \"\");\n const configCdnOrigin = input.bosConfig?.cdn?.origin?.replace(/\\/$/, \"\");\n const storageEnvOrigin = input.env.BOS_STORAGE_ORIGIN?.replace(/\\/$/, \"\");\n const sessionSite = input.session?.siteUrl?.replace(/\\/$/, \"\");\n const apiKey = input.env.BOS_STORAGE_API_KEY ?? input.session?.apiKey;\n const uploadsPlanned = input.uploadsPlanned;\n\n const storageOriginDefault = storageEnvOrigin ?? sessionSite ?? `https://${input.gateway}`;\n\n if (!uploadsPlanned) {\n return { cdnOrigin: undefined, storageOrigin: storageOriginDefault, apiKey };\n }\n\n let cdnOrigin: string | undefined;\n let warning: string | undefined;\n if (envCdnOrigin) {\n cdnOrigin = envCdnOrigin;\n if (isLocalOrigin(envCdnOrigin)) {\n warning = `BOS_BUNDLE_CDN_ORIGIN is a local URL (${envCdnOrigin}) — deploying to a local stack, published bundle URLs will not resolve publicly.`;\n }\n } else if (configCdnOrigin) {\n if (isLocalOrigin(configCdnOrigin)) {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n `the authored config sets cdn.origin to the local URL ${configCdnOrigin} — deploy bundle ` +\n \"origins must be publicly reachable. Set BOS_BUNDLE_CDN_ORIGIN for a local deploy, \" +\n \"or fix cdn.origin (inherited via extends).\",\n };\n }\n cdnOrigin = configCdnOrigin;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n \"CDN deploy requires a bundle origin — set `cdn.origin` in bos.config.json \" +\n \"(children inherit it from the base runtime via extends) or BOS_BUNDLE_CDN_ORIGIN \" +\n \"in the environment.\",\n };\n }\n\n let storageOrigin: string;\n if (storageEnvOrigin) {\n storageOrigin = storageEnvOrigin;\n if (isLocalOrigin(storageEnvOrigin) && !warning) {\n warning = `BOS_STORAGE_ORIGIN is a local URL (${storageEnvOrigin}) — uploading to a local stack.`;\n }\n } else if (sessionSite) {\n if (isLocalOrigin(sessionSite)) {\n return {\n cdnOrigin,\n storageOrigin: sessionSite,\n apiKey,\n error:\n `The CLI session pins uploads to ${sessionSite} — a local site can never receive ` +\n \"production bundles. Re-run `bos login` against the production site, or set \" +\n \"BOS_STORAGE_ORIGIN explicitly.\",\n };\n }\n storageOrigin = sessionSite;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n warning,\n error:\n `Bundle uploads require an upload-service origin — the gateway (${input.gateway}) no longer serves ` +\n \"uploads (ADR 0027: the upload service is standalone at upload.everything.dev). Set BOS_STORAGE_ORIGIN \" +\n \"in the environment (CI) or re-run `bos login` against the production site so the session pins it.\",\n };\n }\n\n if (!apiKey) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error:\n \"CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key \" +\n \"to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)\",\n };\n }\n const uploadIdentities = new Set(\n [input.session?.accountId, input.custodyAccount].filter((id): id is string => !!id),\n );\n if (uploadIdentities.size > 0 && !uploadIdentities.has(input.account)) {\n const sessionAccount = input.session?.accountId;\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error: sessionAccount\n ? `The CLI session was created for ${sessionAccount}, but the configured account is ${input.account}. Re-run bos login under the matching account.`\n : `The configured account is ${input.account}, but the custody publish identity is ${input.custodyAccount}. Set the authored config's account to the custody wallet account.`,\n };\n }\n return { cdnOrigin, storageOrigin, apiKey, warning };\n}\n\n/**\n * Pre-upload probe: the storage origin must actually be the bundle-upload\n * service.\n * `/health` is the primary readiness check, with MCP kept as a compatibility\n * fallback for older fixtures and legacy hosts.\n */\nexport async function probeStorageOrigin(\n storageOrigin: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<string | undefined> {\n const base = storageOrigin.replace(/\\/$/, \"\");\n const healthUrl = `${base}/health`;\n const mcpUrl = `${base}/.well-known/mcp.json`;\n let response: Response;\n try {\n response = await fetchImpl(healthUrl, { headers: { accept: \"text/plain\" } });\n } catch (error) {\n return `unreachable (${String(error instanceof Error ? error.message : error)})`;\n }\n if (response.ok) {\n const body = await response.text().catch(() => \"\");\n if (body.trim() === \"OK\" || body.trim().length === 0) return undefined;\n }\n let mcpResponse: Response;\n try {\n mcpResponse = await fetchImpl(mcpUrl, { headers: { accept: \"application/json\" } });\n } catch {\n return `GET /health responded ${response.status}; fallback /.well-known/mcp.json unreachable`;\n }\n if (!mcpResponse.ok) {\n return `GET /health responded ${response.status}; GET /.well-known/mcp.json responded ${mcpResponse.status}`;\n }\n try {\n const body = await mcpResponse.json();\n if (!body || typeof body !== \"object\") {\n return \"did not return the platform MCP descriptor\";\n }\n return undefined;\n } catch {\n return \"did not return the platform MCP descriptor (non-JSON response)\";\n }\n}\n"],"mappings":"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"}
|
package/dist/cdn-deploy.mjs
CHANGED
|
@@ -87,13 +87,17 @@ function resolveCdnDeployInputs(input) {
|
|
|
87
87
|
warning,
|
|
88
88
|
error: "CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)"
|
|
89
89
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const uploadIdentities = new Set([input.session?.accountId, input.custodyAccount].filter((id) => !!id));
|
|
91
|
+
if (uploadIdentities.size > 0 && !uploadIdentities.has(input.account)) {
|
|
92
|
+
const sessionAccount = input.session?.accountId;
|
|
93
|
+
return {
|
|
94
|
+
cdnOrigin,
|
|
95
|
+
storageOrigin,
|
|
96
|
+
apiKey,
|
|
97
|
+
warning,
|
|
98
|
+
error: sessionAccount ? `The CLI session was created for ${sessionAccount}, but the configured account is ${input.account}. Re-run bos login under the matching account.` : `The configured account is ${input.account}, but the custody publish identity is ${input.custodyAccount}. Set the authored config's account to the custody wallet account.`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
97
101
|
return {
|
|
98
102
|
cdnOrigin,
|
|
99
103
|
storageOrigin,
|
package/dist/cdn-deploy.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdn-deploy.mjs","names":[],"sources":["../src/cdn-deploy.ts"],"sourcesContent":["interface SessionCredentialLike {\n apiKey?: string;\n siteUrl?: string;\n accountId?: string | null;\n}\n\nexport interface CdnDeployInputs {\n cdnOrigin: string | undefined;\n storageOrigin: string;\n apiKey: string | undefined;\n warning?: string;\n error?: string;\n}\n\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\", \"local\"]);\n\n/** Local/private destinations can never serve production bundles. */\nexport function isLocalOrigin(url: string): boolean {\n try {\n const { hostname, protocol } = new URL(url);\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n return (\n LOCAL_HOSTNAMES.has(host) ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^10\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^192\\.168\\.\\d+\\.\\d+$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d+\\.\\d+$/.test(host) ||\n host.startsWith(\"169.254.\") ||\n (host.includes(\":\") &&\n (host.startsWith(\"fe80:\") || host.startsWith(\"fc\") || host.startsWith(\"fd\")))\n );\n } catch {\n return true;\n }\n}\n\n/**\n * Deploy origin resolution (ADR 0020, as amended): the CDN origin comes from\n * the env or the authored `cdn.origin` (inherited via extends) — a hard\n * requirement when uploads are planned, because the host never serves\n * `/bundles` itself. The upload origin is the env, the `bos login` session's\n * siteUrl, or the runtime's own gateway. A session pinned to a local site is\n * a hard error (the origin can only ever be a squatter's port); an env-pinned\n * local origin is deliberate local testing and only warns. Config-only\n * publishes (`uploadsPlanned: false`) ship the committed config verbatim —\n * no origins are resolved and nothing is required.\n */\nexport function resolveCdnDeployInputs(input: {\n env: Record<string, string | undefined>;\n bosConfig: { cdn?: { origin?: string } } | null;\n session: SessionCredentialLike | null;\n account: string;\n gateway: string;\n uploadsPlanned: boolean;\n}): CdnDeployInputs {\n const envCdnOrigin = input.env.BOS_BUNDLE_CDN_ORIGIN?.replace(/\\/$/, \"\");\n const configCdnOrigin = input.bosConfig?.cdn?.origin?.replace(/\\/$/, \"\");\n const storageEnvOrigin = input.env.BOS_STORAGE_ORIGIN?.replace(/\\/$/, \"\");\n const sessionSite = input.session?.siteUrl?.replace(/\\/$/, \"\");\n const apiKey = input.env.BOS_STORAGE_API_KEY ?? input.session?.apiKey;\n const uploadsPlanned = input.uploadsPlanned;\n\n const storageOriginDefault = storageEnvOrigin ?? sessionSite ?? `https://${input.gateway}`;\n\n if (!uploadsPlanned) {\n return { cdnOrigin: undefined, storageOrigin: storageOriginDefault, apiKey };\n }\n\n let cdnOrigin: string | undefined;\n let warning: string | undefined;\n if (envCdnOrigin) {\n cdnOrigin = envCdnOrigin;\n if (isLocalOrigin(envCdnOrigin)) {\n warning = `BOS_BUNDLE_CDN_ORIGIN is a local URL (${envCdnOrigin}) — deploying to a local stack, published bundle URLs will not resolve publicly.`;\n }\n } else if (configCdnOrigin) {\n if (isLocalOrigin(configCdnOrigin)) {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n `the authored config sets cdn.origin to the local URL ${configCdnOrigin} — deploy bundle ` +\n \"origins must be publicly reachable. Set BOS_BUNDLE_CDN_ORIGIN for a local deploy, \" +\n \"or fix cdn.origin (inherited via extends).\",\n };\n }\n cdnOrigin = configCdnOrigin;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n \"CDN deploy requires a bundle origin — set `cdn.origin` in bos.config.json \" +\n \"(children inherit it from the base runtime via extends) or BOS_BUNDLE_CDN_ORIGIN \" +\n \"in the environment.\",\n };\n }\n\n let storageOrigin: string;\n if (storageEnvOrigin) {\n storageOrigin = storageEnvOrigin;\n if (isLocalOrigin(storageEnvOrigin) && !warning) {\n warning = `BOS_STORAGE_ORIGIN is a local URL (${storageEnvOrigin}) — uploading to a local stack.`;\n }\n } else if (sessionSite) {\n if (isLocalOrigin(sessionSite)) {\n return {\n cdnOrigin,\n storageOrigin: sessionSite,\n apiKey,\n error:\n `The CLI session pins uploads to ${sessionSite} — a local site can never receive ` +\n \"production bundles. Re-run `bos login` against the production site, or set \" +\n \"BOS_STORAGE_ORIGIN explicitly.\",\n };\n }\n storageOrigin = sessionSite;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n warning,\n error:\n `Bundle uploads require an upload-service origin — the gateway (${input.gateway}) no longer serves ` +\n \"uploads (ADR 0027: the upload service is standalone at upload.everything.dev). Set BOS_STORAGE_ORIGIN \" +\n \"in the environment (CI) or re-run `bos login` against the production site so the session pins it.\",\n };\n }\n\n if (!apiKey) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error:\n \"CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key \" +\n \"to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)\",\n };\n }\n if (input.session?.accountId && input.session.accountId !== input.account) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error: `The CLI session was created for ${input.session.accountId}, but the configured account is ${input.account}. Re-run bos login under the matching account.`,\n };\n }\n return { cdnOrigin, storageOrigin, apiKey, warning };\n}\n\n/**\n * Pre-upload probe: the storage origin must actually be the bundle-upload\n * service.\n * `/health` is the primary readiness check, with MCP kept as a compatibility\n * fallback for older fixtures and legacy hosts.\n */\nexport async function probeStorageOrigin(\n storageOrigin: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<string | undefined> {\n const base = storageOrigin.replace(/\\/$/, \"\");\n const healthUrl = `${base}/health`;\n const mcpUrl = `${base}/.well-known/mcp.json`;\n let response: Response;\n try {\n response = await fetchImpl(healthUrl, { headers: { accept: \"text/plain\" } });\n } catch (error) {\n return `unreachable (${String(error instanceof Error ? error.message : error)})`;\n }\n if (response.ok) {\n const body = await response.text().catch(() => \"\");\n if (body.trim() === \"OK\" || body.trim().length === 0) return undefined;\n }\n let mcpResponse: Response;\n try {\n mcpResponse = await fetchImpl(mcpUrl, { headers: { accept: \"application/json\" } });\n } catch {\n return `GET /health responded ${response.status}; fallback /.well-known/mcp.json unreachable`;\n }\n if (!mcpResponse.ok) {\n return `GET /health responded ${response.status}; GET /.well-known/mcp.json responded ${mcpResponse.status}`;\n }\n try {\n const body = await mcpResponse.json();\n if (!body || typeof body !== \"object\") {\n return \"did not return the platform MCP descriptor\";\n }\n return undefined;\n } catch {\n return \"did not return the platform MCP descriptor (non-JSON response)\";\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cdn-deploy.mjs","names":[],"sources":["../src/cdn-deploy.ts"],"sourcesContent":["interface SessionCredentialLike {\n apiKey?: string;\n siteUrl?: string;\n accountId?: string | null;\n}\n\nexport interface CdnDeployInputs {\n cdnOrigin: string | undefined;\n storageOrigin: string;\n apiKey: string | undefined;\n warning?: string;\n error?: string;\n}\n\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\", \"local\"]);\n\n/** Local/private destinations can never serve production bundles. */\nexport function isLocalOrigin(url: string): boolean {\n try {\n const { hostname, protocol } = new URL(url);\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n return (\n LOCAL_HOSTNAMES.has(host) ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^10\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^192\\.168\\.\\d+\\.\\d+$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d+\\.\\d+$/.test(host) ||\n host.startsWith(\"169.254.\") ||\n (host.includes(\":\") &&\n (host.startsWith(\"fe80:\") || host.startsWith(\"fc\") || host.startsWith(\"fd\")))\n );\n } catch {\n return true;\n }\n}\n\n/**\n * Deploy origin resolution (ADR 0020, as amended): the CDN origin comes from\n * the env or the authored `cdn.origin` (inherited via extends) — a hard\n * requirement when uploads are planned, because the host never serves\n * `/bundles` itself. The upload origin is the env, the `bos login` session's\n * siteUrl, or the runtime's own gateway. A session pinned to a local site is\n * a hard error (the origin can only ever be a squatter's port); an env-pinned\n * local origin is deliberate local testing and only warns. Config-only\n * publishes (`uploadsPlanned: false`) ship the committed config verbatim —\n * no origins are resolved and nothing is required.\n */\nexport function resolveCdnDeployInputs(input: {\n env: Record<string, string | undefined>;\n bosConfig: { cdn?: { origin?: string } } | null;\n session: SessionCredentialLike | null;\n custodyAccount?: string;\n account: string;\n gateway: string;\n uploadsPlanned: boolean;\n}): CdnDeployInputs {\n const envCdnOrigin = input.env.BOS_BUNDLE_CDN_ORIGIN?.replace(/\\/$/, \"\");\n const configCdnOrigin = input.bosConfig?.cdn?.origin?.replace(/\\/$/, \"\");\n const storageEnvOrigin = input.env.BOS_STORAGE_ORIGIN?.replace(/\\/$/, \"\");\n const sessionSite = input.session?.siteUrl?.replace(/\\/$/, \"\");\n const apiKey = input.env.BOS_STORAGE_API_KEY ?? input.session?.apiKey;\n const uploadsPlanned = input.uploadsPlanned;\n\n const storageOriginDefault = storageEnvOrigin ?? sessionSite ?? `https://${input.gateway}`;\n\n if (!uploadsPlanned) {\n return { cdnOrigin: undefined, storageOrigin: storageOriginDefault, apiKey };\n }\n\n let cdnOrigin: string | undefined;\n let warning: string | undefined;\n if (envCdnOrigin) {\n cdnOrigin = envCdnOrigin;\n if (isLocalOrigin(envCdnOrigin)) {\n warning = `BOS_BUNDLE_CDN_ORIGIN is a local URL (${envCdnOrigin}) — deploying to a local stack, published bundle URLs will not resolve publicly.`;\n }\n } else if (configCdnOrigin) {\n if (isLocalOrigin(configCdnOrigin)) {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n `the authored config sets cdn.origin to the local URL ${configCdnOrigin} — deploy bundle ` +\n \"origins must be publicly reachable. Set BOS_BUNDLE_CDN_ORIGIN for a local deploy, \" +\n \"or fix cdn.origin (inherited via extends).\",\n };\n }\n cdnOrigin = configCdnOrigin;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n \"CDN deploy requires a bundle origin — set `cdn.origin` in bos.config.json \" +\n \"(children inherit it from the base runtime via extends) or BOS_BUNDLE_CDN_ORIGIN \" +\n \"in the environment.\",\n };\n }\n\n let storageOrigin: string;\n if (storageEnvOrigin) {\n storageOrigin = storageEnvOrigin;\n if (isLocalOrigin(storageEnvOrigin) && !warning) {\n warning = `BOS_STORAGE_ORIGIN is a local URL (${storageEnvOrigin}) — uploading to a local stack.`;\n }\n } else if (sessionSite) {\n if (isLocalOrigin(sessionSite)) {\n return {\n cdnOrigin,\n storageOrigin: sessionSite,\n apiKey,\n error:\n `The CLI session pins uploads to ${sessionSite} — a local site can never receive ` +\n \"production bundles. Re-run `bos login` against the production site, or set \" +\n \"BOS_STORAGE_ORIGIN explicitly.\",\n };\n }\n storageOrigin = sessionSite;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n warning,\n error:\n `Bundle uploads require an upload-service origin — the gateway (${input.gateway}) no longer serves ` +\n \"uploads (ADR 0027: the upload service is standalone at upload.everything.dev). Set BOS_STORAGE_ORIGIN \" +\n \"in the environment (CI) or re-run `bos login` against the production site so the session pins it.\",\n };\n }\n\n if (!apiKey) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error:\n \"CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key \" +\n \"to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)\",\n };\n }\n const uploadIdentities = new Set(\n [input.session?.accountId, input.custodyAccount].filter((id): id is string => !!id),\n );\n if (uploadIdentities.size > 0 && !uploadIdentities.has(input.account)) {\n const sessionAccount = input.session?.accountId;\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error: sessionAccount\n ? `The CLI session was created for ${sessionAccount}, but the configured account is ${input.account}. Re-run bos login under the matching account.`\n : `The configured account is ${input.account}, but the custody publish identity is ${input.custodyAccount}. Set the authored config's account to the custody wallet account.`,\n };\n }\n return { cdnOrigin, storageOrigin, apiKey, warning };\n}\n\n/**\n * Pre-upload probe: the storage origin must actually be the bundle-upload\n * service.\n * `/health` is the primary readiness check, with MCP kept as a compatibility\n * fallback for older fixtures and legacy hosts.\n */\nexport async function probeStorageOrigin(\n storageOrigin: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<string | undefined> {\n const base = storageOrigin.replace(/\\/$/, \"\");\n const healthUrl = `${base}/health`;\n const mcpUrl = `${base}/.well-known/mcp.json`;\n let response: Response;\n try {\n response = await fetchImpl(healthUrl, { headers: { accept: \"text/plain\" } });\n } catch (error) {\n return `unreachable (${String(error instanceof Error ? error.message : error)})`;\n }\n if (response.ok) {\n const body = await response.text().catch(() => \"\");\n if (body.trim() === \"OK\" || body.trim().length === 0) return undefined;\n }\n let mcpResponse: Response;\n try {\n mcpResponse = await fetchImpl(mcpUrl, { headers: { accept: \"application/json\" } });\n } catch {\n return `GET /health responded ${response.status}; fallback /.well-known/mcp.json unreachable`;\n }\n if (!mcpResponse.ok) {\n return `GET /health responded ${response.status}; GET /.well-known/mcp.json responded ${mcpResponse.status}`;\n }\n try {\n const body = await mcpResponse.json();\n if (!body || typeof body !== \"object\") {\n return \"did not return the platform MCP descriptor\";\n }\n return undefined;\n } catch {\n return \"did not return the platform MCP descriptor (non-JSON response)\";\n }\n}\n"],"mappings":"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"}
|