@supernovaio/cli
Version:
Supernova.io Command Line Interface
294 lines (292 loc) • 12.8 kB
JavaScript
!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