UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

172 lines (170 loc) • 7.86 kB
import { fetchResponse } from "../../utils/http-client.mjs"; import { buildRegistryConfigUrlForNetwork, fetchPublishedConfigFromFastKv, getRegistryNamespaceForNetwork } from "../registry/fastkv.mjs"; import { getNetworkIdForAccount } from "../../utils/network.mjs"; import { colors } from "../../utils/theme.mjs"; import { selectWorkspaceTargets } from "../workspace/build-train.mjs"; import { readSessionHandle } from "../auth/session.mjs"; import { describeSigningStrategy, resolveSigningStrategy } from "../auth/near-signer.mjs"; import { probeStorageOrigin, resolveCdnDeployInputs } from "./cdn.mjs"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import process from "node:process"; //#region src/services/deploy/preflight.ts const FRAMEWORK_PACKAGE_NAMES = [ "better-near-auth", "every-plugin", "everything-dev" ]; /** * Whether npm serves `name@version` (true), definitively does not (404 — * false), or whether publishability could not be determined (network error, * unexpected status — null; callers must not block on that). */ async function npmVersionPublished(name, version) { try { const res = await fetchResponse(`https://registry.npmjs.org/${name}/${version}`, { headers: { Accept: "application/json" } }); if (res.status === 200) return true; if (res.status === 404) return false; return null; } catch { return null; } } /** * The npm-publish gate: a workspace that vendors the framework packages' * sources must not ship bundles ahead of npm — the update check, child * scaffolds (`bos init`), and template syncs all read those versions from * the registry. Reads only apply when the sources are workspace-local (the * parent repo); child projects install the framework from npm and are * inherently in sync. */ async function findUnpublishedFrameworkPackages(configDir) { const unpublished = []; for (const name of FRAMEWORK_PACKAGE_NAMES) { const manifestPath = join(configDir, "packages", name, "package.json"); if (!existsSync(manifestPath)) continue; const version = JSON.parse(readFileSync(manifestPath, "utf-8")).version; if (!version) continue; const published = await npmVersionPublished(name, version); if (published === false) unpublished.push({ name, version }); else if (published === null) console.log(colors.yellow(` ! Could not verify ${name}@${version} on npm (registry unreachable) — continuing`)); } return unpublished; } async function preflightPublish(input) { const { configDir } = input; const bosConfig = input.bosConfig; const isStaging = input.env === "staging"; const account = isStaging ? bosConfig.staging?.account ?? bosConfig.account : bosConfig.account; const gateway = isStaging ? bosConfig.staging?.domain ?? bosConfig.domain : bosConfig.domain; if (!gateway) return { kind: "error", registryUrl: "", error: "authored config must define domain to publish" }; const network = input.network ?? getNetworkIdForAccount(account); const registryUrl = buildRegistryConfigUrlForNetwork(network, account, gateway, input.registry); const fail = (error) => ({ kind: "error", registryUrl, error }); const sessionFile = readSessionHandle(configDir); const publishAuth = bosConfig.publish?.auth; const governsWalletPublish = (publishAuth === "session" || input.wallet) && !input.privateKey; let credential; if (governsWalletPublish) { if (!sessionFile?.credential) return fail((input.wallet ? "--wallet requires" : "the authored config sets publish.auth = \"session\", but") + " no CLI session is stored in .bos/ for this project. Run bos login to create one."); if (sessionFile.credential.accountId && sessionFile.credential.accountId !== account) return fail(`The CLI session was created for ${sessionFile.credential.accountId}, but the configured account is ${account}. Gasless wallet publish relays the FastKV write under the session's NEAR account. Run bos login again under the matching account.`); credential = sessionFile.credential; } const useCustody = publishAuth === "custody" && !input.privateKey && !input.wallet; let custodyWallet; if (useCustody) { const wallet = sessionFile?.custodyWallet; if (!wallet) return fail("the authored config sets publish.auth = \"custody\", but no custody wallet is stored in .bos/. Run `bos login --custody` first."); if (wallet.network !== network) return fail(`The custody wallet was registered on ${wallet.network}, but this project publishes on ${network}. Run \`bos login --custody\` from a ${network} account.`); if (wallet.nearAccountId !== account) return fail(`publish.auth = "custody" publishes under the custody wallet's account, but the configured account is ${account} and the custody wallet account is ${wallet.nearAccountId}. Set the authored config's account to the custody wallet account.`); custodyWallet = wallet; } if (input.dryRun) return { kind: "dry-run", registryUrl }; const useWallet = input.wallet === true; let strategy; if (useWallet) console.log(` Signing via ${colors.cyan("gasless NEP-366 delegate action (relayed by the platform relayer)")}`); else try { strategy = await resolveSigningStrategy({ privateKey: input.privateKey, account, network }); console.log(` Signing via ${colors.cyan(describeSigningStrategy(strategy))}`); } catch (error) { return fail(error instanceof Error ? error.message : "Unknown error"); } let cdnDeploy = { cdnOrigin: void 0, storageOrigin: "", apiKey: void 0 }; if (input.build) { const unpublished = await findUnpublishedFrameworkPackages(configDir); if (unpublished.length > 0) return fail(`Framework package(s) ${unpublished.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ")} are not published to npm yet — deploys must not ship bundles ahead of the registry (child scaffolds and update checks read these versions). Re-run the Release workflow (or \`pnpm run release\`) first.`); cdnDeploy = resolveCdnDeployInputs({ env: process.env, bosConfig, session: sessionFile?.credential ?? null, custodyAccount: useCustody ? custodyWallet?.nearAccountId : void 0, account, gateway, uploadsPlanned: input.build }); if (cdnDeploy.error) return fail(cdnDeploy.error); if (cdnDeploy.warning) console.log(colors.yellow(` ! ${cdnDeploy.warning}`)); if (!cdnDeploy.cdnOrigin) return fail("CDN deploy resolved no bundle origin"); const probeError = await probeStorageOrigin(cdnDeploy.storageOrigin); if (probeError) return fail(`The bundle upload origin ${cdnDeploy.storageOrigin} is not running the platform API — ${probeError}. Check BOS_STORAGE_ORIGIN / the bos login session, then re-run.`); } let publishedConfig = null; let publishedConfigState = "missing"; try { publishedConfig = await fetchPublishedConfigFromFastKv(registryUrl); publishedConfigState = "fetched"; } catch (error) { if ((error instanceof Error ? error.message : String(error)).startsWith("No config found")) console.log(colors.dim(" Note: no published config found yet (first publish) — continuing")); else { publishedConfigState = "unreachable"; console.log(colors.dim(" Note: registry read failed — continuing")); } } return { kind: "ready", plan: { isStaging, account, gateway, network, registryUrl, registryNamespace: getRegistryNamespaceForNetwork(network, input.registry), targets: selectWorkspaceTargets(input.packages, bosConfig), useWallet, useCustody, strategy, cdnOrigin: cdnDeploy.cdnOrigin, storageOrigin: cdnDeploy.storageOrigin, storageApiKey: cdnDeploy.apiKey, publishedConfig, publishedConfigState, ...custodyWallet ? { custodyWallet } : {}, ...credential ? { credential } : {} } }; } //#endregion export { findUnpublishedFrameworkPackages, npmVersionPublished, preflightPublish }; //# sourceMappingURL=preflight.mjs.map