UNPKG

everything-dev

Version:

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

701 lines (699 loc) • 27 kB
import { fetchResponse } from "./http-client.mjs"; import { mergeAuthoredOverPublished } from "./merge.mjs"; import { buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, getRegistryNamespaceForNetwork } from "./fastkv.mjs"; import { getNetworkIdForAccount } from "./network.mjs"; import { resolveSlotVersion } from "./version-manifest-resolve.mjs"; import { findConfigPath, isAppDescriptorPath, resolveUiRuntimeName } from "./config.mjs"; import { colors, icons } from "./utils/theme.mjs"; import { openResolution } from "./resolution/session.mjs"; import { pointerFingerprint, slotPins } from "./fingerprint.mjs"; import { applyDeployResults } from "./integrity.mjs"; import { formatDuration } from "./cli/timing.mjs"; import { padRight } from "./utils/string.mjs"; import { buildWorkspaceTargets, resolveWorkspaceTarget, selectWorkspaceTargets } from "./build.mjs"; import { generateCodeArtifacts } from "./code-artifacts.mjs"; import { readSessionHandle } from "./auth-session.mjs"; import { probeStorageOrigin, resolveCdnDeployInputs } from "./cdn-deploy.mjs"; import { ensureDelegateKey, submitRegistryWriteDelegated } from "./delegate-signer.mjs"; import { verifyRollbackSnapshot } from "./rollback.mjs"; import { diffWorkspaceFiles, localSriMap, mergedFileIntegrity, pinsFromDeployState, pinsFromPublishedConfig, upsertDeployStatePins } from "./deploy-diff.mjs"; import { describeSigningStrategy, resolveSigningStrategy, submitRegistryWrite } from "./near-signer.mjs"; import { platformUrlDeployEntries, pluginUiUrlDeployEntries } from "./platform-deploy.mjs"; import { collectDistFiles, uploadBundle, uploadWorkspaceDist } from "./storage-upload.mjs"; import { composeWorkspaceVersionManifest, readBuildReport } from "./version-manifest-deploy.mjs"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import process from "node:process"; import { hasFolderFormUi } from "every-plugin/build/ui"; //#region src/publish.ts function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function pinWorkspaceVersionManifest(input) { if (!input.report) return void 0; const versionManifest = composeWorkspaceVersionManifest({ report: input.report, ssrReport: input.ssrReport, integrityMap: input.integrityMap, ...input.files ? { files: input.files } : {} }); if (!versionManifest) return void 0; const manifestFile = `versions/${versionManifest.version}.json`; const manifestUpload = await uploadBundle({ origin: input.origin, apiKey: input.apiKey, account: input.account, gateway: input.gateway, workspace: input.workspace, files: [{ path: manifestFile, bytes: new TextEncoder().encode(`${JSON.stringify(versionManifest, null, 2)}\n`) }] }); const manifestIntegrity = manifestUpload.integrity[manifestFile]; if (!manifestIntegrity) return void 0; console.log(` ${colors.dim(`${padRight("", 28)} version manifest ${manifestFile} (SRI pinned)`)}`); return { file: manifestFile, integrity: manifestIntegrity, storage: manifestUpload.storage }; } /** * One workspace's upload leg with skip-unchanged diffing: resolve the previous * manifest's `files` map, hash the local dist, upload only new/changed files, * and return the merged per-file SRI map for the version manifest. Any failure * to resolve the previous map degrades to a full upload. */ async function diffUploadWorkspace(input) { const localSri = localSriMap(input.files); let uploadFiles = input.files; let prevFiles; if (input.previousPin) try { const resolved = await resolveSlotVersion({ base: input.base, pin: input.previousPin }); if (resolved.files) { const diff = diffWorkspaceFiles(input.files, localSri, resolved.files); prevFiles = resolved.files; uploadFiles = diff.upload; if (diff.skipped > 0) console.log(` ${colors.dim(`${padRight(input.workspace, 28)} ${diff.skipped} unchanged file(s) — skipping (manifest diff)`)}`); } } catch { console.log(` ${colors.dim(`${padRight(input.workspace, 28)} previous manifest unusable — uploading everything`)}`); } console.log(` ${padRight(input.workspace, 28)} uploading ${uploadFiles.length} of ${input.files.length} files (${formatBundleMb(uploadFiles)}) → ${input.base}`); const result = uploadFiles.length > 0 ? await uploadWorkspaceDist({ origin: input.origin, apiKey: input.apiKey, account: input.account, gateway: input.gateway, workspace: input.workspace, files: uploadFiles }) : { stored: 0, totalBytes: 0, integrity: {}, storage: void 0 }; return { result, fileIntegrity: mergedFileIntegrity(input.files, result.integrity, prevFiles), uploaded: result.stored, skipped: input.files.length - uploadFiles.length, total: input.files.length }; } function formatBundleMb(files) { return `${(files.reduce((sum, file) => sum + file.bytes.byteLength, 0) / 1024 / 1024).toFixed(1)} MB`; } async function waitForPublishedConfig(opts) { const envTimeoutMs = Number(process.env.BOS_PUBLISH_CONFIRMATION_TIMEOUT_MS); const envIntervalMs = Number(process.env.BOS_PUBLISH_CONFIRMATION_INTERVAL_MS); const timeoutMs = opts.timeoutMs ?? (Number.isFinite(envTimeoutMs) ? envTimeoutMs : void 0) ?? 12e4; const intervalMs = opts.intervalMs ?? (Number.isFinite(envIntervalMs) ? envIntervalMs : void 0) ?? 3e3; const startedAt = Date.now(); let lastError; while (Date.now() - startedAt < timeoutMs) { try { const verifiedConfig = await fetchBosConfigFromFastKv(`bos://${opts.account}/${opts.gateway}`, opts.registry); if (JSON.stringify(verifiedConfig) === JSON.stringify(opts.publishConfig)) return; } catch (error) { lastError = error; } await sleep(intervalMs); } const reason = lastError instanceof Error ? ` Last error: ${lastError.message}` : ""; throw new Error(`Timed out waiting for publish confirmation at bos://${opts.account}/${opts.gateway}.${reason}`); } async function isConfigAlreadyPublished(opts) { try { const current = await fetchBosConfigFromFastKv(`bos://${opts.account}/${opts.gateway}`, opts.registry); return JSON.stringify(current) === JSON.stringify(opts.publishConfig); } catch { return false; } } 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 publishAuth = bosConfig.publish?.auth; if ((publishAuth === "session" || input.wallet) && !input.privateKey) { const session = readSessionHandle(configDir); if (!session?.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 (session.credential.accountId && session.credential.accountId !== account) return fail(`The CLI session was created for ${session.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.`); } if (publishAuth && !input.privateKey) { if (publishAuth === "custody") return fail("the authored config sets publish.auth = \"custody\", but custody publish is not implemented yet (see NEARBuilders/everything-dev#291)."); } 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"); } const session = readSessionHandle(configDir); 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: session?.credential ?? null, 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 fetchBosConfigFromFastKv(registryUrl, input.registry); 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, strategy, cdnOrigin: cdnDeploy.cdnOrigin, storageOrigin: cdnDeploy.storageOrigin, storageApiKey: cdnDeploy.apiKey, publishedConfig, publishedConfigState } }; } /** * The deploy-train publish core (ADR 0020, as amended): preflight * (storage/CDN creds + signing) fails fast before the build, then build → * upload → version-manifest pin → config write-back → FastKV publish + * read-back. Scoped to one plugin via `packages`, a single-plugin redeploy * ships real bytes to the storage origin — same as the full train, nothing * image-native left. */ async function publishToFastKv(input) { const preflight = await preflightPublish(input); if (preflight.kind === "error") return { status: "error", registryUrl: preflight.registryUrl, error: preflight.error }; if (preflight.kind === "dry-run") return { status: "dry-run", registryUrl: preflight.registryUrl }; const plan = preflight.plan; const { isStaging, account, gateway, network, registryUrl, registryNamespace, targets, useWallet, strategy } = plan; const storageOrigin = plan.storageOrigin; const storageApiKey = plan.storageApiKey; const { configDir } = input; let bosConfig = input.bosConfig; const runtimeConfig = input.runtimeConfig; let built; let skipped; let deployResults; let refreshedRawConfig = null; if (input.build) { await generateCodeArtifacts(configDir, bosConfig, { env: "production", runtimeConfig: runtimeConfig ?? void 0 }); const result = await buildWorkspaceTargets({ configDir, bosConfig, runtimeConfig, targets, deploy: true, verbose: input.verbose }); built = result.built; skipped = result.skipped; deployResults = result.deployResults; if (deployResults) { const failures = deployResults.filter((r) => !r.success); if (failures.length > 0) { const total = deployResults.length; console.log(); console.log(colors.error(` ${icons.err} Deploy failed — ${failures.length} of ${total} workspace${total > 1 ? "s" : ""} failed`)); console.log(); for (const f of failures) { const errorLine = (f.error ?? "Failed").split("\n")[0]; console.log(` ${colors.error(icons.err)} ${padRight(f.key, 28)} ${errorLine}`); } console.log(); if (!input.verbose) { console.log(colors.dim(" Run with --verbose for full build output.")); console.log(); } return { status: "error", registryUrl, built, skipped, deployResults, error: `${failures.length} of ${total} workspaces failed to deploy` }; } } const refreshed = await openResolution({ cwd: configDir }); if (!refreshed?.config) return { status: "error", registryUrl, built, skipped, deployResults, error: "Failed to reload the config after build" }; bosConfig = refreshed.config; refreshedRawConfig = refreshed.rawConfig; } const authoredPath = findConfigPath(configDir); const tsForm = authoredPath !== null && isAppDescriptorPath(authoredPath); let rawConfig; if (refreshedRawConfig) rawConfig = refreshedRawConfig; else { const opened = await openResolution({ cwd: configDir }); if (!opened?.rawConfig) return { status: "error", registryUrl, built, skipped, deployResults, error: "Failed to load the authored config for publish" }; rawConfig = opened.rawConfig; } if (tsForm && !input.build && plan.publishedConfigState === "unreachable") return { status: "error", registryUrl, built, skipped, deployResults, error: "Cannot read the currently published config from the registry — a config-only publish would overwrite live bundle URLs. Retry when the registry is reachable, or run a full deploy." }; let publishPayload = tsForm && !input.build ? mergeAuthoredOverPublished(plan.publishedConfig ?? {}, rawConfig) : rawConfig; const urlOrigin = plan.cdnOrigin ?? `https://${gateway}`; const deployTargets = (built ?? []).filter((key) => targets.includes(key)); const platformEntries = []; const prevPins = input.fullUpload === true || process.env.BOS_FULL_UPLOAD === "1" ? {} : { ...pinsFromPublishedConfig(plan.publishedConfig ?? {}, account, gateway), ...pinsFromDeployState(configDir, account, gateway) }; console.log(); console.log(` CDN deploy — uploading workspace dists to ${storageOrigin}...`); for (const key of deployTargets) { const ws = resolveWorkspaceTarget(key, bosConfig, runtimeConfig, configDir); if (!ws) continue; const distFiles = await collectDistFiles(join(ws.path, "dist")); const workspaceBase = `${urlOrigin}/bundles/${account}/${gateway}/${key}/`; const startedAt = Date.now(); let outcome; try { outcome = await diffUploadWorkspace({ origin: storageOrigin, apiKey: storageApiKey, account, gateway, workspace: key, files: distFiles, base: workspaceBase, previousPin: prevPins[key] }); } catch (error) { return { status: "error", registryUrl, built, skipped, deployResults, error: error instanceof Error ? error.message : `[publish] bundle upload for ${key} failed` }; } const report = readBuildReport(join(ws.path, "dist")); const ssrReport = readBuildReport(join(ws.path, "dist", "ssr")); const integrity = report ? outcome.fileIntegrity[report.entry] : void 0; const ssrIntegrity = ssrReport?.entry ? outcome.fileIntegrity[`ssr/${ssrReport.entry}`] : void 0; const manifestPointer = await pinWorkspaceVersionManifest({ origin: storageOrigin, apiKey: storageApiKey, account, gateway, workspace: key, report, ssrReport, integrityMap: outcome.fileIntegrity, files: outcome.fileIntegrity }); if (!manifestPointer) return { status: "error", registryUrl, built, skipped, deployResults, error: `bundle upload for ${key}: version manifest missing — the dist predates hashed entry names (rebuild required; atomic-deploys hard break)` }; if (outcome.result.storage === "memory" || manifestPointer.storage === "memory") return { status: "error", registryUrl, built, skipped, deployResults, error: "The receiving instance is serving bundle storage from memory (BOS_STORAGE_* R2 credentials are not configured there) — uploaded bytes would be lost on restart. Aborting before publish." }; console.log(` ${colors.green(icons.ok)} ${padRight(key, 28)} → ${urlOrigin}/bundles/${account}/${gateway}/${key}/ (${outcome.uploaded} uploaded, ${outcome.skipped} skipped, ${formatDuration(Date.now() - startedAt)})`); platformEntries.push(...platformUrlDeployEntries({ origin: urlOrigin, account, gateway, key, kind: ws.kind, integrity, ssrIntegrity, pin: manifestPointer })); if (hasFolderFormUi(ws.path)) { const uiDistDir = join(ws.path, "ui", "dist"); const rawUi = ((ws.kind === "app" ? "app" : "plugins") === "app" ? rawConfig.app : rawConfig.plugins)?.[key]?.ui; const uiName = resolveUiRuntimeName(rawUi, join(ws.path, "ui"), key); let uiIntegrity; let uiSsrIntegrity; let uiManifestPointer; if (existsSync(uiDistDir)) { const uiDistFiles = await collectDistFiles(uiDistDir); const uiStartedAt = Date.now(); const uiOutcome = await diffUploadWorkspace({ origin: storageOrigin, apiKey: storageApiKey, account, gateway, workspace: `${key}-ui`, files: uiDistFiles, base: `${urlOrigin}/bundles/${account}/${gateway}/${key}-ui/`, previousPin: prevPins[`${key}-ui`] }); const uiReport = readBuildReport(uiDistDir); const uiSsrReport = readBuildReport(join(uiDistDir, "ssr")); uiIntegrity = uiReport ? uiOutcome.fileIntegrity[uiReport.entry] : void 0; uiSsrIntegrity = uiSsrReport?.entry ? uiOutcome.fileIntegrity[`ssr/${uiSsrReport.entry}`] : void 0; uiManifestPointer = await pinWorkspaceVersionManifest({ origin: storageOrigin, apiKey: storageApiKey, account, gateway, workspace: `${key}-ui`, report: uiReport, ssrReport: uiSsrReport, integrityMap: uiOutcome.fileIntegrity, files: uiOutcome.fileIntegrity }); if (!uiManifestPointer) return { status: "error", registryUrl, built, skipped, deployResults, error: `bundle upload for ${key}-ui: version manifest missing — the dist predates hashed entry names (rebuild required; atomic-deploys hard break)` }; if (uiOutcome.result.storage === "memory" || uiManifestPointer.storage === "memory") return { status: "error", registryUrl, built, skipped, deployResults, error: "The receiving instance is serving bundle storage from memory (BOS_STORAGE_* R2 credentials are not configured there) — uploaded bytes would be lost on restart. Aborting before publish." }; console.log(` ${colors.green(icons.ok)} ${padRight(`${key}-ui`, 28)} → ${urlOrigin}/bundles/${account}/${gateway}/${key}-ui/ (${uiOutcome.uploaded} uploaded, ${uiOutcome.skipped} skipped, ${formatDuration(Date.now() - uiStartedAt)})`); } else console.log(` ${colors.green(icons.ok)} ${padRight(`${key}-ui`, 28)} → ${urlOrigin}/bundles/${account}/${gateway}/${key}-ui/`); platformEntries.push(...pluginUiUrlDeployEntries({ origin: urlOrigin, account, gateway, key, kind: ws.kind, integrity: uiIntegrity, ssrIntegrity: uiSsrIntegrity, name: uiName, pin: uiManifestPointer })); } } if (platformEntries.length > 0) { const merged = applyDeployResults(rawConfig, platformEntries); if (!tsForm) try { writeFileSync(join(configDir, "bos.config.json"), `${JSON.stringify(merged, null, 2)}\n`); } catch (error) { return { status: "error", registryUrl, built, skipped, deployResults, error: `Failed to write bundle URLs to bos.config.json: ${error instanceof Error ? error.message : String(error)}` }; } publishPayload = merged; } if (isStaging) publishPayload = { ...publishPayload, domain: gateway }; const pinVerification = await verifyRollbackSnapshot(publishPayload); if (pinVerification.verifiable && !pinVerification.ok) { const failures = pinVerification.slots.filter((check) => !check.ok).map((check) => ` ${check.slot}: ${check.reason}`).join("\n"); console.error(` ${colors.error(icons.err)} Publish blocked — a pinned slot's bytes do not serve at its production URL:\n${failures}`); return { status: "error", registryUrl, built, skipped, deployResults, error: `Publish blocked — pinned slot bytes missing or SRI mismatched:\n${failures}` }; } const registryKey = `apps/${account}/${gateway}/bos.config.json`; const publishedAt = (/* @__PURE__ */ new Date()).toISOString(); const fingerprint = pointerFingerprint(publishPayload); const publishSlotPins = slotPins(publishPayload); const registryEntries = { [registryKey]: JSON.stringify(publishPayload) }; const manifestKey = `apps/${account}/${gateway}/manifests/${publishedAt.replace(/[:.]/g, "-")}.json`; registryEntries[manifestKey] = JSON.stringify({ account, gateway, network, publishedAt, registryUrl }); console.log(); console.log(" Publishing to:"); console.log(` ${colors.cyan(registryUrl)}`); console.log(` ${colors.dim("Fingerprint:")} ${fingerprint}`); for (const [slot, pin] of Object.entries(publishSlotPins)) { console.log(` ${colors.dim(`${slot}:`)}`); console.log(` ${pin}`); } console.log(` ${colors.dim(`+ per-deploy manifest ${manifestKey.split("/").pop()} (audit trail)`)}`); const persistDeployState = () => { try { const pins = pinsFromPublishedConfig(publishPayload, account, gateway); if (Object.keys(pins).length > 0) upsertDeployStatePins(configDir, account, gateway, pins); } catch (error) { console.log(colors.dim(` Note: could not update .bos/deploy-state.json — ${error instanceof Error ? error.message : String(error)}`)); } }; try { if (await isConfigAlreadyPublished({ account, gateway, publishConfig: publishPayload, registry: input.registry })) { console.log(" Already up to date — skipping transaction"); persistDeployState(); return { status: "published", registryUrl, built, skipped, deployResults, publishConfig: publishPayload }; } console.log(` Submitting transaction on ${network}...`); let result; if (useWallet) { const credential = readSessionHandle(configDir)?.credential; if (!credential || credential.accountId !== account) return { status: "error", registryUrl, error: `--wallet requires a CLI session under ${account}. Run bos login first.` }; const record = await ensureDelegateKey({ configDir, account, contract: registryNamespace, network, siteUrl: credential.siteUrl }); result = await submitRegistryWriteDelegated({ account, contract: registryNamespace, network, args: registryEntries, delegatePrivateKey: record.privateKey, relayEndpoint: `${credential.siteUrl}/api/auth/near/relay`, apiKey: credential.apiKey }); } else { if (!strategy) return { status: "error", registryUrl, error: "non-wallet publish requires a resolved signing strategy" }; result = await submitRegistryWrite({ account, contract: registryNamespace, method: "__fastdata_kv", args: registryEntries, network, privateKey: input.privateKey }, strategy); } if (result.txHash) console.log(` Transaction submitted: ${colors.dim(result.txHash)}`); console.log(" Waiting for publish confirmation..."); await waitForPublishedConfig({ account, gateway, publishConfig: publishPayload, registry: input.registry }); persistDeployState(); return { status: "published", registryUrl, txHash: result.txHash, fingerprint, slotPins: publishSlotPins, built, skipped, deployResults, publishConfig: publishPayload }; } catch (error) { return { status: "error", registryUrl, error: formatNearError(error), built, skipped, deployResults }; } } function formatNearError(error) { const message = error instanceof Error ? error.message : String(error); if (message.includes("does not have enough allowance")) return `The publish access key has insufficient allowance to cover this transaction. Regenerate your key with a higher allowance: bos key generate Original: ${message}`; if (message.includes("exceeded gas") || message.includes("GasLimitExceeded")) return `Transaction exceeded gas limit.\n Original: ${message}`; if (message.includes("timeout") || message.includes("Timeout")) return `Transaction timed out. Check NEAR network status.\n Original: ${message}`; return message; } //#endregion export { findUnpublishedFrameworkPackages, isConfigAlreadyPublished, npmVersionPublished, preflightPublish, publishToFastKv, waitForPublishedConfig }; //# sourceMappingURL=publish.mjs.map