@@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url';
77
88const scriptDir = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
99const repoRoot = path . resolve ( scriptDir , '..' , '..' ) ;
10+ const baseRef = process . env . TEMPLATE_LOCAL_PACKAGE_BASE_REF ?. trim ( ) ;
1011const templateDirArgs = process . argv . slice ( 2 ) ;
1112
1213if ( templateDirArgs . includes ( '-h' ) || templateDirArgs . includes ( '--help' ) ) {
@@ -47,20 +48,28 @@ const templateConfigs = await Promise.all(
4748 } ;
4849 } )
4950) ;
50- const packagesToPrepare = new Map ( ) ;
5151
5252for ( const templateConfig of templateConfigs ) {
53- for ( const dependencyName of templateConfig . localDependencies ) {
54- const workspacePackage = workspacePackages . get ( dependencyName ) ;
55-
56- if ( ! workspacePackage ) continue ;
57-
58- packagesToPrepare . set ( dependencyName , workspacePackage ) ;
59- }
53+ templateConfig . relevantPackageNames = getReachableWorkspacePackageNames (
54+ templateConfig . localDependencies ,
55+ workspacePackages
56+ ) ;
6057}
6158
59+ const packagesToPrepare = getPackagesToPrepare ( {
60+ baseRef,
61+ templateConfigs,
62+ workspacePackages,
63+ } ) ;
64+
6265if ( packagesToPrepare . size === 0 ) {
63- console . log ( 'No local workspace packages referenced by templates.' ) ;
66+ if ( baseRef ) {
67+ console . log (
68+ `No affected local workspace packages referenced by templates for ${ baseRef } ...HEAD.`
69+ ) ;
70+ } else {
71+ console . log ( 'No local workspace packages referenced by templates.' ) ;
72+ }
6473 process . exit ( 0 ) ;
6574}
6675
@@ -103,13 +112,74 @@ function buildWorkspacePackages(workspacePackagesToBuild) {
103112 }
104113}
105114
106- function getLocalDependencies ( packageJson ) {
107- const dependencyNames = new Set ( [
108- ...Object . keys ( packageJson . dependencies ?? { } ) ,
109- ...Object . keys ( packageJson . devDependencies ?? { } ) ,
110- ] ) ;
115+ function getPackagesToPrepare ( { baseRef, templateConfigs, workspacePackages } ) {
116+ const packagesToPrepare = new Map ( ) ;
117+ const dependencyNames = new Set (
118+ templateConfigs . flatMap (
119+ ( templateConfig ) => templateConfig . localDependencies
120+ )
121+ ) ;
122+
123+ if ( ! baseRef ) {
124+ for ( const dependencyName of dependencyNames ) {
125+ const workspacePackage = workspacePackages . get ( dependencyName ) ;
126+
127+ if ( ! workspacePackage ) continue ;
128+
129+ packagesToPrepare . set ( dependencyName , workspacePackage ) ;
130+ }
131+
132+ return packagesToPrepare ;
133+ }
134+
135+ const changedPackageNames = getChangedWorkspacePackageNames (
136+ baseRef ,
137+ workspacePackages
138+ ) ;
139+
140+ if ( changedPackageNames . size === 0 ) {
141+ return packagesToPrepare ;
142+ }
143+
144+ const relevantPackageNames = new Set (
145+ templateConfigs . flatMap ( ( templateConfig ) => [
146+ ...templateConfig . relevantPackageNames ,
147+ ] )
148+ ) ;
149+ const selectedPackageNames = [ ...changedPackageNames ] . filter ( ( packageName ) =>
150+ relevantPackageNames . has ( packageName )
151+ ) ;
152+
153+ if ( selectedPackageNames . length === 0 ) {
154+ return packagesToPrepare ;
155+ }
156+
157+ console . log (
158+ `Changed workspace packages for ${ baseRef } ...HEAD: ${ [
159+ ...changedPackageNames ,
160+ ]
161+ . sort ( )
162+ . join ( ', ' ) } `
163+ ) ;
164+ console . log (
165+ `Selected workspace packages for templates: ${ selectedPackageNames
166+ . toSorted ( )
167+ . join ( ', ' ) } `
168+ ) ;
169+
170+ for ( const packageName of selectedPackageNames ) {
171+ const workspacePackage = workspacePackages . get ( packageName ) ;
172+
173+ if ( ! workspacePackage ) continue ;
111174
112- return [ ...dependencyNames ] . filter ( ( dependencyName ) =>
175+ packagesToPrepare . set ( packageName , workspacePackage ) ;
176+ }
177+
178+ return packagesToPrepare ;
179+ }
180+
181+ function getLocalDependencies ( packageJson ) {
182+ return getDependencyNames ( packageJson ) . filter ( ( dependencyName ) =>
113183 workspacePackages . has ( dependencyName )
114184 ) ;
115185}
@@ -130,10 +200,18 @@ async function getWorkspacePackages() {
130200
131201 workspacePackagesByName . set ( packageJson . name , {
132202 directory : directoryEntry ,
203+ packageJson,
204+ relativeDirectory : toPosixPath ( path . relative ( repoRoot , directoryEntry ) ) ,
133205 } ) ;
134206 }
135207 }
136208
209+ for ( const workspacePackage of workspacePackagesByName . values ( ) ) {
210+ workspacePackage . localDependencyNames = getDependencyNames (
211+ workspacePackage . packageJson
212+ ) . filter ( ( dependencyName ) => workspacePackagesByName . has ( dependencyName ) ) ;
213+ }
214+
137215 return workspacePackagesByName ;
138216}
139217
@@ -157,6 +235,66 @@ async function listDirectories(parentDirectory) {
157235 return directories ;
158236}
159237
238+ function getChangedWorkspacePackageNames ( baseRef , workspacePackages ) {
239+ const result = spawnSync (
240+ 'git' ,
241+ [ 'diff' , '--name-only' , `${ baseRef } ...HEAD` ] ,
242+ {
243+ cwd : repoRoot ,
244+ encoding : 'utf8' ,
245+ }
246+ ) ;
247+
248+ if ( result . status !== 0 ) {
249+ console . error (
250+ `Failed to determine changed workspace packages for ${ baseRef } ...HEAD`
251+ ) ;
252+ process . exit ( result . status ?? 1 ) ;
253+ }
254+
255+ const changedFiles = result . stdout
256+ . split ( '\n' )
257+ . map ( ( filePath ) => filePath . trim ( ) )
258+ . filter ( Boolean ) ;
259+ const changedPackageNames = new Set ( ) ;
260+
261+ for ( const changedFile of changedFiles ) {
262+ for ( const [ packageName , workspacePackage ] of workspacePackages ) {
263+ const packagePrefix = `${ workspacePackage . relativeDirectory } /` ;
264+
265+ if ( changedFile . startsWith ( packagePrefix ) ) {
266+ changedPackageNames . add ( packageName ) ;
267+ }
268+ }
269+ }
270+
271+ return changedPackageNames ;
272+ }
273+
274+ function getReachableWorkspacePackageNames (
275+ initialPackageNames ,
276+ workspacePackages
277+ ) {
278+ const relevantPackageNames = new Set ( ) ;
279+ const pendingPackageNames = [ ...initialPackageNames ] ;
280+
281+ while ( pendingPackageNames . length > 0 ) {
282+ const packageName = pendingPackageNames . shift ( ) ;
283+
284+ if ( ! packageName || relevantPackageNames . has ( packageName ) ) continue ;
285+
286+ relevantPackageNames . add ( packageName ) ;
287+
288+ const workspacePackage = workspacePackages . get ( packageName ) ;
289+
290+ if ( ! workspacePackage ) continue ;
291+
292+ pendingPackageNames . push ( ...workspacePackage . localDependencyNames ) ;
293+ }
294+
295+ return relevantPackageNames ;
296+ }
297+
160298function packWorkspacePackage ( packageDirectory ) {
161299 const result = spawnSync (
162300 'pnpm' ,
@@ -182,9 +320,22 @@ function packWorkspacePackage(packageDirectory) {
182320 return tarballPath ;
183321}
184322
323+ function getDependencyNames ( packageJson ) {
324+ const dependencyNames = new Set ( [
325+ ...Object . keys ( packageJson . dependencies ?? { } ) ,
326+ ...Object . keys ( packageJson . devDependencies ?? { } ) ,
327+ ...Object . keys ( packageJson . optionalDependencies ?? { } ) ,
328+ ...Object . keys ( packageJson . peerDependencies ?? { } ) ,
329+ ] ) ;
330+
331+ return [ ...dependencyNames ] ;
332+ }
333+
185334function rewriteTemplatePackageJson ( templateConfig , tarballsByPackageName ) {
186- const { packageJson, packageJsonPath, templateDir } = templateConfig ;
335+ const { packageJson, packageJsonPath, relevantPackageNames, templateDir } =
336+ templateConfig ;
187337 const dependencySections = [ 'dependencies' , 'devDependencies' ] ;
338+ const rewrittenPackageNames = new Set ( ) ;
188339
189340 for ( const section of dependencySections ) {
190341 const dependencies = packageJson [ section ] ;
@@ -203,6 +354,32 @@ function rewriteTemplatePackageJson(templateConfig, tarballsByPackageName) {
203354 }
204355
205356 dependencies [ dependencyName ] = `file:${ toPosixPath ( relativeTarballPath ) } ` ;
357+ rewrittenPackageNames . add ( dependencyName ) ;
358+ }
359+ }
360+
361+ const missingDependencyNames = [ ...tarballsByPackageName . keys ( ) ] . filter (
362+ ( dependencyName ) =>
363+ relevantPackageNames . has ( dependencyName ) &&
364+ ! rewrittenPackageNames . has ( dependencyName )
365+ ) ;
366+
367+ if ( missingDependencyNames . length > 0 ) {
368+ packageJson . devDependencies ??= { } ;
369+
370+ for ( const dependencyName of missingDependencyNames . toSorted ( ) ) {
371+ const tarballPath = tarballsByPackageName . get ( dependencyName ) ;
372+
373+ if ( ! tarballPath ) continue ;
374+
375+ let relativeTarballPath = path . relative ( templateDir , tarballPath ) ;
376+
377+ if ( ! relativeTarballPath . startsWith ( '.' ) ) {
378+ relativeTarballPath = `./${ relativeTarballPath } ` ;
379+ }
380+
381+ packageJson . devDependencies [ dependencyName ] =
382+ `file:${ toPosixPath ( relativeTarballPath ) } ` ;
206383 }
207384 }
208385
0 commit comments