everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
211 lines (209 loc) • 7.92 kB
JavaScript
const require_http_client = require('../../utils/http-client.cjs');
const require_services_registry_fastkv = require('../registry/fastkv.cjs');
const require_load = require('./load.cjs');
const require_bos_ref = require('../../utils/bos-ref.cjs');
const require_catalog = require('../workspace/catalog.cjs');
const require_services_resolution_session = require('./session.cjs');
let node_fs = require("node:fs");
let node_path = require("node:path");
let execa = require("execa");
let node_os = require("node:os");
let node_stream_promises = require("node:stream/promises");
let tar = require("tar");
//#region src/services/resolution/init-source.ts
async function resolveCatalogChainSource(opts) {
const entry = require_load.findConfigPath((0, node_path.resolve)(opts.sourceDir)) ?? (0, node_path.join)((0, node_path.resolve)(opts.sourceDir), "bos.config.json");
const catalogs = [];
const cleanups = [];
const resolvedRefs = [];
let repository;
try {
await require_services_resolution_session.walkExtendsChain(entry, {
env: "production",
collectCatalogs: true,
registerCleanup: (fn) => cleanups.push(fn),
visit: async (link) => {
resolvedRefs.unshift(link.ref.startsWith("bos://") ? link.ref : (0, node_path.join)(link.baseDir, (0, node_path.basename)(link.ref)));
catalogs.push(require_catalog.readWorkspaceCatalog(link.sourceDir ?? link.baseDir));
const repositoryValue = link.config.repository;
if (typeof repositoryValue === "string" && repository === void 0) repository = repositoryValue;
}
});
} finally {
for (const cleanup of [...cleanups].reverse()) await cleanup();
}
const originRef = opts.originRef ?? (opts.extendsAccount !== void 0 && opts.extendsGateway !== void 0 ? `bos://${opts.extendsAccount}/${opts.extendsGateway}` : void 0);
return {
catalog: Object.assign({}, ...catalogs),
repository,
extendsChain: originRef ? [originRef, ...resolvedRefs.slice(1)] : resolvedRefs
};
}
async function resolveSourceDir(opts) {
if (opts.source) {
const sourceDir = (0, node_path.resolve)(opts.source);
const configPath = require_load.findConfigPath(sourceDir);
if (!configPath) throw new Error(`No authored config (bos.app.ts or bos.config.json) found in source directory: ${sourceDir}`);
return {
sourceDir,
parentConfig: require_load.isAppDescriptorPath(configPath) ? await require_load.loadAppDescriptorConfig(configPath) : JSON.parse((0, node_fs.readFileSync)(configPath, "utf-8")),
cleanup: async () => {}
};
}
const parentConfig = await fetchParentConfig(opts.extendsAccount, opts.extendsGateway);
if (opts.ref) {
if (!parentConfig.repository) throw new Error(`Release ${opts.ref} requested but the config at bos://${opts.extendsAccount}/${opts.extendsGateway} names no repository`);
const { dir: sourceDir, cleanup, resolvedRef, resolvedSha } = await downloadTarball(parentConfig.repository, opts.ref);
return {
sourceDir,
parentConfig,
cleanup,
resolvedRef,
resolvedSha
};
}
const chainResult = await resolveRepositoryViaExtendsChain(opts.extendsAccount, opts.extendsGateway);
if (chainResult?.repository) {
const { dir: sourceDir, cleanup, resolvedRef, resolvedSha } = await downloadTarball(chainResult.repository, opts.ref);
return {
sourceDir,
parentConfig: chainResult.config,
cleanup,
resolvedRef,
resolvedSha
};
}
return {
sourceDir: "",
parentConfig,
cleanup: async () => {}
};
}
async function fetchParentConfig(extendsAccount, extendsGateway) {
const bosUrl = `bos://${extendsAccount}/${extendsGateway}`;
return require_services_registry_fastkv.fetchBosConfigFromFastKv(bosUrl);
}
async function resolveRepositoryViaExtendsChain(extendsAccount, extendsGateway, visited = /* @__PURE__ */ new Set()) {
const key = `bos://${extendsAccount}/${extendsGateway}`;
if (visited.has(key)) return null;
visited.add(key);
try {
const config = await fetchParentConfig(extendsAccount, extendsGateway);
if (config.repository) return {
repository: config.repository,
config
};
const extendsRef = require_bos_ref.getExtendsRef(config);
if (extendsRef) {
const normalized = extendsRef.startsWith("bos://") ? extendsRef : `bos://${extendsRef}`;
const parsed = require_bos_ref.parseBosRef(normalized);
if (parsed) {
const result = await resolveRepositoryViaExtendsChain(parsed.account, parsed.gateway, visited);
if (result) return result;
}
}
return null;
} catch {
return null;
}
}
/**
* The release tarball ref (ADR 0034 pt 4): the canonical `v<version>` tag —
* sync's workflow pins and the release script both target it. One ref, one
* resolution: a version names exactly one release.
*/
function releaseRefCandidates(frameworkVersion) {
return `v${frameworkVersion}`;
}
async function downloadTarball(repoUrl, ref) {
const parsed = parseGitHubUrl(repoUrl);
if (!parsed) throw new Error(`Cannot parse repository URL: ${repoUrl}`);
const { owner, repo } = parsed;
let response = null;
let resolvedRef;
const candidates = ref ? Array.isArray(ref) ? [...ref] : [ref] : ["main", "master"];
for (const candidateRef of candidates) {
const candidate = await require_http_client.fetchResponse(`https://api.github.com/repos/${owner}/${repo}/tarball/${encodeURIComponent(candidateRef)}`, {
headers: { "User-Agent": "everything-dev" },
redirect: "follow",
timeout: "60 seconds"
});
if (candidate.ok) {
response = candidate;
resolvedRef = candidateRef;
break;
}
if (candidate.status !== 404) throw new Error(`GitHub tarball download failed: ${candidate.status} ${candidate.statusText}`);
}
if (!response) throw new Error(ref ? `GitHub tarball download failed for ${repoUrl}: ref not found (tried ${candidates.join(", ")})` : `GitHub tarball download failed for ${repoUrl}: tried main and master`);
if (!response.body) throw new Error("GitHub tarball download returned empty body");
const resolvedSha = response.url.match(/\/([0-9a-f]{40})(?:\.tar\.gz)?\b/)?.[1];
const tmpDir = mkTmpDir("bos-init-tarball-");
const tarballPath = (0, node_path.join)(tmpDir, "source.tar.gz");
const fileStream = (0, node_fs.createWriteStream)(tarballPath);
const reader = response.body;
await (0, node_stream_promises.pipeline)(reader, fileStream);
const extractDir = mkTmpDir("bos-init-extract-");
try {
await (0, tar.extract)({
cwd: extractDir,
file: tarballPath,
strip: 1
});
} catch {
await execCommand("tar", [
"-xzf",
tarballPath,
"--strip-components=1",
"-C",
extractDir
]);
}
(0, node_fs.rmSync)(tmpDir, {
recursive: true,
force: true
});
return {
dir: extractDir,
resolvedRef,
resolvedSha,
cleanup: async () => {
(0, node_fs.rmSync)(extractDir, {
recursive: true,
force: true
});
}
};
}
function parseGitHubUrl(url) {
const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
if (httpsMatch?.[1] && httpsMatch[2]) return {
owner: httpsMatch[1],
repo: httpsMatch[2]
};
const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
if (sshMatch?.[1] && sshMatch[2]) return {
owner: sshMatch[1],
repo: sshMatch[2]
};
return null;
}
function mkTmpDir(prefix) {
return (0, node_fs.mkdtempSync)((0, node_path.join)((0, node_os.tmpdir)(), `${prefix}-`));
}
async function execCommand(command, args) {
await (0, execa.execa)(command, args, {
stdio: "pipe",
timeout: 12e4
});
}
//#endregion
exports.downloadTarball = downloadTarball;
exports.fetchParentConfig = fetchParentConfig;
exports.parseGitHubUrl = parseGitHubUrl;
exports.readWorkspaceCatalog = require_catalog.readWorkspaceCatalog;
exports.releaseRefCandidates = releaseRefCandidates;
exports.resolveCatalogChainSource = resolveCatalogChainSource;
exports.resolveRepositoryViaExtendsChain = resolveRepositoryViaExtendsChain;
exports.resolveSourceDir = resolveSourceDir;
//# sourceMappingURL=init-source.cjs.map