blob: 74ae5769961e43a9bd88990c1406f9f371b3f89b [file]
import { execFileSync, spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import {
chmodSync,
copyFileSync,
cpSync,
existsSync,
lstatSync,
mkdtempSync,
mkdirSync,
readFileSync,
readdirSync,
realpathSync,
rmSync,
statSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
import { npmSpawnOptions } from './npm-spawn.mjs';
import { validateCliReleaseArtifactMetrics } from './release-cli-artifact-policy.mjs';
import {
isMakaDevelopmentArtifact,
isThirdPartyDevelopmentArtifact,
orderWorkspaceBuilds,
releaseNpmEnvironment,
resolveReleaseWorkspacePackages,
resolveWorkspaceReleaseFiles,
} from './release-cli-file-policy.mjs';
const repoRoot = resolve(import.meta.dirname, '..');
const cliSource = join(repoRoot, 'packages/cli');
const releaseRoot = join(cliSource, 'release');
const stageRoot = join(releaseRoot, 'package');
const allowDirty = process.argv.includes('--allow-dirty');
const preparedTree = process.env.MAKA_CLI_RELEASE_PREPARED_TREE === '1';
const unsupportedArguments = process.argv
.slice(2)
.filter((argument) => argument !== '--allow-dirty');
if (unsupportedArguments.length > 0) {
throw new Error(`Unsupported release argument: ${unsupportedArguments.join(', ')}`);
}
const workspacePackages = resolveReleaseWorkspacePackages(repoRoot);
const internalPackageNames = workspacePackages
.map(({ name }) => name)
.filter((name) => name !== 'maka-agent');
const internalPackageSet = new Set(internalPackageNames);
const buildOrder = orderWorkspaceBuilds(workspacePackages);
const strippedInstallScripts = new Map([
// The clean repository install has already produced every generated file and
// platform prebuild copied below. Do not run advisory postinstalls on an end
// user's machine.
['node-pty@1.2.0-beta.15', new Set(['install', 'postinstall'])],
['protobufjs@7.6.5', new Set(['postinstall'])],
]);
main();
function main() {
validateToolchain();
if (!preparedTree && !allowDirty) {
validateCleanWorktree();
buildFromCleanDependencyTree();
return;
}
if (preparedTree && allowDirty) {
throw new Error('--allow-dirty cannot be combined with a prepared release tree');
}
if (preparedTree && existsSync(join(repoRoot, '.git'))) {
throw new Error('A prepared release build must run inside the isolated source archive');
}
if (allowDirty) {
console.warn(
'[release-cli] WARNING: producing a private development tarball with publishing disabled',
);
}
buildRuntimeWorkspaces();
checkProductionAudit();
runNpm(['run', 'check:cli-third-party-notices']);
const dependencyTree = readCliDependencyTree();
const cli = dependencyTree.dependencies?.['maka-agent'];
if (!cli) throw new Error('npm ls did not return the maka-agent workspace');
rmSync(releaseRoot, { recursive: true, force: true });
mkdirSync(stageRoot, { recursive: true, mode: 0o755 });
copyCliRuntime();
const expectedDependencyManifests = copyDependencyClosure(cli);
copyEvalMirror();
copyReleaseDocuments();
writeReleaseManifest(cli, preparedTree);
validateStaging();
const [pack] = JSON.parse(
runNpm(['pack', stageRoot, '--json', '--pack-destination', releaseRoot], {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
}),
);
if (!pack?.filename || !Array.isArray(pack.files)) {
throw new Error('npm pack did not return one JSON package result');
}
validateCliReleaseArtifactMetrics({
compressedBytes: pack.size,
unpackedBytes: pack.unpackedSize,
entryCount: pack.entryCount,
});
const tarballPath = join(releaseRoot, pack.filename);
validatePackedFiles(pack.files, expectedDependencyManifests);
const sha256 = digestFile(tarballPath);
writeFileSync(`${tarballPath}.sha256`, `${sha256} ${pack.filename}\n`, 'utf8');
writeFileSync(
join(releaseRoot, `${pack.filename}.files.json`),
`${JSON.stringify(pack.files, null, 2)}\n`,
'utf8',
);
console.log(`[release-cli] tarball: ${tarballPath}`);
console.log(`[release-cli] sha256: ${sha256}`);
console.log(
`[release-cli] size: ${formatBytes(pack.size)} compressed, ${formatBytes(pack.unpackedSize)} unpacked, ${pack.entryCount} files`,
);
}
function buildFromCleanDependencyTree() {
const temporaryRoot = mkdtempSync(join(tmpdir(), 'maka-cli-release-build-'));
const archivePath = join(temporaryRoot, 'source.tar');
const cleanRoot = join(temporaryRoot, 'source');
try {
mkdirSync(cleanRoot, { recursive: true, mode: 0o755 });
execFileSync('git', ['archive', '--format=tar', `--output=${archivePath}`, 'HEAD'], {
cwd: repoRoot,
stdio: 'inherit',
});
execFileSync('tar', ['-xf', archivePath, '-C', cleanRoot], { stdio: 'inherit' });
console.log('[release-cli] installing the committed dependency tree with npm ci');
const cleanEnvironment = releaseNpmEnvironment(process.env, join(cleanRoot, '.npmrc'));
execFileSync(
'npm',
['ci'],
npmSpawnOptions({ cwd: cleanRoot, env: cleanEnvironment, stdio: 'inherit' }),
);
execFileSync(process.execPath, [join(cleanRoot, 'scripts/release-cli-package.mjs')], {
cwd: cleanRoot,
env: { ...cleanEnvironment, MAKA_CLI_RELEASE_PREPARED_TREE: '1' },
stdio: 'inherit',
});
const cleanReleaseRoot = join(cleanRoot, 'packages/cli/release');
if (!existsSync(cleanReleaseRoot)) {
throw new Error('The isolated release build did not produce a release directory');
}
rmSync(releaseRoot, { recursive: true, force: true });
cpSync(cleanReleaseRoot, releaseRoot, { recursive: true, preserveTimestamps: true });
console.log(`[release-cli] copied the isolated release artifacts to ${releaseRoot}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
}
function validateToolchain() {
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
if (major < 22 || (major === 22 && minor < 19)) {
throw new Error(`Node.js >=22.19.0 is required; found ${process.versions.node}`);
}
const packageManager = readJson(join(repoRoot, 'package.json')).packageManager;
const requiredNpmVersion = /^npm@(.+)$/.exec(packageManager)?.[1];
if (!requiredNpmVersion) {
throw new Error(
`The root packageManager must pin an exact npm version; found ${packageManager}`,
);
}
const npmVersion = runNpm(['--version'], { encoding: 'utf8' }).trim();
if (npmVersion !== requiredNpmVersion) {
throw new Error(`npm ${requiredNpmVersion} is required; found ${npmVersion}`);
}
}
function validateCleanWorktree() {
const status = execFileSync('git', ['status', '--porcelain', '--untracked-files=all'], {
cwd: repoRoot,
encoding: 'utf8',
}).trim();
if (status) {
throw new Error('Refusing to build a release tarball from a dirty worktree; commit first');
}
}
function buildRuntimeWorkspaces() {
for (const workspace of buildOrder) runNpm(['--workspace', workspace, 'run', 'clean']);
for (const workspace of buildOrder) runNpm(['--workspace', workspace, 'run', 'build']);
}
function checkProductionAudit() {
const audit = spawnSync(
'npm',
['audit', '--omit=dev', '--workspace', 'maka-agent', '--json'],
npmSpawnOptions({
cwd: repoRoot,
encoding: 'utf8',
env: releaseNpmEnvironment(process.env, join(repoRoot, '.npmrc')),
maxBuffer: 64 * 1024 * 1024,
}),
);
const report = JSON.parse(audit.stdout || '{}');
const vulnerabilities = report.metadata?.vulnerabilities;
if (audit.error || audit.status !== 0 || vulnerabilities?.total !== 0) {
throw new Error(
`CLI production dependency audit failed: ${JSON.stringify(vulnerabilities ?? report.error ?? audit.error)}`,
);
}
}
function readCliDependencyTree() {
return JSON.parse(
runNpm(['ls', '--workspace', 'maka-agent', '--omit=dev', '--all', '--long', '--json'], {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
}),
);
}
function copyCliRuntime() {
copyRuntimeDist(cliSource, stageRoot);
chmodSync(join(stageRoot, 'dist/cli.js'), 0o755);
}
function copyDependencyClosure(cli) {
const copiedDestinations = new Map();
const visit = (node, parentDestination) => {
for (const dependency of Object.values(node.dependencies ?? {})) {
if (!dependency || typeof dependency !== 'object') continue;
const peerPolicy = dependencyPeerPolicy(node, dependency.name);
if (peerPolicy === 'optional') continue;
if (typeof dependency.path === 'string' && existsSync(dependency.path)) {
const destination = dependencyDestination(dependency);
const source = realpathSync(dependency.path);
const previous = copiedDestinations.get(destination);
if (previous && previous !== source) {
throw new Error(
`Dependency destination collision at ${destination}: ${previous} vs ${source}`,
);
}
if (!previous) {
if (internalPackageSet.has(dependency.name)) {
copyInternalPackage(source, destination);
} else {
copyThirdPartyPackage(source, destination);
}
copiedDestinations.set(destination, source);
}
if (
peerPolicy === 'required' &&
parentDestination &&
destination === join(parentDestination, 'node_modules', ...dependency.name.split('/'))
) {
addBundledDependency(parentDestination, dependency.name, dependency.version);
}
visit(dependency, destination);
}
}
};
visit(cli, stageRoot);
const evalUndici = findDependency(cli, 'undici', '8.10.0');
if (!evalUndici?.path || !existsSync(evalUndici.path)) {
throw new Error('The installed CLI closure does not contain undici@8.10.0');
}
copyThirdPartyPackage(realpathSync(evalUndici.path), join(stageRoot, 'node_modules/undici'));
copiedDestinations.set(join(stageRoot, 'node_modules/undici'), realpathSync(evalUndici.path));
return [...copiedDestinations.keys()].map(
(destination) => `${relative(stageRoot, destination).split(sep).join('/')}/package.json`,
);
}
function dependencyPeerPolicy(parent, dependencyName) {
if (!dependencyName || typeof parent.path !== 'string' || !existsSync(parent.path)) return 'none';
const manifest = readJson(join(realpathSync(parent.path), 'package.json'));
if (!manifest.peerDependencies?.[dependencyName]) return 'none';
return manifest.peerDependenciesMeta?.[dependencyName]?.optional ? 'optional' : 'required';
}
function addBundledDependency(packageRoot, dependencyName, dependencyVersion) {
const manifestPath = join(packageRoot, 'package.json');
const manifest = readJson(manifestPath);
manifest.dependencies = { ...manifest.dependencies, [dependencyName]: dependencyVersion };
manifest.bundledDependencies = [
...new Set([...(manifest.bundledDependencies ?? []), dependencyName]),
].sort();
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
}
function dependencyDestination(dependency) {
if (internalPackageSet.has(dependency.name)) {
return join(stageRoot, 'node_modules', ...dependency.name.split('/'));
}
if (dependency.name?.startsWith('@maka/')) {
throw new Error(`Unexpected private workspace in the CLI closure: ${dependency.name}`);
}
const sourcePath = resolve(dependency.path);
const rootModules = join(repoRoot, 'node_modules');
if (isInside(rootModules, sourcePath)) {
return join(stageRoot, 'node_modules', relative(rootModules, sourcePath));
}
for (const packageName of internalPackageNames) {
const workspaceRoot = realpathSync(join(repoRoot, 'node_modules', ...packageName.split('/')));
const workspaceModules = join(workspaceRoot, 'node_modules');
if (isInside(workspaceModules, sourcePath)) {
return join(
stageRoot,
'node_modules',
...packageName.split('/'),
'node_modules',
relative(workspaceModules, sourcePath),
);
}
}
throw new Error(`Dependency path is outside the supported installed tree: ${sourcePath}`);
}
function copyInternalPackage(source, destination) {
mkdirSync(destination, { recursive: true, mode: 0o755 });
const manifest = readJson(join(source, 'package.json'));
const allowedFields = [
'name',
'version',
'description',
'license',
'type',
'sideEffects',
'main',
'exports',
'bin',
'engines',
'dependencies',
'optionalDependencies',
'peerDependencies',
'peerDependenciesMeta',
];
const releaseManifest = Object.fromEntries(
allowedFields
.filter((field) => manifest[field] !== undefined)
.map((field) => [field, manifest[field]]),
);
writeFileSync(join(destination, 'package.json'), `${JSON.stringify(releaseManifest, null, 2)}\n`);
for (const releaseFile of resolveWorkspaceReleaseFiles(source, manifest)) {
if (releaseFile === 'dist') copyRuntimeDist(source, destination);
else copyDeclaredFile(source, destination, releaseFile);
}
}
function copyRuntimeDist(source, destination) {
const sourceDist = join(source, 'dist');
if (!existsSync(sourceDist)) throw new Error(`Missing build output: ${sourceDist}`);
copyTreeFiles(sourceDist, join(destination, 'dist'), (relativePath) => {
return !isMakaDevelopmentArtifact(join('dist', relativePath));
});
}
function copyThirdPartyPackage(source, destination) {
if (!existsSync(join(source, 'package.json'))) {
throw new Error(`Installed dependency has no package.json: ${source}`);
}
rmSync(destination, { recursive: true, force: true });
cpSync(source, destination, {
recursive: true,
preserveTimestamps: true,
filter: (path) => {
if (path === source) return true;
const relativePath = relative(source, path);
return (
!relativePath.split(sep).includes('node_modules') &&
!isThirdPartyDevelopmentArtifact(relativePath)
);
},
});
stripReviewedInstallScripts(destination);
}
function stripReviewedInstallScripts(destination) {
const manifestPath = join(destination, 'package.json');
const manifest = readJson(manifestPath);
const packageKey = `${manifest.name}@${manifest.version}`;
const reviewed = strippedInstallScripts.get(packageKey);
const present = ['preinstall', 'install', 'postinstall'].filter(
(name) => typeof manifest.scripts?.[name] === 'string',
);
if (present.length === 0) return;
if (!reviewed || present.some((name) => !reviewed.has(name))) {
throw new Error(`${packageKey} has an unreviewed install script: ${present.join(', ')}`);
}
for (const name of present) delete manifest.scripts[name];
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
if (packageKey === 'node-pty@1.2.0-beta.15') pruneNodePtyBuildInputs(destination);
}
function pruneNodePtyBuildInputs(destination) {
// Without an explicit install script npm treats binding.gyp as a request to
// run node-gyp. This release supports the platforms validated below through
// shipped prebuilds, so source compilation would only add network/toolchain
// dependence and bypass the reviewed artifact.
for (const path of ['binding.gyp', 'scripts', 'src', 'third_party', 'typings']) {
rmSync(join(destination, path), { recursive: true, force: true });
}
for (const path of walkFiles(destination)) {
if (lstatSync(path).isFile() && /\.(?:map|pdb)$/.test(path)) rmSync(path, { force: true });
}
}
function copyEvalMirror() {
const stagedEval = join(stageRoot, 'node_modules/@maka/eval');
const mirror = join(stageRoot, 'packages/eval');
cpSync(stagedEval, mirror, {
recursive: true,
preserveTimestamps: true,
filter: (path) =>
path === stagedEval || !relative(stagedEval, path).split(sep).includes('node_modules'),
});
}
function copyReleaseDocuments() {
copyFileSync(join(cliSource, 'README.md'), join(stageRoot, 'README.md'));
copyFileSync(join(cliSource, 'README.zh-CN.md'), join(stageRoot, 'README.zh-CN.md'));
copyFileSync(join(repoRoot, 'LICENSE'), join(stageRoot, 'LICENSE'));
copyFileSync(join(repoRoot, 'NOTICE'), join(stageRoot, 'NOTICE'));
// Incubator policy: podling releases carry the incubating disclaimer, kept
// next to LICENSE/NOTICE. The npm tarball is a release like the installers.
copyFileSync(join(repoRoot, 'DISCLAIMER-WIP'), join(stageRoot, 'DISCLAIMER-WIP'));
copyFileSync(
join(cliSource, 'THIRD_PARTY_NOTICES.txt'),
join(stageRoot, 'THIRD_PARTY_NOTICES.txt'),
);
}
function writeReleaseManifest(cli, publishable) {
const source = readJson(join(cliSource, 'package.json'));
const root = readJson(join(repoRoot, 'package.json'));
if (source.version !== cli.version) {
throw new Error(
`CLI manifest and installed lockfile disagree: ${source.version} vs ${cli.version}`,
);
}
const undici = findDependency(cli, 'undici', '8.10.0');
const dependencies = { ...source.dependencies, undici: undici.version };
const manifest = {
name: source.name,
version: source.version,
description: 'Local-first agent workspace for the terminal.',
license: source.license,
type: source.type,
exports: {},
bin: source.bin,
engines: root.engines,
repository: {
type: 'git',
url: 'git+https://github.com/apache/maka.git',
directory: 'packages/cli',
},
homepage: 'https://github.com/apache/maka#readme',
bugs: { url: 'https://github.com/apache/maka/issues' },
keywords: ['ai', 'agent', 'cli', 'tui', 'local-first'],
publishConfig: publishable
? {
access: 'public',
registry: 'https://registry.npmjs.org/',
tag: source.version.includes('-') ? 'next' : 'latest',
}
: {
access: 'restricted',
registry: 'http://127.0.0.1:9/',
tag: 'development',
},
files: [
'dist',
'packages/eval',
'README.md',
'README.zh-CN.md',
'LICENSE',
'NOTICE',
'DISCLAIMER-WIP',
'THIRD_PARTY_NOTICES.txt',
],
dependencies,
bundledDependencies: Object.keys(dependencies).sort(),
...(!publishable ? { private: true } : {}),
};
if (!publishable) {
manifest.version = developmentPackageVersion(source.version, manifest);
}
writeFileSync(join(stageRoot, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`);
}
function developmentPackageVersion(baseVersion, manifest) {
const digest = createHash('sha256');
for (const path of walkFiles(stageRoot)
.filter((candidate) => lstatSync(candidate).isFile())
.sort()) {
digest.update(relative(stageRoot, path).split(sep).join('/'));
digest.update('\0');
digest.update(readFileSync(path));
digest.update('\0');
}
const { version: _version, ...manifestIdentity } = manifest;
digest.update('package.json\0');
digest.update(JSON.stringify(manifestIdentity));
digest.update('\0');
return `${baseVersion}${baseVersion.includes('-') ? '.' : '-'}dev-${digest.digest('hex').slice(0, 12)}`;
}
function validateStaging() {
const required = [
'dist/cli.js',
'README.zh-CN.md',
'DISCLAIMER-WIP',
'node_modules/@maka/runtime/dist/workers/filesystem-worker.js',
'node_modules/@maka/runtime-host/dist/execution-candidate-main.js',
'packages/eval/dist/harbor-external-subject.js',
'packages/eval/harbor/relay_agent.py',
'packages/eval/harbor/docker-compose-egress-proxy.yaml',
'node_modules/node-pty/prebuilds/linux-x64/pty.node',
'node_modules/node-pty/prebuilds/darwin-arm64/pty.node',
'node_modules/node-pty/prebuilds/win32-x64/conpty.node',
'node_modules/fs-native-extensions/prebuilds/linux-x64/fs-native-extensions.node',
'node_modules/fs-native-extensions/prebuilds/darwin-arm64/fs-native-extensions.node',
'node_modules/fs-native-extensions/prebuilds/win32-x64/fs-native-extensions.node',
];
for (const path of required) {
if (!existsSync(join(stageRoot, path)))
throw new Error(`Required release file is missing: ${path}`);
}
assertPatchedFile(
'node_modules/node-pty/lib/unixTerminal.js',
'CustomWriteStream.prototype._ownsFileDescriptor',
);
assertPatchedFile('node_modules/@ai-sdk/provider-utils/dist/index.js', 'function absentIfBlank');
const manifest = readJson(join(stageRoot, 'package.json'));
for (const [name, specifier] of Object.entries(manifest.dependencies ?? {})) {
if (
typeof specifier !== 'string' ||
/^(?:file:|workspace:)|^[A-Za-z]:[\\/]|^\//.test(specifier)
) {
throw new Error(`Release dependency must use a registry version: ${name}@${specifier}`);
}
if (
name.startsWith('@maka/') &&
!existsSync(join(stageRoot, 'node_modules', ...name.split('/')))
) {
throw new Error(`Private dependency is not physically bundled: ${name}`);
}
}
for (const path of walkFiles(stageRoot)) {
const relativePath = relative(stageRoot, path);
const status = lstatSync(path);
if (status.isSymbolicLink())
throw new Error(`Release staging contains a symlink: ${relativePath}`);
if (!status.isFile()) continue;
if (basename(path) === 'package.json') {
const packageManifest = readJson(path);
const installScript = ['preinstall', 'install', 'postinstall'].find(
(name) => typeof packageManifest.scripts?.[name] === 'string',
);
if (installScript) {
throw new Error(
`Release dependency retains an install script: ${relativePath} (${installScript})`,
);
}
}
const content = readFileSync(path);
if (content.includes(Buffer.from(repoRoot))) {
throw new Error(`Release file contains the repository path: ${relativePath}`);
}
}
}
function validatePackedFiles(files, expectedDependencyManifests) {
const paths = files.map((file) => file.path);
for (const file of files) {
const { path } = file;
if (isAbsolute(path) || path.split(/[\\/]/).includes('..')) {
throw new Error(`Unsafe tarball path: ${path}`);
}
const segments = path.split('/');
const makaOwned =
path.startsWith('dist/') ||
path.startsWith('packages/eval/') ||
(segments[0] === 'node_modules' && segments[1] === '@maka' && segments[3] !== 'node_modules');
if (makaOwned && isMakaDevelopmentArtifact(path)) {
throw new Error(`Development artifact escaped into the tarball: ${path}`);
}
if (!makaOwned && path.startsWith('node_modules/') && isThirdPartyDevelopmentArtifact(path)) {
throw new Error(`Third-party development artifact escaped into the tarball: ${path}`);
}
const fileName = basename(path).toLowerCase();
if (
/^\.env(?:\..*)?$/.test(fileName) ||
fileName === 'deepseek.key' ||
/\.(?:key|p12|pfx|pem)$/.test(fileName) ||
fileName === 'id_rsa' ||
fileName === 'id_ed25519'
) {
throw new Error(`Credential-like file escaped into the tarball: ${path}`);
}
}
const requiredPacked = [
'dist/cli.js',
'DISCLAIMER-WIP',
'node_modules/@maka/runtime/dist/workers/filesystem-worker.js',
'node_modules/@maka/runtime-host/dist/execution-candidate-main.js',
'packages/eval/harbor/relay_agent.py',
];
for (const suffix of requiredPacked) {
if (!paths.some((path) => path.endsWith(suffix))) {
throw new Error(`Required file was not packed: ${suffix}`);
}
}
for (const manifestPath of expectedDependencyManifests) {
if (!paths.includes(manifestPath)) {
throw new Error(`Production dependency was not packed: ${manifestPath}`);
}
}
const bin = files.find((file) => file.path === 'dist/cli.js');
if (!bin || (bin.mode & 0o111) === 0) {
throw new Error('The packed CLI entrypoint is not executable');
}
}
function assertPatchedFile(path, marker) {
const content = readFileSync(join(stageRoot, path), 'utf8');
if (!content.includes(marker)) throw new Error(`Patched dependency marker is missing: ${path}`);
console.log(`[release-cli] patch: ${path} sha256=${digestFile(join(stageRoot, path))}`);
}
function findDependency(root, name, version) {
let result;
const visit = (node) => {
for (const dependency of Object.values(node.dependencies ?? {})) {
if (!dependency || typeof dependency !== 'object') continue;
if (dependency.name === name && dependency.version === version) result ??= dependency;
visit(dependency);
}
};
visit(root);
return result;
}
function copyDeclaredFile(source, destination, relativePath) {
const from = join(source, relativePath);
if (!existsSync(from) || !statSync(from).isFile()) {
throw new Error(`Declared Eval runtime asset is missing: ${from}`);
}
const to = join(destination, relativePath);
mkdirSync(dirname(to), { recursive: true, mode: 0o755 });
copyFileSync(from, to);
}
function copyTreeFiles(source, destination, allow) {
for (const entry of readdirSync(source, { withFileTypes: true })) {
const path = join(source, entry.name);
const relativePath = relative(source, path);
if (entry.isDirectory()) {
copyTreeFiles(path, join(destination, entry.name), (nestedPath) =>
allow(join(entry.name, nestedPath)),
);
} else if (entry.isFile() && allow(relativePath)) {
mkdirSync(destination, { recursive: true, mode: 0o755 });
copyFileSync(path, join(destination, entry.name));
} else if (entry.isSymbolicLink()) {
throw new Error(`Maka build output contains an unexpected symlink: ${path}`);
}
}
}
function walkFiles(root) {
const paths = [];
for (const entry of readdirSync(root, { withFileTypes: true })) {
const path = join(root, entry.name);
paths.push(path);
if (entry.isDirectory()) paths.push(...walkFiles(path));
}
return paths;
}
function isInside(parent, child) {
const path = relative(parent, child);
return path !== '' && !path.startsWith(`..${sep}`) && path !== '..' && !isAbsolute(path);
}
function readJson(path) {
return JSON.parse(readFileSync(path, 'utf8'));
}
function runNpm(args, options = {}) {
const environment = releaseNpmEnvironment(options.env ?? process.env, join(repoRoot, '.npmrc'));
return execFileSync(
'npm',
args,
npmSpawnOptions({
cwd: repoRoot,
stdio: options.encoding ? undefined : 'inherit',
...options,
env: environment,
}),
);
}
function digestFile(path) {
return createHash('sha256').update(readFileSync(path)).digest('hex');
}
function formatBytes(value) {
if (!Number.isFinite(value)) return 'unknown';
const units = ['B', 'KiB', 'MiB', 'GiB'];
let size = value;
let unit = 0;
while (size >= 1024 && unit < units.length - 1) {
size /= 1024;
unit += 1;
}
return `${size.toFixed(unit === 0 ? 0 : 1)} ${units[unit]}`;
}