everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1 lines • 16.1 kB
Source Map (JSON)
{"version":3,"file":"preflight.cjs","names":["fetchResponse","join","existsSync","readFileSync","colors","getNetworkIdForAccount","buildRegistryConfigUrlForNetwork","readSessionHandle","resolveSigningStrategy","describeSigningStrategy","resolveCdnDeployInputs","process","probeStorageOrigin","fetchPublishedConfigFromFastKv","getRegistryNamespaceForNetwork","selectWorkspaceTargets"],"sources":["../../../src/services/deploy/preflight.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { selectWorkspaceTargets } from \"../../services/workspace/build-train\";\nimport type { BosConfig, PublishConfig } from \"../../types\";\nimport { fetchResponse } from \"../../utils/http-client\";\nimport { getNetworkIdForAccount } from \"../../utils/network\";\nimport { colors } from \"../../utils/theme\";\nimport {\n describeSigningStrategy,\n resolveSigningStrategy,\n type SigningStrategy,\n} from \"../auth/near-signer\";\nimport type { CustodyWalletRecord, SessionCredential } from \"../auth/session\";\nimport { readSessionHandle } from \"../auth/session\";\nimport {\n buildRegistryConfigUrlForNetwork,\n fetchPublishedConfigFromFastKv,\n getRegistryNamespaceForNetwork,\n type NetworkId,\n} from \"../registry/fastkv\";\nimport { type CdnDeployInputs, probeStorageOrigin, resolveCdnDeployInputs } from \"./cdn\";\n\n/**\n * Pure publish preflight (resolve, don't mutate) — every failure returns\n * before the build train runs (issue #287). Dry-run exits after the auth\n * guards, before any signing (keychain/TTY side effects).\n *\n * The validated auth handles are captured on the plan (guards once per run,\n * ticket #422): the train's submit phase consumes `plan.custodyWallet` /\n * `plan.credential` instead of re-reading and re-validating the session file.\n */\n\nexport type PublishedConfigState = \"fetched\" | \"missing\" | \"unreachable\";\n\nexport interface PublishPreflightPlan {\n isStaging: boolean;\n account: string;\n gateway: string;\n network: NetworkId;\n registryUrl: string;\n registryNamespace: string;\n targets: string[];\n useWallet: boolean;\n useCustody: boolean;\n strategy?: SigningStrategy;\n cdnOrigin: string | undefined;\n storageOrigin: string;\n storageApiKey?: string;\n /** The config currently published to FastKV (null when missing or unreachable). */\n publishedConfig: Record<string, unknown> | null;\n /** Whether the preflight FastKV read succeeded, found nothing, or failed. */\n publishedConfigState: PublishedConfigState;\n /** Validated by the custody guard — present iff useCustody. */\n custodyWallet?: CustodyWalletRecord;\n /** Validated by the session guard — present iff useWallet. */\n credential?: SessionCredential;\n}\n\nexport type PublishPreflight =\n | { kind: \"dry-run\"; registryUrl: string }\n | { kind: \"error\"; registryUrl: string; error: string }\n | { kind: \"ready\"; plan: PublishPreflightPlan };\n\nconst FRAMEWORK_PACKAGE_NAMES = [\"better-near-auth\", \"every-plugin\", \"everything-dev\"] as const;\n\n/**\n * Whether npm serves `name@version` (true), definitively does not (404 —\n * false), or whether publishability could not be determined (network error,\n * unexpected status — null; callers must not block on that).\n */\nexport async function npmVersionPublished(name: string, version: string): Promise<boolean | null> {\n try {\n const res = await fetchResponse(`https://registry.npmjs.org/${name}/${version}`, {\n headers: { Accept: \"application/json\" },\n });\n if (res.status === 200) return true;\n if (res.status === 404) return false;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * The npm-publish gate: a workspace that vendors the framework packages'\n * sources must not ship bundles ahead of npm — the update check, child\n * scaffolds (`bos init`), and template syncs all read those versions from\n * the registry. Reads only apply when the sources are workspace-local (the\n * parent repo); child projects install the framework from npm and are\n * inherently in sync.\n */\nexport async function findUnpublishedFrameworkPackages(\n configDir: string,\n): Promise<Array<{ name: string; version: string }>> {\n const unpublished: Array<{ name: string; version: string }> = [];\n for (const name of FRAMEWORK_PACKAGE_NAMES) {\n const manifestPath = join(configDir, \"packages\", name, \"package.json\");\n if (!existsSync(manifestPath)) continue;\n const version = (JSON.parse(readFileSync(manifestPath, \"utf-8\")) as { version?: string })\n .version;\n if (!version) continue;\n const published = await npmVersionPublished(name, version);\n if (published === false) {\n unpublished.push({ name, version });\n } else if (published === null) {\n console.log(\n colors.yellow(\n ` ! Could not verify ${name}@${version} on npm (registry unreachable) — continuing`,\n ),\n );\n }\n }\n return unpublished;\n}\n\ninterface PreflightInput {\n bosConfig: BosConfig;\n configDir: string;\n env: \"production\" | \"staging\";\n build: boolean;\n dryRun: boolean;\n packages: string;\n network?: \"mainnet\" | \"testnet\";\n privateKey?: string;\n wallet?: boolean;\n registry?: string;\n}\n\nexport async function preflightPublish(input: PreflightInput): Promise<PublishPreflight> {\n const { configDir } = input;\n const bosConfig = input.bosConfig;\n\n const isStaging = input.env === \"staging\";\n const account = isStaging ? (bosConfig.staging?.account ?? bosConfig.account) : bosConfig.account;\n const gateway = isStaging ? (bosConfig.staging?.domain ?? bosConfig.domain) : bosConfig.domain;\n if (!gateway) {\n return {\n kind: \"error\",\n registryUrl: \"\",\n error: \"authored config must define domain to publish\",\n };\n }\n\n const network: NetworkId = input.network ?? getNetworkIdForAccount(account);\n const registryUrl = buildRegistryConfigUrlForNetwork(network, account, gateway, input.registry);\n const fail = (error: string): PublishPreflight => ({ kind: \"error\", registryUrl, error });\n\n // One session-file read for the whole run (guards once, ticket #422): the\n // wallet/custody guards validate + capture their handles on the plan, and\n // CDN credential derivation consumes the raw credential as before.\n const sessionFile = readSessionHandle(configDir);\n const publishAuth: PublishConfig[\"auth\"] = bosConfig.publish?.auth;\n const governsWalletPublish = (publishAuth === \"session\" || input.wallet) && !input.privateKey;\n let credential: SessionCredential | undefined;\n if (governsWalletPublish) {\n if (!sessionFile?.credential) {\n return fail(\n (input.wallet\n ? \"--wallet requires\"\n : 'the authored config sets publish.auth = \"session\", but') +\n \" no CLI session is stored in .bos/ for this project. Run bos login to create one.\",\n );\n }\n if (sessionFile.credential.accountId && sessionFile.credential.accountId !== account) {\n return fail(\n `The CLI session was created for ${sessionFile.credential.accountId}, but the configured ` +\n `account is ${account}. Gasless wallet publish relays the FastKV write under the session's ` +\n \"NEAR account. Run bos login again under the matching account.\",\n );\n }\n credential = sessionFile.credential;\n }\n const useCustody = publishAuth === \"custody\" && !input.privateKey && !input.wallet;\n let custodyWallet: CustodyWalletRecord | undefined;\n if (useCustody) {\n const wallet = sessionFile?.custodyWallet;\n if (!wallet) {\n return fail(\n 'the authored config sets publish.auth = \"custody\", but no custody wallet is stored in ' +\n \".bos/. Run `bos login --custody` first.\",\n );\n }\n if (wallet.network !== network) {\n return fail(\n `The custody wallet was registered on ${wallet.network}, but this project publishes ` +\n `on ${network}. Run \\`bos login --custody\\` from a ${network} account.`,\n );\n }\n if (wallet.nearAccountId !== account) {\n return fail(\n `publish.auth = \"custody\" publishes under the custody wallet's account, but the configured ` +\n `account is ${account} and the custody wallet account is ${wallet.nearAccountId}. ` +\n \"Set the authored config's account to the custody wallet account.\",\n );\n }\n custodyWallet = wallet;\n }\n\n if (input.dryRun) {\n return { kind: \"dry-run\", registryUrl };\n }\n\n const useWallet = input.wallet === true;\n let strategy: SigningStrategy | undefined;\n if (useWallet) {\n console.log(\n ` Signing via ${colors.cyan(\"gasless NEP-366 delegate action (relayed by the platform relayer)\")}`,\n );\n } else {\n try {\n strategy = await resolveSigningStrategy({ privateKey: input.privateKey, account, network });\n console.log(` Signing via ${colors.cyan(describeSigningStrategy(strategy))}`);\n } catch (error) {\n return fail(error instanceof Error ? error.message : \"Unknown error\");\n }\n }\n\n // CDN deploy credentials (ADR 0020) — resolved before the build train so a\n // missing bundle origin or BOS_STORAGE_API_KEY fails fast instead of after\n // the full build. Config-only publishes ship the committed config verbatim;\n // nothing is uploaded, so no deploy origins are required.\n let cdnDeploy: CdnDeployInputs = {\n cdnOrigin: undefined,\n storageOrigin: \"\",\n apiKey: undefined,\n };\n if (input.build) {\n const unpublished = await findUnpublishedFrameworkPackages(configDir);\n if (unpublished.length > 0) {\n const listing = unpublished.map((pkg) => `${pkg.name}@${pkg.version}`).join(\", \");\n return fail(\n `Framework package(s) ${listing} are not published to npm yet — deploys must not ship ` +\n \"bundles ahead of the registry (child scaffolds and update checks read these versions). \" +\n \"Re-run the Release workflow (or `pnpm run release`) first.\",\n );\n }\n\n cdnDeploy = resolveCdnDeployInputs({\n env: process.env as Record<string, string | undefined>,\n bosConfig,\n session: sessionFile?.credential ?? null,\n custodyAccount: useCustody ? custodyWallet?.nearAccountId : undefined,\n account,\n gateway,\n uploadsPlanned: input.build,\n });\n if (cdnDeploy.error) {\n return fail(cdnDeploy.error);\n }\n if (cdnDeploy.warning) {\n console.log(colors.yellow(` ! ${cdnDeploy.warning}`));\n }\n const cdnOrigin = cdnDeploy.cdnOrigin;\n if (!cdnOrigin) {\n return fail(\"CDN deploy resolved no bundle origin\");\n }\n const probeError = await probeStorageOrigin(cdnDeploy.storageOrigin);\n if (probeError) {\n return fail(\n `The bundle upload origin ${cdnDeploy.storageOrigin} is not running the platform API — ` +\n `${probeError}. Check BOS_STORAGE_ORIGIN / the bos login session, then re-run.`,\n );\n }\n }\n\n // Registry reachability + published-state capture: one FastKV read. A\n // missing config is fine for a first publish — an early signal, not a hard\n // gate. The captured config is the merge base for a config-only publish on\n // a TS-form project (authored hand-edits win; live pipeline state carries).\n let publishedConfig: Record<string, unknown> | null = null;\n let publishedConfigState: PublishedConfigState = \"missing\";\n try {\n publishedConfig = await fetchPublishedConfigFromFastKv<Record<string, unknown>>(registryUrl);\n publishedConfigState = \"fetched\";\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.startsWith(\"No config found\")) {\n console.log(colors.dim(\" Note: no published config found yet (first publish) — continuing\"));\n } else {\n publishedConfigState = \"unreachable\";\n console.log(colors.dim(\" Note: registry read failed — continuing\"));\n }\n }\n\n return {\n kind: \"ready\",\n plan: {\n isStaging,\n account,\n gateway,\n network,\n registryUrl,\n registryNamespace: getRegistryNamespaceForNetwork(network, input.registry),\n targets: selectWorkspaceTargets(input.packages, bosConfig),\n useWallet,\n useCustody,\n strategy,\n cdnOrigin: cdnDeploy.cdnOrigin,\n storageOrigin: cdnDeploy.storageOrigin,\n storageApiKey: cdnDeploy.apiKey,\n publishedConfig,\n publishedConfigState,\n ...(custodyWallet ? { custodyWallet } : {}),\n ...(credential ? { credential } : {}),\n },\n };\n}\n"],"mappings":"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"}