everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
217 lines (215 loc) • 7.43 kB
JavaScript
import { buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, getRegistryNamespaceForNetwork } from "./fastkv.mjs";
import { getNetworkIdForAccount } from "./network.mjs";
import { loadResolvedConfig } from "./config.mjs";
import { padRight } from "./utils/string.mjs";
import { colors, icons } from "./utils/theme.mjs";
import { buildWorkspaceTargets, selectWorkspaceTargets } from "./build.mjs";
import { generateCodeArtifacts } from "./code-artifacts.mjs";
import { ensureNearCli, executeTransaction, resolveNearSigningMode } from "./near-cli.mjs";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { Effect } from "effect";
import process from "node:process";
//#region src/publish.ts
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function extractPublishedUrl(output) {
const deployMatch = output.match(/🚀.*Deployed:\s*(https?:\S+)/);
if (deployMatch) return deployMatch[1];
const match = output.match(/https?:\/\/[^\s"'<>]+/g);
if (!match || match.length === 0) return null;
return match[match.length - 1] ?? null;
}
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}`);
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 publishToFastKv(input) {
const { env, dryRun, configDir } = input;
let bosConfig = input.bosConfig;
const runtimeConfig = input.runtimeConfig;
const isStaging = env === "staging";
const account = bosConfig.account;
const gateway = isStaging ? bosConfig.staging?.domain ?? bosConfig.domain : bosConfig.domain;
if (!gateway) return {
status: "error",
registryUrl: "",
error: "bos.config.json must define domain to publish"
};
const network = input.network ?? getNetworkIdForAccount(account);
const registryUrl = buildRegistryConfigUrlForNetwork(network, account, gateway);
const targets = selectWorkspaceTargets(input.packages, bosConfig);
let built;
let skipped;
let deployResults;
if (dryRun) return {
status: "dry-run",
registryUrl,
built,
skipped
};
const privateKey = input.privateKey || process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY;
let signingMode;
try {
signingMode = resolveNearSigningMode(privateKey);
} catch (error) {
return {
status: "error",
registryUrl,
error: error instanceof Error ? error.message : "Unknown error"
};
}
if (input.build) {
console.log(" Ensuring NEAR CLI...");
await Effect.runPromise(ensureNearCli);
console.log(" NEAR CLI ready");
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 loadResolvedConfig({ cwd: configDir });
if (!refreshed?.config) return {
status: "error",
registryUrl,
built,
skipped,
deployResults,
error: "Failed to reload bos.config.json after build"
};
bosConfig = refreshed.config;
}
const rawConfigPath = join(configDir, "bos.config.json");
const rawConfig = JSON.parse(readFileSync(rawConfigPath, "utf-8"));
const publishPayload = isStaging ? {
...rawConfig,
domain: gateway
} : rawConfig;
const registryEntries = { [`apps/${account}/${gateway}/bos.config.json`]: JSON.stringify(publishPayload) };
const payload = JSON.stringify(registryEntries);
const argsBase64 = Buffer.from(payload).toString("base64");
console.log();
console.log(" Publishing to:");
console.log(` ${colors.cyan(registryUrl)}`);
try {
let txHash;
console.log(` Submitting transaction on ${network}...`);
try {
const tx = await Effect.runPromise(executeTransaction({
account,
contract: getRegistryNamespaceForNetwork(network),
method: "__fastdata_kv",
argsBase64,
network,
privateKey: signingMode._tag === "privateKey" ? signingMode.privateKey : void 0,
gas: "300Tgas",
deposit: "0NEAR",
verbose: input.verbose
}, signingMode));
txHash = tx.txHash;
if (txHash && !tx.output?.includes("CodeDoesNotExist")) console.log(` Transaction submitted: ${colors.dim(txHash)}`);
} catch (error) {
console.log(colors.dim(" Transaction reported an error — verifying publish..."));
try {
await waitForPublishedConfig({
account,
gateway,
publishConfig: publishPayload,
timeoutMs: 3e4,
intervalMs: 2e3
});
txHash = extractTransactionHash(error);
} catch {
throw error;
}
}
console.log(" Waiting for publish confirmation...");
await waitForPublishedConfig({
account,
gateway,
publishConfig: publishPayload
});
return {
status: "published",
registryUrl,
txHash,
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("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;
}
function extractTransactionHash(error) {
return (error instanceof Error ? error.message : String(error)).match(/Transaction ID:\s*([A-Za-z0-9]+)/i)?.[1];
}
//#endregion
export { extractPublishedUrl, publishToFastKv };
//# sourceMappingURL=publish.mjs.map