UNPKG

@supernovaio/cli

Version:

Supernova.io Command Line Interface

312 lines (255 loc) 10.3 kB
import { spawnSync } from "node:child_process" import fs from "node:fs" import path from "node:path" const PRIVATE_DEPS_DIR_NAME = "private-deps" const REWRITE_MARKER_FILE = ".supernova-private-deps-rewritten" const NPM_PUBLIC_REGISTRY_HOST = "registry.npmjs.org" ;(() => { const packageJsonPath = path.join(process.cwd(), "package.json") const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()) const packageLock = readPackageLockJson() const declaredPrivateDependencies = packageJson.supernova?.privateDependencies if (declaredPrivateDependencies && !Array.isArray(declaredPrivateDependencies)) { throw new TypeError( `supernova.privateDependencies must be an array, example: "privateDependencies": ["@example/package"]`, ) } const parsedDeclaredPrivateDependencies = declaredPrivateDependencies ? validateAndParseDeclaredPrivateDependencies(declaredPrivateDependencies) : { exactDependencies: [], scopePatterns: [] } const detectedByLockfileHost = detectPrivateDependenciesFromPackageLock(packageLock) const detectedByPrivateDependencyScopes = detectPrivateDependenciesFromScopePatterns( packageLock, parsedDeclaredPrivateDependencies.scopePatterns, packageJson, ) const detectedPrivateDependencies = [...new Set([...detectedByLockfileHost, ...detectedByPrivateDependencyScopes])] const privateDependencies = [ ...new Set([...detectedPrivateDependencies, ...parsedDeclaredPrivateDependencies.exactDependencies]), ] if (privateDependencies.length === 0) return console.log( `private dependency detection: lockfileHost=${detectedByLockfileHost.length}, scopePattern=${detectedByPrivateDependencyScopes.length}, fallback=${parsedDeclaredPrivateDependencies.exactDependencies.length}, total=${privateDependencies.length}`, ) if (detectedByLockfileHost.length > 0) { console.log(`lockfile-host private deps: ${detectedByLockfileHost.join(", ")}`) } if (parsedDeclaredPrivateDependencies.scopePatterns.length > 0) { console.log( `configured private dependency scope patterns: ${parsedDeclaredPrivateDependencies.scopePatterns.join(", ")}`, ) } if (detectedByPrivateDependencyScopes.length > 0) { console.log(`scope-detected private deps: ${detectedByPrivateDependencyScopes.join(", ")}`) } if (parsedDeclaredPrivateDependencies.exactDependencies.length > 0) { console.log(`fallback private deps: ${parsedDeclaredPrivateDependencies.exactDependencies.join(", ")}`) } const privateDepsDir = path.join(process.cwd(), PRIVATE_DEPS_DIR_NAME) fs.rmSync(privateDepsDir, { recursive: true, force: true }) fs.mkdirSync(privateDepsDir, { recursive: true }) if (typeof packageJson.dependencies !== "object" || packageJson.dependencies === null) { packageJson.dependencies = {} } for (const dependency of privateDependencies) { // Create a tarball from already installed dependency source. const src = resolveDependencySourceFullPath(dependency, packageLock) if (!src) { throw new Error(`Private dependency ${dependency} must be present in node_modules`) } try { const tarballFileName = packDependencyTarball(src, privateDepsDir) packageJson.dependencies[dependency] = `file:${getDependencyTarballRelativePath(tarballFileName)}` console.log(`packed ${dependency} -> private-deps/${tarballFileName}`) } catch (error) { console.error(`Couldn't pack dependency ${dependency} from ${src}`) throw error } } fs.writeFileSync(packageJsonPath, Buffer.from(JSON.stringify(packageJson, null, 2) + "\n")) fs.writeFileSync(path.join(process.cwd(), REWRITE_MARKER_FILE), "") console.log(`rewritten root dependencies to file: tarballs and created ${REWRITE_MARKER_FILE}`) })() function validateAndParseDeclaredPrivateDependencies(privateDependencies) { const exactDependencies = [] const scopePatterns = [] for (const [i, d] of privateDependencies.entries()) { if (typeof d !== "string") { throw new TypeError(`Invalid value at supernova.privateDependencies[${i}]: '${d}'`) } if (!isValidPrivateDependencyEntry(d)) { throw new TypeError( `supernova.privateDependencies[${i}] must be a package name or scope wildcard like "@example/*"`, ) } if (isPrivateDependencyScopePattern(d)) { scopePatterns.push(d) } else { exactDependencies.push(d) } } return { exactDependencies, scopePatterns } } function readPackageLockJson() { const packageLockPath = path.join(process.cwd(), "package-lock.json") if (!fs.existsSync(packageLockPath)) { return null } try { return JSON.parse(fs.readFileSync(packageLockPath, "utf8")) } catch { return null } } function detectPrivateDependenciesFromPackageLock(packageLock) { if ( !packageLock || typeof packageLock !== "object" || !packageLock.packages || typeof packageLock.packages !== "object" ) { return [] } const privatePackages = new Set() for (const [packagePath, packageMeta] of Object.entries(packageLock.packages)) { const packageName = extractPackageNameFromLockfilePath(packagePath) if (!packageName || !packageMeta || typeof packageMeta !== "object") { continue } const { resolved } = packageMeta if (typeof resolved !== "string") { continue } if (isPrivateResolvedUrl(resolved)) { privatePackages.add(packageName) } } return [...privatePackages].sort() } function detectPrivateDependenciesFromScopePatterns(packageLock, privateDependencyScopePatterns, packageJson) { if (privateDependencyScopePatterns.length === 0) return [] const packageNames = new Set(collectPackageNamesFromPackageLock(packageLock)) if (packageNames.size === 0) { for (const depName of Object.keys(packageJson.dependencies ?? {})) { packageNames.add(depName) } } return [...packageNames] .filter(packageName => matchesAnyPrivateDependencyScopePattern(packageName, privateDependencyScopePatterns)) .sort() } function collectPackageNamesFromPackageLock(packageLock) { if ( !packageLock || typeof packageLock !== "object" || !packageLock.packages || typeof packageLock.packages !== "object" ) { return [] } const names = new Set() for (const packagePath of Object.keys(packageLock.packages)) { const packageName = extractPackageNameFromLockfilePath(packagePath) if (packageName) { names.add(packageName) } } return [...names] } function matchesAnyPrivateDependencyScopePattern(packageName, privateDependencyScopePatterns) { return privateDependencyScopePatterns.some(pattern => { const scope = pattern.slice(0, -2) return packageName === scope || packageName.startsWith(`${scope}/`) }) } function isPrivateDependencyScopePattern(entry) { return typeof entry === "string" && entry.endsWith("/*") } function isValidPrivateDependencyEntry(entry) { if (!entry || typeof entry !== "string" || entry.includes(" ")) return false if (isPrivateDependencyScopePattern(entry)) { const scope = entry.slice(0, -2) return scope.startsWith("@") && !scope.includes("/") } return true } function isPrivateResolvedUrl(resolved) { if (!resolved || resolved.startsWith("file:")) return false let url try { url = new URL(resolved) } catch { return false } return url.hostname !== NPM_PUBLIC_REGISTRY_HOST } function extractPackageNameFromLockfilePath(packagePath) { if (typeof packagePath !== "string" || packagePath.length === 0) return null const segments = packagePath.split("/") for (let i = segments.length - 1; i >= 0; i--) { if (segments[i] !== "node_modules") continue const first = segments[i + 1] if (!first) return null if (first.startsWith("@")) { const second = segments[i + 2] return second ? `${first}/${second}` : null } return first } return null } function resolveDependencySourceFullPath(packageName, packageLock) { const topLevelPath = path.join(process.cwd(), "node_modules", packageName) if (fs.existsSync(topLevelPath)) { return topLevelPath } if ( !packageLock || typeof packageLock !== "object" || !packageLock.packages || typeof packageLock.packages !== "object" ) { return null } const candidatePaths = Object.keys(packageLock.packages) .filter(packagePath => extractPackageNameFromLockfilePath(packagePath) === packageName) .filter(packagePath => packagePath.length > 0) .sort((a, b) => a.split("/").length - b.split("/").length) for (const packagePath of candidatePaths) { const fullPath = path.join(process.cwd(), packagePath) if (fs.existsSync(fullPath)) { return fullPath } } return null } function packDependencyTarball(sourcePath, destinationDir) { const existingTarballs = new Set(listTarballs(destinationDir)) const result = spawnSync("npm", ["pack", sourcePath, "--pack-destination", destinationDir, "--ignore-scripts"], { stdio: ["ignore", "ignore", "inherit"], env: { ...process.env, }, }) if (result.error) { throw result.error } if (result.status !== 0) { throw new Error(`npm pack failed for ${sourcePath} with exit code ${result.status ?? "unknown"}`) } const newlyCreatedTarballs = listTarballs(destinationDir).filter(fileName => !existingTarballs.has(fileName)) const tarballFileName = newlyCreatedTarballs.at(-1) if (!tarballFileName) { throw new Error(`npm pack did not produce tarball in ${destinationDir} for ${sourcePath}`) } return tarballFileName } function getDependencyTarballRelativePath(tarballFileName) { return path.join(".", PRIVATE_DEPS_DIR_NAME, tarballFileName) } function listTarballs(dirPath) { if (!fs.existsSync(dirPath)) return [] return fs.readdirSync(dirPath).filter(fileName => fileName.endsWith(".tgz")).sort() } !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="34904248-2a35-51b7-a9d5-4b260156d051")}catch(e){}}(); //# debugId=34904248-2a35-51b7-a9d5-4b260156d051