@@ -4,6 +4,7 @@ const fs = require('fs-extra');
44const opta = require ( 'opta' ) ;
55const parseList = require ( 'safe-parse-list' ) ;
66const { create, load } = require ( '@npmcli/package-json' ) ;
7+ const mapWorkspaces = require ( '@npmcli/map-workspaces' ) ;
78const { Loggerr } = require ( 'loggerr' ) ;
89const packageName = require ( './lib/package-name' ) ;
910const git = require ( './lib/git' ) ;
@@ -123,6 +124,22 @@ function initOpts () {
123124 }
124125 } ,
125126
127+ workspaceRoot : {
128+ type : 'string' ,
129+ flag : {
130+ key : 'workspace-root'
131+ } ,
132+ prompt : {
133+ message : 'Workspace Root:' ,
134+ when : ( promptInput , defaultWhen , allInput ) => {
135+ if ( allInput . workspaceRoot !== allInput . cwd ) {
136+ return true ;
137+ }
138+ return defaultWhen ;
139+ }
140+ }
141+ } ,
142+
126143 type : {
127144 type : 'string' ,
128145 prompt : {
@@ -255,6 +272,47 @@ async function readPackageJson (options, { log } = {}) {
255272 log . error ( e ) ;
256273 }
257274
275+ // Check to see if we are in a monorepo context
276+ if ( ! pkg . workspaces ) {
277+ let rootPkg ;
278+ let rootDir = opts . workspaceRoot ;
279+ if ( ! rootDir ) {
280+ const [ _rootPkg , _rootDir ] = await npm . findWorkspaceRoot ( opts . cwd ) ;
281+ rootPkg = _rootPkg ;
282+ rootDir = _rootDir ;
283+ } else {
284+ try {
285+ rootPkg = await npm . readPkg ( rootDir ) ;
286+ } catch ( e ) {
287+ if ( e . code !== 'ENOENT' ) {
288+ throw e ;
289+ }
290+ // ignore
291+ }
292+ }
293+
294+ // If we are *not* inside what looks like an existing monorepo setup,
295+ // check what packages may show up and suggest them as defaults
296+ // for the `workspaces` key
297+ if ( rootDir === opts . cwd ) {
298+ const maybeWorkspaces = await npm . searchForWorkspaces ( opts . cwd , pkg ) ;
299+ if ( maybeWorkspaces . size ) {
300+ pkg . workspaces = [ ] ;
301+ for ( const wsDir of maybeWorkspaces . values ( ) ) {
302+ pkg . workspaces . push ( path . relative ( opts . cwd , wsDir ) ) ;
303+ }
304+ }
305+ } else {
306+ // We are in a workspace context, don't ask about
307+ // workspaces and set the workspaceRoot
308+ options . overrides ( {
309+ workspaces : null ,
310+ workspaceRoot : rootDir ,
311+ workspaceRootPkg : rootPkg
312+ } ) ;
313+ }
314+ }
315+
258316 let author ;
259317 if ( ! pkg || ! pkg . author ) {
260318 const gitAuthor = await git . author ( { cwd : opts . cwd } ) ;
@@ -285,7 +343,8 @@ async function readPackageJson (options, { log } = {}) {
285343 repository : repo ,
286344 keywords : pkg . keywords ,
287345 scripts : pkg . scripts ,
288- license : pkg . license
346+ license : pkg . license ,
347+ workspaces : pkg . workspaces
289348 } ) ;
290349
291350 return packageInstance . update ( pkg ) ;
@@ -364,26 +423,79 @@ module.exports.write = write;
364423// TODO: look at https://npm.im/json-file-plus for writing
365424async function write ( opts , pkg , { log } = { } ) {
366425 const pkgPath = path . resolve ( opts . cwd , 'package.json' ) ;
426+
427+ // Ensure directory exists
428+ await fs . mkdirp ( path . dirname ( pkgPath ) ) ;
429+
367430 // Write package json
368431 log . info ( `Writing package.json\n${ pkgPath } ` ) ;
369432 await pkg . save ( ) ;
370433
434+ let shouldRunFinalInstall = false ;
435+
436+ // If we dont have workspaceRootPkg then we are
437+ // already working on the root workspace package.json
438+ // which means we don't need to do anything with updating
439+ // the monorepo
440+ let workspaceRelativePath = null ;
441+ if ( opts . workspaceRoot && opts . workspaceRootPkg ) {
442+ workspaceRelativePath = path . relative ( opts . workspaceRoot , opts . cwd ) ;
443+
444+ // Check if this wis already part of the workspace
445+ const ws = await mapWorkspaces ( {
446+ pkg : opts . workspaceRootPkg ,
447+ cwd : opts . workspaceRoot
448+ } ) ;
449+ if ( Array . from ( ws . values ( ) ) . includes ( opts . cwd ) ) {
450+ log . debug ( 'New package already exists in workspaces' ) ;
451+ } else {
452+ log . info ( 'Adding new package to workspace root package.json' ) ;
453+ const rootPkg = await load ( opts . workspaceRoot ) ;
454+ rootPkg . update ( {
455+ workspaces : [ ...opts . workspaceRootPkg . workspaces , workspaceRelativePath ]
456+ } ) ;
457+ await rootPkg . save ( ) ;
458+ }
459+
460+ // We should run a final install for a new package
461+ // in a monorepo even if it already matches the glob
462+ shouldRunFinalInstall = true ;
463+ }
464+
371465 // Run installs
372466 if ( opts . dependencies && opts . dependencies . length ) {
373467 log . info ( 'Installing dependencies' , opts . dependencies ) ;
374468 await npm . install ( opts . dependencies , {
375469 save : 'prod' ,
376- directory : opts . cwd ,
377- exact : ! ! opts . saveExact
470+ directory : opts . workspaceRoot || opts . cwd ,
471+ exact : ! ! opts . saveExact ,
472+ workspace : workspaceRelativePath
378473 } ) ;
474+
475+ // If we run either of these installs we can skip the final one
476+ shouldRunFinalInstall = false ;
379477 }
380478 if ( opts . devDependencies && opts . devDependencies . length ) {
381479 log . info ( 'Installing dev dependencies' , opts . devDependencies ) ;
382480 await npm . install ( opts . devDependencies , {
383481 save : 'dev' ,
384- directory : opts . cwd ,
385- exact : ! ! opts . saveExact
482+ directory : opts . workspaceRoot || opts . cwd ,
483+ exact : ! ! opts . saveExact ,
484+ workspace : workspaceRelativePath
485+ } ) ;
486+
487+ // If we run either of these installs we can skip the final one
488+ shouldRunFinalInstall = false ;
489+ }
490+
491+ if ( shouldRunFinalInstall ) {
492+ log . info ( 'Running final install' ) ;
493+ await npm . install ( null , {
494+ directory : opts . workspaceRoot || opts . cwd
386495 } ) ;
496+
497+ // If we run either of these installs we can skip the final one
498+ shouldRunFinalInstall = false ;
387499 }
388500
389501 // Read full package back to return
0 commit comments