UNPKG

@supernovaio/cli

Version:

Supernova.io Command Line Interface

294 lines (292 loc) 12.8 kB
!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]="b91a9dd9-d973-5b14-9e18-936ed1479bc7")}catch(e){}}(); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import { SentryTraced } from "@sentry/nestjs"; import { z } from "zod"; import { SentryCommand } from "../../types/sentry-command.js"; import { commonFlags } from "../../types/flags.js"; import { execFileSync } 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"; export default class ContainerPrepare extends SentryCommand { static args = {}; static description = "Prepare a container package to be uploaded to Supernova. " + "This makes sure private packages are available directly in the container. " + "Must be executed in the package root next to package.json file. " + "The package must use NPM as its package manager."; static examples = ["<%= config.bin %> <%= command.id %>"]; static hidden = true; static flags = commonFlags; get commandId() { return ContainerPrepare.id; } get configSchema() { return z.object({}); } async run() { const packageJsonPath = path.join(process.cwd(), "package.json"); const packageJson = readPackageJson(); const packageLock = readPackageLockJson(); const declaredPrivateDependencies = packageJson.supernova?.privateDependencies; 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; const privateDepsDir = path.join(process.cwd(), PRIVATE_DEPS_DIR_NAME); fs.rmSync(privateDepsDir, { recursive: true, force: true }); fs.mkdirSync(privateDepsDir, { recursive: true }); for (const dependency of privateDependencies) { 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}`); } } __decorate([ SentryTraced(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], ContainerPrepare.prototype, "run", null); function getPackageJsonPath() { return path.join(process.cwd(), "package.json"); } function readPackageJson() { const packageJson = JSON.parse(fs.readFileSync(getPackageJsonPath()).toString()); if (typeof packageJson !== "object" || packageJson === null) { throw new TypeError(`Error parsing package.json: expected an object at the root`); } if (!("dependencies" in packageJson) || typeof packageJson.dependencies !== "object" || packageJson.dependencies === null) { throw new TypeError(`Error parsing package.json: dependencies must be an object`); } for (const entry of Object.entries(packageJson.dependencies)) { if (typeof entry[1] !== "string") { throw new TypeError(`Error parsing package.json: dependencies[${entry[0]}] must be a string`); } } if ("supernova" in packageJson) { if (typeof packageJson.supernova !== "object" || packageJson.supernova === null) { throw new TypeError(`Error parsing package.json: supernova must be an object`); } if ("privateDependencies" in packageJson.supernova) { if (!Array.isArray(packageJson.supernova.privateDependencies)) { throw new TypeError(`Error parsing package.json: supernova.privateDependencies must be an array`); } for (const [i, dep] of packageJson.supernova.privateDependencies.entries()) { if (typeof dep !== "string") { throw new TypeError(`Error parsing package.json: supernova.privateDependencies[${i}] must be a string`); } } packageJson.supernova.privateDependencies = packageJson.supernova.privateDependencies; } } return packageJson; } 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" || !("packages" in packageLock) || typeof packageLock.packages !== "object" || packageLock.packages === null) { 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" || !("packages" in packageLock) || typeof packageLock.packages !== "object" || packageLock.packages === null) { 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 output = execFileSync("npm", ["pack", sourcePath, "--pack-destination", destinationDir, "--ignore-scripts"], { encoding: "utf8", }); const lines = output .trim() .split("\n") .map(line => line.trim()) .filter(Boolean); const tarballFileName = lines.at(-1); if (!tarballFileName) { throw new Error(`npm pack did not return tarball name for ${sourcePath}`); } return tarballFileName; } function getDependencyTarballRelativePath(tarballFileName) { return path.join(".", PRIVATE_DEPS_DIR_NAME, tarballFileName); } //# sourceMappingURL=prepare.js.map //# debugId=b91a9dd9-d973-5b14-9e18-936ed1479bc7