@supernovaio/cli
Version:
Supernova.io Command Line Interface
312 lines (255 loc) • 10.3 kB
JavaScript
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