everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
704 lines (702 loc) • 25.7 kB
JavaScript
const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
const require_merge = require('../resolution/merge.cjs');
const require_services_registry_fastkv = require('../registry/fastkv.cjs');
const require_load = require('../resolution/load.cjs');
const require_runtime$1 = require('../resolution/runtime.cjs');
const require_run = require('../../utils/run.cjs');
const require_theme = require('../../utils/theme.cjs');
const require_services_resolution_session = require('../resolution/session.cjs');
const require_services_registry_fingerprint = require('../registry/fingerprint.cjs');
const require_services_deploy_integrity = require('./integrity.cjs');
const require_string = require('../../utils/string.cjs');
const require_artifacts = require('../codegen/artifacts.cjs');
const require_build_train = require('../workspace/build-train.cjs');
const require_platform_manifest = require('./platform-manifest.cjs');
const require_rollback = require('./rollback.cjs');
const require_custody_signer = require('../auth/custody-signer.cjs');
const require_delegate_signer = require('../auth/delegate-signer.cjs');
const require_near_signer = require('../auth/near-signer.cjs');
const require_signer = require('../auth/signer.cjs');
const require_deps = require('./deps.cjs');
const require_diff = require('./diff.cjs');
const require_errors = require('./errors.cjs');
const require_format = require('./format.cjs');
const require_platform = require('./platform.cjs');
const require_preflight = require('./preflight.cjs');
const require_render = require('./render.cjs');
const require_upload_leg = require('./upload-leg.cjs');
let node_fs = require("node:fs");
let node_path = require("node:path");
let effect = require("effect");
let node_process = require("node:process");
node_process = require_runtime.__toESM(node_process, 1);
let every_plugin_build_ui = require("every-plugin/build/ui");
//#region src/services/deploy/train.ts
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/** Best-effort repo HEAD for the version manifest (`gitSha`): non-git
* contexts (child bundles built outside a repo) omit the field silently —
* it never fails the deploy. */
async function resolveGitSha(cwd) {
try {
const result = await require_run.run("git", ["rev-parse", "HEAD"], {
cwd,
capture: true
});
const sha = result?.stdout.trim();
return result && result.exitCode === 0 && sha ? sha : void 0;
} catch {
return;
}
}
async function waitForPublishedConfig(opts) {
const fetchConfig = opts.fetchConfig ?? require_services_registry_fastkv.fetchBosConfigFromFastKv;
const envTimeoutMs = Number(node_process.default.env.BOS_PUBLISH_CONFIRMATION_TIMEOUT_MS);
const envIntervalMs = Number(node_process.default.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;
let txProbeSkips = 0;
const TX_PROBE_SKIP_LIMIT = 2;
while (Date.now() - startedAt < timeoutMs) {
try {
let trackByTx = opts.txHash !== void 0 && opts.fetchHistory !== void 0;
if (trackByTx && txProbeSkips >= TX_PROBE_SKIP_LIMIT) trackByTx = false;
if (trackByTx) try {
if ((await opts.fetchHistory({
accountId: opts.account,
gatewayId: opts.gateway,
registry: opts.registry,
limit: 5
})).some((entry) => entry.txHash === opts.txHash)) txProbeSkips = 0;
else {
txProbeSkips += 1;
await sleep(intervalMs);
continue;
}
} catch (historyError) {
lastError = historyError;
}
const verifiedConfig = await fetchConfig(`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) {
const fetchConfig = opts.fetchConfig ?? require_services_registry_fastkv.fetchBosConfigFromFastKv;
try {
const current = await fetchConfig(`bos://${opts.account}/${opts.gateway}`, opts.registry);
return JSON.stringify(current) === JSON.stringify(opts.publishConfig);
} catch {
return false;
}
}
/**
* 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.
*
* Terminal failures throw `PublishTrainError` (the typed channel, ticket
* #422); callers behind the service map it to wire shapes mechanically.
*/
async function runPublishTrain(input, deps) {
const preflight = await require_preflight.preflightPublish(input);
if (preflight.kind === "error") throw new require_errors.PublishTrainError({
kind: "preflight",
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, useCustody, strategy } = plan;
const storageOrigin = plan.storageOrigin;
const storageApiKey = plan.storageApiKey;
const { configDir } = input;
const render = deps.render;
let bosConfig = input.bosConfig;
const runtimeConfig = input.runtimeConfig;
let built;
let skipped;
let deployResults;
const urlOrigin = plan.cdnOrigin ?? `https://${gateway}`;
const platformEntries = [];
const fullUpload = input.fullUpload === true || node_process.default.env.BOS_FULL_UPLOAD === "1";
const sourcePublish = bosConfig.publish?.sourcePublish === true;
const gitSha = await resolveGitSha(configDir);
const memoryBackendAbort = () => new require_errors.PublishTrainError({
kind: "memory-backend",
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."
});
const prevPins = fullUpload ? {} : {
...require_diff.pinsFromPublishedConfig(plan.publishedConfig ?? {}, account, gateway),
...require_diff.pinsFromDeployState(configDir, account, gateway)
};
const authoredPath = require_load.findConfigPath(configDir);
const tsForm = authoredPath !== null && require_load.isAppDescriptorPath(authoredPath);
const openedRaw = await require_services_resolution_session.openResolution({ cwd: configDir });
if (!openedRaw?.rawConfig) throw new require_errors.PublishTrainError({
kind: "reload",
registryUrl,
built,
skipped,
deployResults,
error: "Failed to load the authored config for publish"
});
const authoredRawConfig = openedRaw.rawConfig;
let publishRawConfig = authoredRawConfig;
render?.cdnBanner?.(storageOrigin);
const uploadOneWorkspace = async (key, rawConfig) => {
const startedAt = Date.now();
const ws = require_build_train.resolveWorkspaceTarget(key, bosConfig, runtimeConfig, configDir);
if (!ws) return {
entries: [],
detail: {
base: "",
uploaded: 0,
skipped: 0,
durationMs: 0,
notes: []
},
shared: {}
};
const outcome = await require_upload_leg.uploadWorkspaceBundle({
account,
gateway,
workspaceKey: key,
distDir: (0, node_path.join)(ws.path, "dist"),
storageOrigin,
storageApiKey,
urlOrigin,
previousPin: prevPins[key],
...sourcePublish ? { source: require_upload_leg.sourceLegDirs(ws.path) } : {},
...gitSha ? { gitSha } : {},
deps: deps.storage
});
if (outcome.kind === "error") throw new require_errors.PublishTrainError({
kind: "upload",
registryUrl,
built,
skipped,
deployResults,
error: outcome.message
});
if (outcome.kind === "memory") throw memoryBackendAbort();
if (outcome.kind === "missing-manifest") throw new require_errors.PublishTrainError({
kind: "missing-manifest",
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 (sourcePublish && outcome.storage === "memory") throw memoryBackendAbort();
const entries = require_platform.platformUrlDeployEntries({
origin: urlOrigin,
account,
gateway,
key,
kind: ws.kind,
integrity: outcome.entryIntegrity,
ssrIntegrity: outcome.ssrEntryIntegrity,
pin: outcome.pin
});
let uiOutcomeShared;
if ((0, every_plugin_build_ui.hasFolderFormUi)(ws.path)) {
const uiDistDir = (0, node_path.join)(ws.path, "ui", "dist");
const rawUi = ((ws.kind === "app" ? "app" : "plugins") === "app" ? rawConfig.app : rawConfig.plugins)?.[key]?.ui;
const uiName = require_runtime$1.resolveUiRuntimeName(rawUi, (0, node_path.join)(ws.path, "ui"), key);
let uiIntegrity;
let uiSsrIntegrity;
let uiPin;
if ((0, node_fs.existsSync)(uiDistDir)) {
const uiOutcome = await require_upload_leg.uploadWorkspaceBundle({
account,
gateway,
workspaceKey: `${key}-ui`,
distDir: uiDistDir,
storageOrigin,
storageApiKey,
urlOrigin,
previousPin: prevPins[`${key}-ui`],
deps: deps.storage
});
if (uiOutcome.kind === "error") throw new require_errors.PublishTrainError({
kind: "upload",
registryUrl,
built,
skipped,
deployResults,
error: uiOutcome.message
});
if (uiOutcome.kind === "memory") throw memoryBackendAbort();
if (uiOutcome.kind === "missing-manifest") throw new require_errors.PublishTrainError({
kind: "missing-manifest",
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)`
});
uiIntegrity = uiOutcome.entryIntegrity;
uiSsrIntegrity = uiOutcome.ssrEntryIntegrity;
uiPin = uiOutcome.pin;
uiOutcomeShared = uiOutcome.shared;
} else console.log(` ${require_theme.colors.green(require_theme.icons.ok)} ${require_string.padRight(`${key}-ui`, 28)} → ${urlOrigin}/bundles/${account}/${gateway}/${key}-ui/`);
entries.push(...require_platform.pluginUiUrlDeployEntries({
origin: urlOrigin,
account,
gateway,
key,
kind: ws.kind,
integrity: uiIntegrity,
ssrIntegrity: uiSsrIntegrity,
name: uiName,
pin: uiPin
}));
}
return {
entries,
detail: {
base: outcome.detail.base,
uploaded: outcome.uploaded,
skipped: outcome.skipped,
durationMs: Date.now() - startedAt,
notes: outcome.detail.notes
},
shared: {
...outcome.shared ? { [key]: outcome.shared } : {},
...uiOutcomeShared ? { [`${key}-ui`]: uiOutcomeShared } : {}
}
};
};
const uploadConcurrency = Math.max(1, Number(node_process.default.env.BOS_UPLOAD_CONCURRENCY) || 4);
const uploaded = /* @__PURE__ */ new Map();
const trackUpload = (key, pending) => {
uploaded.set(key, pending);
pending.catch(() => {});
};
let inFlight = 0;
const waiters = [];
const acquireUploadSlot = () => {
if (inFlight < uploadConcurrency) {
inFlight++;
return Promise.resolve();
}
return new Promise((resolve) => waiters.push(resolve));
};
const releaseUploadSlot = () => {
inFlight--;
waiters.shift()?.();
};
const startUpload = (key) => {
if (uploaded.has(key)) return;
trackUpload(key, (async () => {
await acquireUploadSlot();
try {
return await uploadOneWorkspace(key, authoredRawConfig);
} finally {
releaseUploadSlot();
}
})());
};
if (input.build) {
await require_artifacts.generateCodeArtifacts(configDir, bosConfig, {
env: "production",
runtimeConfig: runtimeConfig ?? void 0
});
const result = await deps.buildTrain.buildTargets({
configDir,
bosConfig,
runtimeConfig,
targets,
deploy: true,
verbose: input.verbose,
...input.forceBuild ? { forceBuild: true } : {}
}, { onWorkspaceBuilt: (key) => {
if (targets.includes(key)) startUpload(key);
} });
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(require_theme.colors.error(` ${require_theme.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(` ${require_theme.colors.error(require_theme.icons.err)} ${require_string.padRight(f.key, 28)} ${errorLine}`);
}
console.log();
if (!input.verbose) {
console.log(require_theme.colors.dim(" Run with --verbose for full build output."));
console.log();
}
throw new require_errors.PublishTrainError({
kind: "build",
registryUrl,
built,
skipped,
deployResults,
error: `${failures.length} of ${total} workspaces failed to deploy`
});
}
}
const refreshed = await require_services_resolution_session.openResolution({ cwd: configDir });
if (!refreshed?.config) throw new require_errors.PublishTrainError({
kind: "reload",
registryUrl,
built,
skipped,
deployResults,
error: "Failed to reload the config after build"
});
bosConfig = refreshed.config;
if (refreshed.rawConfig) publishRawConfig = refreshed.rawConfig;
}
if (tsForm && !input.build && input.exactPayload !== true && plan.publishedConfigState === "unreachable") throw new require_errors.PublishTrainError({
kind: "config-state",
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 = input.exactPayload === true ? input.bosConfig : tsForm && !input.build ? require_merge.mergeAuthoredOverPublished(plan.publishedConfig ?? {}, publishRawConfig) : publishRawConfig;
for (const key of built ?? []) if (targets.includes(key)) startUpload(key);
const deployTargets = (built ?? []).filter((key) => targets.includes(key));
const settled = await Promise.allSettled(deployTargets.map((key) => uploaded.get(key) ?? Promise.resolve({
entries: [],
detail: {
base: "",
uploaded: 0,
skipped: 0,
durationMs: 0,
notes: []
},
shared: {}
})));
const sharedByWorkspace = /* @__PURE__ */ new Map();
const uploadFailures = settled.map((result, index) => result.status === "rejected" ? {
key: deployTargets[index],
error: result.reason
} : null).filter((entry) => entry !== null);
for (let index = 0; index < deployTargets.length; index++) {
const result = settled[index];
const key = deployTargets[index];
if (result.status === "fulfilled") {
platformEntries.push(...result.value.entries);
for (const [bundleKey, shared] of Object.entries(result.value.shared)) sharedByWorkspace.set(bundleKey, shared);
render?.uploadOutcome?.({
key,
ok: true,
...input.verbose ? { detailNotes: result.value.detail.notes } : {},
...result.value.detail
});
} else render?.uploadOutcome?.({
key,
ok: false
});
}
if (uploadFailures.length > 0) {
const thrown = uploadFailures[0].error;
if (thrown instanceof require_errors.PublishTrainError) throw thrown;
throw new require_errors.PublishTrainError({
kind: "upload",
registryUrl,
built,
skipped,
deployResults,
error: thrown instanceof Error ? thrown.message : String(thrown)
});
}
if (platformEntries.length > 0) {
const merged = require_services_deploy_integrity.applyDeployResults(publishRawConfig, platformEntries);
if (!tsForm) try {
(0, node_fs.writeFileSync)((0, node_path.join)(configDir, "bos.config.json"), `${JSON.stringify(merged, null, 2)}\n`);
} catch (error) {
throw new require_errors.PublishTrainError({
kind: "config-write",
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 require_rollback.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(` ${require_theme.colors.error(require_theme.icons.err)} Publish blocked — a pinned slot's bytes do not serve at its production URL:\n${failures}`);
throw new require_errors.PublishTrainError({
kind: "pin-verification",
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 = require_services_registry_fingerprint.pointerFingerprint(publishPayload);
const publishSlotPins = require_services_registry_fingerprint.slotPins(publishPayload);
const registryEntries = { [registryKey]: JSON.stringify(publishPayload) };
const manifestKey = `apps/${account}/${gateway}/manifests/${publishedAt.replace(/[:.]/g, "-")}.json`;
let platformManifest = null;
if (!isStaging && input.build) {
try {
const previous = await require_services_registry_fastkv.fetchRegistryKey(account, gateway, "platform.json");
platformManifest = require_platform_manifest.composePlatformManifestPublishEntry({
configDir,
account,
gateway,
previousFrameworkVersion: previous?.frameworkVersion,
gitSha
});
} catch (error) {
console.warn(` platform manifest skipped this deploy — the previous release could not be read: ${error instanceof Error ? error.message : String(error)}`);
}
if (platformManifest) {
const violations = require_platform_manifest.platformTruthViolations(platformManifest.manifest, sharedByWorkspace);
if (violations.length > 0) throw new require_errors.PublishTrainError({
kind: "platform-truth",
registryUrl,
built,
skipped,
deployResults,
error: `platform manifest does not match the uploaded bundles:\n` + violations.map((line) => ` ${line}`).join("\n")
});
}
}
registryEntries[manifestKey] = JSON.stringify({
account,
gateway,
network,
publishedAt,
registryUrl,
...platformManifest ? { platformManifest: {
file: platformManifest.key,
integrity: platformManifest.integrity
} } : {}
});
if (platformManifest) registryEntries[platformManifest.key] = platformManifest.value;
render?.publish?.({
registryUrl,
fingerprint,
slotPins: Object.entries(publishSlotPins),
auditManifestFile: manifestKey.split("/").pop() ?? manifestKey
});
const persistDeployState = () => {
try {
const pins = require_diff.pinsFromPublishedConfig(publishPayload, account, gateway);
if (Object.keys(pins).length > 0) require_diff.upsertDeployStatePins(configDir, account, gateway, pins);
} catch (error) {
console.log(require_theme.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,
fetchConfig: deps.registry.fetchConfig
})) {
console.log(" Already up to date — skipping transaction");
persistDeployState();
return {
status: "published",
registryUrl,
built,
skipped,
deployResults,
publishConfig: publishPayload
};
}
console.log(` Submitting transaction on ${network}...`);
const result = await (async () => {
if (useCustody) {
if (!plan.custodyWallet) throw new require_errors.PublishTrainError({
kind: "submit",
registryUrl,
error: "custody publish requires the preflight custody wallet"
});
return deps.signer.submit({
mode: "custody",
account,
contract: registryNamespace,
args: registryEntries,
custodyWallet: plan.custodyWallet,
idempotencyKey: `bos-publish-${fingerprint}`
});
}
if (useWallet) {
const credential = plan.credential;
if (!credential || credential.accountId !== account) throw new require_errors.PublishTrainError({
kind: "submit",
registryUrl,
error: `--wallet requires a CLI session under ${account}. Run bos login first.`
});
return deps.signer.submit({
mode: "wallet",
account,
contract: registryNamespace,
network,
args: registryEntries,
credential,
configDir
});
}
if (!strategy) throw new require_errors.PublishTrainError({
kind: "submit",
registryUrl,
error: "non-wallet publish requires a resolved signing strategy"
});
return deps.signer.submit({
mode: "key",
account,
contract: registryNamespace,
network,
args: registryEntries,
privateKey: input.privateKey,
strategy
});
})();
if (result.txHash) console.log(` Transaction submitted: ${require_theme.colors.dim(result.txHash)}`);
console.log(" Waiting for publish confirmation...");
try {
await waitForPublishedConfig({
account,
gateway,
publishConfig: publishPayload,
registry: input.registry,
fetchConfig: deps.registry.fetchConfig,
...result.txHash ? {
txHash: result.txHash,
fetchHistory: deps.registry.fetchHistory
} : {}
});
} catch (error) {
throw new require_errors.PublishTrainError({
kind: "confirm",
registryUrl,
error: require_format.formatNearError(error),
built,
skipped,
deployResults
});
}
persistDeployState();
return {
status: "published",
registryUrl,
txHash: result.txHash,
fingerprint,
slotPins: publishSlotPins,
built,
skipped,
deployResults,
publishConfig: publishPayload
};
} catch (error) {
if (error instanceof require_errors.PublishTrainError) throw error;
throw new require_errors.PublishTrainError({
kind: "sign",
registryUrl,
error: require_format.formatNearError(error),
built,
skipped,
deployResults
});
}
}
/** Procedural entry point: the train behind a plain promise — legacy callers
* and tests see the string-error result shape, the Effect service maps the
* typed channel. */
async function publishToFastKv(input) {
try {
return await runPublishTrain(input, realTrainDeps);
} catch (error) {
if (error instanceof require_errors.PublishTrainError) return error.toResult();
throw error;
}
}
const defaultBuildTrain = { buildTargets: (input, deps) => require_build_train.buildWorkspaceTargets(input, deps) };
const realTrainDeps = {
buildTrain: defaultBuildTrain,
storage: require_upload_leg.realStorageLegDeps,
registry: {
fetchConfig: require_services_registry_fastkv.fetchBosConfigFromFastKv,
fetchHistory: require_services_registry_fastkv.fetchConfigHistory
},
signer: {
submitRegistryWrite: require_near_signer.submitRegistryWrite,
submitRegistryWriteDelegated: require_delegate_signer.submitRegistryWriteDelegated,
submitRegistryWriteCustody: require_custody_signer.submitRegistryWriteCustody,
ensureDelegateKey: require_delegate_signer.ensureDelegateKey,
ensureCustodyBalance: require_custody_signer.ensureCustodyBalance,
submit: require_signer.dispatchSubmit
}
};
/**
* The train as a Context.Service (ticket #422): `publish` fails with the
* typed `PublishTrainError`; handlers yield the service and map failures
* mechanically.
*/
var DeployTrainService = class DeployTrainService extends effect.Context.Service()("everything-dev/deploy/train") {
static Live = effect.Layer.effect(this, effect.Effect.gen(function* () {
const storage = yield* require_deps.BundleStorage;
const registry = yield* require_deps.RegistryStore;
const signer = yield* require_signer.SignerService;
const buildTrainOption = yield* effect.Effect.serviceOption(require_build_train.BuildTrainService);
const renderOption = yield* effect.Effect.serviceOption(require_render.DeployTrainRenderService);
return DeployTrainService.of({ publish: (input) => effect.Effect.tryPromise({
try: () => runPublishTrain(input, {
storage,
registry,
signer,
buildTrain: effect.Option.getOrUndefined(buildTrainOption) ?? defaultBuildTrain,
render: effect.Option.getOrUndefined(renderOption)
}),
catch: (error) => error instanceof require_errors.PublishTrainError ? error : new require_errors.PublishTrainError({
kind: "submit",
registryUrl: "",
error: String(error)
})
}) });
}));
};
//#endregion
exports.DeployTrainService = DeployTrainService;
exports.isConfigAlreadyPublished = isConfigAlreadyPublished;
exports.publishToFastKv = publishToFastKv;
exports.realTrainDeps = realTrainDeps;
exports.runPublishTrain = runPublishTrain;
exports.waitForPublishedConfig = waitForPublishedConfig;
//# sourceMappingURL=train.cjs.map