/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ 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 { assertProductNightlyVersion } from './release-version.mjs'; import { isCurrentDevelopmentJavaScript, isMakaDevelopmentArtifact, isThirdPartyDevelopmentArtifact, orderWorkspaceBuilds, renderNpmReadme, releaseNpmEnvironment, resolveReleaseWorkspacePackages, resolveWorkspaceReleaseFiles, workspaceReleaseManifest, } from './release-cli-file-policy.mjs'; const repoRoot = resolve(import.meta.dirname, '..'); const cliSource = join(repoRoot, 'packages/cli'); const allowDirty = process.argv.includes('--allow-dirty'); const developmentBuild = process.argv.includes('--development'); const nightlyVersion = process.env.MAKA_CLI_NIGHTLY_VERSION?.trim(); const preparedTree = process.env.MAKA_CLI_RELEASE_PREPARED_TREE === '1'; const releaseRoot = join(cliSource, 'release'); const artifactRoot = developmentBuild ? createDevelopmentArtifactRoot() : releaseRoot; const stageRoot = join(artifactRoot, 'package'); const peerPrebuildTargets = ['darwin-arm64', 'linux-arm64', 'linux-x64', 'win32-x64']; const privatePeerTarget = developmentBuild ? resolveDevelopmentPeerTarget() : `${process.platform}-${process.arch}`; const unsupportedArguments = process.argv .slice(2) .filter((argument) => !['--allow-dirty', '--development'].includes(argument)); 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 developmentGeneratedFiles = new Map([ ['@maka/runtime', new Set(['workers/filesystem-worker.js'])], ]); 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'])], ]); try { main(); } catch (error) { if (developmentBuild) rmSync(artifactRoot, { recursive: true, force: true }); throw error; } function main() { validateNodeVersion(); if (developmentBuild && nightlyVersion) { throw new Error('A public Nightly cannot be combined with --development'); } if (allowDirty && nightlyVersion) { throw new Error('A public Nightly cannot be built from a dirty worktree'); } if (nightlyVersion) { const productVersion = readJson(join(repoRoot, 'package.json')).version; assertProductNightlyVersion(nightlyVersion, productVersion); } if (!developmentBuild) validateReleaseNpmVersion(); if (developmentBuild) { if (allowDirty || preparedTree) { throw new Error('--development cannot be combined with release build options'); } console.warn('[release-cli] producing a private development tarball'); buildRuntimeWorkspaces({ clean: false }); packageCli(false); return; } 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({ clean: true }); checkProductionAudit(); runNpm(['run', 'check:cli-third-party-notices']); runNpm(['run', 'check:runtime-host-peer-dependencies']); runNpm(['run', 'check:runtime-host-peer-notices']); packageCli(preparedTree); } function packageCli(publishable) { const dependencyTree = readCliDependencyTree(); const cli = dependencyTree.dependencies?.['maka-agent']; if (!cli) throw new Error('npm ls did not return the maka-agent workspace'); rmSync(artifactRoot, { recursive: true, force: true }); mkdirSync(stageRoot, { recursive: true, mode: 0o755 }); copyCliRuntime(); copyRuntimeHostPeerPrebuilds(publishable); const expectedDependencyManifests = copyDependencyClosure(cli); copyReleaseDocuments(); writeReleaseManifest(cli, publishable); validateStaging(publishable); const packOutput = JSON.parse( runNpm(['pack', stageRoot, '--json', '--pack-destination', artifactRoot], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, }), ); const packs = Array.isArray(packOutput) ? packOutput : Object.values(packOutput); const [pack] = packs; if (packs.length !== 1 || !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(artifactRoot, pack.filename); validatePackedFiles(pack.files, expectedDependencyManifests, publishable); const sha256 = digestFile(tarballPath); writeFileSync(`${tarballPath}.sha256`, `${sha256} ${pack.filename}\n`, 'utf8'); writeFileSync( join(artifactRoot, `${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 createDevelopmentArtifactRoot() { const developmentRoot = join(cliSource, '.development'); mkdirSync(developmentRoot, { recursive: true, mode: 0o755 }); const parent = process.env.MAKA_CLI_DEVELOPMENT_OUTPUT_ROOT?.trim(); if (!parent) return mkdtempSync(join(developmentRoot, 'artifact-')); const resolvedParent = realpathSync(parent); const relativeParent = relative(realpathSync(developmentRoot), resolvedParent); if ( !relativeParent || relativeParent.startsWith('..') || isAbsolute(relativeParent) || !statSync(resolvedParent).isDirectory() ) { throw new Error('The CLI development output root must be a directory'); } return join(resolvedParent, 'artifact'); } 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' }); const preparedPeerPrebuilds = copyPeerPrebuildInputToCleanTree(cleanRoot); 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', ...(preparedPeerPrebuilds ? { MAKA_RUNTIME_HOST_PEER_PREBUILDS: preparedPeerPrebuilds } : {}), }, 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 copyPeerPrebuildInputToCleanTree(cleanRoot) { const source = process.env.MAKA_RUNTIME_HOST_PEER_PREBUILDS?.trim(); if (!source) return undefined; const destination = join(cleanRoot, '.release-runtime-host-peer-prebuilds'); cpSync(realpathSync(source), destination, { recursive: true, preserveTimestamps: true }); return destination; } function validateNodeVersion() { 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}`); } } function validateReleaseNpmVersion() { 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(options) { if (options.clean) { 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 releaseManifest = workspaceReleaseManifest(manifest); writeFileSync(join(destination, 'package.json'), `${JSON.stringify(releaseManifest, null, 2)}\n`); for (const releaseFile of resolveWorkspaceReleaseFiles(source, manifest)) { if (releaseFile === 'dist') copyRuntimeDist(source, destination, manifest.name); else copyDeclaredReleaseFile(source, destination, releaseFile); } } function copyRuntimeDist(source, destination, packageName = 'maka-agent') { const sourceDist = join(source, 'dist'); if (!existsSync(sourceDist)) throw new Error(`Missing build output: ${sourceDist}`); copyTreeFiles(sourceDist, join(destination, 'dist'), (relativePath) => { if (isMakaDevelopmentArtifact(join('dist', relativePath))) return false; return ( !developmentBuild || isCurrentDevelopmentJavaScript( source, relativePath, developmentGeneratedFiles.get(packageName), ) ); }); } 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 copyReleaseDocuments() { const readme = readFileSync(join(cliSource, 'README.md'), 'utf8'); const readmeZhCn = readFileSync(join(cliSource, 'README.zh-CN.md'), 'utf8'); const disclaimer = readFileSync(join(repoRoot, 'DISCLAIMER-WIP'), 'utf8'); const nightlyNotice = nightlyVersion ? '> **Developer snapshot:** This Nightly is not an Apache release and is not intended for production use. Its version is bound to one exact `apache/maka` commit.\n\n' : ''; const nightlyNoticeZhCn = nightlyVersion ? '> **开发快照:**此 Nightly 不是 Apache Release,不用于生产环境;其版本只对应一个精确的 `apache/maka` commit。\n\n' : ''; writeFileSync( join(stageRoot, 'README.md'), `${nightlyNotice}${renderNpmReadme(readme, disclaimer)}`, 'utf8', ); writeFileSync(join(stageRoot, 'README.zh-CN.md'), `${nightlyNoticeZhCn}${readmeZhCn}`, 'utf8'); copyFileSync(join(repoRoot, 'LICENSE'), join(stageRoot, 'LICENSE')); copyFileSync(join(repoRoot, 'NOTICE'), join(stageRoot, 'NOTICE')); // Incubator policy: every public podling artifact carries the incubating // disclaimer next to LICENSE/NOTICE, including developer-only Nightlies. copyFileSync(join(repoRoot, 'DISCLAIMER-WIP'), join(stageRoot, 'DISCLAIMER-WIP')); copyFileSync( join(cliSource, 'THIRD_PARTY_NOTICES.txt'), join(stageRoot, 'THIRD_PARTY_NOTICES.txt'), ); copyFileSync( join(cliSource, 'RUNTIME_HOST_PEER_DEPENDENCIES.rust.tsv'), join(stageRoot, 'RUNTIME_HOST_PEER_DEPENDENCIES.rust.tsv'), ); copyFileSync( join(cliSource, 'RUNTIME_HOST_PEER_THIRD_PARTY_NOTICES.txt'), join(stageRoot, 'RUNTIME_HOST_PEER_THIRD_PARTY_NOTICES.txt'), ); } function copyRuntimeHostPeerPrebuilds(publishable) { const sourceRoot = process.env.MAKA_RUNTIME_HOST_PEER_PREBUILDS?.trim(); const targets = publishable ? peerPrebuildTargets : privatePeerTarget === 'none' ? [] : [privatePeerTarget]; if (targets.length === 0) return; const destinationRoot = join(stageRoot, 'native/runtime-host-peer/prebuilds'); if (!sourceRoot && !publishable) { const [target] = targets; const destination = join(destinationRoot, target, 'maka_runtime_host_peer.node'); buildDevelopmentPeerAddon(target, destination); return; } if (!sourceRoot) { throw new Error('MAKA_RUNTIME_HOST_PEER_PREBUILDS must contain all release platform addons'); } for (const target of targets) { const source = join(sourceRoot, target, 'maka_runtime_host_peer.node'); if (!existsSync(source) || !statSync(source).isFile()) { throw new Error(`Runtime Host peer prebuild is missing: ${target}`); } const destination = join(destinationRoot, target, 'maka_runtime_host_peer.node'); mkdirSync(dirname(destination), { recursive: true, mode: 0o755 }); copyFileSync(source, destination); } } function resolveDevelopmentPeerTarget() { const configured = process.env.MAKA_CLI_DEVELOPMENT_PEER_TARGET?.trim(); const target = configured || `${process.platform}-${process.arch}`; if (target !== 'none' && !peerPrebuildTargets.includes(target)) { throw new Error( `MAKA_CLI_DEVELOPMENT_PEER_TARGET must be none or a supported target; found ${target}`, ); } return target; } function buildDevelopmentPeerAddon(target, output) { const hostTarget = `${process.platform}-${process.arch}`; const buildScript = join(repoRoot, 'native/runtime-host-peer/build.mjs'); if (target === hostTarget) { execFileSync(process.execPath, [buildScript], { cwd: repoRoot, env: { ...process.env, MAKA_RUNTIME_HOST_PEER_OUTPUT: output }, stdio: 'inherit', }); return; } const rustTarget = { 'linux-arm64': 'aarch64-unknown-linux-gnu.2.28', 'linux-x64': 'x86_64-unknown-linux-gnu.2.28', }[target]; if (!rustTarget) { throw new Error( `Cannot build the ${target} direct-peer addon from ${hostTarget}; run Desktop on that target or provide MAKA_RUNTIME_HOST_PEER_PREBUILDS`, ); } requireDevelopmentCommand( 'zig', ['version'], `Cross-compiling the ${target} direct-peer addon requires Zig on PATH (CI uses 0.16.x)`, ); requireDevelopmentCommand( 'cargo-zigbuild', ['--version'], `Cross-compiling the ${target} direct-peer addon requires cargo-zigbuild (cargo install cargo-zigbuild --version 0.23.2 --locked)`, ); execFileSync(process.execPath, [buildScript], { cwd: repoRoot, env: { ...process.env, MAKA_RUNTIME_HOST_PEER_CARGO_SUBCOMMAND: 'zigbuild', MAKA_RUNTIME_HOST_PEER_CARGO_TARGET: rustTarget, MAKA_RUNTIME_HOST_PEER_OUTPUT: output, }, stdio: 'inherit', }); } function requireDevelopmentCommand(command, args, message) { const result = spawnSync(command, args, { cwd: repoRoot, encoding: 'utf8' }); if (result.status === 0) return; throw new Error(`${message}; install it before setting up this development Runtime Host`); } 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 updateCompatibility = source.maka?.managedRuntimeHostUpdateCompatibility; if (!Number.isSafeInteger(updateCompatibility) || updateCompatibility < 1) { throw new Error( 'CLI manifest must define a positive managed Runtime Host update compatibility', ); } const manifest = { name: source.name, version: publishable && nightlyVersion ? nightlyVersion : source.version, description: nightlyVersion ? 'Apache Maka (Incubating) developer snapshot; not an Apache release.' : 'Apache Maka (Incubating), a local-first agent workspace for the terminal.', license: source.license, type: source.type, exports: {}, bin: source.bin, maka: { managedRuntimeHostUpdateCompatibility: updateCompatibility }, 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: ['apache', 'ai', 'agent', 'cli', 'tui', 'local-first'], publishConfig: publishable ? { access: 'public', registry: 'https://registry.npmjs.org/', tag: nightlyVersion ? 'nightly' : 'latest', } : { access: 'restricted', registry: 'http://127.0.0.1:9/', tag: 'development', }, files: [ 'dist', 'native', 'README.md', 'README.zh-CN.md', 'LICENSE', 'NOTICE', 'DISCLAIMER-WIP', 'THIRD_PARTY_NOTICES.txt', 'RUNTIME_HOST_PEER_DEPENDENCIES.rust.tsv', 'RUNTIME_HOST_PEER_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(publishable) { const required = [ 'dist/cli.js', 'README.zh-CN.md', 'DISCLAIMER-WIP', 'RUNTIME_HOST_PEER_DEPENDENCIES.rust.tsv', 'RUNTIME_HOST_PEER_THIRD_PARTY_NOTICES.txt', 'node_modules/@maka/runtime/dist/workers/filesystem-worker.js', 'node_modules/@maka/runtime-host/dist/execution-candidate-main.js', 'node_modules/@maka/eval/dist/harbor-external-subject.js', 'node_modules/@maka/eval/harbor/relay_agent.py', 'node_modules/@maka/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', ]; if (publishable) { required.push( ...peerPrebuildTargets.map( (target) => `native/runtime-host-peer/prebuilds/${target}/maka_runtime_host_peer.node`, ), ); } else if (privatePeerTarget !== 'none') { required.push( `native/runtime-host-peer/prebuilds/${privatePeerTarget}/maka_runtime_host_peer.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, publishable) { 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/') || (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', 'node_modules/@maka/eval/harbor/relay_agent.py', ...(publishable || privatePeerTarget !== 'none' ? ['native/runtime-host-peer/prebuilds/'] : []), ]; for (const suffix of requiredPacked) { if ( !paths.some((path) => suffix.endsWith('/') ? path.startsWith(suffix) : 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 || (process.platform !== 'win32' && (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 copyDeclaredReleaseFile(source, destination, relativePath) { const from = join(source, relativePath); if (!existsSync(from)) throw new Error(`Declared release asset is missing: ${from}`); const entry = statSync(from); if (entry.isDirectory()) { for (const child of readdirSync(from, { withFileTypes: true })) { if (child.isSymbolicLink()) { throw new Error(`Declared release directory contains a symlink: ${join(from, child.name)}`); } copyDeclaredReleaseFile(source, destination, join(relativePath, child.name)); } } else if (entry.isFile()) { const to = join(destination, relativePath); mkdirSync(dirname(to), { recursive: true, mode: 0o755 }); copyFileSync(from, to); } else { throw new Error(`Declared release asset is not a regular file or directory: ${from}`); } } 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]}`; }