@@ -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,46 @@ 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 = maybeWorkspaces . values ( ) . map ( ( wsDir ) => {
301+ return path . relative ( opts . cwd , wsDir ) ;
302+ } ) . toArray ( ) ;
303+ }
304+ } else {
305+ // We are in a workspace context, don't ask about
306+ // workspaces and set the workspaceRoot
307+ options . overrides ( {
308+ workspaces : null ,
309+ workspaceRoot : rootDir ,
310+ workspaceRootPkg : rootPkg
311+ } ) ;
312+ }
313+ }
314+
258315 let author ;
259316 if ( ! pkg || ! pkg . author ) {
260317 const gitAuthor = await git . author ( { cwd : opts . cwd } ) ;
@@ -285,7 +342,8 @@ async function readPackageJson (options, { log } = {}) {
285342 repository : repo ,
286343 keywords : pkg . keywords ,
287344 scripts : pkg . scripts ,
288- license : pkg . license
345+ license : pkg . license ,
346+ workspaces : pkg . workspaces
289347 } ) ;
290348
291349 return packageInstance . update ( pkg ) ;
@@ -364,26 +422,79 @@ module.exports.write = write;
364422// TODO: look at https://npm.im/json-file-plus for writing
365423async function write ( opts , pkg , { log } = { } ) {
366424 const pkgPath = path . resolve ( opts . cwd , 'package.json' ) ;
425+
426+ // Ensure directory exists
427+ await fs . mkdirp ( path . dirname ( pkgPath ) ) ;
428+
367429 // Write package json
368430 log . info ( `Writing package.json\n${ pkgPath } ` ) ;
369431 await pkg . save ( ) ;
370432
433+ let shouldRunFinalInstall = false ;
434+
435+ // If we dont have workspaceRootPkg then we are
436+ // already working on the root workspace package.json
437+ // which means we don't need to do anything with updating
438+ // the monorepo
439+ let workspaceRelativePath = null ;
440+ if ( opts . workspaceRoot && opts . workspaceRootPkg ) {
441+ workspaceRelativePath = path . relative ( opts . workspaceRoot , opts . cwd ) ;
442+
443+ // Check if this wis already part of the workspace
444+ const ws = await mapWorkspaces ( {
445+ pkg : opts . workspaceRootPkg ,
446+ cwd : opts . workspaceRoot
447+ } ) ;
448+ if ( Array . from ( ws . values ( ) ) . includes ( opts . cwd ) ) {
449+ log . debug ( 'New package already exists in workspaces' ) ;
450+ } else {
451+ log . info ( 'Adding new package to workspace root package.json' ) ;
452+ const rootPkg = await load ( opts . workspaceRoot ) ;
453+ rootPkg . update ( {
454+ workspaces : [ ...opts . workspaceRootPkg . workspaces , workspaceRelativePath ]
455+ } ) ;
456+ await rootPkg . save ( ) ;
457+ }
458+
459+ // We should run a final install for a new package
460+ // in a monorepo even if it already matches the glob
461+ shouldRunFinalInstall = true ;
462+ }
463+
371464 // Run installs
372465 if ( opts . dependencies && opts . dependencies . length ) {
373466 log . info ( 'Installing dependencies' , opts . dependencies ) ;
374467 await npm . install ( opts . dependencies , {
375468 save : 'prod' ,
376- directory : opts . cwd ,
377- exact : ! ! opts . saveExact
469+ directory : opts . workspaceRoot || opts . cwd ,
470+ exact : ! ! opts . saveExact ,
471+ workspace : workspaceRelativePath
378472 } ) ;
473+
474+ // If we run either of these installs we can skip the final one
475+ shouldRunFinalInstall = false ;
379476 }
380477 if ( opts . devDependencies && opts . devDependencies . length ) {
381478 log . info ( 'Installing dev dependencies' , opts . devDependencies ) ;
382479 await npm . install ( opts . devDependencies , {
383480 save : 'dev' ,
384- directory : opts . cwd ,
385- exact : ! ! opts . saveExact
481+ directory : opts . workspaceRoot || opts . cwd ,
482+ exact : ! ! opts . saveExact ,
483+ workspace : workspaceRelativePath
484+ } ) ;
485+
486+ // If we run either of these installs we can skip the final one
487+ shouldRunFinalInstall = false ;
488+ }
489+
490+ if ( shouldRunFinalInstall ) {
491+ log . info ( 'Running final install' ) ;
492+ await npm . install ( null , {
493+ directory : opts . workspaceRoot || opts . cwd
386494 } ) ;
495+
496+ // If we run either of these installs we can skip the final one
497+ shouldRunFinalInstall = false ;
387498 }
388499
389500 // Read full package back to return
0 commit comments