npm-needs-publish 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/comparators/file-content.js +3 -2
- package/dist/cjs/comparators/file-content.js.map +1 -1
- package/dist/cjs/needs-publish.d.cts +1 -2
- package/dist/cjs/needs-publish.d.ts +1 -2
- package/dist/cjs/needs-publish.js +2 -12
- package/dist/cjs/needs-publish.js.map +1 -1
- package/dist/esm/comparators/file-content.js +3 -2
- package/dist/esm/comparators/file-content.js.map +1 -1
- package/dist/esm/needs-publish.d.ts +1 -2
- package/dist/esm/needs-publish.js +3 -14
- package/dist/esm/needs-publish.js.map +1 -1
- package/package.json +24 -25
|
@@ -35,7 +35,7 @@ _export(exports, {
|
|
|
35
35
|
var _crypto = /*#__PURE__*/ _interop_require_default(require("crypto"));
|
|
36
36
|
var _module = /*#__PURE__*/ _interop_require_default(require("module"));
|
|
37
37
|
var _stream = require("stream");
|
|
38
|
-
var
|
|
38
|
+
var _util = require("util");
|
|
39
39
|
var _zlib = /*#__PURE__*/ _interop_require_default(require("zlib"));
|
|
40
40
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
41
41
|
try {
|
|
@@ -162,6 +162,7 @@ function _ts_generator(thisArg, body) {
|
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
var pipeline = (0, _util.promisify)(_stream.pipeline);
|
|
165
166
|
var _require = typeof require === 'undefined' ? _module.default.createRequire(require("url").pathToFileURL(__filename).toString()) : require;
|
|
166
167
|
// Lazy load tar
|
|
167
168
|
var _tar = null;
|
|
@@ -288,7 +289,7 @@ function comparePackageFiles(localTarball, registryTarball) {
|
|
|
288
289
|
// Process the tarball
|
|
289
290
|
return [
|
|
290
291
|
4,
|
|
291
|
-
|
|
292
|
+
pipeline(_stream.Readable.from(tarball), _zlib.default.createGunzip(), parser)
|
|
292
293
|
];
|
|
293
294
|
case 1:
|
|
294
295
|
_state.sent();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/comparators/file-content.ts"],"sourcesContent":["/**\n * File content comparison for package tarballs\n *\n * Compares files between local and registry tarballs:\n * 1. Fast path: Compare tarball hashes\n * 2. If different: Extract and compare file-by-file\n * 3. Special handling for package.json (semantic comparison elsewhere)\n */\n\nimport crypto from 'crypto';\nimport Module from 'module';\nimport { Readable } from 'stream';\nimport { pipeline } from 'stream/promises';\nimport zlib from 'zlib';\nimport type { FileChange, FileComparison } from '../types.ts';\n\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n// Lazy load tar\nlet _tar: typeof import('tar') | null = null;\n\nfunction getTar(): typeof import('tar') {\n if (!_tar) {\n _tar = _require('tar');\n }\n return _tar;\n}\n\n/**\n * Compare package files from two tarballs\n *\n * @param localTarball - Local package tarball as Buffer\n * @param registryTarball - Registry package tarball as Buffer\n * @returns File comparison result\n */\nexport async function comparePackageFiles(localTarball: Buffer, registryTarball: Buffer): Promise<FileComparison> {\n // Fast path: identical tarballs\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n return {\n identical: true,\n fileChanges: [],\n packageJsonOnly: false,\n };\n }\n\n // Extract both tarballs to memory\n const localFiles = await extractTarball(localTarball);\n const registryFiles = await extractTarball(registryTarball);\n\n const changes: FileChange[] = [];\n\n // Build combined set of all paths\n const allPaths: Record<string, boolean> = {};\n const localKeys = Object.keys(localFiles);\n for (let i = 0; i < localKeys.length; i++) {\n allPaths[localKeys[i]] = true;\n }\n const registryKeys = Object.keys(registryFiles);\n for (let i = 0; i < registryKeys.length; i++) {\n allPaths[registryKeys[i]] = true;\n }\n\n let onlyPackageJsonDiffers = true;\n\n const pathKeys = Object.keys(allPaths);\n for (let i = 0; i < pathKeys.length; i++) {\n const filePath = pathKeys[i];\n const isPackageJson = filePath === 'package/package.json' || filePath.indexOf('/package.json') === filePath.length - 13;\n const localContent = localFiles[filePath];\n const registryContent = registryFiles[filePath];\n\n if (!registryContent) {\n // File added\n changes.push({ path: filePath, action: 'added' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!localContent) {\n // File removed\n changes.push({ path: filePath, action: 'removed' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!buffersEqual(localContent, registryContent)) {\n // File modified\n changes.push({ path: filePath, action: 'modified' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n }\n }\n\n return {\n identical: changes.length === 0,\n fileChanges: changes,\n packageJsonOnly: onlyPackageJsonDiffers && changes.length > 0,\n };\n}\n\n/**\n * Extract tarball contents to memory\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Map of file paths to content buffers\n */\nasync function extractTarball(tarball: Buffer): Promise<Record<string, Buffer>> {\n const files: Record<string, Buffer> = {};\n const tar = getTar();\n\n // Create a parser that collects file contents\n const parser = new tar.Parser({\n onReadEntry: (entry) => {\n if (entry.type === 'File') {\n const chunks: Buffer[] = [];\n\n entry.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n entry.on('end', () => {\n files[entry.path] = Buffer.concat(chunks);\n });\n } else {\n // Drain non-file entries (directories, etc.)\n entry.resume();\n }\n },\n });\n\n // Process the tarball\n await pipeline(Readable.from(tarball), zlib.createGunzip(), parser);\n\n return files;\n}\n\n/**\n * Hash a buffer using SHA-512\n */\nexport function hashBuffer(buffer: Buffer): string {\n return crypto.createHash('sha512').update(buffer).digest('base64');\n}\n\n/**\n * Compare two buffers for equality\n */\nfunction buffersEqual(a: Buffer, b: Buffer): boolean {\n if (a.length !== b.length) return false;\n return crypto.timingSafeEqual(a, b);\n}\n\n/**\n * Get a summary of file changes\n */\nexport function getFileChangeSummary(changes: FileChange[]): string {\n if (changes.length === 0) {\n return 'No file changes';\n }\n\n const added = changes.filter((c) => c.action === 'added');\n const removed = changes.filter((c) => c.action === 'removed');\n const modified = changes.filter((c) => c.action === 'modified');\n\n const parts: string[] = [];\n\n if (added.length > 0) {\n parts.push(`${added.length} added`);\n }\n if (removed.length > 0) {\n parts.push(`${removed.length} removed`);\n }\n if (modified.length > 0) {\n parts.push(`${modified.length} modified`);\n }\n\n return `Files: ${parts.join(', ')} (${changes.length} total)`;\n}\n\n/**\n * Check if changes are only in package.json\n */\nexport function isOnlyPackageJsonChange(changes: FileChange[]): boolean {\n if (changes.length === 0) return false;\n\n for (let i = 0; i < changes.length; i++) {\n const path = changes[i].path;\n if (path !== 'package/package.json' && path.indexOf('/package.json') !== path.length - 13) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Extract package.json from a tarball\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Parsed package.json object\n */\nexport async function extractPackageJson(tarball: Buffer): Promise<unknown> {\n const files = await extractTarball(tarball);\n\n // Look for package.json in the tarball\n const pkgJsonPath = Object.keys(files).find((p) => p === 'package/package.json' || p.indexOf('/package.json') === p.length - 13);\n\n if (!pkgJsonPath || !files[pkgJsonPath]) {\n throw new Error('package.json not found in tarball');\n }\n\n return JSON.parse(files[pkgJsonPath].toString('utf8'));\n}\n"],"names":["comparePackageFiles","extractPackageJson","getFileChangeSummary","hashBuffer","isOnlyPackageJsonChange","_require","require","Module","createRequire","_tar","getTar","localTarball","registryTarball","localHash","registryHash","localFiles","registryFiles","changes","allPaths","localKeys","i","registryKeys","onlyPackageJsonDiffers","pathKeys","filePath","isPackageJson","localContent","registryContent","identical","fileChanges","packageJsonOnly","extractTarball","Object","keys","length","indexOf","push","path","action","buffersEqual","tarball","files","tar","parser","Parser","onReadEntry","entry","type","chunks","on","chunk","Buffer","concat","resume","pipeline","Readable","from","zlib","createGunzip","buffer","crypto","createHash","update","digest","a","b","timingSafeEqual","added","filter","c","removed","modified","parts","join","pkgJsonPath","find","p","Error","JSON","parse","toString"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/comparators/file-content.ts"],"sourcesContent":["/**\n * File content comparison for package tarballs\n *\n * Compares files between local and registry tarballs:\n * 1. Fast path: Compare tarball hashes\n * 2. If different: Extract and compare file-by-file\n * 3. Special handling for package.json (semantic comparison elsewhere)\n */\n\nimport crypto from 'crypto';\nimport Module from 'module';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport { promisify } from 'util';\nimport zlib from 'zlib';\n\nconst pipeline = promisify(pipelineCb);\n\nimport type { FileChange, FileComparison } from '../types.ts';\n\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n// Lazy load tar\nlet _tar: typeof import('tar') | null = null;\n\nfunction getTar(): typeof import('tar') {\n if (!_tar) {\n _tar = _require('tar');\n }\n return _tar;\n}\n\n/**\n * Compare package files from two tarballs\n *\n * @param localTarball - Local package tarball as Buffer\n * @param registryTarball - Registry package tarball as Buffer\n * @returns File comparison result\n */\nexport async function comparePackageFiles(localTarball: Buffer, registryTarball: Buffer): Promise<FileComparison> {\n // Fast path: identical tarballs\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n return {\n identical: true,\n fileChanges: [],\n packageJsonOnly: false,\n };\n }\n\n // Extract both tarballs to memory\n const localFiles = await extractTarball(localTarball);\n const registryFiles = await extractTarball(registryTarball);\n\n const changes: FileChange[] = [];\n\n // Build combined set of all paths\n const allPaths: Record<string, boolean> = {};\n const localKeys = Object.keys(localFiles);\n for (let i = 0; i < localKeys.length; i++) {\n allPaths[localKeys[i]] = true;\n }\n const registryKeys = Object.keys(registryFiles);\n for (let i = 0; i < registryKeys.length; i++) {\n allPaths[registryKeys[i]] = true;\n }\n\n let onlyPackageJsonDiffers = true;\n\n const pathKeys = Object.keys(allPaths);\n for (let i = 0; i < pathKeys.length; i++) {\n const filePath = pathKeys[i];\n const isPackageJson = filePath === 'package/package.json' || filePath.indexOf('/package.json') === filePath.length - 13;\n const localContent = localFiles[filePath];\n const registryContent = registryFiles[filePath];\n\n if (!registryContent) {\n // File added\n changes.push({ path: filePath, action: 'added' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!localContent) {\n // File removed\n changes.push({ path: filePath, action: 'removed' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!buffersEqual(localContent, registryContent)) {\n // File modified\n changes.push({ path: filePath, action: 'modified' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n }\n }\n\n return {\n identical: changes.length === 0,\n fileChanges: changes,\n packageJsonOnly: onlyPackageJsonDiffers && changes.length > 0,\n };\n}\n\n/**\n * Extract tarball contents to memory\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Map of file paths to content buffers\n */\nasync function extractTarball(tarball: Buffer): Promise<Record<string, Buffer>> {\n const files: Record<string, Buffer> = {};\n const tar = getTar();\n\n // Create a parser that collects file contents\n const parser = new tar.Parser({\n onReadEntry: (entry) => {\n if (entry.type === 'File') {\n const chunks: Buffer[] = [];\n\n entry.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n entry.on('end', () => {\n files[entry.path] = Buffer.concat(chunks);\n });\n } else {\n // Drain non-file entries (directories, etc.)\n entry.resume();\n }\n },\n });\n\n // Process the tarball\n await pipeline(Readable.from(tarball), zlib.createGunzip(), parser);\n\n return files;\n}\n\n/**\n * Hash a buffer using SHA-512\n */\nexport function hashBuffer(buffer: Buffer): string {\n return crypto.createHash('sha512').update(buffer).digest('base64');\n}\n\n/**\n * Compare two buffers for equality\n */\nfunction buffersEqual(a: Buffer, b: Buffer): boolean {\n if (a.length !== b.length) return false;\n return crypto.timingSafeEqual(a, b);\n}\n\n/**\n * Get a summary of file changes\n */\nexport function getFileChangeSummary(changes: FileChange[]): string {\n if (changes.length === 0) {\n return 'No file changes';\n }\n\n const added = changes.filter((c) => c.action === 'added');\n const removed = changes.filter((c) => c.action === 'removed');\n const modified = changes.filter((c) => c.action === 'modified');\n\n const parts: string[] = [];\n\n if (added.length > 0) {\n parts.push(`${added.length} added`);\n }\n if (removed.length > 0) {\n parts.push(`${removed.length} removed`);\n }\n if (modified.length > 0) {\n parts.push(`${modified.length} modified`);\n }\n\n return `Files: ${parts.join(', ')} (${changes.length} total)`;\n}\n\n/**\n * Check if changes are only in package.json\n */\nexport function isOnlyPackageJsonChange(changes: FileChange[]): boolean {\n if (changes.length === 0) return false;\n\n for (let i = 0; i < changes.length; i++) {\n const path = changes[i].path;\n if (path !== 'package/package.json' && path.indexOf('/package.json') !== path.length - 13) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Extract package.json from a tarball\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Parsed package.json object\n */\nexport async function extractPackageJson(tarball: Buffer): Promise<unknown> {\n const files = await extractTarball(tarball);\n\n // Look for package.json in the tarball\n const pkgJsonPath = Object.keys(files).find((p) => p === 'package/package.json' || p.indexOf('/package.json') === p.length - 13);\n\n if (!pkgJsonPath || !files[pkgJsonPath]) {\n throw new Error('package.json not found in tarball');\n }\n\n return JSON.parse(files[pkgJsonPath].toString('utf8'));\n}\n"],"names":["comparePackageFiles","extractPackageJson","getFileChangeSummary","hashBuffer","isOnlyPackageJsonChange","pipeline","promisify","pipelineCb","_require","require","Module","createRequire","_tar","getTar","localTarball","registryTarball","localHash","registryHash","localFiles","registryFiles","changes","allPaths","localKeys","i","registryKeys","onlyPackageJsonDiffers","pathKeys","filePath","isPackageJson","localContent","registryContent","identical","fileChanges","packageJsonOnly","extractTarball","Object","keys","length","indexOf","push","path","action","buffersEqual","tarball","files","tar","parser","Parser","onReadEntry","entry","type","chunks","on","chunk","Buffer","concat","resume","Readable","from","zlib","createGunzip","buffer","crypto","createHash","update","digest","a","b","timingSafeEqual","added","filter","c","removed","modified","parts","join","pkgJsonPath","find","p","Error","JSON","parse","toString"],"mappings":"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"}
|
|
@@ -15,8 +15,7 @@ import type { NeedsPublishOptions, NeedsPublishResult } from './types.js';
|
|
|
15
15
|
*/
|
|
16
16
|
export type NeedsPublishCallback = (error: Error | null, result?: NeedsPublishResult) => void;
|
|
17
17
|
/**
|
|
18
|
-
* Callback-based needsPublish
|
|
19
|
-
* Uses worker for older Node versions, runs locally on Node 14+
|
|
18
|
+
* Callback-based needsPublish
|
|
20
19
|
*/
|
|
21
20
|
export declare function needsPublishCb(options: NeedsPublishOptions, callback: NeedsPublishCallback): void;
|
|
22
21
|
/**
|
|
@@ -15,8 +15,7 @@ import type { NeedsPublishOptions, NeedsPublishResult } from './types.js';
|
|
|
15
15
|
*/
|
|
16
16
|
export type NeedsPublishCallback = (error: Error | null, result?: NeedsPublishResult) => void;
|
|
17
17
|
/**
|
|
18
|
-
* Callback-based needsPublish
|
|
19
|
-
* Uses worker for older Node versions, runs locally on Node 14+
|
|
18
|
+
* Callback-based needsPublish
|
|
20
19
|
*/
|
|
21
20
|
export declare function needsPublishCb(options: NeedsPublishOptions, callback: NeedsPublishCallback): void;
|
|
22
21
|
/**
|
|
@@ -29,8 +29,6 @@ _export(exports, {
|
|
|
29
29
|
var _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
|
|
30
30
|
var _module = /*#__PURE__*/ _interop_require_default(require("module"));
|
|
31
31
|
var _path = /*#__PURE__*/ _interop_require_default(require("path"));
|
|
32
|
-
var _tsdslib = require("tsds-lib");
|
|
33
|
-
var _url = /*#__PURE__*/ _interop_require_default(require("url"));
|
|
34
32
|
var _compatts = require("./compat.js");
|
|
35
33
|
function _array_like_to_array(arr, len) {
|
|
36
34
|
if (len == null || len > arr.length) len = arr.length;
|
|
@@ -251,16 +249,8 @@ function _ts_generator(thisArg, body) {
|
|
|
251
249
|
};
|
|
252
250
|
}
|
|
253
251
|
}
|
|
254
|
-
// Version check for worker pattern - use worker for Node < 14
|
|
255
|
-
var major = +process.versions.node.split('.')[0];
|
|
256
|
-
var version = major > 14 ? 'local' : 'stable';
|
|
257
252
|
var _require = typeof require === 'undefined' ? _module.default.createRequire(require("url").pathToFileURL(__filename).toString()) : require;
|
|
258
|
-
|
|
259
|
-
var dist = _path.default.join(__dirname, '..');
|
|
260
|
-
var workerWrapper = (0, _tsdslib.wrapWorker)(_path.default.join(dist, 'cjs', 'needs-publish.js'));
|
|
261
|
-
/**
|
|
262
|
-
* Worker function that runs on Node 14+
|
|
263
|
-
*/ function worker(options, callback) {
|
|
253
|
+
function needsPublishImpl(options, callback) {
|
|
264
254
|
var cwd = options.cwd || process.cwd();
|
|
265
255
|
// Load local package.json
|
|
266
256
|
var localPkg = options.package || JSON.parse(_fs.default.readFileSync(_path.default.join(cwd, 'package.json'), 'utf8'));
|
|
@@ -601,7 +591,7 @@ var workerWrapper = (0, _tsdslib.wrapWorker)(_path.default.join(dist, 'cjs', 'ne
|
|
|
601
591
|
})().catch(callback);
|
|
602
592
|
}
|
|
603
593
|
function needsPublishCb(options, callback) {
|
|
604
|
-
|
|
594
|
+
needsPublishImpl(options, callback);
|
|
605
595
|
}
|
|
606
596
|
function needsPublish() {
|
|
607
597
|
var options = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/needs-publish.ts"],"sourcesContent":["/**\n * Main orchestration logic for npm-needs-publish\n *\n * Algorithm:\n * 1. Fetch registry packument → if E404, return needsPublish=true (first publish)\n * 2. Version check → if different, return needsPublish=true (intentional bump)\n * 3. Fast hash check → if identical, return needsPublish=false (no changes)\n * 4. Extract both tarballs, compare file-by-file (excluding package.json)\n * 5. If non-package.json files differ → return needsPublish=true\n * 6. If only package.json differs → do semantic comparison\n */\n\nimport fs from 'fs';\nimport Module from 'module';\nimport path from 'path';\nimport { wrapWorker } from 'tsds-lib';\nimport url from 'url';\nimport { stringStartsWith } from './compat.ts';\nimport type { ChangeDetail, NeedsPublishOptions, NeedsPublishResult, PackageJson } from './types.ts';\n\n// Version check for worker pattern - use worker for Node < 14\nconst major = +process.versions.node.split('.')[0];\nconst version = major > 14 ? 'local' : 'stable';\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\nconst __dirname = path.dirname(typeof __filename === 'undefined' ? url.fileURLToPath(import.meta.url) : __filename);\nconst dist = path.join(__dirname, '..');\nconst workerWrapper = wrapWorker(path.join(dist, 'cjs', 'needs-publish.js'));\n\n/**\n * Callback type for needsPublish\n */\nexport type NeedsPublishCallback = (error: Error | null, result?: NeedsPublishResult) => void;\n\n/**\n * Worker function that runs on Node 14+\n */\nfunction worker(options: NeedsPublishOptions, callback: NeedsPublishCallback): undefined {\n const cwd = options.cwd || process.cwd();\n\n // Load local package.json\n const localPkg: PackageJson = options.package || JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));\n\n // Skip private packages\n if (localPkg.private) {\n callback(null, {\n needsPublish: false,\n reason: 'Package is private',\n });\n return;\n }\n\n (async () => {\n const pacote = _require('pacote');\n const Arborist = _require('@npmcli/arborist');\n const npa = _require('npm-package-arg');\n const { execFile } = _require('child_process');\n const { promisify } = _require('util');\n const execFileAsync = promisify(execFile);\n\n // Dynamic import for comparators (they use modern features)\n const { comparePackageFiles, comparePackageJson, extractPackageJson, hashBuffer } = await import('./comparators/index.ts');\n\n // Get registry URL for scoped packages\n let registry: string | undefined = options.registry;\n if (!registry) {\n const scope = stringStartsWith(localPkg.name, '@') ? localPkg.name.split('/')[0] : undefined;\n if (scope) {\n try {\n const { stdout } = await execFileAsync('npm', ['config', 'get', `${scope}:registry`]);\n registry = stdout.trim();\n if (registry === 'undefined') registry = undefined;\n } catch {\n // Fallback to default registry\n }\n }\n }\n\n // Step 1: Try to fetch registry packument\n let registryPkg: PackageJson;\n let registryTarball: Buffer;\n\n try {\n const packument = await pacote.packument(localPkg.name, {\n Arborist,\n ...(registry && { registry }),\n });\n\n const latestVersion = packument['dist-tags']?.latest;\n if (!latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: 'No latest version found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryPkg = packument.versions[latestVersion] as unknown as PackageJson;\n\n // Step 2: Version comparison (fast path)\n if (localPkg.version !== latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: `Version differs (local: ${localPkg.version}, registry: ${latestVersion})`,\n changes: [\n {\n type: 'version',\n field: 'version',\n oldValue: latestVersion,\n newValue: localPkg.version,\n significance: 'critical',\n },\n ],\n });\n return;\n }\n\n // Fetch registry tarball for comparison\n const tarballUrl = registryPkg.dist?.tarball;\n if (!tarballUrl) {\n callback(null, {\n needsPublish: true,\n reason: 'Registry package has no tarball URL',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryTarball = await pacote.tarball(tarballUrl, {\n Arborist,\n integrity: registryPkg.dist?.integrity,\n });\n } catch (err: unknown) {\n const error = err as { code?: string; message?: string };\n // Package not found in registry (first publish)\n if (error.code === 'E404') {\n callback(null, {\n needsPublish: true,\n reason: 'Package not found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n // Unknown error - assume changed to be safe\n callback(null, {\n needsPublish: true,\n reason: `Error checking registry: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 3: Pack local package\n let localTarball: Buffer;\n try {\n const spec = npa(cwd);\n const manifest = await pacote.manifest(spec, { Arborist });\n localTarball = await pacote.tarball(manifest._resolved, {\n Arborist,\n integrity: manifest._integrity,\n });\n } catch (err: unknown) {\n const error = err as { message?: string };\n callback(null, {\n needsPublish: true,\n reason: `Error packing local package: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 4: Fast hash comparison\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n callback(null, {\n needsPublish: false,\n reason: `No changes detected (hash: ${localHash.substring(0, 16)}...)`,\n });\n return;\n }\n\n // Step 5: File-by-file comparison\n const fileComparison = await comparePackageFiles(localTarball, registryTarball);\n\n if (fileComparison.identical) {\n // Hash mismatch but files identical - likely tarball metadata difference\n callback(null, {\n needsPublish: false,\n reason: 'Files identical (tarball metadata differs)',\n });\n return;\n }\n\n // Step 6: If only package.json differs, do semantic comparison\n if (fileComparison.packageJsonOnly && !options.packageJsonOnly) {\n // Extract package.json from both tarballs for accurate comparison\n // (packument metadata is missing fields like 'files')\n const localTarballPkg = (await extractPackageJson(localTarball)) as PackageJson;\n const registryTarballPkg = (await extractPackageJson(registryTarball)) as PackageJson;\n\n const pkgJsonComparison = comparePackageJson(localTarballPkg, registryTarballPkg, {\n includeOptionalDeps: options.includeOptionalDeps,\n additionalSignificantFields: options.additionalSignificantFields,\n ignoreFields: options.ignoreFields,\n treatNarrowingAsEquivalent: options.treatNarrowingAsEquivalent,\n });\n\n if (!pkgJsonComparison.hasSignificantChanges) {\n callback(null, {\n needsPublish: false,\n reason: 'Package.json changes are not significant for consumers',\n changes: pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: 'informational' as const,\n })),\n });\n return;\n }\n\n // Build changes list\n const changes: ChangeDetail[] = [\n ...pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: fc.significance,\n })),\n ...pkgJsonComparison.dependencyChanges\n .filter((dc) => dc.semanticChange !== 'equivalent' && dc.semanticChange !== 'none')\n .map((dc) => ({\n type: 'dependency' as const,\n field: `${dc.type}.${dc.name}`,\n oldValue: dc.oldSpec,\n newValue: dc.newSpec,\n significance: 'significant' as const,\n })),\n ];\n\n callback(null, {\n needsPublish: true,\n reason: pkgJsonComparison.summary,\n changes,\n });\n return;\n }\n\n // Step 7: Other files changed\n callback(null, {\n needsPublish: true,\n reason: `Code changes detected (${fileComparison.fileChanges.length} files changed)`,\n changes: fileComparison.fileChanges.map((fc) => ({\n type: 'file' as const,\n field: fc.path,\n significance: 'significant' as const,\n })),\n });\n })().catch(callback);\n}\n\n/**\n * Callback-based needsPublish - works on Node 0.8+\n * Uses worker for older Node versions, runs locally on Node 14+\n */\nexport function needsPublishCb(options: NeedsPublishOptions, callback: NeedsPublishCallback): void {\n version !== 'local' ? workerWrapper(version, options, callback) : worker(options, callback);\n}\n\n/**\n * Determine if a package needs to be published to npm.\n *\n * @example\n * ```ts\n * import { needsPublish } from 'npm-needs-publish';\n *\n * const result = await needsPublish({ cwd: process.cwd() });\n * if (result.needsPublish) {\n * console.log('Publish needed:', result.reason);\n * }\n * ```\n */\nexport function needsPublish(options: NeedsPublishOptions = {}): Promise<NeedsPublishResult> {\n return new Promise((resolve, reject) => {\n needsPublishCb(options, (error, result) => {\n if (error) reject(error);\n else if (result) resolve(result);\n else reject(new Error('No result returned'));\n });\n });\n}\n"],"names":["needsPublish","needsPublishCb","major","process","versions","node","split","version","_require","require","Module","createRequire","__dirname","path","dirname","__filename","url","fileURLToPath","dist","join","workerWrapper","wrapWorker","worker","options","callback","cwd","localPkg","package","JSON","parse","fs","readFileSync","private","reason","pacote","Arborist","npa","execFile","promisify","execFileAsync","comparePackageFiles","comparePackageJson","extractPackageJson","hashBuffer","registry","scope","stdout","registryPkg","registryTarball","packument","latestVersion","tarballUrl","err","error","localTarball","spec","manifest","localHash","registryHash","fileComparison","localTarballPkg","registryTarballPkg","pkgJsonComparison","changes","stringStartsWith","name","undefined","trim","latest","type","significance","field","oldValue","newValue","tarball","integrity","code","message","_resolved","_integrity","substring","identical","packageJsonOnly","includeOptionalDeps","additionalSignificantFields","ignoreFields","treatNarrowingAsEquivalent","hasSignificantChanges","fieldChanges","map","fc","dependencyChanges","filter","dc","semanticChange","oldSpec","newSpec","summary","fileChanges","length","catch","Promise","resolve","reject","result","Error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/needs-publish.ts"],"sourcesContent":["/**\n * Main orchestration logic for npm-needs-publish\n *\n * Algorithm:\n * 1. Fetch registry packument → if E404, return needsPublish=true (first publish)\n * 2. Version check → if different, return needsPublish=true (intentional bump)\n * 3. Fast hash check → if identical, return needsPublish=false (no changes)\n * 4. Extract both tarballs, compare file-by-file (excluding package.json)\n * 5. If non-package.json files differ → return needsPublish=true\n * 6. If only package.json differs → do semantic comparison\n */\n\nimport fs from 'fs';\nimport Module from 'module';\nimport path from 'path';\nimport { stringStartsWith } from './compat.ts';\nimport type { ChangeDetail, NeedsPublishOptions, NeedsPublishResult, PackageJson } from './types.ts';\n\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n/**\n * Callback type for needsPublish\n */\nexport type NeedsPublishCallback = (error: Error | null, result?: NeedsPublishResult) => void;\n\nfunction needsPublishImpl(options: NeedsPublishOptions, callback: NeedsPublishCallback): undefined {\n const cwd = options.cwd || process.cwd();\n\n // Load local package.json\n const localPkg: PackageJson = options.package || JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));\n\n // Skip private packages\n if (localPkg.private) {\n callback(null, {\n needsPublish: false,\n reason: 'Package is private',\n });\n return;\n }\n\n (async () => {\n const pacote = _require('pacote');\n const Arborist = _require('@npmcli/arborist');\n const npa = _require('npm-package-arg');\n const { execFile } = _require('child_process');\n const { promisify } = _require('util');\n const execFileAsync = promisify(execFile);\n\n // Dynamic import for comparators (they use modern features)\n const { comparePackageFiles, comparePackageJson, extractPackageJson, hashBuffer } = await import('./comparators/index.ts');\n\n // Get registry URL for scoped packages\n let registry: string | undefined = options.registry;\n if (!registry) {\n const scope = stringStartsWith(localPkg.name, '@') ? localPkg.name.split('/')[0] : undefined;\n if (scope) {\n try {\n const { stdout } = await execFileAsync('npm', ['config', 'get', `${scope}:registry`]);\n registry = stdout.trim();\n if (registry === 'undefined') registry = undefined;\n } catch {\n // Fallback to default registry\n }\n }\n }\n\n // Step 1: Try to fetch registry packument\n let registryPkg: PackageJson;\n let registryTarball: Buffer;\n\n try {\n const packument = await pacote.packument(localPkg.name, {\n Arborist,\n ...(registry && { registry }),\n });\n\n const latestVersion = packument['dist-tags']?.latest;\n if (!latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: 'No latest version found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryPkg = packument.versions[latestVersion] as unknown as PackageJson;\n\n // Step 2: Version comparison (fast path)\n if (localPkg.version !== latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: `Version differs (local: ${localPkg.version}, registry: ${latestVersion})`,\n changes: [\n {\n type: 'version',\n field: 'version',\n oldValue: latestVersion,\n newValue: localPkg.version,\n significance: 'critical',\n },\n ],\n });\n return;\n }\n\n // Fetch registry tarball for comparison\n const tarballUrl = registryPkg.dist?.tarball;\n if (!tarballUrl) {\n callback(null, {\n needsPublish: true,\n reason: 'Registry package has no tarball URL',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryTarball = await pacote.tarball(tarballUrl, {\n Arborist,\n integrity: registryPkg.dist?.integrity,\n });\n } catch (err: unknown) {\n const error = err as { code?: string; message?: string };\n // Package not found in registry (first publish)\n if (error.code === 'E404') {\n callback(null, {\n needsPublish: true,\n reason: 'Package not found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n // Unknown error - assume changed to be safe\n callback(null, {\n needsPublish: true,\n reason: `Error checking registry: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 3: Pack local package\n let localTarball: Buffer;\n try {\n const spec = npa(cwd);\n const manifest = await pacote.manifest(spec, { Arborist });\n localTarball = await pacote.tarball(manifest._resolved, {\n Arborist,\n integrity: manifest._integrity,\n });\n } catch (err: unknown) {\n const error = err as { message?: string };\n callback(null, {\n needsPublish: true,\n reason: `Error packing local package: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 4: Fast hash comparison\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n callback(null, {\n needsPublish: false,\n reason: `No changes detected (hash: ${localHash.substring(0, 16)}...)`,\n });\n return;\n }\n\n // Step 5: File-by-file comparison\n const fileComparison = await comparePackageFiles(localTarball, registryTarball);\n\n if (fileComparison.identical) {\n // Hash mismatch but files identical - likely tarball metadata difference\n callback(null, {\n needsPublish: false,\n reason: 'Files identical (tarball metadata differs)',\n });\n return;\n }\n\n // Step 6: If only package.json differs, do semantic comparison\n if (fileComparison.packageJsonOnly && !options.packageJsonOnly) {\n // Extract package.json from both tarballs for accurate comparison\n // (packument metadata is missing fields like 'files')\n const localTarballPkg = (await extractPackageJson(localTarball)) as PackageJson;\n const registryTarballPkg = (await extractPackageJson(registryTarball)) as PackageJson;\n\n const pkgJsonComparison = comparePackageJson(localTarballPkg, registryTarballPkg, {\n includeOptionalDeps: options.includeOptionalDeps,\n additionalSignificantFields: options.additionalSignificantFields,\n ignoreFields: options.ignoreFields,\n treatNarrowingAsEquivalent: options.treatNarrowingAsEquivalent,\n });\n\n if (!pkgJsonComparison.hasSignificantChanges) {\n callback(null, {\n needsPublish: false,\n reason: 'Package.json changes are not significant for consumers',\n changes: pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: 'informational' as const,\n })),\n });\n return;\n }\n\n // Build changes list\n const changes: ChangeDetail[] = [\n ...pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: fc.significance,\n })),\n ...pkgJsonComparison.dependencyChanges\n .filter((dc) => dc.semanticChange !== 'equivalent' && dc.semanticChange !== 'none')\n .map((dc) => ({\n type: 'dependency' as const,\n field: `${dc.type}.${dc.name}`,\n oldValue: dc.oldSpec,\n newValue: dc.newSpec,\n significance: 'significant' as const,\n })),\n ];\n\n callback(null, {\n needsPublish: true,\n reason: pkgJsonComparison.summary,\n changes,\n });\n return;\n }\n\n // Step 7: Other files changed\n callback(null, {\n needsPublish: true,\n reason: `Code changes detected (${fileComparison.fileChanges.length} files changed)`,\n changes: fileComparison.fileChanges.map((fc) => ({\n type: 'file' as const,\n field: fc.path,\n significance: 'significant' as const,\n })),\n });\n })().catch(callback);\n}\n\n/**\n * Callback-based needsPublish\n */\nexport function needsPublishCb(options: NeedsPublishOptions, callback: NeedsPublishCallback): void {\n needsPublishImpl(options, callback);\n}\n\n/**\n * Determine if a package needs to be published to npm.\n *\n * @example\n * ```ts\n * import { needsPublish } from 'npm-needs-publish';\n *\n * const result = await needsPublish({ cwd: process.cwd() });\n * if (result.needsPublish) {\n * console.log('Publish needed:', result.reason);\n * }\n * ```\n */\nexport function needsPublish(options: NeedsPublishOptions = {}): Promise<NeedsPublishResult> {\n return new Promise((resolve, reject) => {\n needsPublishCb(options, (error, result) => {\n if (error) reject(error);\n else if (result) resolve(result);\n else reject(new Error('No result returned'));\n });\n });\n}\n"],"names":["needsPublish","needsPublishCb","_require","require","Module","createRequire","needsPublishImpl","options","callback","cwd","process","localPkg","package","JSON","parse","fs","readFileSync","path","join","private","reason","pacote","Arborist","npa","execFile","promisify","execFileAsync","comparePackageFiles","comparePackageJson","extractPackageJson","hashBuffer","registry","scope","stdout","registryPkg","registryTarball","packument","latestVersion","tarballUrl","err","error","localTarball","spec","manifest","localHash","registryHash","fileComparison","localTarballPkg","registryTarballPkg","pkgJsonComparison","changes","stringStartsWith","name","split","undefined","trim","latest","type","significance","versions","version","field","oldValue","newValue","dist","tarball","integrity","code","message","_resolved","_integrity","substring","identical","packageJsonOnly","includeOptionalDeps","additionalSignificantFields","ignoreFields","treatNarrowingAsEquivalent","hasSignificantChanges","fieldChanges","map","fc","dependencyChanges","filter","dc","semanticChange","oldSpec","newSpec","summary","fileChanges","length","catch","Promise","resolve","reject","result","Error"],"mappings":"AAAA;;;;;;;;;;CAUC;;;;;;;;;;;QAsQeA;eAAAA;;QAjBAC;eAAAA;;;yDAnPD;6DACI;2DACF;wBACgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGjC,IAAMC,WAAW,OAAOC,YAAY,cAAcC,eAAM,CAACC,aAAa,CAAC,uDAAmBF;AAO1F,SAASG,iBAAiBC,OAA4B,EAAEC,QAA8B;IACpF,IAAMC,MAAMF,QAAQE,GAAG,IAAIC,QAAQD,GAAG;IAEtC,0BAA0B;IAC1B,IAAME,WAAwBJ,QAAQK,OAAO,IAAIC,KAAKC,KAAK,CAACC,WAAE,CAACC,YAAY,CAACC,aAAI,CAACC,IAAI,CAACT,KAAK,iBAAiB;IAE5G,wBAAwB;IACxB,IAAIE,SAASQ,OAAO,EAAE;QACpBX,SAAS,MAAM;YACbR,cAAc;YACdoB,QAAQ;QACV;QACA;IACF;IAEC,CAAA;;gBACOC,QACAC,UACAC,KACEC,UACAC,WACFC,eAG8E,MAA5EC,qBAAqBC,oBAAoBC,oBAAoBC,YAGjEC,UAEIC,OAGMC,WAUVC,aACAC,iBAQoBC,qBA+BHF,mBAYNA,oBAhDPE,WAKAC,eA+BAC,YAcCC,KACDC,OAmBJC,cAEIC,MACAC,UAKCJ,MACDC,QASFI,WACAC,cAWAC,gBAeEC,iBACAC,oBAEAC,mBAuBAC;;;;wBA3KF7B,SAASnB,SAAS;wBAClBoB,WAAWpB,SAAS;wBACpBqB,MAAMrB,SAAS;wBACbsB,WAAatB,SAAS,iBAAtBsB;wBACAC,YAAcvB,SAAS,QAAvBuB;wBACFC,gBAAgBD,UAAUD;wBAGoD;;4BAAM;+EAAA,QAAO;;;;wBAAb,OAAA,eAA5EG,sBAA4E,KAA5EA,qBAAqBC,qBAAuD,KAAvDA,oBAAoBC,qBAAmC,KAAnCA,oBAAoBC,aAAe,KAAfA;wBAErE,uCAAuC;wBACnCC,WAA+BxB,QAAQwB,QAAQ;6BAC/C,CAACA,UAAD;;;;wBACIC,QAAQmB,IAAAA,0BAAgB,EAACxC,SAASyC,IAAI,EAAE,OAAOzC,SAASyC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAGC;6BAC/EtB,OAAAA;;;;;;;;;;;;wBAEmB;;4BAAMN,cAAc;gCAAQ;gCAAU;gCAAQ,GAAQ,OAANM,OAAM;;;;wBAAjEC,SAAW,cAAXA;wBACRF,WAAWE,OAAOsB,IAAI;wBACtB,IAAIxB,aAAa,aAAaA,WAAWuB;;;;;;;;;;;;;;;;;;wBAY3B;;4BAAMjC,OAAOe,SAAS,CAACzB,SAASyC,IAAI,EAAE;gCACtD9B,UAAAA;+BACIS,YAAY;gCAAEA,UAAAA;4BAAS;;;wBAFvBK,YAAY;wBAKZC,iBAAgBD,sBAAAA,SAAS,CAAC,YAAY,cAAtBA,0CAAAA,oBAAwBoB,MAAM;wBACpD,IAAI,CAACnB,eAAe;4BAClB7B,SAAS,MAAM;gCACbR,cAAc;gCACdoB,QAAQ;gCACR8B,OAAO;oCAAG;wCAAEO,MAAM;wCAAiBC,cAAc;oCAAW;;4BAC9D;4BACA;;;wBACF;wBAEAxB,cAAcE,UAAUuB,QAAQ,CAACtB,cAAc;wBAE/C,yCAAyC;wBACzC,IAAI1B,SAASiD,OAAO,KAAKvB,eAAe;4BACtC7B,SAAS,MAAM;gCACbR,cAAc;gCACdoB,QAAQ,AAAC,2BAAyDiB,OAA/B1B,SAASiD,OAAO,EAAC,gBAA4B,OAAdvB,eAAc;gCAChFa,OAAO;oCACL;wCACEO,MAAM;wCACNI,OAAO;wCACPC,UAAUzB;wCACV0B,UAAUpD,SAASiD,OAAO;wCAC1BF,cAAc;oCAChB;;4BAEJ;4BACA;;;wBACF;wBAEA,wCAAwC;wBAClCpB,cAAaJ,oBAAAA,YAAY8B,IAAI,cAAhB9B,wCAAAA,kBAAkB+B,OAAO;wBAC5C,IAAI,CAAC3B,YAAY;4BACf9B,SAAS,MAAM;gCACbR,cAAc;gCACdoB,QAAQ;gCACR8B,OAAO;oCAAG;wCAAEO,MAAM;wCAAiBC,cAAc;oCAAW;;4BAC9D;4BACA;;;wBACF;wBAEkB;;4BAAMrC,OAAO4C,OAAO,CAAC3B,YAAY;gCACjDhB,UAAAA;gCACA4C,SAAS,GAAEhC,qBAAAA,YAAY8B,IAAI,cAAhB9B,yCAAAA,mBAAkBgC,SAAS;4BACxC;;;wBAHA/B,kBAAkB;;;;;;wBAIXI;wBACDC,QAAQD;wBACd,gDAAgD;wBAChD,IAAIC,MAAM2B,IAAI,KAAK,QAAQ;4BACzB3D,SAAS,MAAM;gCACbR,cAAc;gCACdoB,QAAQ;gCACR8B,OAAO;oCAAG;wCAAEO,MAAM;wCAAiBC,cAAc;oCAAW;;4BAC9D;4BACA;;;wBACF;wBACA,4CAA4C;wBAC5ClD,SAAS,MAAM;4BACbR,cAAc;4BACdoB,QAAQ,AAAC,4BAA4D,OAAjCoB,MAAM4B,OAAO,IAAI;wBACvD;wBACA;;;;;;;;;;wBAMM1B,OAAOnB,IAAId;wBACA;;4BAAMY,OAAOsB,QAAQ,CAACD,MAAM;gCAAEpB,UAAAA;4BAAS;;;wBAAlDqB,WAAW;wBACF;;4BAAMtB,OAAO4C,OAAO,CAACtB,SAAS0B,SAAS,EAAE;gCACtD/C,UAAAA;gCACA4C,WAAWvB,SAAS2B,UAAU;4BAChC;;;wBAHA7B,eAAe;;;;;;wBAIRF;wBACDC,SAAQD;wBACd/B,SAAS,MAAM;4BACbR,cAAc;4BACdoB,QAAQ,AAAC,gCAAgE,OAAjCoB,OAAM4B,OAAO,IAAI;wBAC3D;wBACA;;;;wBAGF,+BAA+B;wBACzBxB,YAAYd,WAAWW;wBACvBI,eAAef,WAAWK;wBAEhC,IAAIS,cAAcC,cAAc;4BAC9BrC,SAAS,MAAM;gCACbR,cAAc;gCACdoB,QAAQ,AAAC,8BAAwD,OAA3BwB,UAAU2B,SAAS,CAAC,GAAG,KAAI;4BACnE;4BACA;;;wBACF;wBAGuB;;4BAAM5C,oBAAoBc,cAAcN;;;wBAAzDW,iBAAiB;wBAEvB,IAAIA,eAAe0B,SAAS,EAAE;4BAC5B,yEAAyE;4BACzEhE,SAAS,MAAM;gCACbR,cAAc;gCACdoB,QAAQ;4BACV;4BACA;;;wBACF;6BAGI0B,CAAAA,eAAe2B,eAAe,IAAI,CAAClE,QAAQkE,eAAe,AAAD,GAAzD3B;;;;wBAGuB;;4BAAMjB,mBAAmBY;;;wBAA5CM,kBAAmB;wBACG;;4BAAMlB,mBAAmBM;;;wBAA/Ca,qBAAsB;wBAEtBC,oBAAoBrB,mBAAmBmB,iBAAiBC,oBAAoB;4BAChF0B,qBAAqBnE,QAAQmE,mBAAmB;4BAChDC,6BAA6BpE,QAAQoE,2BAA2B;4BAChEC,cAAcrE,QAAQqE,YAAY;4BAClCC,4BAA4BtE,QAAQsE,0BAA0B;wBAChE;wBAEA,IAAI,CAAC5B,kBAAkB6B,qBAAqB,EAAE;4BAC5CtE,SAAS,MAAM;gCACbR,cAAc;gCACdoB,QAAQ;gCACR8B,SAASD,kBAAkB8B,YAAY,CAACC,GAAG,CAAC,SAACC;2CAAQ;wCACnDxB,MAAM;wCACNI,OAAOoB,GAAGpB,KAAK;wCACfC,UAAUmB,GAAGnB,QAAQ;wCACrBC,UAAUkB,GAAGlB,QAAQ;wCACrBL,cAAc;oCAChB;;4BACF;4BACA;;;wBACF;wBAEA,qBAAqB;wBACfR,UAA0B,AAC9B,qBAAGD,kBAAkB8B,YAAY,CAACC,GAAG,CAAC,SAACC;mCAAQ;gCAC7CxB,MAAM;gCACNI,OAAOoB,GAAGpB,KAAK;gCACfC,UAAUmB,GAAGnB,QAAQ;gCACrBC,UAAUkB,GAAGlB,QAAQ;gCACrBL,cAAcuB,GAAGvB,YAAY;4BAC/B;mCACA,qBAAGT,kBAAkBiC,iBAAiB,CACnCC,MAAM,CAAC,SAACC;mCAAOA,GAAGC,cAAc,KAAK,gBAAgBD,GAAGC,cAAc,KAAK;2BAC3EL,GAAG,CAAC,SAACI;mCAAQ;gCACZ3B,MAAM;gCACNI,OAAO,AAAC,GAAauB,OAAXA,GAAG3B,IAAI,EAAC,KAAW,OAAR2B,GAAGhC,IAAI;gCAC5BU,UAAUsB,GAAGE,OAAO;gCACpBvB,UAAUqB,GAAGG,OAAO;gCACpB7B,cAAc;4BAChB;;wBAGJlD,SAAS,MAAM;4BACbR,cAAc;4BACdoB,QAAQ6B,kBAAkBuC,OAAO;4BACjCtC,SAAAA;wBACF;wBACA;;;;wBAGF,8BAA8B;wBAC9B1C,SAAS,MAAM;4BACbR,cAAc;4BACdoB,QAAQ,AAAC,0BAA2D,OAAlC0B,eAAe2C,WAAW,CAACC,MAAM,EAAC;4BACpExC,SAASJ,eAAe2C,WAAW,CAACT,GAAG,CAAC,SAACC;uCAAQ;oCAC/CxB,MAAM;oCACNI,OAAOoB,GAAGhE,IAAI;oCACdyC,cAAc;gCAChB;;wBACF;;;;;;QACF;KAAA,IAAKiC,KAAK,CAACnF;AACb;AAKO,SAASP,eAAeM,OAA4B,EAAEC,QAA8B;IACzFF,iBAAiBC,SAASC;AAC5B;AAeO,SAASR;QAAaO,UAAAA,iEAA+B,CAAC;IAC3D,OAAO,IAAIqF,QAAQ,SAACC,SAASC;QAC3B7F,eAAeM,SAAS,SAACiC,OAAOuD;YAC9B,IAAIvD,OAAOsD,OAAOtD;iBACb,IAAIuD,QAAQF,QAAQE;iBACpBD,OAAO,IAAIE,MAAM;QACxB;IACF;AACF"}
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* 3. Special handling for package.json (semantic comparison elsewhere)
|
|
8
8
|
*/ import crypto from 'crypto';
|
|
9
9
|
import Module from 'module';
|
|
10
|
-
import { Readable } from 'stream';
|
|
11
|
-
import {
|
|
10
|
+
import { pipeline as pipelineCb, Readable } from 'stream';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
12
|
import zlib from 'zlib';
|
|
13
|
+
const pipeline = promisify(pipelineCb);
|
|
13
14
|
const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;
|
|
14
15
|
// Lazy load tar
|
|
15
16
|
let _tar = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/comparators/file-content.ts"],"sourcesContent":["/**\n * File content comparison for package tarballs\n *\n * Compares files between local and registry tarballs:\n * 1. Fast path: Compare tarball hashes\n * 2. If different: Extract and compare file-by-file\n * 3. Special handling for package.json (semantic comparison elsewhere)\n */\n\nimport crypto from 'crypto';\nimport Module from 'module';\nimport { Readable } from 'stream';\nimport { pipeline } from 'stream/promises';\nimport zlib from 'zlib';\nimport type { FileChange, FileComparison } from '../types.ts';\n\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n// Lazy load tar\nlet _tar: typeof import('tar') | null = null;\n\nfunction getTar(): typeof import('tar') {\n if (!_tar) {\n _tar = _require('tar');\n }\n return _tar;\n}\n\n/**\n * Compare package files from two tarballs\n *\n * @param localTarball - Local package tarball as Buffer\n * @param registryTarball - Registry package tarball as Buffer\n * @returns File comparison result\n */\nexport async function comparePackageFiles(localTarball: Buffer, registryTarball: Buffer): Promise<FileComparison> {\n // Fast path: identical tarballs\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n return {\n identical: true,\n fileChanges: [],\n packageJsonOnly: false,\n };\n }\n\n // Extract both tarballs to memory\n const localFiles = await extractTarball(localTarball);\n const registryFiles = await extractTarball(registryTarball);\n\n const changes: FileChange[] = [];\n\n // Build combined set of all paths\n const allPaths: Record<string, boolean> = {};\n const localKeys = Object.keys(localFiles);\n for (let i = 0; i < localKeys.length; i++) {\n allPaths[localKeys[i]] = true;\n }\n const registryKeys = Object.keys(registryFiles);\n for (let i = 0; i < registryKeys.length; i++) {\n allPaths[registryKeys[i]] = true;\n }\n\n let onlyPackageJsonDiffers = true;\n\n const pathKeys = Object.keys(allPaths);\n for (let i = 0; i < pathKeys.length; i++) {\n const filePath = pathKeys[i];\n const isPackageJson = filePath === 'package/package.json' || filePath.indexOf('/package.json') === filePath.length - 13;\n const localContent = localFiles[filePath];\n const registryContent = registryFiles[filePath];\n\n if (!registryContent) {\n // File added\n changes.push({ path: filePath, action: 'added' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!localContent) {\n // File removed\n changes.push({ path: filePath, action: 'removed' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!buffersEqual(localContent, registryContent)) {\n // File modified\n changes.push({ path: filePath, action: 'modified' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n }\n }\n\n return {\n identical: changes.length === 0,\n fileChanges: changes,\n packageJsonOnly: onlyPackageJsonDiffers && changes.length > 0,\n };\n}\n\n/**\n * Extract tarball contents to memory\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Map of file paths to content buffers\n */\nasync function extractTarball(tarball: Buffer): Promise<Record<string, Buffer>> {\n const files: Record<string, Buffer> = {};\n const tar = getTar();\n\n // Create a parser that collects file contents\n const parser = new tar.Parser({\n onReadEntry: (entry) => {\n if (entry.type === 'File') {\n const chunks: Buffer[] = [];\n\n entry.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n entry.on('end', () => {\n files[entry.path] = Buffer.concat(chunks);\n });\n } else {\n // Drain non-file entries (directories, etc.)\n entry.resume();\n }\n },\n });\n\n // Process the tarball\n await pipeline(Readable.from(tarball), zlib.createGunzip(), parser);\n\n return files;\n}\n\n/**\n * Hash a buffer using SHA-512\n */\nexport function hashBuffer(buffer: Buffer): string {\n return crypto.createHash('sha512').update(buffer).digest('base64');\n}\n\n/**\n * Compare two buffers for equality\n */\nfunction buffersEqual(a: Buffer, b: Buffer): boolean {\n if (a.length !== b.length) return false;\n return crypto.timingSafeEqual(a, b);\n}\n\n/**\n * Get a summary of file changes\n */\nexport function getFileChangeSummary(changes: FileChange[]): string {\n if (changes.length === 0) {\n return 'No file changes';\n }\n\n const added = changes.filter((c) => c.action === 'added');\n const removed = changes.filter((c) => c.action === 'removed');\n const modified = changes.filter((c) => c.action === 'modified');\n\n const parts: string[] = [];\n\n if (added.length > 0) {\n parts.push(`${added.length} added`);\n }\n if (removed.length > 0) {\n parts.push(`${removed.length} removed`);\n }\n if (modified.length > 0) {\n parts.push(`${modified.length} modified`);\n }\n\n return `Files: ${parts.join(', ')} (${changes.length} total)`;\n}\n\n/**\n * Check if changes are only in package.json\n */\nexport function isOnlyPackageJsonChange(changes: FileChange[]): boolean {\n if (changes.length === 0) return false;\n\n for (let i = 0; i < changes.length; i++) {\n const path = changes[i].path;\n if (path !== 'package/package.json' && path.indexOf('/package.json') !== path.length - 13) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Extract package.json from a tarball\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Parsed package.json object\n */\nexport async function extractPackageJson(tarball: Buffer): Promise<unknown> {\n const files = await extractTarball(tarball);\n\n // Look for package.json in the tarball\n const pkgJsonPath = Object.keys(files).find((p) => p === 'package/package.json' || p.indexOf('/package.json') === p.length - 13);\n\n if (!pkgJsonPath || !files[pkgJsonPath]) {\n throw new Error('package.json not found in tarball');\n }\n\n return JSON.parse(files[pkgJsonPath].toString('utf8'));\n}\n"],"names":["crypto","Module","Readable","pipeline","zlib","_require","require","createRequire","url","_tar","getTar","comparePackageFiles","localTarball","registryTarball","localHash","hashBuffer","registryHash","identical","fileChanges","packageJsonOnly","localFiles","extractTarball","registryFiles","changes","allPaths","localKeys","Object","keys","i","length","registryKeys","onlyPackageJsonDiffers","pathKeys","filePath","isPackageJson","indexOf","localContent","registryContent","push","path","action","buffersEqual","tarball","files","tar","parser","Parser","onReadEntry","entry","type","chunks","on","chunk","Buffer","concat","resume","from","createGunzip","buffer","createHash","update","digest","a","b","timingSafeEqual","getFileChangeSummary","added","filter","c","removed","modified","parts","join","isOnlyPackageJsonChange","extractPackageJson","pkgJsonPath","find","p","Error","JSON","parse","toString"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/comparators/file-content.ts"],"sourcesContent":["/**\n * File content comparison for package tarballs\n *\n * Compares files between local and registry tarballs:\n * 1. Fast path: Compare tarball hashes\n * 2. If different: Extract and compare file-by-file\n * 3. Special handling for package.json (semantic comparison elsewhere)\n */\n\nimport crypto from 'crypto';\nimport Module from 'module';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport { promisify } from 'util';\nimport zlib from 'zlib';\n\nconst pipeline = promisify(pipelineCb);\n\nimport type { FileChange, FileComparison } from '../types.ts';\n\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n// Lazy load tar\nlet _tar: typeof import('tar') | null = null;\n\nfunction getTar(): typeof import('tar') {\n if (!_tar) {\n _tar = _require('tar');\n }\n return _tar;\n}\n\n/**\n * Compare package files from two tarballs\n *\n * @param localTarball - Local package tarball as Buffer\n * @param registryTarball - Registry package tarball as Buffer\n * @returns File comparison result\n */\nexport async function comparePackageFiles(localTarball: Buffer, registryTarball: Buffer): Promise<FileComparison> {\n // Fast path: identical tarballs\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n return {\n identical: true,\n fileChanges: [],\n packageJsonOnly: false,\n };\n }\n\n // Extract both tarballs to memory\n const localFiles = await extractTarball(localTarball);\n const registryFiles = await extractTarball(registryTarball);\n\n const changes: FileChange[] = [];\n\n // Build combined set of all paths\n const allPaths: Record<string, boolean> = {};\n const localKeys = Object.keys(localFiles);\n for (let i = 0; i < localKeys.length; i++) {\n allPaths[localKeys[i]] = true;\n }\n const registryKeys = Object.keys(registryFiles);\n for (let i = 0; i < registryKeys.length; i++) {\n allPaths[registryKeys[i]] = true;\n }\n\n let onlyPackageJsonDiffers = true;\n\n const pathKeys = Object.keys(allPaths);\n for (let i = 0; i < pathKeys.length; i++) {\n const filePath = pathKeys[i];\n const isPackageJson = filePath === 'package/package.json' || filePath.indexOf('/package.json') === filePath.length - 13;\n const localContent = localFiles[filePath];\n const registryContent = registryFiles[filePath];\n\n if (!registryContent) {\n // File added\n changes.push({ path: filePath, action: 'added' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!localContent) {\n // File removed\n changes.push({ path: filePath, action: 'removed' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n } else if (!buffersEqual(localContent, registryContent)) {\n // File modified\n changes.push({ path: filePath, action: 'modified' });\n if (!isPackageJson) onlyPackageJsonDiffers = false;\n }\n }\n\n return {\n identical: changes.length === 0,\n fileChanges: changes,\n packageJsonOnly: onlyPackageJsonDiffers && changes.length > 0,\n };\n}\n\n/**\n * Extract tarball contents to memory\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Map of file paths to content buffers\n */\nasync function extractTarball(tarball: Buffer): Promise<Record<string, Buffer>> {\n const files: Record<string, Buffer> = {};\n const tar = getTar();\n\n // Create a parser that collects file contents\n const parser = new tar.Parser({\n onReadEntry: (entry) => {\n if (entry.type === 'File') {\n const chunks: Buffer[] = [];\n\n entry.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n entry.on('end', () => {\n files[entry.path] = Buffer.concat(chunks);\n });\n } else {\n // Drain non-file entries (directories, etc.)\n entry.resume();\n }\n },\n });\n\n // Process the tarball\n await pipeline(Readable.from(tarball), zlib.createGunzip(), parser);\n\n return files;\n}\n\n/**\n * Hash a buffer using SHA-512\n */\nexport function hashBuffer(buffer: Buffer): string {\n return crypto.createHash('sha512').update(buffer).digest('base64');\n}\n\n/**\n * Compare two buffers for equality\n */\nfunction buffersEqual(a: Buffer, b: Buffer): boolean {\n if (a.length !== b.length) return false;\n return crypto.timingSafeEqual(a, b);\n}\n\n/**\n * Get a summary of file changes\n */\nexport function getFileChangeSummary(changes: FileChange[]): string {\n if (changes.length === 0) {\n return 'No file changes';\n }\n\n const added = changes.filter((c) => c.action === 'added');\n const removed = changes.filter((c) => c.action === 'removed');\n const modified = changes.filter((c) => c.action === 'modified');\n\n const parts: string[] = [];\n\n if (added.length > 0) {\n parts.push(`${added.length} added`);\n }\n if (removed.length > 0) {\n parts.push(`${removed.length} removed`);\n }\n if (modified.length > 0) {\n parts.push(`${modified.length} modified`);\n }\n\n return `Files: ${parts.join(', ')} (${changes.length} total)`;\n}\n\n/**\n * Check if changes are only in package.json\n */\nexport function isOnlyPackageJsonChange(changes: FileChange[]): boolean {\n if (changes.length === 0) return false;\n\n for (let i = 0; i < changes.length; i++) {\n const path = changes[i].path;\n if (path !== 'package/package.json' && path.indexOf('/package.json') !== path.length - 13) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Extract package.json from a tarball\n *\n * @param tarball - Tarball as Buffer (gzipped)\n * @returns Parsed package.json object\n */\nexport async function extractPackageJson(tarball: Buffer): Promise<unknown> {\n const files = await extractTarball(tarball);\n\n // Look for package.json in the tarball\n const pkgJsonPath = Object.keys(files).find((p) => p === 'package/package.json' || p.indexOf('/package.json') === p.length - 13);\n\n if (!pkgJsonPath || !files[pkgJsonPath]) {\n throw new Error('package.json not found in tarball');\n }\n\n return JSON.parse(files[pkgJsonPath].toString('utf8'));\n}\n"],"names":["crypto","Module","pipeline","pipelineCb","Readable","promisify","zlib","_require","require","createRequire","url","_tar","getTar","comparePackageFiles","localTarball","registryTarball","localHash","hashBuffer","registryHash","identical","fileChanges","packageJsonOnly","localFiles","extractTarball","registryFiles","changes","allPaths","localKeys","Object","keys","i","length","registryKeys","onlyPackageJsonDiffers","pathKeys","filePath","isPackageJson","indexOf","localContent","registryContent","push","path","action","buffersEqual","tarball","files","tar","parser","Parser","onReadEntry","entry","type","chunks","on","chunk","Buffer","concat","resume","from","createGunzip","buffer","createHash","update","digest","a","b","timingSafeEqual","getFileChangeSummary","added","filter","c","removed","modified","parts","join","isOnlyPackageJsonChange","extractPackageJson","pkgJsonPath","find","p","Error","JSON","parse","toString"],"mappings":"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"}
|
|
@@ -15,8 +15,7 @@ import type { NeedsPublishOptions, NeedsPublishResult } from './types.js';
|
|
|
15
15
|
*/
|
|
16
16
|
export type NeedsPublishCallback = (error: Error | null, result?: NeedsPublishResult) => void;
|
|
17
17
|
/**
|
|
18
|
-
* Callback-based needsPublish
|
|
19
|
-
* Uses worker for older Node versions, runs locally on Node 14+
|
|
18
|
+
* Callback-based needsPublish
|
|
20
19
|
*/
|
|
21
20
|
export declare function needsPublishCb(options: NeedsPublishOptions, callback: NeedsPublishCallback): void;
|
|
22
21
|
/**
|
|
@@ -11,19 +11,9 @@
|
|
|
11
11
|
*/ import fs from 'fs';
|
|
12
12
|
import Module from 'module';
|
|
13
13
|
import path from 'path';
|
|
14
|
-
import { wrapWorker } from 'tsds-lib';
|
|
15
|
-
import url from 'url';
|
|
16
14
|
import { stringStartsWith } from './compat.js';
|
|
17
|
-
// Version check for worker pattern - use worker for Node < 14
|
|
18
|
-
const major = +process.versions.node.split('.')[0];
|
|
19
|
-
const version = major > 14 ? 'local' : 'stable';
|
|
20
15
|
const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;
|
|
21
|
-
|
|
22
|
-
const dist = path.join(__dirname, '..');
|
|
23
|
-
const workerWrapper = wrapWorker(path.join(dist, 'cjs', 'needs-publish.js'));
|
|
24
|
-
/**
|
|
25
|
-
* Worker function that runs on Node 14+
|
|
26
|
-
*/ function worker(options, callback) {
|
|
16
|
+
function needsPublishImpl(options, callback) {
|
|
27
17
|
const cwd = options.cwd || process.cwd();
|
|
28
18
|
// Load local package.json
|
|
29
19
|
const localPkg = options.package || JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
@@ -249,10 +239,9 @@ const workerWrapper = wrapWorker(path.join(dist, 'cjs', 'needs-publish.js'));
|
|
|
249
239
|
})().catch(callback);
|
|
250
240
|
}
|
|
251
241
|
/**
|
|
252
|
-
* Callback-based needsPublish
|
|
253
|
-
* Uses worker for older Node versions, runs locally on Node 14+
|
|
242
|
+
* Callback-based needsPublish
|
|
254
243
|
*/ export function needsPublishCb(options, callback) {
|
|
255
|
-
|
|
244
|
+
needsPublishImpl(options, callback);
|
|
256
245
|
}
|
|
257
246
|
/**
|
|
258
247
|
* Determine if a package needs to be published to npm.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/needs-publish.ts"],"sourcesContent":["/**\n * Main orchestration logic for npm-needs-publish\n *\n * Algorithm:\n * 1. Fetch registry packument → if E404, return needsPublish=true (first publish)\n * 2. Version check → if different, return needsPublish=true (intentional bump)\n * 3. Fast hash check → if identical, return needsPublish=false (no changes)\n * 4. Extract both tarballs, compare file-by-file (excluding package.json)\n * 5. If non-package.json files differ → return needsPublish=true\n * 6. If only package.json differs → do semantic comparison\n */\n\nimport fs from 'fs';\nimport Module from 'module';\nimport path from 'path';\nimport { wrapWorker } from 'tsds-lib';\nimport url from 'url';\nimport { stringStartsWith } from './compat.ts';\nimport type { ChangeDetail, NeedsPublishOptions, NeedsPublishResult, PackageJson } from './types.ts';\n\n// Version check for worker pattern - use worker for Node < 14\nconst major = +process.versions.node.split('.')[0];\nconst version = major > 14 ? 'local' : 'stable';\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\nconst __dirname = path.dirname(typeof __filename === 'undefined' ? url.fileURLToPath(import.meta.url) : __filename);\nconst dist = path.join(__dirname, '..');\nconst workerWrapper = wrapWorker(path.join(dist, 'cjs', 'needs-publish.js'));\n\n/**\n * Callback type for needsPublish\n */\nexport type NeedsPublishCallback = (error: Error | null, result?: NeedsPublishResult) => void;\n\n/**\n * Worker function that runs on Node 14+\n */\nfunction worker(options: NeedsPublishOptions, callback: NeedsPublishCallback): undefined {\n const cwd = options.cwd || process.cwd();\n\n // Load local package.json\n const localPkg: PackageJson = options.package || JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));\n\n // Skip private packages\n if (localPkg.private) {\n callback(null, {\n needsPublish: false,\n reason: 'Package is private',\n });\n return;\n }\n\n (async () => {\n const pacote = _require('pacote');\n const Arborist = _require('@npmcli/arborist');\n const npa = _require('npm-package-arg');\n const { execFile } = _require('child_process');\n const { promisify } = _require('util');\n const execFileAsync = promisify(execFile);\n\n // Dynamic import for comparators (they use modern features)\n const { comparePackageFiles, comparePackageJson, extractPackageJson, hashBuffer } = await import('./comparators/index.ts');\n\n // Get registry URL for scoped packages\n let registry: string | undefined = options.registry;\n if (!registry) {\n const scope = stringStartsWith(localPkg.name, '@') ? localPkg.name.split('/')[0] : undefined;\n if (scope) {\n try {\n const { stdout } = await execFileAsync('npm', ['config', 'get', `${scope}:registry`]);\n registry = stdout.trim();\n if (registry === 'undefined') registry = undefined;\n } catch {\n // Fallback to default registry\n }\n }\n }\n\n // Step 1: Try to fetch registry packument\n let registryPkg: PackageJson;\n let registryTarball: Buffer;\n\n try {\n const packument = await pacote.packument(localPkg.name, {\n Arborist,\n ...(registry && { registry }),\n });\n\n const latestVersion = packument['dist-tags']?.latest;\n if (!latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: 'No latest version found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryPkg = packument.versions[latestVersion] as unknown as PackageJson;\n\n // Step 2: Version comparison (fast path)\n if (localPkg.version !== latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: `Version differs (local: ${localPkg.version}, registry: ${latestVersion})`,\n changes: [\n {\n type: 'version',\n field: 'version',\n oldValue: latestVersion,\n newValue: localPkg.version,\n significance: 'critical',\n },\n ],\n });\n return;\n }\n\n // Fetch registry tarball for comparison\n const tarballUrl = registryPkg.dist?.tarball;\n if (!tarballUrl) {\n callback(null, {\n needsPublish: true,\n reason: 'Registry package has no tarball URL',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryTarball = await pacote.tarball(tarballUrl, {\n Arborist,\n integrity: registryPkg.dist?.integrity,\n });\n } catch (err: unknown) {\n const error = err as { code?: string; message?: string };\n // Package not found in registry (first publish)\n if (error.code === 'E404') {\n callback(null, {\n needsPublish: true,\n reason: 'Package not found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n // Unknown error - assume changed to be safe\n callback(null, {\n needsPublish: true,\n reason: `Error checking registry: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 3: Pack local package\n let localTarball: Buffer;\n try {\n const spec = npa(cwd);\n const manifest = await pacote.manifest(spec, { Arborist });\n localTarball = await pacote.tarball(manifest._resolved, {\n Arborist,\n integrity: manifest._integrity,\n });\n } catch (err: unknown) {\n const error = err as { message?: string };\n callback(null, {\n needsPublish: true,\n reason: `Error packing local package: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 4: Fast hash comparison\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n callback(null, {\n needsPublish: false,\n reason: `No changes detected (hash: ${localHash.substring(0, 16)}...)`,\n });\n return;\n }\n\n // Step 5: File-by-file comparison\n const fileComparison = await comparePackageFiles(localTarball, registryTarball);\n\n if (fileComparison.identical) {\n // Hash mismatch but files identical - likely tarball metadata difference\n callback(null, {\n needsPublish: false,\n reason: 'Files identical (tarball metadata differs)',\n });\n return;\n }\n\n // Step 6: If only package.json differs, do semantic comparison\n if (fileComparison.packageJsonOnly && !options.packageJsonOnly) {\n // Extract package.json from both tarballs for accurate comparison\n // (packument metadata is missing fields like 'files')\n const localTarballPkg = (await extractPackageJson(localTarball)) as PackageJson;\n const registryTarballPkg = (await extractPackageJson(registryTarball)) as PackageJson;\n\n const pkgJsonComparison = comparePackageJson(localTarballPkg, registryTarballPkg, {\n includeOptionalDeps: options.includeOptionalDeps,\n additionalSignificantFields: options.additionalSignificantFields,\n ignoreFields: options.ignoreFields,\n treatNarrowingAsEquivalent: options.treatNarrowingAsEquivalent,\n });\n\n if (!pkgJsonComparison.hasSignificantChanges) {\n callback(null, {\n needsPublish: false,\n reason: 'Package.json changes are not significant for consumers',\n changes: pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: 'informational' as const,\n })),\n });\n return;\n }\n\n // Build changes list\n const changes: ChangeDetail[] = [\n ...pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: fc.significance,\n })),\n ...pkgJsonComparison.dependencyChanges\n .filter((dc) => dc.semanticChange !== 'equivalent' && dc.semanticChange !== 'none')\n .map((dc) => ({\n type: 'dependency' as const,\n field: `${dc.type}.${dc.name}`,\n oldValue: dc.oldSpec,\n newValue: dc.newSpec,\n significance: 'significant' as const,\n })),\n ];\n\n callback(null, {\n needsPublish: true,\n reason: pkgJsonComparison.summary,\n changes,\n });\n return;\n }\n\n // Step 7: Other files changed\n callback(null, {\n needsPublish: true,\n reason: `Code changes detected (${fileComparison.fileChanges.length} files changed)`,\n changes: fileComparison.fileChanges.map((fc) => ({\n type: 'file' as const,\n field: fc.path,\n significance: 'significant' as const,\n })),\n });\n })().catch(callback);\n}\n\n/**\n * Callback-based needsPublish - works on Node 0.8+\n * Uses worker for older Node versions, runs locally on Node 14+\n */\nexport function needsPublishCb(options: NeedsPublishOptions, callback: NeedsPublishCallback): void {\n version !== 'local' ? workerWrapper(version, options, callback) : worker(options, callback);\n}\n\n/**\n * Determine if a package needs to be published to npm.\n *\n * @example\n * ```ts\n * import { needsPublish } from 'npm-needs-publish';\n *\n * const result = await needsPublish({ cwd: process.cwd() });\n * if (result.needsPublish) {\n * console.log('Publish needed:', result.reason);\n * }\n * ```\n */\nexport function needsPublish(options: NeedsPublishOptions = {}): Promise<NeedsPublishResult> {\n return new Promise((resolve, reject) => {\n needsPublishCb(options, (error, result) => {\n if (error) reject(error);\n else if (result) resolve(result);\n else reject(new Error('No result returned'));\n });\n });\n}\n"],"names":["fs","Module","path","wrapWorker","url","stringStartsWith","major","process","versions","node","split","version","_require","require","createRequire","__dirname","dirname","__filename","fileURLToPath","dist","join","workerWrapper","worker","options","callback","cwd","localPkg","package","JSON","parse","readFileSync","private","needsPublish","reason","pacote","Arborist","npa","execFile","promisify","execFileAsync","comparePackageFiles","comparePackageJson","extractPackageJson","hashBuffer","registry","scope","name","undefined","stdout","trim","registryPkg","registryTarball","packument","latestVersion","latest","changes","type","significance","field","oldValue","newValue","tarballUrl","tarball","integrity","err","error","code","message","localTarball","spec","manifest","_resolved","_integrity","localHash","registryHash","substring","fileComparison","identical","packageJsonOnly","localTarballPkg","registryTarballPkg","pkgJsonComparison","includeOptionalDeps","additionalSignificantFields","ignoreFields","treatNarrowingAsEquivalent","hasSignificantChanges","fieldChanges","map","fc","dependencyChanges","filter","dc","semanticChange","oldSpec","newSpec","summary","fileChanges","length","catch","needsPublishCb","Promise","resolve","reject","result","Error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/npm/npm-needs-publish/src/needs-publish.ts"],"sourcesContent":["/**\n * Main orchestration logic for npm-needs-publish\n *\n * Algorithm:\n * 1. Fetch registry packument → if E404, return needsPublish=true (first publish)\n * 2. Version check → if different, return needsPublish=true (intentional bump)\n * 3. Fast hash check → if identical, return needsPublish=false (no changes)\n * 4. Extract both tarballs, compare file-by-file (excluding package.json)\n * 5. If non-package.json files differ → return needsPublish=true\n * 6. If only package.json differs → do semantic comparison\n */\n\nimport fs from 'fs';\nimport Module from 'module';\nimport path from 'path';\nimport { stringStartsWith } from './compat.ts';\nimport type { ChangeDetail, NeedsPublishOptions, NeedsPublishResult, PackageJson } from './types.ts';\n\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n/**\n * Callback type for needsPublish\n */\nexport type NeedsPublishCallback = (error: Error | null, result?: NeedsPublishResult) => void;\n\nfunction needsPublishImpl(options: NeedsPublishOptions, callback: NeedsPublishCallback): undefined {\n const cwd = options.cwd || process.cwd();\n\n // Load local package.json\n const localPkg: PackageJson = options.package || JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));\n\n // Skip private packages\n if (localPkg.private) {\n callback(null, {\n needsPublish: false,\n reason: 'Package is private',\n });\n return;\n }\n\n (async () => {\n const pacote = _require('pacote');\n const Arborist = _require('@npmcli/arborist');\n const npa = _require('npm-package-arg');\n const { execFile } = _require('child_process');\n const { promisify } = _require('util');\n const execFileAsync = promisify(execFile);\n\n // Dynamic import for comparators (they use modern features)\n const { comparePackageFiles, comparePackageJson, extractPackageJson, hashBuffer } = await import('./comparators/index.ts');\n\n // Get registry URL for scoped packages\n let registry: string | undefined = options.registry;\n if (!registry) {\n const scope = stringStartsWith(localPkg.name, '@') ? localPkg.name.split('/')[0] : undefined;\n if (scope) {\n try {\n const { stdout } = await execFileAsync('npm', ['config', 'get', `${scope}:registry`]);\n registry = stdout.trim();\n if (registry === 'undefined') registry = undefined;\n } catch {\n // Fallback to default registry\n }\n }\n }\n\n // Step 1: Try to fetch registry packument\n let registryPkg: PackageJson;\n let registryTarball: Buffer;\n\n try {\n const packument = await pacote.packument(localPkg.name, {\n Arborist,\n ...(registry && { registry }),\n });\n\n const latestVersion = packument['dist-tags']?.latest;\n if (!latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: 'No latest version found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryPkg = packument.versions[latestVersion] as unknown as PackageJson;\n\n // Step 2: Version comparison (fast path)\n if (localPkg.version !== latestVersion) {\n callback(null, {\n needsPublish: true,\n reason: `Version differs (local: ${localPkg.version}, registry: ${latestVersion})`,\n changes: [\n {\n type: 'version',\n field: 'version',\n oldValue: latestVersion,\n newValue: localPkg.version,\n significance: 'critical',\n },\n ],\n });\n return;\n }\n\n // Fetch registry tarball for comparison\n const tarballUrl = registryPkg.dist?.tarball;\n if (!tarballUrl) {\n callback(null, {\n needsPublish: true,\n reason: 'Registry package has no tarball URL',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n\n registryTarball = await pacote.tarball(tarballUrl, {\n Arborist,\n integrity: registryPkg.dist?.integrity,\n });\n } catch (err: unknown) {\n const error = err as { code?: string; message?: string };\n // Package not found in registry (first publish)\n if (error.code === 'E404') {\n callback(null, {\n needsPublish: true,\n reason: 'Package not found in registry (first publish)',\n changes: [{ type: 'first-publish', significance: 'critical' }],\n });\n return;\n }\n // Unknown error - assume changed to be safe\n callback(null, {\n needsPublish: true,\n reason: `Error checking registry: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 3: Pack local package\n let localTarball: Buffer;\n try {\n const spec = npa(cwd);\n const manifest = await pacote.manifest(spec, { Arborist });\n localTarball = await pacote.tarball(manifest._resolved, {\n Arborist,\n integrity: manifest._integrity,\n });\n } catch (err: unknown) {\n const error = err as { message?: string };\n callback(null, {\n needsPublish: true,\n reason: `Error packing local package: ${error.message || 'Unknown error'}`,\n });\n return;\n }\n\n // Step 4: Fast hash comparison\n const localHash = hashBuffer(localTarball);\n const registryHash = hashBuffer(registryTarball);\n\n if (localHash === registryHash) {\n callback(null, {\n needsPublish: false,\n reason: `No changes detected (hash: ${localHash.substring(0, 16)}...)`,\n });\n return;\n }\n\n // Step 5: File-by-file comparison\n const fileComparison = await comparePackageFiles(localTarball, registryTarball);\n\n if (fileComparison.identical) {\n // Hash mismatch but files identical - likely tarball metadata difference\n callback(null, {\n needsPublish: false,\n reason: 'Files identical (tarball metadata differs)',\n });\n return;\n }\n\n // Step 6: If only package.json differs, do semantic comparison\n if (fileComparison.packageJsonOnly && !options.packageJsonOnly) {\n // Extract package.json from both tarballs for accurate comparison\n // (packument metadata is missing fields like 'files')\n const localTarballPkg = (await extractPackageJson(localTarball)) as PackageJson;\n const registryTarballPkg = (await extractPackageJson(registryTarball)) as PackageJson;\n\n const pkgJsonComparison = comparePackageJson(localTarballPkg, registryTarballPkg, {\n includeOptionalDeps: options.includeOptionalDeps,\n additionalSignificantFields: options.additionalSignificantFields,\n ignoreFields: options.ignoreFields,\n treatNarrowingAsEquivalent: options.treatNarrowingAsEquivalent,\n });\n\n if (!pkgJsonComparison.hasSignificantChanges) {\n callback(null, {\n needsPublish: false,\n reason: 'Package.json changes are not significant for consumers',\n changes: pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: 'informational' as const,\n })),\n });\n return;\n }\n\n // Build changes list\n const changes: ChangeDetail[] = [\n ...pkgJsonComparison.fieldChanges.map((fc) => ({\n type: 'field' as const,\n field: fc.field,\n oldValue: fc.oldValue,\n newValue: fc.newValue,\n significance: fc.significance,\n })),\n ...pkgJsonComparison.dependencyChanges\n .filter((dc) => dc.semanticChange !== 'equivalent' && dc.semanticChange !== 'none')\n .map((dc) => ({\n type: 'dependency' as const,\n field: `${dc.type}.${dc.name}`,\n oldValue: dc.oldSpec,\n newValue: dc.newSpec,\n significance: 'significant' as const,\n })),\n ];\n\n callback(null, {\n needsPublish: true,\n reason: pkgJsonComparison.summary,\n changes,\n });\n return;\n }\n\n // Step 7: Other files changed\n callback(null, {\n needsPublish: true,\n reason: `Code changes detected (${fileComparison.fileChanges.length} files changed)`,\n changes: fileComparison.fileChanges.map((fc) => ({\n type: 'file' as const,\n field: fc.path,\n significance: 'significant' as const,\n })),\n });\n })().catch(callback);\n}\n\n/**\n * Callback-based needsPublish\n */\nexport function needsPublishCb(options: NeedsPublishOptions, callback: NeedsPublishCallback): void {\n needsPublishImpl(options, callback);\n}\n\n/**\n * Determine if a package needs to be published to npm.\n *\n * @example\n * ```ts\n * import { needsPublish } from 'npm-needs-publish';\n *\n * const result = await needsPublish({ cwd: process.cwd() });\n * if (result.needsPublish) {\n * console.log('Publish needed:', result.reason);\n * }\n * ```\n */\nexport function needsPublish(options: NeedsPublishOptions = {}): Promise<NeedsPublishResult> {\n return new Promise((resolve, reject) => {\n needsPublishCb(options, (error, result) => {\n if (error) reject(error);\n else if (result) resolve(result);\n else reject(new Error('No result returned'));\n });\n });\n}\n"],"names":["fs","Module","path","stringStartsWith","_require","require","createRequire","url","needsPublishImpl","options","callback","cwd","process","localPkg","package","JSON","parse","readFileSync","join","private","needsPublish","reason","pacote","Arborist","npa","execFile","promisify","execFileAsync","comparePackageFiles","comparePackageJson","extractPackageJson","hashBuffer","registry","scope","name","split","undefined","stdout","trim","registryPkg","registryTarball","packument","latestVersion","latest","changes","type","significance","versions","version","field","oldValue","newValue","tarballUrl","dist","tarball","integrity","err","error","code","message","localTarball","spec","manifest","_resolved","_integrity","localHash","registryHash","substring","fileComparison","identical","packageJsonOnly","localTarballPkg","registryTarballPkg","pkgJsonComparison","includeOptionalDeps","additionalSignificantFields","ignoreFields","treatNarrowingAsEquivalent","hasSignificantChanges","fieldChanges","map","fc","dependencyChanges","filter","dc","semanticChange","oldSpec","newSpec","summary","fileChanges","length","catch","needsPublishCb","Promise","resolve","reject","result","Error"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "npm-needs-publish",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Smart publish detection for npm packages - semantic package.json comparison with semver-aware dependency analysis",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"npm",
|
|
@@ -55,34 +55,33 @@
|
|
|
55
55
|
"version": "tsds version"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@npmcli/arborist": "
|
|
59
|
-
"fs-copy-compat": "
|
|
60
|
-
"fs-remove-compat": "
|
|
61
|
-
"mkdirp-classic": "
|
|
62
|
-
"npm-package-arg": "
|
|
63
|
-
"pacote": "
|
|
64
|
-
"semver": "
|
|
65
|
-
"tar": "
|
|
66
|
-
"temp-suffix": "
|
|
67
|
-
"tsds-lib": "*"
|
|
58
|
+
"@npmcli/arborist": "^7.5.4",
|
|
59
|
+
"fs-copy-compat": "^0.1.3",
|
|
60
|
+
"fs-remove-compat": "^0.2.1",
|
|
61
|
+
"mkdirp-classic": "^0.5.3",
|
|
62
|
+
"npm-package-arg": "^13.0.2",
|
|
63
|
+
"pacote": "^17.0.7",
|
|
64
|
+
"semver": "^7.7.3",
|
|
65
|
+
"tar": "^7.5.2",
|
|
66
|
+
"temp-suffix": "^1.0.8"
|
|
68
67
|
},
|
|
69
68
|
"devDependencies": {
|
|
70
|
-
"@types/mocha": "
|
|
71
|
-
"@types/node": "
|
|
69
|
+
"@types/mocha": "^10.0.10",
|
|
70
|
+
"@types/node": "^24.10.1",
|
|
72
71
|
"@types/pacote": "^11.1.8",
|
|
73
|
-
"@types/semver": "
|
|
74
|
-
"@types/tar": "
|
|
75
|
-
"module-link-unlink": "
|
|
76
|
-
"node-version-use": "
|
|
77
|
-
"os-shim": "
|
|
78
|
-
"queue-cb": "
|
|
79
|
-
"resolve": "
|
|
80
|
-
"short-hash": "
|
|
81
|
-
"ts-dev-stack": "
|
|
82
|
-
"tsds-config": "
|
|
83
|
-
"tsds-lib-test": "
|
|
72
|
+
"@types/semver": "^7.7.1",
|
|
73
|
+
"@types/tar": "^6.1.13",
|
|
74
|
+
"module-link-unlink": "^1.0.9",
|
|
75
|
+
"node-version-use": "^1.9.7",
|
|
76
|
+
"os-shim": "^0.1.3",
|
|
77
|
+
"queue-cb": "^1.6.1",
|
|
78
|
+
"resolve": "^1.22.11",
|
|
79
|
+
"short-hash": "^1.0.0",
|
|
80
|
+
"ts-dev-stack": "^1.21.2",
|
|
81
|
+
"tsds-config": "^0.2.0",
|
|
82
|
+
"tsds-lib-test": "^1.19.4"
|
|
84
83
|
},
|
|
85
84
|
"engines": {
|
|
86
|
-
"node": ">=16"
|
|
85
|
+
"node": ">=16.14.0"
|
|
87
86
|
}
|
|
88
87
|
}
|