/* * 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 { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs'; import { isAbsolute, join, relative, resolve, sep } from 'node:path'; const DEVELOPMENT_DIRECTORIES = new Set([ '.nyc_output', '__fixtures__', '__tests__', 'coverage', 'fixture', 'fixtures', 'test', 'tests', ]); const LOCAL_PACKAGE_PREFIX = '@maka/'; const ASF_WIP_DISCLAIMER_MARKER = ''; function manifestFromEntry(entry) { return entry?.manifest ?? entry; } export function renderNpmReadme(readme, disclaimer) { const markerCount = readme.split(ASF_WIP_DISCLAIMER_MARKER).length - 1; if (markerCount !== 1) { throw new Error(`CLI README must contain exactly one ${ASF_WIP_DISCLAIMER_MARKER} marker.`); } if (!disclaimer.trim()) throw new Error('DISCLAIMER-WIP must not be empty.'); return readme.replace(ASF_WIP_DISCLAIMER_MARKER, disclaimer.trimEnd()); } export function collectWorkspaceDependencyClosure(entryName, manifestsByName) { const closure = []; const complete = new Set(); const visiting = new Set(); function visit(packageName) { if (complete.has(packageName)) return; if (visiting.has(packageName)) { throw new Error(`Workspace dependency cycle reached ${packageName}.`); } const entry = manifestsByName.get(packageName); if (!entry) throw new Error(`Workspace package ${packageName} is missing.`); visiting.add(packageName); const manifest = manifestFromEntry(entry); for (const dependencyName of Object.keys(manifest.dependencies ?? {}).sort()) { if (manifestsByName.has(dependencyName)) visit(dependencyName); else if (dependencyName.startsWith(LOCAL_PACKAGE_PREFIX)) { throw new Error( `${packageName} depends on local package ${dependencyName}, but it is not in workspaces.`, ); } } visiting.delete(packageName); complete.add(packageName); closure.push(packageName); } visit(entryName); return closure; } export function orderWorkspaceBuilds(workspaces) { const workspacesByName = new Map(workspaces.map((workspace) => [workspace.name, workspace])); const order = []; const complete = new Set(); const visiting = new Set(); function visit(packageName) { if (complete.has(packageName)) return; if (visiting.has(packageName)) { throw new Error(`Workspace build dependency cycle reached ${packageName}.`); } const workspace = workspacesByName.get(packageName); if (!workspace) throw new Error(`Selected workspace package ${packageName} is missing.`); visiting.add(packageName); const manifest = manifestFromEntry(workspace); const dependencies = { ...manifest.dependencies, ...manifest.devDependencies, }; for (const dependencyName of Object.keys(dependencies).sort()) { if (workspacesByName.has(dependencyName)) visit(dependencyName); } visiting.delete(packageName); complete.add(packageName); order.push(packageName); } for (const { name } of workspaces) visit(name); return order; } export function workspaceReleaseFiles(manifest) { const declared = Object.hasOwn(manifest, 'releaseFiles') ? manifest.releaseFiles : ['dist']; if (!Array.isArray(declared) || declared.length === 0) { throw new Error(`${manifest.name ?? 'Workspace package'} releaseFiles must be non-empty.`); } const releaseFiles = declared.map((path) => { if (typeof path !== 'string' || path.length === 0) { throw new Error(`${manifest.name ?? 'Workspace package'} releaseFiles must be paths.`); } if ( isAbsolute(path) || path.includes('\\') || /[*?[\]{}]/u.test(path) || path.split('/').some((segment) => segment === '' || segment === '.' || segment === '..') ) { throw new Error(`${manifest.name ?? 'Workspace package'} has unsafe release file ${path}.`); } return path; }); if (new Set(releaseFiles).size !== releaseFiles.length) { throw new Error(`${manifest.name ?? 'Workspace package'} releaseFiles contain duplicates.`); } for (const [index, path] of releaseFiles.entries()) { const overlap = releaseFiles .slice(index + 1) .find((candidate) => path.startsWith(`${candidate}/`) || candidate.startsWith(`${path}/`)); if (overlap) { throw new Error( `${manifest.name ?? 'Workspace package'} releaseFiles overlap at ${path} and ${overlap}.`, ); } } if (!releaseFiles.includes('dist')) { throw new Error(`${manifest.name ?? 'Workspace package'} releaseFiles must include dist.`); } return releaseFiles; } export function resolveWorkspaceReleaseFiles(directory, manifest) { const releaseFiles = workspaceReleaseFiles(manifest); for (const releaseFile of releaseFiles) { const entry = lstatSync(join(directory, ...releaseFile.split('/')), { throwIfNoEntry: false }); if (!entry) { throw new Error( `${manifest.name ?? 'Workspace package'} release file is missing: ${releaseFile}`, ); } if (releaseFile === 'dist') { if (!entry.isDirectory()) { throw new Error( `${manifest.name ?? 'Workspace package'} release dist must be a directory.`, ); } } else if (!entry.isFile() && !entry.isDirectory()) { throw new Error( `${manifest.name ?? 'Workspace package'} release asset must be a regular file or directory: ${releaseFile}`, ); } } return releaseFiles; } export function resolveReleaseWorkspacePackages(repoRoot, entryName = 'maka-agent') { const resolvedRepoRoot = realpathSync(repoRoot); const rootManifest = JSON.parse(readFileSync(join(resolvedRepoRoot, 'package.json'), 'utf8')); const manifestsByName = new Map(); for (const workspacePath of rootManifest.workspaces ?? []) { if (typeof workspacePath !== 'string' || /[*?[\]{}]/u.test(workspacePath)) { throw new Error(`CLI release requires explicit workspace paths, found ${workspacePath}.`); } const directory = realpathSync(resolve(resolvedRepoRoot, workspacePath)); const pathFromRepo = relative(resolvedRepoRoot, directory); if (pathFromRepo === '..' || pathFromRepo.startsWith(`..${sep}`) || isAbsolute(pathFromRepo)) { throw new Error(`Workspace path escapes the repository: ${workspacePath}`); } const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')); if (typeof manifest.name !== 'string' || !manifest.name) { throw new Error(`${workspacePath}/package.json is missing a package name.`); } if (manifestsByName.has(manifest.name)) { throw new Error(`Duplicate workspace package name ${manifest.name}.`); } manifestsByName.set(manifest.name, { directory, manifest, workspacePath }); } return collectWorkspaceDependencyClosure(entryName, manifestsByName).map((name) => ({ name, ...manifestsByName.get(name), })); } export function releaseNpmEnvironment(environment, userConfigPath) { return { ...Object.fromEntries( Object.entries(environment).filter( ([name]) => name.toLowerCase() !== 'npm_config_allow_scripts' && name.toLowerCase() !== 'npm_config_userconfig', ), ), npm_config_userconfig: userConfigPath, }; } export function isThirdPartyDevelopmentArtifact(relativePath) { const segments = relativePath.split(/[\\/]/).filter(Boolean); if (segments.some((segment) => DEVELOPMENT_DIRECTORIES.has(segment))) return true; const file = segments.at(-1) ?? ''; return ( /\.(?:spec|test)\.(?:cjs|js|mjs)$/.test(file) || /\.d\.ts(?:\.map)?$/.test(file) || /\.(?:cjs|js|mjs)\.map$/.test(file) || /\.(?:cts|mts|ts|tsx)$/.test(file) || file.endsWith('.tsbuildinfo') ); } // Modules that only a test or E2E entry point may import. They are a // Maka-owned convention, so they are not part of DEVELOPMENT_DIRECTORIES: a // third-party package is free to ship a directory by that name. const MAKA_TEST_ONLY_DIRECTORY = 'test-only'; export function isMakaDevelopmentArtifact(relativePath) { const segments = relativePath.split(/[\\/]/).filter(Boolean); if (segments.some((segment) => segment === 'src')) return true; if (segments.some((segment) => segment === MAKA_TEST_ONLY_DIRECTORY)) return true; if (segments.some((segment) => DEVELOPMENT_DIRECTORIES.has(segment))) return true; const file = segments.at(-1) ?? ''; return ( file === 'dev-cli.js' || /\.(?:spec|test)\.js$/.test(file) || /\.d\.ts(?:\.map)?$/.test(file) || /\.js\.map$/.test(file) ); } function releaseExportTarget(target) { if (typeof target === 'string') { return isMakaDevelopmentArtifact(target) ? undefined : target; } if (Array.isArray(target)) { const projected = target.map(releaseExportTarget).filter((entry) => entry !== undefined); return projected.length === 0 ? undefined : projected; } if (target && typeof target === 'object') { const projected = Object.fromEntries( Object.entries(target) .map(([condition, value]) => [condition, releaseExportTarget(value)]) .filter(([, value]) => value !== undefined), ); return Object.keys(projected).length === 0 ? undefined : projected; } return target; } const WORKSPACE_RELEASE_MANIFEST_FIELDS = [ 'name', 'version', 'description', 'license', 'type', 'sideEffects', 'main', 'exports', 'bin', 'engines', 'dependencies', 'optionalDependencies', 'peerDependencies', 'peerDependenciesMeta', ]; export function workspaceReleaseManifest(manifest) { const releaseManifest = Object.fromEntries( WORKSPACE_RELEASE_MANIFEST_FIELDS.filter((field) => manifest[field] !== undefined).map( (field) => [field, manifest[field]], ), ); if (releaseManifest.exports !== undefined) { const projected = releaseExportTarget(releaseManifest.exports); releaseManifest.exports = projected === undefined ? {} : projected; } return releaseManifest; } export function isCurrentDevelopmentJavaScript( workspaceRoot, relativePath, generatedFiles = new Set(), ) { const portablePath = relativePath.split(/[\\/]/u).join('/'); if (generatedFiles.has(portablePath)) return true; if (!portablePath.endsWith('.js')) return false; const sourcePath = portablePath.slice(0, -'.js'.length); return ['.ts', '.tsx', '.mts', '.cts'].some((extension) => existsSync(join(workspaceRoot, 'src', `${sourcePath}${extension}`)), ); }