UNPKG

rigjs

Version:

A multi-repos dev tool based on yarn and git.Rigjs is intended to be the simplest way to develop,share and deliver codes between different developers or different projects.

147 lines (134 loc) 5.41 kB
#!/usr/bin/env node // Thin wrapper around `npm publish`: // - reads NPM_TOKEN or NODE_AUTH_TOKEN from the environment // - never accepts credentials on argv // - fails fast with a clear hint when neither is present // - writes a short-lived temp .npmrc with the token + registry, points // npm at it via --userconfig (token never lives in argv → no `ps` leak, // never lives in the repo, deleted on exit) // - passes everything else through to npm publish import { spawnSync } from 'node:child_process'; import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const REGISTRY = 'https://registry.npmjs.org/'; const argv = process.argv.slice(2); const token = process.env.NPM_TOKEN || process.env.NODE_AUTH_TOKEN; if (argv.some((arg) => arg === '--token' || arg.startsWith('--token='))) { process.stderr.write('npm publish: --token is refused because command arguments may be logged. Use NPM_TOKEN or NODE_AUTH_TOKEN.\n'); process.exit(2); } const passthrough = argv; // --- check token --- if (!token) { process.stderr.write([ '', 'npm publish: NPM_TOKEN not found.', '', 'Provide NPM_TOKEN or NODE_AUTH_TOKEN through a private credential manager.', 'Maintainers of this repository should use the rig-deliver skill, which', 'resolves the token from the private OPS registry at runtime.', '', 'Generate one at: https://www.npmjs.com/settings/<your-user>/tokens', 'Choose "Automation" if you have 2FA enabled.', '', ].join('\n')); process.exit(1); } // --- show what's about to ship --- const pkg = JSON.parse(readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); process.stdout.write(`\npublishing ${pkg.name}@${pkg.version} to ${REGISTRY}\n\n`); // --- write throwaway userconfig with the real token --- const tmpDir = mkdtempSync(path.join(tmpdir(), 'rigjs-publish-')); const tmpRc = path.join(tmpDir, '.npmrc'); writeFileSync(tmpRc, [ `registry=${REGISTRY}`, `//registry.npmjs.org/:_authToken=${token}`, '', ].join('\n'), { mode: 0o600 }); // Scrub registry-affecting `npm_config_*` vars before spawning npm. yarn // injects npm_config_registry=https://registry.yarnpkg.com, which otherwise // wins over the registry= line in our temp .npmrc. Keep an explicit cache // override, and never pass the publish token to npm after writing the temp rc. const cleanEnv = Object.fromEntries( Object.entries(process.env).filter(([k]) => { if (k === 'NPM_TOKEN' || k === 'NODE_AUTH_TOKEN') return false; return !/^npm_config_/i.test(k) || k.toLowerCase() === 'npm_config_cache'; }) ); let status = 1; try { // `--registry` on the CLI is the strongest override (CLI flag > env > rc). // Belt-and-braces with the scrubbed env above. const res = spawnSync('npm', [ 'publish', '--registry', REGISTRY, `--userconfig=${tmpRc}`, ...passthrough, ], { cwd: repoRoot, stdio: 'inherit', env: cleanEnv, }); status = res.status ?? 1; } finally { rmSync(tmpDir, { recursive: true, force: true }); } if (status === 0) { await syncNpmmirror(pkg.name); } process.exit(status); /** * Tell registry.npmmirror.com to pull the newly published version from * upstream npmjs. Fire the POST kickoff, then poll the sync log a few * times — we don't block release on completion, just surface progress. * * Failures (network, 5xx, timeout) are warnings, not hard errors: the * publish itself succeeded; the mirror will catch up on its own. */ async function syncNpmmirror(pkgName) { const slug = encodeURIComponent(pkgName); const kickoffUrl = `https://registry.npmmirror.com/-/package/${slug}/syncs?sync_upstream=true`; process.stdout.write(`\nsyncing ${pkgName} to npmmirror.com...\n`); let logId; try { const r = await fetch(kickoffUrl, { method: 'PUT' }); if (!r.ok) { process.stderr.write(` kickoff failed (HTTP ${r.status}) — mirror will sync on its own schedule\n`); return; } const body = await r.json(); logId = body.logId || body.id; if (!logId) { process.stdout.write(` kicked off (no logId returned, treating as fire-and-forget)\n`); return; } process.stdout.write(` kicked off (logId=${logId})\n`); } catch (e) { process.stderr.write(` kickoff error: ${e.message} — mirror will sync on its own schedule\n`); return; } // Poll the sync log: 3s interval, give up after 60s. const statusUrl = `https://registry.npmmirror.com/-/package/${slug}/syncs/${logId}`; const start = Date.now(); const TIMEOUT_MS = 60_000; while (Date.now() - start < TIMEOUT_MS) { await new Promise(r => setTimeout(r, 3000)); try { const r = await fetch(statusUrl); if (!r.ok) continue; const s = await r.json(); if (s.syncDone || s.state === 'success') { process.stdout.write(` npmmirror sync complete\n`); return; } if (s.state === 'fail') { process.stderr.write(` npmmirror sync failed: ${s.error || '(no error message)'}\n`); return; } } catch { /* transient — keep polling */ } } process.stdout.write(` still in progress after ${TIMEOUT_MS / 1000}s — npmmirror will finish in the background\n`); }