diff --git a/packages/core/integration-tests/test/parcel-link.js b/packages/core/integration-tests/test/parcel-link.js index 11dc577f843..e00e51e71c6 100644 --- a/packages/core/integration-tests/test/parcel-link.js +++ b/packages/core/integration-tests/test/parcel-link.js @@ -1,6 +1,5 @@ // @flow strict-local -import type {FileSystem} from '@parcel/fs'; import type {ProgramOptions} from '@parcel/link'; import {createProgram as _createProgram} from '@parcel/link'; @@ -11,7 +10,7 @@ import assert from 'assert'; import path from 'path'; import sinon from 'sinon'; -function createProgram(opts: {|...ProgramOptions, fs: FileSystem|}) { +function createProgram(opts: ProgramOptions) { let program = _createProgram(opts).exitOverride(); function cli(command: string = ''): Promise { diff --git a/packages/dev/parcel-link/src/cli.js b/packages/dev/parcel-link/src/cli.js index 0be98d85e32..a2b37d4dfbb 100644 --- a/packages/dev/parcel-link/src/cli.js +++ b/packages/dev/parcel-link/src/cli.js @@ -1,115 +1,25 @@ // @flow strict-local /* eslint-disable no-console */ -import type {FileSystem} from '@parcel/fs'; +import type {LinkCommandOptions} from './link'; +import type {UnlinkCommandOptions} from './unlink'; // $FlowFixMe[untyped-import] import {version} from '../package.json'; -import {ParcelLinkConfig} from './ParcelLinkConfig'; -import {link as linkAction} from './link'; -import {unlink as unlinkAction} from './unlink'; -import {NodeFS} from '@parcel/fs'; +import {createLinkCommand} from './link'; +import {createUnlinkCommand} from './unlink'; import commander from 'commander'; -import path from 'path'; -export type ProgramOptions = {| - +fs?: FileSystem, - +link?: typeof linkAction, - +unlink?: typeof unlinkAction, -|}; +export type ProgramOptions = {|...LinkCommandOptions, ...UnlinkCommandOptions|}; +// $FlowFixMe[invalid-exported-annotation] export function createProgram(opts?: ProgramOptions): commander.Command { - const { - fs = new NodeFS(), - link = linkAction, - unlink = unlinkAction, - } = opts ?? {}; - - const program = new commander.Command(); - - program + let {fs, log = console.log, link, unlink} = opts ?? {}; + return new commander.Command() .version(version, '-V, --version') .description('A tool for linking a dev copy of Parcel into an app') - .addHelpText('after', `\nThe link command is the default command.`); - - program - .command('link [packageRoot]', {isDefault: true}) - .description('Link a dev copy of Parcel into an app', { - packageRoot: - 'Path to the Parcel package root\nDefaults to the package root containing this package', - }) - .option('-d, --dry-run', 'Do not write any changes') - .option('-n, --namespace ', 'Namespace for packages', '@parcel') - .option( - '-g, --node-modules-glob ', - 'Location where node_modules should be linked in the app.\nCan be repeated with multiple globs.', - (glob, globs) => globs.concat([glob.replace(/["']/g, '')]), - ['node_modules'], - ) - .action(async (packageRoot, options) => { - if (options.dryRun) console.log('Dry run...'); - let appRoot = process.cwd(); - - let parcelLinkConfig; - - try { - parcelLinkConfig = await ParcelLinkConfig.load(appRoot, {fs}); - } catch (e) { - // boop! - } - - if (parcelLinkConfig) { - throw new Error( - 'A Parcel link already exists! Try `parcel-link unlink` to re-link.', - ); - } - - parcelLinkConfig = new ParcelLinkConfig({ - fs, - appRoot, - packageRoot: packageRoot ?? path.join(__dirname, '../../../'), - namespace: options.namespace, - nodeModulesGlobs: options.nodeModulesGlob, - }); - - await link(parcelLinkConfig, {dryRun: options.dryRun, log: console.log}); - - if (!options.dryRun) await parcelLinkConfig.save(); - - console.log('🎉 Linking successful'); - }); - - program - .command('unlink') - .description('Unlink a dev copy of Parcel into an app') - .option('-d, --dry-run', 'Do not write any changes') - .option('-f, --force-install', 'Force a reinstall after unlinking') - .action(async options => { - if (options.dryRun) console.log('Dry run...'); - let appRoot = process.cwd(); - - let parcelLinkConfig; - try { - parcelLinkConfig = await ParcelLinkConfig.load(appRoot, {fs}); - } catch (e) { - // boop! - } - - if (parcelLinkConfig) { - await unlink(parcelLinkConfig, { - dryRun: options.dryRun, - forceInstall: options.forceInstall, - log: console.log, - }); - - if (!options.dryRun) await parcelLinkConfig.delete(); - } else { - throw new Error('A Parcel link could not be found!'); - } - - console.log('🎉 Unlinking successful'); - }); - - return program; + .addHelpText('after', `\nThe link command is the default command.`) + .addCommand(createLinkCommand({fs, log, link}), {isDefault: true}) + .addCommand(createUnlinkCommand({fs, log, unlink})); } diff --git a/packages/dev/parcel-link/src/link.js b/packages/dev/parcel-link/src/link.js index 3c6e7189466..b1fde68c90f 100644 --- a/packages/dev/parcel-link/src/link.js +++ b/packages/dev/parcel-link/src/link.js @@ -1,8 +1,9 @@ // @flow strict-local -import type {ParcelLinkConfig} from './ParcelLinkConfig'; import type {CmdOptions} from './utils'; +import type {FileSystem} from '@parcel/fs'; +import {ParcelLinkConfig} from './ParcelLinkConfig'; import { findParcelPackages, mapNamespacePackageAliases, @@ -14,15 +15,25 @@ import { import nullthrows from 'nullthrows'; import path from 'path'; +import {NodeFS} from '@parcel/fs'; +import commander from 'commander'; export type LinkOptions = {| dryRun?: boolean, log?: (...data: mixed[]) => void, |}; +export type LinkCommandOptions = {| + +link?: typeof link, + +fs?: FileSystem, + +log?: (...data: mixed[]) => void, +|}; + +const NOOP: (...data: mixed[]) => void = () => {}; + export async function link( config: ParcelLinkConfig, - {dryRun = false, log = () => {}}: LinkOptions, + {dryRun = false, log = NOOP}: LinkOptions, ): Promise { config.validate(); @@ -128,3 +139,59 @@ export async function link( } } } + +export function createLinkCommand( + opts?: LinkCommandOptions, + // $FlowFixMe[invalid-exported-annotation] +): commander.Command { + let action = opts?.link ?? link; + let log = opts?.log ?? NOOP; + let fs = opts?.fs ?? new NodeFS(); + + return new commander.Command('link') + .arguments('[packageRoot]') + .description('Link a dev copy of Parcel into an app', { + packageRoot: + 'Path to the Parcel package root\nDefaults to the package root containing this package', + }) + .option('-d, --dry-run', 'Do not write any changes') + .option('-n, --namespace ', 'Namespace for packages', '@parcel') + .option( + '-g, --node-modules-glob ', + 'Location where node_modules should be linked in the app.\nCan be repeated with multiple globs.', + (glob, globs) => globs.concat([glob.replace(/["']/g, '')]), + ['node_modules'], + ) + .action(async (packageRoot, options) => { + if (options.dryRun) log('Dry run...'); + let appRoot = process.cwd(); + + let parcelLinkConfig; + + try { + parcelLinkConfig = await ParcelLinkConfig.load(appRoot, {fs}); + } catch (e) { + // boop! + } + + if (parcelLinkConfig) { + throw new Error( + 'A Parcel link already exists! Try `parcel-link unlink` to re-link.', + ); + } + + parcelLinkConfig = new ParcelLinkConfig({ + fs, + appRoot, + packageRoot: packageRoot ?? path.join(__dirname, '../../../'), + namespace: options.namespace, + nodeModulesGlobs: options.nodeModulesGlob, + }); + + await action(parcelLinkConfig, {dryRun: options.dryRun, log}); + + if (!options.dryRun) await parcelLinkConfig.save(); + + log('🎉 Linking successful'); + }); +} diff --git a/packages/dev/parcel-link/src/unlink.js b/packages/dev/parcel-link/src/unlink.js index 984d7378417..75f79b3837f 100644 --- a/packages/dev/parcel-link/src/unlink.js +++ b/packages/dev/parcel-link/src/unlink.js @@ -1,8 +1,9 @@ // @flow strict-local -import type {ParcelLinkConfig} from './ParcelLinkConfig'; import type {CmdOptions} from './utils'; +import type {FileSystem} from '@parcel/fs'; +import {ParcelLinkConfig} from './ParcelLinkConfig'; import { cleanupBin, cleanupNodeModules, @@ -13,6 +14,8 @@ import { } from './utils'; import path from 'path'; +import {NodeFS} from '@parcel/fs'; +import commander from 'commander'; export type UnlinkOptions = {| dryRun?: boolean, @@ -20,9 +23,17 @@ export type UnlinkOptions = {| log?: (...data: mixed[]) => void, |}; +export type UnlinkCommandOptions = {| + +unlink?: typeof unlink, + +fs?: FileSystem, + +log?: (...data: mixed[]) => void, +|}; + +const NOOP: (...data: mixed[]) => void = () => {}; + export async function unlink( config: ParcelLinkConfig, - {dryRun = false, forceInstall = false, log = () => {}}: UnlinkOptions, + {dryRun = false, forceInstall = false, log = NOOP}: UnlinkOptions, ) { config.validate(); @@ -117,3 +128,42 @@ export async function unlink( log('Run `yarn install --force` (or similar) to restore dependencies'); } } + +export function createUnlinkCommand( + opts?: UnlinkCommandOptions, + // $FlowFixMe[invalid-exported-annotation] +): commander.Command { + let action = opts?.unlink ?? unlink; + let log = opts?.log ?? NOOP; + let fs = opts?.fs ?? new NodeFS(); + + return new commander.Command('unlink') + .description('Unlink a dev copy of Parcel from an app') + .option('-d, --dry-run', 'Do not write any changes') + .option('-f, --force-install', 'Force a reinstall after unlinking') + .action(async options => { + if (options.dryRun) log('Dry run...'); + let appRoot = process.cwd(); + + let parcelLinkConfig; + try { + parcelLinkConfig = await ParcelLinkConfig.load(appRoot, {fs}); + } catch (e) { + // boop! + } + + if (parcelLinkConfig) { + await action(parcelLinkConfig, { + dryRun: options.dryRun, + forceInstall: options.forceInstall, + log, + }); + + if (!options.dryRun) await parcelLinkConfig.delete(); + } else { + throw new Error('A Parcel link could not be found!'); + } + + log('🎉 Unlinking successful'); + }); +}