@@ -5,13 +5,14 @@ import { tmpdir } from "node:os";
55import path from "node:path" ;
66import { fileURLToPath } from "node:url" ;
77import { parse as parseYaml } from "yaml" ;
8+ import { listChangedPathsFromGit , listStagedChangedPaths } from "./changed-lanes.mjs" ;
89
910const ROOT_DIR = path . resolve ( path . dirname ( fileURLToPath ( import . meta. url ) ) , ".." ) ;
1011const EXACT_VERSION_PATTERN = / ^ \d + \. \d + \. \d + (?: [ - + ] [ 0 - 9 A - Z a - z . - ] + ) ? $ / u;
1112
1213function usage ( ) {
1314 return [
14- "Usage: node scripts/generate-npm-shrinkwrap.mjs [--check] [--all|--plugins|--package-dir <dir>]" ,
15+ "Usage: node scripts/generate-npm-shrinkwrap.mjs [--check] [--all|--plugins|--changed|-- package-dir <dir>] [--base <ref>] [--head <ref>] [--staged ]" ,
1516 " default: root package only" ,
1617 ] . join ( "\n" ) ;
1718}
@@ -81,11 +82,10 @@ function readPnpmLockPackages() {
8182 ) ;
8283}
8384
84- function readPnpmLockSingleVersionOverrides ( ) {
85- const lockfile = parseYaml ( readFileSync ( path . join ( ROOT_DIR , "pnpm-lock.yaml" ) , "utf8" ) ) ;
85+ function collectPnpmLockPackageVersions ( lockfile ) {
8686 const packages = lockfile ?. packages ;
8787 if ( ! packages || typeof packages !== "object" || Array . isArray ( packages ) ) {
88- throw new Error ( "pnpm-lock.yaml is missing package resolution data." ) ;
88+ return new Map ( ) ;
8989 }
9090 const versionsByName = new Map ( ) ;
9191 for ( const packageKey of Object . keys ( packages ) ) {
@@ -97,6 +97,15 @@ function readPnpmLockSingleVersionOverrides() {
9797 versions . add ( parsed . version ) ;
9898 versionsByName . set ( parsed . name , versions ) ;
9999 }
100+ return versionsByName ;
101+ }
102+
103+ function readPnpmLockSingleVersionOverrides ( ) {
104+ const lockfile = parseYaml ( readFileSync ( path . join ( ROOT_DIR , "pnpm-lock.yaml" ) , "utf8" ) ) ;
105+ const versionsByName = collectPnpmLockPackageVersions ( lockfile ) ;
106+ if ( versionsByName . size === 0 ) {
107+ throw new Error ( "pnpm-lock.yaml is missing package resolution data." ) ;
108+ }
100109 return Object . fromEntries (
101110 [ ...versionsByName . entries ( ) ]
102111 . filter ( ( [ , versions ] ) => versions . size === 1 )
@@ -105,6 +114,10 @@ function readPnpmLockSingleVersionOverrides() {
105114 ) ;
106115}
107116
117+ function setKey ( values ) {
118+ return [ ...values ] . toSorted ( ( left , right ) => left . localeCompare ( right ) ) . join ( "\0" ) ;
119+ }
120+
108121function mergeOverrides ( packageOverrides , workspaceOverrides , pnpmLockOverrides ) {
109122 const merged = normalizeOverrides ( packageOverrides ) ;
110123 for ( const [ name , spec ] of [
@@ -113,9 +126,7 @@ function mergeOverrides(packageOverrides, workspaceOverrides, pnpmLockOverrides)
113126 ] ) {
114127 const current = merged [ name ] ;
115128 if ( current !== undefined && JSON . stringify ( current ) !== JSON . stringify ( spec ) ) {
116- throw new Error (
117- `package.json overrides.${ name } conflicts with pnpm lock policy for ${ name } ` ,
118- ) ;
129+ throw new Error ( `package.json overrides.${ name } conflicts with pnpm lock policy for ${ name } ` ) ;
119130 }
120131 merged [ name ] = spec ;
121132 }
@@ -394,21 +405,86 @@ function listPublishablePluginPackageDirs() {
394405 . toSorted ( ( left , right ) => left . localeCompare ( right ) ) ;
395406}
396407
408+ function shrinkwrapPackageDirsForChangedPaths ( changedPaths ) {
409+ const packageDirs = new Set ( ) ;
410+ const publishablePluginPackageDirs = new Set ( listPublishablePluginPackageDirs ( ) ) ;
411+ let hasAmbiguousDependencyPolicyChange = false ;
412+ let hasLockfileChange = false ;
413+
414+ for ( const rawPath of changedPaths ) {
415+ const changedPath = String ( rawPath ?? "" )
416+ . trim ( )
417+ . replaceAll ( "\\" , "/" )
418+ . replace ( / ^ \. \/ + / u, "" ) ;
419+ if ( ! changedPath ) {
420+ continue ;
421+ }
422+ if ( changedPath === "package.json" || changedPath === "npm-shrinkwrap.json" ) {
423+ packageDirs . add ( ROOT_DIR ) ;
424+ continue ;
425+ }
426+ const extensionMatch = changedPath . match (
427+ / ^ ( e x t e n s i o n s \/ [ ^ / ] + ) \/ (?: p a c k a g e \. j s o n | n p m - s h r i n k w r a p \. j s o n ) $ / u,
428+ ) ;
429+ if ( extensionMatch && publishablePluginPackageDirs . has ( extensionMatch [ 1 ] ) ) {
430+ packageDirs . add ( path . resolve ( ROOT_DIR , extensionMatch [ 1 ] ) ) ;
431+ continue ;
432+ }
433+ if ( changedPath === "pnpm-lock.yaml" ) {
434+ hasLockfileChange = true ;
435+ continue ;
436+ }
437+ if (
438+ changedPath === "pnpm-workspace.yaml" ||
439+ changedPath === "scripts/generate-npm-shrinkwrap.mjs"
440+ ) {
441+ hasAmbiguousDependencyPolicyChange = true ;
442+ }
443+ }
444+
445+ if ( hasAmbiguousDependencyPolicyChange ) {
446+ return [
447+ ROOT_DIR ,
448+ ...listPublishablePluginPackageDirs ( ) . map ( ( dir ) => path . resolve ( ROOT_DIR , dir ) ) ,
449+ ] ;
450+ }
451+
452+ if ( hasLockfileChange ) {
453+ return [
454+ ROOT_DIR ,
455+ ...listPublishablePluginPackageDirs ( ) . map ( ( dir ) => path . resolve ( ROOT_DIR , dir ) ) ,
456+ ] ;
457+ }
458+ return [ ...packageDirs ] . toSorted ( ( left , right ) =>
459+ packageLabel ( left ) . localeCompare ( packageLabel ( right ) ) ,
460+ ) ;
461+ }
462+
397463function resolvePackageDirs ( args ) {
398464 const packageDirs = [ ] ;
399465 const check = args . includes ( "--check" ) ;
400466 const all = args . includes ( "--all" ) ;
401467 const plugins = args . includes ( "--plugins" ) ;
468+ const changed = args . includes ( "--changed" ) ;
469+ const staged = args . includes ( "--staged" ) ;
402470 const packageDirIndex = args . indexOf ( "--package-dir" ) ;
403- if ( packageDirIndex !== - 1 && ( all || plugins ) ) {
404- throw new Error ( "--package-dir cannot be combined with --all or --plugins." ) ;
471+ const baseIndex = args . indexOf ( "--base" ) ;
472+ const headIndex = args . indexOf ( "--head" ) ;
473+ if ( packageDirIndex !== - 1 && ( all || plugins || changed ) ) {
474+ throw new Error ( "--package-dir cannot be combined with --all, --plugins, or --changed." ) ;
405475 }
406- if ( all && plugins ) {
407- throw new Error ( "--all cannot be combined with --plugins ." ) ;
476+ if ( [ all , plugins , changed ] . filter ( Boolean ) . length > 1 ) {
477+ throw new Error ( "--all, --plugins, and --changed cannot be combined." ) ;
408478 }
409479 for ( let index = 0 ; index < args . length ; index += 1 ) {
410480 const arg = args [ index ] ;
411- if ( arg === "--check" || arg === "--all" || arg === "--plugins" ) {
481+ if (
482+ arg === "--check" ||
483+ arg === "--all" ||
484+ arg === "--plugins" ||
485+ arg === "--changed" ||
486+ arg === "--staged"
487+ ) {
412488 continue ;
413489 }
414490 if ( arg === "--package-dir" ) {
@@ -420,9 +496,21 @@ function resolvePackageDirs(args) {
420496 index += 1 ;
421497 continue ;
422498 }
499+ if ( arg === "--base" || arg === "--head" ) {
500+ const value = args [ index + 1 ] ;
501+ if ( ! value || value . startsWith ( "--" ) ) {
502+ throw new Error ( `${ arg } requires a git ref.` ) ;
503+ }
504+ index += 1 ;
505+ continue ;
506+ }
423507 throw new Error ( usage ( ) ) ;
424508 }
425509
510+ if ( ! changed && ( baseIndex !== - 1 || headIndex !== - 1 || staged ) ) {
511+ throw new Error ( "--base, --head, and --staged require --changed." ) ;
512+ }
513+
426514 if ( all ) {
427515 return {
428516 check,
@@ -438,6 +526,20 @@ function resolvePackageDirs(args) {
438526 packageDirs : listPublishablePluginPackageDirs ( ) . map ( ( dir ) => path . resolve ( ROOT_DIR , dir ) ) ,
439527 } ;
440528 }
529+ if ( changed ) {
530+ const base = baseIndex === - 1 ? "origin/main" : args [ baseIndex + 1 ] ;
531+ const head = headIndex === - 1 ? "HEAD" : args [ headIndex + 1 ] ;
532+ const changedPaths = staged
533+ ? listStagedChangedPaths ( )
534+ : listChangedPathsFromGit ( {
535+ base,
536+ head,
537+ } ) ;
538+ return {
539+ check,
540+ packageDirs : shrinkwrapPackageDirsForChangedPaths ( changedPaths ) ,
541+ } ;
542+ }
441543 return { check, packageDirs : packageDirs . length > 0 ? packageDirs : [ ROOT_DIR ] } ;
442544}
443545
@@ -469,6 +571,10 @@ function updateOrCheckPackage(packageDir, check) {
469571
470572function main ( ) {
471573 const { check, packageDirs } = resolvePackageDirs ( process . argv . slice ( 2 ) ) ;
574+ if ( packageDirs . length === 0 ) {
575+ process . stdout . write ( "No shrinkwrap-managed package changes detected.\n" ) ;
576+ return ;
577+ }
472578 for ( const packageDir of packageDirs ) {
473579 updateOrCheckPackage ( packageDir , check ) ;
474580 }
@@ -492,4 +598,5 @@ export {
492598 normalizeNpmVersionDrift ,
493599 parsePnpmPackageKey ,
494600 parseLockPackagePath ,
601+ shrinkwrapPackageDirsForChangedPaths ,
495602} ;
0 commit comments