scripts/build-npm-package.js
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
import { execFile } from "node:child_process";
import {
copyFile,
cp,
mkdir,
mkdtemp,
readFile,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { collectSiwcRuntimeAssets } from "../src/services/siwc-runtime-assets.js";
const execFileAsync = promisify(execFile);
const sourceRoot = fileURLToPath(new URL("..", import.meta.url));
const defaultOutputDirectory = join(sourceRoot, "dist", "npm");
export function resolveNpmInvocation({
env = process.env,
execPath = process.execPath,
platform = process.platform,
} = {}) {
if (platform !== "win32") {
return { command: "npm", prefixArgs: [] };
}
const npmExecPathKey = Object.keys(env ?? {}).find(
(key) => key.toLowerCase() === "npm_execpath",
);
const npmExecPath =
npmExecPathKey === undefined ? undefined : env[npmExecPathKey];
let npmCliPath;
if (typeof npmExecPath === "string") {
if (/(?:^|[\\/])npm-cli\.js$/iu.test(npmExecPath)) {
npmCliPath = resolve(npmExecPath);
} else if (/(?:^|[\\/])npx-cli\.js$/iu.test(npmExecPath)) {
npmCliPath = resolve(dirname(npmExecPath), "npm-cli.js");
}
}
return {
command: execPath,
// Windows cannot execute npm.cmd directly with shell:false.
prefixArgs: [
npmCliPath ??
resolve(dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js"),
],
};
}
// Two published files come from a differently named source. npm never publishes
// package-lock.json; shipped as npm-shrinkwrap.json, the same file makes every install use the
// reviewed versions of the packages below the direct dependencies.
const publishSources = new Map([
["README.md", join("npm", "README.md")],
["npm-shrinkwrap.json", "package-lock.json"],
]);
async function copyPublishEntry({ entry, stagingDirectory, unpublished }) {
const source = join(sourceRoot, entry);
const target = join(stagingDirectory, entry);
if (publishSources.has(entry)) {
await copyFile(join(sourceRoot, publishSources.get(entry)), target);
return;
}
await cp(source, target, {
recursive: true,
filter: (path) => !unpublished.has(path),
});
}
export async function stageNpmPackage(stagingDirectory) {
const packageJson = JSON.parse(
await readFile(join(sourceRoot, "package.json"), "utf8"),
);
const files = packageJson.files ?? [];
// A `!path` entry in `files` stays in the repository but is not published. npm leaves it
// out of the tarball; it is not copied into the staging folder either.
const unpublished = new Set(
files
.filter((entry) => entry.startsWith("!"))
.map((entry) => join(sourceRoot, entry.slice(1))),
);
const publishEntries = new Set([
"package.json",
...files.filter((entry) => !entry.startsWith("!")),
]);
await mkdir(stagingDirectory, { recursive: true });
for (const entry of publishEntries) {
await copyPublishEntry({ entry, stagingDirectory, unpublished });
}
const { packageLock } = await collectSiwcRuntimeAssets();
await writeFile(join(stagingDirectory, "src", "services", "siwc-runtime-lock.json"), packageLock, {
mode: 0o644,
});
return packageJson;
}
export async function buildNpmPackage({
outputDirectory = defaultOutputDirectory,
} = {}) {
const stagingDirectory = await mkdtemp(join(tmpdir(), "relmio-package-"));
try {
const packageJson = await stageNpmPackage(stagingDirectory);
await mkdir(outputDirectory, { recursive: true });
const expectedFilename = `${packageJson.name}-${packageJson.version}.tgz`;
const expectedPath = join(outputDirectory, expectedFilename);
await rm(expectedPath, { force: true });
const npmInvocation = resolveNpmInvocation();
const { stdout } = await execFileAsync(
npmInvocation.command,
[
...npmInvocation.prefixArgs,
"pack",
stagingDirectory,
"--json",
"--ignore-scripts",
"--pack-destination",
outputDirectory,
],
{
cwd: sourceRoot,
env: {
...process.env,
npm_config_cache: join(stagingDirectory, ".npm-cache"),
},
},
);
const [packedPackage] = JSON.parse(stdout);
const tarballPath = resolve(outputDirectory, packedPackage.filename);
if (basename(tarballPath) !== expectedFilename) {
throw new Error(
`npm produced ${basename(tarballPath)} instead of ${expectedFilename}.`,
);
}
return { packageJson, packedPackage, tarballPath };
} finally {
await rm(stagingDirectory, { recursive: true, force: true });
}
}
async function main() {
const outputArgument = process.argv[2];
const outputDirectory =
outputArgument === undefined
? defaultOutputDirectory
: resolve(process.cwd(), outputArgument);
const result = await buildNpmPackage({ outputDirectory });
console.log(result.tarballPath);
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}