diff --git a/.prettierrc b/.prettierrc
index f13c4e0c..0c2cb34d 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,6 +1,6 @@
{
"arrowParens": "always",
- "printWidth": 80,
+ "printWidth": 120,
"proseWrap": "always",
"singleQuote": true,
"trailingComma": "all"
diff --git a/README.md b/README.md
index 05998ebf..e36df586 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ Ensure that multiple packages requiring the same dependency define the same vers
-p, --prod include dependencies
-d, --dev include devDependencies
-P, --peer include peerDependencies
+ -f, --filter [pattern] regex for dependency filter
-i, --indent [value] override indentation. defaults to " "
-h, --help output usage information
@@ -54,11 +55,11 @@ syncpack fix-mismatches
syncpack fix-mismatches --source "apps/*/package.json"
# multiple globs can be provided like this
syncpack fix-mismatches --source "apps/*/package.json" --source "core/*/package.json"
-# uses packages that pass the regex defined by --filter when provided
-syncpack fix-mismatches --filter "^package_name$"
-# only fix "devDependencies"
+# uses dependencies regular expression defined by --filter when provided
+syncpack fix-mismatches --filter "typescript|tslint"
+# only inspect "devDependencies"
syncpack fix-mismatches --dev
-# only fix "devDependencies" and "peerDependencies"
+# only inspect "devDependencies" and "peerDependencies"
syncpack fix-mismatches --dev --peer
# indent package.json with 4 spaces instead of 2
syncpack fix-mismatches --indent " "
@@ -106,6 +107,7 @@ List all dependencies required by your packages.
-p, --prod include dependencies
-d, --dev include devDependencies
-P, --peer include peerDependencies
+ -f, --filter [pattern] regex for dependency filter
-h, --help output usage information
@@ -120,6 +122,8 @@ syncpack list
syncpack list --source "apps/*/package.json"
# multiple globs can be provided like this
syncpack list --source "apps/*/package.json" --source "core/*/package.json"
+# uses dependencies regular expression defined by --filter when provided
+syncpack list --filter "typescript|tslint"
# only inspect "devDependencies"
syncpack list --dev
# only inspect "devDependencies" and "peerDependencies"
@@ -139,6 +143,7 @@ List dependencies which are required by multiple packages, where the version is
-p, --prod include dependencies
-d, --dev include devDependencies
-P, --peer include peerDependencies
+ -f, --filter [pattern] regex for dependency filter
-h, --help output usage information
@@ -153,9 +158,11 @@ syncpack list-mismatches
syncpack list-mismatches --source "apps/*/package.json"
# multiple globs can be provided like this
syncpack list-mismatches --source "apps/*/package.json" --source "core/*/package.json"
-# only list "devDependencies"
+# uses dependencies regular expression defined by --filter when provided
+syncpack list-mismatches --filter "typescript|tslint"
+# only inspect "devDependencies"
syncpack list-mismatches --dev
-# only list "devDependencies" and "peerDependencies"
+# only inspect "devDependencies" and "peerDependencies"
syncpack list-mismatches --dev --peer
```
@@ -168,12 +175,13 @@ Ensure dependency versions used within `"dependencies"`, `"devDependencies"`, an
Options
- -r, --semver-range <, <=, "", ~, ^, >=, >, or *. defaults to ""
-s, --source [pattern] glob pattern for package.json files to read from
-p, --prod include dependencies
-d, --dev include devDependencies
-P, --peer include peerDependencies
+ -f, --filter [pattern] regex for dependency filter
-i, --indent [value] override indentation. defaults to " "
+ -r, --semver-range see supported ranges below. defaults to ""
-h, --help output usage information
@@ -188,6 +196,8 @@ syncpack set-semver-ranges
syncpack set-semver-ranges --source "apps/*/package.json"
# multiple globs can be provided like this
syncpack set-semver-ranges --source "apps/*/package.json" --source "core/*/package.json"
+# uses dependencies regular expression defined by --filter when provided
+syncpack set-semver-ranges --filter "typescript|tslint"
# use ~ range instead of default ""
syncpack set-semver-ranges --semver-range ~
# set ~ range in "devDependencies"
diff --git a/global.d.ts b/global.d.ts
deleted file mode 100644
index c5ba6121..00000000
--- a/global.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-import 'expect-more-jest';
diff --git a/jest.config.js b/jest.config.js
index ddddf20a..a4df4962 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,19 +1,18 @@
module.exports = {
collectCoverage: true,
- collectCoverageFrom: ['src/**/*.ts', '!src/**/typings.ts', '!src/**/bin*.ts'],
+ collectCoverageFrom: ['src/**/*.ts', '!src/**/bin*.ts'],
coverageReporters: ['html', 'lcov'],
coverageThreshold: {
global: {
- branches: 95,
- functions: 95,
- lines: 95,
- statements: 95
- }
+ branches: 82,
+ functions: 72,
+ lines: 81,
+ statements: 77,
+ },
},
moduleFileExtensions: ['ts', 'tsx', 'js'],
- setupFilesAfterEnv: ['/test/setup.ts'],
testMatch: ['/src/**/*.spec.(ts|tsx|js)'],
transform: {
- '^.+\\.tsx?$': 'ts-jest'
- }
+ '^.+\\.tsx?$': 'ts-jest',
+ },
};
diff --git a/src/bin-fix-mismatches.ts b/src/bin-fix-mismatches.ts
index d9100de9..e512b0d2 100644
--- a/src/bin-fix-mismatches.ts
+++ b/src/bin-fix-mismatches.ts
@@ -2,62 +2,61 @@
import chalk from 'chalk';
import program = require('commander');
-import { run } from './fix-mismatches';
+import { fixMismatchesToDisk } from './commands/fix-mismatches';
+import { option } from './constants';
program.description(
`
Ensure that multiple packages requiring the same dependency define the same
version, so that every package requires eg. react@16.4.2, instead of a
- combination of react@16.4.2, react@0.15.9, and react@16.0.0.`.replace(
- /^\n/,
- '',
- ),
+ combination of react@16.4.2, react@0.15.9, and react@16.0.0.`.replace(/^\n/, ''),
);
program.on('--help', () => {
- console.log('');
- console.log(`Examples:
- ${chalk.grey('# uses defaults for resolving packages')}
+ console.log(chalk`
+Resolving Packages:
+ 1. If {yellow --source} globs are provided, use those.
+ 2. If using Yarn Workspaces, read {yellow workspaces} from {yellow package.json}.
+ 3. If using Lerna, read {yellow packages} from {yellow lerna.json}.
+ 4. Default to {yellow "package.json"} and {yellow "packages/*/package.json"}.
+
+Examples:
+ {dim # uses defaults for resolving packages}
syncpack fix-mismatches
- ${chalk.grey('# uses packages defined by --source when provided')}
- syncpack fix-mismatches --source ${chalk.yellow('"apps/*/package.json"')}
- ${chalk.grey(
- '# uses dependencies regular expression defined by --filter when provided',
- )}
- syncpack fix-mismatches --filter ${chalk.yellow('"typescript|tslint"')}
- ${chalk.grey('# multiple globs can be provided like this')}
- syncpack fix-mismatches --source ${chalk.yellow(
- '"apps/*/package.json"',
- )} --source ${chalk.yellow('"core/*/package.json"')}
- ${chalk.grey('# only fix "devDependencies"')}
+ {dim # uses packages defined by --source when provided}
+ syncpack fix-mismatches --source {yellow "apps/*/package.json"}
+ {dim # multiple globs can be provided like this}
+ syncpack fix-mismatches --source {yellow "apps/*/package.json"} --source {yellow "core/*/package.json"}
+ {dim # uses dependencies regular expression defined by --filter when provided}
+ syncpack fix-mismatches --filter {yellow "typescript|tslint"}
+ {dim # only inspect "devDependencies"}
syncpack fix-mismatches --dev
- ${chalk.grey('# only fix "devDependencies" and "peerDependencies"')}
+ {dim # only inspect "devDependencies" and "peerDependencies"}
syncpack fix-mismatches --dev --peer
- ${chalk.grey('# indent package.json with 4 spaces instead of 2')}
- syncpack fix-mismatches --indent ${chalk.yellow('" "')}
- `);
- console.log(`Resolving Packages:
- 1. If ${chalk.yellow(`--source`)} globs are provided, use those.
- 2. If using Yarn Workspaces, read ${chalk.yellow(
- `workspaces`,
- )} from ${chalk.yellow(`package.json`)}.
- 3. If using Lerna, read ${chalk.yellow(`packages`)} from ${chalk.yellow(
- `lerna.json`,
- )}.
- 4. Default to ${chalk.yellow(`"package.json"`)} and ${chalk.yellow(
- `"packages/*/package.json"`,
- )}.
- `);
- console.log(`Reference:
- globs ${chalk.blue.underline(
- 'https://github.com/isaacs/node-glob#glob-primer',
- )}
- lerna.json ${chalk.blue.underline(
- 'https://github.com/lerna/lerna#lernajson',
- )}
- Yarn Workspaces ${chalk.blue.underline(
- 'https://yarnpkg.com/lang/en/docs/workspaces',
- )}`);
+ {dim # indent package.json with 4 spaces instead of 2}
+ syncpack fix-mismatches --indent {yellow " "}
+
+Reference:
+ globs {blue.underline https://github.com/isaacs/node-glob#glob-primer}
+ lerna.json {blue.underline https://github.com/lerna/lerna#lernajson}
+ Yarn Workspaces {blue.underline https://yarnpkg.com/lang/en/docs/workspaces}
+`);
});
-run(program);
+program
+ .option(...option.source)
+ .option(...option.prod)
+ .option(...option.dev)
+ .option(...option.peer)
+ .option(...option.filter)
+ .option(...option.indent)
+ .parse(process.argv);
+
+fixMismatchesToDisk({
+ dev: Boolean(program.dev),
+ filter: new RegExp(program.filter ? program.filter : '.'),
+ indent: program.indent ? program.indent : ' ',
+ peer: Boolean(program.peer),
+ prod: Boolean(program.prod),
+ source: Array.isArray(program.source) ? program.source : [],
+});
diff --git a/src/bin-format.ts b/src/bin-format.ts
index 63a0f255..75b10c86 100644
--- a/src/bin-format.ts
+++ b/src/bin-format.ts
@@ -2,7 +2,8 @@
import chalk from 'chalk';
import program = require('commander');
-import { run } from './format';
+import { formatToDisk } from './commands/format';
+import { option } from './constants';
program.description(
`
@@ -13,41 +14,36 @@ program.description(
);
program.on('--help', () => {
- console.log('');
- console.log(`Examples:
- ${chalk.grey('# uses defaults for resolving packages')}
+ console.log(chalk`
+Examples:
+ {dim # uses defaults for resolving packages}
syncpack format
- ${chalk.grey('# uses packages defined by --source when provided')}
- syncpack format --source ${chalk.yellow('"apps/*/package.json"')}
- ${chalk.grey('# multiple globs can be provided like this')}
- syncpack format --source ${chalk.yellow(
- '"apps/*/package.json"',
- )} --source ${chalk.yellow('"core/*/package.json"')}
- ${chalk.grey('# indent package.json with 4 spaces instead of 2')}
- syncpack format --indent ${chalk.yellow('" "')}
- `);
- console.log(`Resolving Packages:
- 1. If ${chalk.yellow(`--source`)} globs are provided, use those.
- 2. If using Yarn Workspaces, read ${chalk.yellow(
- `workspaces`,
- )} from ${chalk.yellow(`package.json`)}.
- 3. If using Lerna, read ${chalk.yellow(`packages`)} from ${chalk.yellow(
- `lerna.json`,
- )}.
- 4. Default to ${chalk.yellow(`"package.json"`)} and ${chalk.yellow(
- `"packages/*/package.json"`,
- )}.
- `);
- console.log(`Reference:
- globs ${chalk.blue.underline(
- 'https://github.com/isaacs/node-glob#glob-primer',
- )}
- lerna.json ${chalk.blue.underline(
- 'https://github.com/lerna/lerna#lernajson',
- )}
- Yarn Workspaces ${chalk.blue.underline(
- 'https://yarnpkg.com/lang/en/docs/workspaces',
- )}`);
+ {dim # uses packages defined by --source when provided}
+ syncpack format --source {yellow "apps/*/package.json"}
+ {dim # multiple globs can be provided like this}
+ syncpack format --source {yellow "apps/*/package.json"} --source {yellow "core/*/package.json"}
+ {dim # indent package.json with 4 spaces instead of 2}
+ syncpack format --indent {yellow " "}
+
+Resolving Packages:
+ 1. If {yellow --source} globs are provided, use those.
+ 2. If using Yarn Workspaces, read {yellow workspaces} from {yellow package.json}.
+ 3. If using Lerna, read {yellow packages} from {yellow lerna.json}.
+ 4. Default to {yellow "package.json"} and {yellow "packages/*/package.json"}.
+
+Reference:
+ globs {blue.underline https://github.com/isaacs/node-glob#glob-primer}
+ lerna.json {blue.underline https://github.com/lerna/lerna#lernajson}
+ Yarn Workspaces {blue.underline https://yarnpkg.com/lang/en/docs/workspaces}
+`);
});
-run(program);
+program
+ .option(...option.source)
+ .option(...option.indent)
+ .parse(process.argv);
+
+formatToDisk({
+ indent: program.indent ? program.indent : ' ',
+ source: Array.isArray(program.source) ? program.source : [],
+});
diff --git a/src/bin-list-mismatches.ts b/src/bin-list-mismatches.ts
index 7b7def39..25099e17 100644
--- a/src/bin-list-mismatches.ts
+++ b/src/bin-list-mismatches.ts
@@ -2,7 +2,8 @@
import chalk from 'chalk';
import program = require('commander');
-import { run } from './list-mismatches';
+import { listMismatchesFromDisk } from './commands/list-mismatches';
+import { option } from './constants';
program.description(
`
@@ -11,47 +12,46 @@ program.description(
);
program.on('--help', () => {
- console.log('');
- console.log(`Examples:
- ${chalk.grey('# uses defaults for resolving packages')}
+ console.log(chalk`
+Examples:
+ {dim # uses defaults for resolving packages}
syncpack list-mismatches
- ${chalk.grey('# uses packages defined by --source when provided')}
- syncpack list-mismatches --source ${chalk.yellow('"apps/*/package.json"')}
- ${chalk.grey('# multiple globs can be provided like this')}
- syncpack list-mismatches --source ${chalk.yellow(
- '"apps/*/package.json"',
- )} --source ${chalk.yellow('"core/*/package.json"')}
- ${chalk.grey(
- '# uses dependencies regular expression defined by --filter when provided',
- )}
- syncpack list-mismatches --filter ${chalk.yellow('"typescript|tslint"')}
- ${chalk.grey('# only list "devDependencies"')}
+ {dim # uses packages defined by --source when provided}
+ syncpack list-mismatches --source {yellow "apps/*/package.json"}
+ {dim # multiple globs can be provided like this}
+ syncpack list-mismatches --source {yellow "apps/*/package.json"} --source {yellow "core/*/package.json"}
+ {dim # uses dependencies regular expression defined by --filter when provided}
+ syncpack list-mismatches --filter {yellow "typescript|tslint"}
+ {dim # only inspect "devDependencies"}
syncpack list-mismatches --dev
- ${chalk.grey('# only list "devDependencies" and "peerDependencies"')}
+ {dim # only inspect "devDependencies" and "peerDependencies"}
syncpack list-mismatches --dev --peer
- `);
- console.log(`Resolving Packages:
- 1. If ${chalk.yellow(`--source`)} globs are provided, use those.
- 2. If using Yarn Workspaces, read ${chalk.yellow(
- `workspaces`,
- )} from ${chalk.yellow(`package.json`)}.
- 3. If using Lerna, read ${chalk.yellow(`packages`)} from ${chalk.yellow(
- `lerna.json`,
- )}.
- 4. Default to ${chalk.yellow(`"package.json"`)} and ${chalk.yellow(
- `"packages/*/package.json"`,
- )}.
- `);
- console.log(`Reference:
- globs ${chalk.blue.underline(
- 'https://github.com/isaacs/node-glob#glob-primer',
- )}
- lerna.json ${chalk.blue.underline(
- 'https://github.com/lerna/lerna#lernajson',
- )}
- Yarn Workspaces ${chalk.blue.underline(
- 'https://yarnpkg.com/lang/en/docs/workspaces',
- )}`);
+
+Resolving Packages:
+ 1. If {yellow --source} globs are provided, use those.
+ 2. If using Yarn Workspaces, read {yellow workspaces} from {yellow package.json}.
+ 3. If using Lerna, read {yellow packages} from {yellow lerna.json}.
+ 4. Default to {yellow "package.json"} and {yellow "packages/*/package.json"}.
+
+Reference:
+ globs {blue.underline https://github.com/isaacs/node-glob#glob-primer}
+ lerna.json {blue.underline https://github.com/lerna/lerna#lernajson}
+ Yarn Workspaces {blue.underline https://yarnpkg.com/lang/en/docs/workspaces}
+`);
});
-run(program);
+program
+ .option(...option.source)
+ .option(...option.prod)
+ .option(...option.dev)
+ .option(...option.peer)
+ .option(...option.filter)
+ .parse(process.argv);
+
+listMismatchesFromDisk({
+ dev: Boolean(program.dev),
+ filter: new RegExp(program.filter ? program.filter : '.'),
+ peer: Boolean(program.peer),
+ prod: Boolean(program.prod),
+ source: Array.isArray(program.source) ? program.source : [],
+});
diff --git a/src/bin-list.ts b/src/bin-list.ts
index c181d42d..6656c57c 100644
--- a/src/bin-list.ts
+++ b/src/bin-list.ts
@@ -2,55 +2,52 @@
import chalk from 'chalk';
import program = require('commander');
-import { run } from './list';
+import { listFromDisk } from './commands/list';
+import { option } from './constants';
-program.description(
- `
- List all dependencies required by your packages.`.replace(/^\n/, ''),
-);
+program.description(' List all dependencies required by your packages.');
program.on('--help', () => {
- console.log('');
- console.log(`Examples:
- ${chalk.grey('# uses defaults for resolving packages')}
+ console.log(chalk`
+Examples:
+ {dim # uses defaults for resolving packages}
syncpack list
- ${chalk.grey('# uses packages defined by --source when provided')}
- syncpack list --source ${chalk.yellow('"apps/*/package.json"')}
- ${chalk.grey(
- '# uses dependencies regular expression defined by --filter when provided',
- )}
- syncpack list --filter ${chalk.yellow('"typescript|tslint"')}
- ${chalk.grey('# multiple globs can be provided like this')}
- syncpack list --source ${chalk.yellow(
- '"apps/*/package.json"',
- )} --source ${chalk.yellow('"core/*/package.json"')}
- ${chalk.grey('# only inspect "devDependencies"')}
+ {dim # uses packages defined by --source when provided}
+ syncpack list --source {yellow "apps/*/package.json"}
+ {dim # multiple globs can be provided like this}
+ syncpack list --source {yellow "apps/*/package.json"} --source {yellow "core/*/package.json"}
+ {dim # uses dependencies regular expression defined by --filter when provided}
+ syncpack list --filter {yellow "typescript|tslint"}
+ {dim # only inspect "devDependencies"}
syncpack list --dev
- ${chalk.grey('# only inspect "devDependencies" and "peerDependencies"')}
+ {dim # only inspect "devDependencies" and "peerDependencies"}
syncpack list --dev --peer
- `);
- console.log(`Resolving Packages:
- 1. If ${chalk.yellow(`--source`)} globs are provided, use those.
- 2. If using Yarn Workspaces, read ${chalk.yellow(
- `workspaces`,
- )} from ${chalk.yellow(`package.json`)}.
- 3. If using Lerna, read ${chalk.yellow(`packages`)} from ${chalk.yellow(
- `lerna.json`,
- )}.
- 4. Default to ${chalk.yellow(`"package.json"`)} and ${chalk.yellow(
- `"packages/*/package.json"`,
- )}.
- `);
- console.log(`Reference:
- globs ${chalk.blue.underline(
- 'https://github.com/isaacs/node-glob#glob-primer',
- )}
- lerna.json ${chalk.blue.underline(
- 'https://github.com/lerna/lerna#lernajson',
- )}
- Yarn Workspaces ${chalk.blue.underline(
- 'https://yarnpkg.com/lang/en/docs/workspaces',
- )}`);
+
+Resolving Packages:
+ 1. If {yellow --source} globs are provided, use those.
+ 2. If using Yarn Workspaces, read {yellow workspaces} from {yellow package.json}.
+ 3. If using Lerna, read {yellow packages} from {yellow lerna.json}.
+ 4. Default to {yellow "package.json"} and {yellow "packages/*/package.json"}.
+
+Reference:
+ globs {blue.underline https://github.com/isaacs/node-glob#glob-primer}
+ lerna.json {blue.underline https://github.com/lerna/lerna#lernajson}
+ Yarn Workspaces {blue.underline https://yarnpkg.com/lang/en/docs/workspaces}
+`);
});
-run(program);
+program
+ .option(...option.source)
+ .option(...option.prod)
+ .option(...option.dev)
+ .option(...option.peer)
+ .option(...option.filter)
+ .parse(process.argv);
+
+listFromDisk({
+ dev: Boolean(program.dev),
+ filter: new RegExp(program.filter ? program.filter : '.'),
+ peer: Boolean(program.peer),
+ prod: Boolean(program.prod),
+ source: Array.isArray(program.source) ? program.source : [],
+});
diff --git a/src/bin-set-semver-ranges.ts b/src/bin-set-semver-ranges.ts
index 14e049a9..1d73481d 100644
--- a/src/bin-set-semver-ranges.ts
+++ b/src/bin-set-semver-ranges.ts
@@ -2,7 +2,8 @@
import chalk from 'chalk';
import program = require('commander');
-import { run } from './set-semver-ranges';
+import { setSemverRangesToDisk } from './commands/set-semver-ranges';
+import { option } from './constants';
program.description(
`
@@ -11,57 +12,64 @@ program.description(
);
program.on('--help', () => {
- console.log('');
- console.log(`Examples:
- ${chalk.grey('# uses defaults for resolving packages')}
+ console.log(chalk`
+Examples:
+ {dim # uses defaults for resolving packages}
syncpack set-semver-ranges
- ${chalk.grey('# uses packages defined by --source when provided')}
- syncpack set-semver-ranges --source ${chalk.yellow('"apps/*/package.json"')}
- ${chalk.grey('# multiple globs can be provided like this')}
- syncpack set-semver-ranges --source ${chalk.yellow(
- '"apps/*/package.json"',
- )} --source ${chalk.yellow('"core/*/package.json"')}
- ${chalk.grey('# use ~ range instead of default ""')}
+ {dim # uses packages defined by --source when provided}
+ syncpack set-semver-ranges --source {yellow "apps/*/package.json"}
+ {dim # multiple globs can be provided like this}
+ syncpack set-semver-ranges --source {yellow "apps/*/package.json"} --source {yellow "core/*/package.json"}
+ {dim # uses dependencies regular expression defined by --filter when provided}
+ syncpack set-semver-ranges --filter {yellow "typescript|tslint"}
+ {dim # use ~ range instead of default ""}
syncpack set-semver-ranges --semver-range ~
- ${chalk.grey('# set ~ range in "devDependencies"')}
+ {dim # set ~ range in "devDependencies"}
syncpack set-semver-ranges --dev --semver-range ~
- ${chalk.grey('# set ~ range in "devDependencies" and "peerDependencies"')}
+ {dim # set ~ range in "devDependencies" and "peerDependencies"}
syncpack set-semver-ranges --dev --peer --semver-range ~
- ${chalk.grey('# indent package.json with 4 spaces instead of 2')}
- syncpack set-semver-ranges --indent ${chalk.yellow('" "')}
- `);
- console.log(`Supported Ranges:
- < ${chalk.grey('<1.4.2')}
- <= ${chalk.grey('<=1.4.2')}
- "" ${chalk.grey('1.4.2')}
- ~ ${chalk.grey('~1.4.2')}
- ^ ${chalk.grey('^1.4.2')}
- >= ${chalk.grey('>=1.4.2')}
- > ${chalk.grey('>1.4.2')}
- * ${chalk.grey('*')}
- `);
- console.log(`Resolving Packages:
- 1. If ${chalk.yellow(`--source`)} globs are provided, use those.
- 2. If using Yarn Workspaces, read ${chalk.yellow(
- `workspaces`,
- )} from ${chalk.yellow(`package.json`)}.
- 3. If using Lerna, read ${chalk.yellow(`packages`)} from ${chalk.yellow(
- `lerna.json`,
- )}.
- 4. Default to ${chalk.yellow(`"package.json"`)} and ${chalk.yellow(
- `"packages/*/package.json"`,
- )}.
- `);
- console.log(`Reference:
- globs ${chalk.blue.underline(
- 'https://github.com/isaacs/node-glob#glob-primer',
- )}
- lerna.json ${chalk.blue.underline(
- 'https://github.com/lerna/lerna#lernajson',
- )}
- Yarn Workspaces ${chalk.blue.underline(
- 'https://yarnpkg.com/lang/en/docs/workspaces',
- )}`);
+ {dim # indent package.json with 4 spaces instead of 2}
+ syncpack set-semver-ranges --indent {yellow " "}
+
+Supported Ranges:
+ < {dim <1.4.2}
+ <= {dim <=1.4.2}
+ "" {dim 1.4.2}
+ ~ {dim ~1.4.2}
+ ^ {dim ^1.4.2}
+ >= {dim >=1.4.2}
+ > {dim >1.4.2}
+ * {dim *}
+
+Resolving Packages:
+ 1. If {yellow --source} globs are provided, use those.
+ 2. If using Yarn Workspaces, read {yellow workspaces} from {yellow package.json}.
+ 3. If using Lerna, read {yellow packages} from {yellow lerna.json}.
+ 4. Default to {yellow "package.json"} and {yellow "packages/*/package.json"}.
+
+Reference:
+ globs {blue.underline https://github.com/isaacs/node-glob#glob-primer}
+ lerna.json {blue.underline https://github.com/lerna/lerna#lernajson}
+ Yarn Workspaces {blue.underline https://yarnpkg.com/lang/en/docs/workspaces}
+`);
});
-run(program);
+program
+ .option(...option.source)
+ .option(...option.prod)
+ .option(...option.dev)
+ .option(...option.peer)
+ .option(...option.filter)
+ .option(...option.indent)
+ .option(...option.semverRange)
+ .parse(process.argv);
+
+setSemverRangesToDisk({
+ dev: Boolean(program.dev),
+ filter: new RegExp(program.filter ? program.filter : '.'),
+ indent: program.indent ? program.indent : ' ',
+ peer: Boolean(program.peer),
+ prod: Boolean(program.prod),
+ semverRange: program.semverRange ? program.semverRange : '',
+ source: Array.isArray(program.source) ? program.source : [],
+});
diff --git a/src/bin.ts b/src/bin.ts
index b0417dd2..bf2d36a9 100644
--- a/src/bin.ts
+++ b/src/bin.ts
@@ -1,6 +1,12 @@
#!/usr/bin/env node
import program = require('commander');
-import { run } from './syncpack';
-run(program);
+program
+ .version(require('../package.json').version)
+ .command('fix-mismatches', 'set dependencies used with different versions to the same version')
+ .command('format', 'sort and shorten properties according to a convention')
+ .command('list', 'list every dependency used in your packages', { isDefault: true })
+ .command('list-mismatches', 'list every dependency used with different versions in your packages')
+ .command('set-semver-ranges', 'set semver ranges to the given format')
+ .parse(process.argv);
diff --git a/src/commands/__snapshots__/fix-mismatches.spec.ts.snap b/src/commands/__snapshots__/fix-mismatches.spec.ts.snap
new file mode 100644
index 00000000..5215d374
--- /dev/null
+++ b/src/commands/__snapshots__/fix-mismatches.spec.ts.snap
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`fixMismatches sets all dependencies installed with different versions to the highest version 1`] = `
+Array [
+ Object {
+ "contents": Object {
+ "dependencies": Object {
+ "foo": "0.2.0",
+ },
+ },
+ "filePath": "/a/package.json",
+ },
+ Object {
+ "contents": Object {
+ "dependencies": Object {
+ "foo": "0.2.0",
+ },
+ },
+ "filePath": "/b/package.json",
+ },
+]
+`;
diff --git a/src/commands/__snapshots__/list-mismatches.spec.ts.snap b/src/commands/__snapshots__/list-mismatches.spec.ts.snap
new file mode 100644
index 00000000..928b1677
--- /dev/null
+++ b/src/commands/__snapshots__/list-mismatches.spec.ts.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`listMismatches outputs all dependencies installed with different versions 1`] = `
+Array [
+ Array [
+ "[31m✕ foo[39m",
+ ],
+ Array [
+ "[2m-[22m 0.1.0 [2min dependencies of undefined[22m",
+ ],
+ Array [
+ "[2m-[22m 0.2.0 [2min dependencies of undefined[22m",
+ ],
+]
+`;
diff --git a/src/commands/__snapshots__/list.spec.ts.snap b/src/commands/__snapshots__/list.spec.ts.snap
new file mode 100644
index 00000000..9576a237
--- /dev/null
+++ b/src/commands/__snapshots__/list.spec.ts.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`list outputs all dependencies 1`] = `
+Array [
+ Array [
+ "[31m✕ foo[39m [2m[31m0.1.0, 0.2.0[39m[22m",
+ ],
+]
+`;
diff --git a/src/commands/fix-mismatches.spec.ts b/src/commands/fix-mismatches.spec.ts
new file mode 100644
index 00000000..91337e54
--- /dev/null
+++ b/src/commands/fix-mismatches.spec.ts
@@ -0,0 +1,22 @@
+import * as mock from '../../test/mock';
+
+describe('fixMismatches', () => {
+ let fixMismatches: any;
+ let log: any;
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ beforeEach(() => {
+ jest.mock('./lib/log', () => ({ log: jest.fn() }));
+ fixMismatches = require('./fix-mismatches').fixMismatches;
+ log = require('./lib/log').log;
+ });
+
+ it('sets all dependencies installed with different versions to the highest version', () => {
+ const wrappers = [mock.wrapper('a', ['foo@0.1.0']), mock.wrapper('b', ['foo@0.2.0'])];
+ fixMismatches(['dependencies'], /./, wrappers);
+ expect(wrappers).toMatchSnapshot();
+ });
+});
diff --git a/src/commands/fix-mismatches.ts b/src/commands/fix-mismatches.ts
new file mode 100644
index 00000000..57af4ea6
--- /dev/null
+++ b/src/commands/fix-mismatches.ts
@@ -0,0 +1,52 @@
+import chalk from 'chalk';
+import { writeFileSync } from 'fs-extra';
+import { EOL } from 'os';
+import { relative } from 'path';
+import { DependencyType } from '../constants';
+import { getDependencyTypes } from './lib/get-dependency-types';
+import { getHighestVersion } from './lib/get-highest-version';
+import { getMismatchedDependencies } from './lib/get-installations';
+import { getWrappers, SourceWrapper } from './lib/get-wrappers';
+import { log } from './lib/log';
+
+interface Options {
+ dev: boolean;
+ filter: RegExp;
+ indent: string;
+ peer: boolean;
+ prod: boolean;
+ source: string[];
+}
+
+export const fixMismatches = (dependencyTypes: DependencyType[], filter: RegExp, wrappers: SourceWrapper[]) => {
+ const iterator = getMismatchedDependencies(dependencyTypes, wrappers);
+ const mismatches = Array.from(iterator).filter(({ name }) => name.search(filter) !== -1);
+
+ mismatches.forEach((installedPackage) => {
+ const newest = getHighestVersion(installedPackage.installations.map((installation) => installation.version));
+ installedPackage.installations.forEach(({ type, name, source }) => {
+ source.contents[type][name] = newest;
+ });
+ });
+};
+
+export const fixMismatchesToDisk = ({ dev, filter, indent, peer, prod, source }: Options) => {
+ const toJson = (wrapper: SourceWrapper) => `${JSON.stringify(wrapper.contents, null, indent)}${EOL}`;
+ const dependencyTypes = getDependencyTypes({ dev, peer, prod });
+ const wrappers = getWrappers({ source });
+ const allBefore = wrappers.map(toJson);
+
+ fixMismatches(dependencyTypes, filter, wrappers);
+
+ wrappers.forEach((wrapper, i) => {
+ const shortPath = relative(process.cwd(), wrapper.filePath);
+ const before = allBefore[i];
+ const after = toJson(wrapper);
+ if (before !== after) {
+ writeFileSync(wrapper.filePath, after);
+ log(chalk.green('✓'), shortPath);
+ } else {
+ log(chalk.dim('-', shortPath));
+ }
+ });
+};
diff --git a/src/commands/format.spec.ts b/src/commands/format.spec.ts
new file mode 100644
index 00000000..0a1772e3
--- /dev/null
+++ b/src/commands/format.spec.ts
@@ -0,0 +1,42 @@
+import { format } from './format';
+import { Source } from './lib/get-wrappers';
+
+const createWrapper = (contents: Source) => ({ contents, filePath: '' });
+
+describe('format', () => {
+ it('sorts Array properties alphabetically by value', () => {
+ const wrapper = createWrapper({ keywords: ['B', 'A'] });
+ expect(format(wrapper, { sortAz: ['keywords'] })).toEqual({
+ keywords: ['A', 'B'],
+ });
+ });
+ it('sorts Object properties alphabetically by key', () => {
+ const wrapper = createWrapper({ scripts: { B: '', A: '' } });
+ expect(format(wrapper, { sortAz: ['scripts'] })).toEqual({
+ scripts: { A: '', B: '' },
+ });
+ });
+ it('sorts named properties first, then the rest alphabetically', () => {
+ const wrapper = createWrapper({ A: '', C: '', F: '', B: '', D: '', E: '' });
+ expect(format(wrapper, { sortFirst: ['D', 'E', 'f'] })).toEqual({
+ D: '',
+ E: '',
+ F: '',
+ A: '',
+ B: '',
+ C: '',
+ });
+ });
+ it('uses shorthand format for "bugs"', () => {
+ const wrapper = createWrapper({ bugs: { url: 'https://github.com/User/repo/issues' } });
+ expect(format(wrapper)).toEqual({ bugs: 'https://github.com/User/repo/issues' });
+ });
+ it('uses shorthand format for "repository"', () => {
+ const wrapper = createWrapper({ repository: { url: 'git://gitlab.com/User/repo', type: 'git' } });
+ expect(format(wrapper)).toEqual({ repository: 'git://gitlab.com/User/repo' });
+ });
+ it('uses github shorthand format for "repository"', () => {
+ const wrapper = createWrapper({ repository: { url: 'git://github.com/User/repo', type: 'git' } });
+ expect(format(wrapper)).toEqual({ repository: 'User/repo' });
+ });
+});
diff --git a/src/commands/format.ts b/src/commands/format.ts
new file mode 100644
index 00000000..3b107244
--- /dev/null
+++ b/src/commands/format.ts
@@ -0,0 +1,59 @@
+import { SORT_AZ, SORT_FIRST } from '../constants';
+import { getWrappers, SourceWrapper, Source } from './lib/get-wrappers';
+import { writeIfChanged } from './lib/write-if-changed';
+
+interface FormatConfig {
+ sortAz?: string[];
+ sortFirst?: string[];
+}
+
+interface Options {
+ indent: string;
+ source: string[];
+}
+
+const sortObject = (sortedKeys: string[] | Set, obj: any) => {
+ sortedKeys.forEach((key: string) => {
+ const value = obj[key];
+ delete obj[key];
+ obj[key] = value;
+ });
+};
+
+const sortAlphabetically = (value: any) => {
+ if (Array.isArray(value)) {
+ value.sort();
+ } else if (Object.prototype.toString.call(value) === '[object Object]') {
+ sortObject(Object.keys(value).sort(), value);
+ }
+};
+
+export const format = (wrapper: SourceWrapper, { sortAz = SORT_AZ, sortFirst = SORT_FIRST }: FormatConfig = {}) => {
+ const { contents } = wrapper;
+ const sortedKeys = Object.keys(contents).sort();
+ const keys = new Set(sortFirst.concat(sortedKeys));
+
+ if (contents.bugs && typeof contents.bugs === 'object' && contents.bugs.url) {
+ contents.bugs = contents.bugs.url;
+ }
+
+ if (contents.repository && typeof contents.repository === 'object' && contents.repository.url) {
+ if (contents.repository.url.includes('github.com')) {
+ contents.repository = contents.repository.url.replace(/^.+github\.com\//, '');
+ } else {
+ contents.repository = contents.repository.url;
+ }
+ }
+
+ sortAz.forEach((key) => sortAlphabetically(contents[key]));
+ sortObject(keys, contents);
+ return contents;
+};
+
+export const formatToDisk = ({ indent, source }: Options) => {
+ getWrappers({ source }).forEach((wrapper) => {
+ writeIfChanged(indent, wrapper, () => {
+ format(wrapper);
+ });
+ });
+};
diff --git a/src/commands/lib/get-dependency-types.spec.ts b/src/commands/lib/get-dependency-types.spec.ts
new file mode 100644
index 00000000..5127298b
--- /dev/null
+++ b/src/commands/lib/get-dependency-types.spec.ts
@@ -0,0 +1,13 @@
+import { getDependencyTypes } from './get-dependency-types';
+
+describe('getDependencyTypes', () => {
+ it('returns all if none are set or true if any are set', () => {
+ const prod = 'dependencies';
+ const dev = 'devDependencies';
+ const peer = 'peerDependencies';
+ expect(getDependencyTypes({ dev: false, peer: false, prod: false })).toEqual([prod, dev, peer]);
+ expect(getDependencyTypes({ dev: true, peer: false, prod: false })).toEqual([dev]);
+ expect(getDependencyTypes({ dev: false, peer: true, prod: false })).toEqual([peer]);
+ expect(getDependencyTypes({ dev: false, peer: false, prod: true })).toEqual([prod]);
+ });
+});
diff --git a/src/lib/get-dependency-types.ts b/src/commands/lib/get-dependency-types.ts
similarity index 54%
rename from src/lib/get-dependency-types.ts
rename to src/commands/lib/get-dependency-types.ts
index 39b2d60c..ed7525c7 100644
--- a/src/lib/get-dependency-types.ts
+++ b/src/commands/lib/get-dependency-types.ts
@@ -1,7 +1,13 @@
-import { DEPENDENCY_TYPES } from '../constants';
-import { CommanderApi, IManifestKey } from '../typings';
+import { DEPENDENCY_TYPES } from '../../constants';
+import { DependencyType } from '../../constants';
-export const getDependencyTypes = (program: CommanderApi): IManifestKey[] =>
+interface Options {
+ prod: boolean;
+ dev: boolean;
+ peer: boolean;
+}
+
+export const getDependencyTypes = (program: Options): DependencyType[] =>
program.prod || program.dev || program.peer
? DEPENDENCY_TYPES.filter(
(type) =>
diff --git a/src/commands/lib/get-highest-version.spec.ts b/src/commands/lib/get-highest-version.spec.ts
new file mode 100644
index 00000000..64a03fbb
--- /dev/null
+++ b/src/commands/lib/get-highest-version.spec.ts
@@ -0,0 +1,54 @@
+import { getHighestVersion } from './get-highest-version';
+
+const shuffle = (array: any[]) => {
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
+ return array;
+};
+
+describe('getHighestVersion', () => {
+ it('returns the newest version from an array of versions', () => {
+ const a = ['<1.0.0'];
+ const b = shuffle([...a, '<=1.0.0']);
+ const c = shuffle([...b, '1.0.0']);
+ const d = shuffle([...c, '~1.0.0']);
+ const e = shuffle([...d, '1.x.x']);
+ const f = shuffle([...e, '^1.0.0']);
+ const g = shuffle([...f, '>=1.0.0']);
+ const h = shuffle([...g, '>1.0.0']);
+ const i = shuffle([...h, '*']);
+ const j = shuffle([...i, 'http://asdf.com/asdf.tar.gz']);
+ const k = shuffle([...j, 'file:../foo/bar']);
+ const l = shuffle([...k, 'latest']);
+ const m = shuffle([...l, 'git+ssh://git@github.com:npm/cli.git#v1.0.27']);
+ const n = shuffle([...m, 'git+ssh://git@github.com:npm/cli#semver:^5.0']);
+ const o = shuffle([...n, 'git+https://isaacs@github.com/npm/cli.git']);
+ const p = shuffle([...o, 'git://github.com/npm/cli.git#v1.0.27']);
+ const q = shuffle([...p, 'expressjs/express']);
+ const r = shuffle([...q, 'mochajs/mocha#4727d357ea']);
+ const s = shuffle([...r, 'user/repo#feature/branch']);
+ // valid semver
+ expect(getHighestVersion(a)).toEqual('<1.0.0');
+ expect(getHighestVersion(b)).toEqual('<=1.0.0');
+ expect(getHighestVersion(c)).toEqual('1.0.0');
+ expect(getHighestVersion(d)).toEqual('~1.0.0');
+ expect(getHighestVersion(e)).toEqual('1.x.x');
+ expect(getHighestVersion(f)).toEqual('^1.0.0');
+ expect(getHighestVersion(g)).toEqual('>=1.0.0');
+ expect(getHighestVersion(h)).toEqual('>1.0.0');
+ expect(getHighestVersion(i)).toEqual('*');
+ // invalid semver
+ expect(getHighestVersion(j)).toEqual('*');
+ expect(getHighestVersion(k)).toEqual('*');
+ expect(getHighestVersion(l)).toEqual('*');
+ expect(getHighestVersion(m)).toEqual('*');
+ expect(getHighestVersion(n)).toEqual('*');
+ expect(getHighestVersion(o)).toEqual('*');
+ expect(getHighestVersion(p)).toEqual('*');
+ expect(getHighestVersion(q)).toEqual('*');
+ expect(getHighestVersion(r)).toEqual('*');
+ expect(getHighestVersion(s)).toEqual('*');
+ });
+});
diff --git a/src/commands/lib/get-highest-version.ts b/src/commands/lib/get-highest-version.ts
new file mode 100644
index 00000000..b897aa1b
--- /dev/null
+++ b/src/commands/lib/get-highest-version.ts
@@ -0,0 +1,49 @@
+import { coerce, eq, gt, valid } from 'semver';
+import {
+ RANGE_ANY,
+ RANGE_EXACT,
+ RANGE_GT,
+ RANGE_GTE,
+ RANGE_LT,
+ RANGE_LTE,
+ RANGE_MINOR,
+ RANGE_PATCH,
+} from '../../constants';
+
+const getRange = (version: string) => version.slice(0, version.search(/[0-9]/));
+
+const getRangeScore = (version: string | null) => {
+ if (version === null) return 0;
+ if (version === RANGE_ANY) return 8;
+ const range = getRange(version);
+ if (range === RANGE_GT) return 7;
+ if (range === RANGE_GTE) return 6;
+ if (range === RANGE_MINOR) return 5;
+ if (version.indexOf('.x') !== -1) return 4;
+ if (range === RANGE_PATCH) return 3;
+ if (range === RANGE_EXACT) return 2;
+ if (range === RANGE_LTE) return 1;
+ if (range === RANGE_LT) return 0;
+ return 0;
+};
+
+export const getHighestVersion = (versions: string[]): string => {
+ let rawHighest: string | null = null;
+ for (const raw of versions) {
+ if (raw === '*') return raw;
+ const version = valid(coerce(raw));
+ if (version === null) continue;
+ const highest = valid(coerce(rawHighest));
+ if (
+ highest === null ||
+ gt(version, highest) ||
+ (eq(version, highest) && getRangeScore(raw) > getRangeScore(rawHighest))
+ ) {
+ rawHighest = raw;
+ }
+ }
+ if (rawHighest === null) {
+ throw new Error(`Failed to find highest version of ${versions.join(', ')}`);
+ }
+ return rawHighest;
+};
diff --git a/src/commands/lib/get-installations.spec.ts b/src/commands/lib/get-installations.spec.ts
new file mode 100644
index 00000000..8f7a2b9b
--- /dev/null
+++ b/src/commands/lib/get-installations.spec.ts
@@ -0,0 +1,38 @@
+import 'expect-more-jest';
+import { DependencyType } from '../../constants';
+import { getDependencies, getMismatchedDependencies } from './get-installations';
+import { SourceWrapper } from './get-wrappers';
+
+const mocked = {
+ projects: (): SourceWrapper[] => [
+ { filePath: '', contents: { dependencies: { chalk: '2.3.0' } } },
+ { filePath: '', contents: { devDependencies: { jest: '22.1.4' } } },
+ { filePath: '', contents: { peerDependencies: { jest: '22.1.4' } } },
+ { filePath: '', contents: { dependencies: { chalk: '1.0.0' } } },
+ { filePath: '', contents: { dependencies: { biggy: '0.1.0' } } },
+ ],
+ types: (): DependencyType[] => ['dependencies', 'devDependencies', 'peerDependencies'],
+};
+
+const getShape = (name: string, ...installations: Array<[string, string]>) => ({
+ installations: installations.map(([type, version]) => expect.objectContaining({ name, type, version })),
+ name,
+});
+
+describe('getDependencies', () => {
+ it('lists all dependencies and their versions', () => {
+ const iterator = getDependencies(mocked.types(), mocked.projects());
+ expect(Array.from(iterator)).toEqual([
+ getShape('chalk', ['dependencies', '2.3.0'], ['dependencies', '1.0.0']),
+ getShape('biggy', ['dependencies', '0.1.0']),
+ getShape('jest', ['devDependencies', '22.1.4'], ['peerDependencies', '22.1.4']),
+ ]);
+ });
+});
+
+describe('getMismatchedDependencies', () => {
+ it('lists dependencies installed with different versions', () => {
+ const iterator = getMismatchedDependencies(mocked.types(), mocked.projects());
+ expect(Array.from(iterator)).toEqual([getShape('chalk', ['dependencies', '2.3.0'], ['dependencies', '1.0.0'])]);
+ });
+});
diff --git a/src/commands/lib/get-installations.ts b/src/commands/lib/get-installations.ts
new file mode 100644
index 00000000..75ccabff
--- /dev/null
+++ b/src/commands/lib/get-installations.ts
@@ -0,0 +1,88 @@
+import { DependencyType } from '../../constants';
+import { SourceWrapper } from './get-wrappers';
+
+export interface Installation {
+ /** which section the package was installed in */
+ type: DependencyType;
+ /** eg 'lodash' */
+ name: string;
+ /** package.json file contents */
+ source: any;
+ /** eg '0.1.0' */
+ version: string;
+}
+
+export interface InstalledPackage {
+ /** eg 'lodash' */
+ name: string;
+ /** each location this package is installed */
+ installations: Installation[];
+}
+
+function* getInstallationsOf(
+ name: string,
+ types: DependencyType[],
+ wrappers: SourceWrapper[],
+): Generator {
+ for (const type of types) {
+ for (const wrapper of wrappers) {
+ const dependencies = wrapper.contents[type];
+ if (dependencies && dependencies[name]) {
+ yield {
+ name,
+ source: wrapper,
+ type,
+ version: dependencies[name],
+ };
+ }
+ }
+ }
+}
+
+export function* getDependencies(types: DependencyType[], wrappers: SourceWrapper[]): Generator {
+ const visited: any = {};
+ for (const type of types) {
+ for (const wrapper of wrappers) {
+ if (wrapper.contents[type]) {
+ for (const name in wrapper.contents[type]) {
+ if (visited[name] === undefined) {
+ visited[name] = true;
+ yield {
+ installations: Array.from(getInstallationsOf(name, types, wrappers)),
+ name,
+ };
+ }
+ }
+ }
+ }
+ }
+}
+
+export function* getMismatchedDependencies(
+ types: DependencyType[],
+ wrappers: SourceWrapper[],
+): Generator {
+ const iterator = getDependencies(types, wrappers);
+ for (const installedPackage of iterator) {
+ const { installations } = installedPackage;
+ const len = installations.length;
+ if (len > 1) {
+ for (let i = 1; i < len; i++) {
+ if (installations[i].version !== installations[i - 1].version) {
+ yield installedPackage;
+ break;
+ }
+ }
+ }
+ }
+}
+
+export const sortByName = (a: InstalledPackage, b: InstalledPackage) => {
+ if (a.name < b.name) {
+ return -1;
+ }
+ if (a.name > b.name) {
+ return 1;
+ }
+ return 0;
+};
diff --git a/src/lib/get-packages.spec.ts b/src/commands/lib/get-wrappers.spec.ts
similarity index 54%
rename from src/lib/get-packages.spec.ts
rename to src/commands/lib/get-wrappers.spec.ts
index a5de07fb..95af3968 100644
--- a/src/lib/get-packages.spec.ts
+++ b/src/commands/lib/get-wrappers.spec.ts
@@ -1,13 +1,16 @@
import { removeSync } from 'fs-extra';
-import { getMockCommander } from '../../test/helpers';
-import { getPackages } from './get-packages';
import mock = require('mock-fs');
+import { getWrappers, SourceWrapper } from './get-wrappers';
-describe('getPackages', () => {
+describe('getWrappers', () => {
afterEach(() => {
mock.restore();
});
+ beforeAll(() => {
+ console.log('https://github.com/tschaub/mock-fs/issues/234');
+ });
+
beforeEach(() => {
mock({
'cli/a/package.json': JSON.stringify({ name: 'cli-a' }),
@@ -20,28 +23,30 @@ describe('getPackages', () => {
});
});
+ const getShape = (name: string, filePath: string): SourceWrapper => ({ contents: { name }, filePath });
+
it('prefers CLI', () => {
- const program = getMockCommander(['cli/*/package.json']);
- expect(getPackages(program)).toEqual([
- { data: { name: 'cli-a' }, path: 'cli/a/package.json' },
- { data: { name: 'cli-b' }, path: 'cli/b/package.json' },
+ const program = { source: ['cli/*/package.json'] };
+ expect(getWrappers(program)).toEqual([
+ getShape('cli-a', 'cli/a/package.json'),
+ getShape('cli-b', 'cli/b/package.json'),
]);
});
it('falls back to lerna.json if present', () => {
- const program = getMockCommander([]);
- expect(getPackages(program)).toEqual([
- { data: { name: 'lerna-a' }, path: 'lerna/a/package.json' },
- { data: { name: 'lerna-b' }, path: 'lerna/b/package.json' },
+ const program = { source: [] };
+ expect(getWrappers(program)).toEqual([
+ getShape('lerna-a', 'lerna/a/package.json'),
+ getShape('lerna-b', 'lerna/b/package.json'),
]);
});
it('resorts to defaults', () => {
- const program = getMockCommander([]);
+ const program = { source: [] };
removeSync('lerna.json');
- expect(getPackages(program)).toEqual([
- { data: { name: 'packages-a' }, path: 'packages/a/package.json' },
- { data: { name: 'packages-b' }, path: 'packages/b/package.json' },
+ expect(getWrappers(program)).toEqual([
+ getShape('packages-a', 'packages/a/package.json'),
+ getShape('packages-b', 'packages/b/package.json'),
]);
});
@@ -52,17 +57,17 @@ describe('getPackages', () => {
beforeEach(() => {
mock({
+ 'package.json': JSON.stringify({ workspaces: ['packages/*'] }),
'packages/a/package.json': JSON.stringify({ name: 'packages-a' }),
'packages/b/package.json': JSON.stringify({ name: 'packages-b' }),
- 'package.json': JSON.stringify({ workspaces: ['packages/*'] }),
});
});
it('should resolve correctly', () => {
- const program = getMockCommander([]);
- expect(getPackages(program)).toEqual([
- { data: { name: 'packages-a' }, path: 'packages/a/package.json' },
- { data: { name: 'packages-b' }, path: 'packages/b/package.json' },
+ const program = { source: [] };
+ expect(getWrappers(program)).toEqual([
+ getShape('packages-a', 'packages/a/package.json'),
+ getShape('packages-b', 'packages/b/package.json'),
]);
});
});
diff --git a/src/commands/lib/get-wrappers.ts b/src/commands/lib/get-wrappers.ts
new file mode 100644
index 00000000..03e25a9d
--- /dev/null
+++ b/src/commands/lib/get-wrappers.ts
@@ -0,0 +1,50 @@
+import { readJsonSync } from 'fs-extra';
+import { sync } from 'glob';
+import { join, resolve } from 'path';
+import { ALL_PATTERNS } from '../../constants';
+
+interface Options {
+ source: string[];
+}
+
+export interface Source {
+ bugs?: { url: string } | string;
+ dependencies?: { [key: string]: string };
+ description?: string;
+ devDependencies?: { [key: string]: string };
+ keywords?: string[];
+ name?: string;
+ peerDependencies?: { [key: string]: string };
+ repository?: { type: string; url: string } | string;
+ resolutions?: { [key: string]: string };
+ scripts?: { [key: string]: string };
+ version?: string;
+ [otherProps: string]: string | string[] | { [key: string]: string } | undefined;
+}
+
+export interface SourceWrapper {
+ filePath: string;
+ contents: Source;
+}
+
+const getPatternsFromConfig = (fileName: string, propName: string): string[] | null => {
+ const filePath = resolve(process.cwd(), fileName);
+ const config = readJsonSync(filePath, { throws: false });
+ const isNonEmptyArray = config && config[propName] && config[propName].length > 0;
+ return isNonEmptyArray ? config[propName].map((dirPath: string) => join(dirPath, 'package.json')) : null;
+};
+
+const hasCliPatterns = (program: Options) => program.source && program.source.length > 0;
+const getCliPatterns = (program: Options) => program.source;
+const getYarnPatterns = () => getPatternsFromConfig('package.json', 'workspaces');
+const getLernaPatterns = () => getPatternsFromConfig('lerna.json', 'packages');
+const getDefaultPatterns = () => ALL_PATTERNS;
+const resolvePattern = (pattern: string) => sync(pattern);
+const reduceFlatArray = (all: string[], next: string[]) => all.concat(next);
+const createWrapper = (filePath: string) => ({ contents: readJsonSync(filePath), filePath });
+
+export const getWrappers = (program: Options): SourceWrapper[] =>
+ (hasCliPatterns(program) ? getCliPatterns(program) : getYarnPatterns() || getLernaPatterns() || getDefaultPatterns())
+ .map(resolvePattern)
+ .reduce(reduceFlatArray, [])
+ .map(createWrapper);
diff --git a/src/commands/lib/is-semver.spec.ts b/src/commands/lib/is-semver.spec.ts
new file mode 100644
index 00000000..62678c55
--- /dev/null
+++ b/src/commands/lib/is-semver.spec.ts
@@ -0,0 +1,19 @@
+import 'expect-more-jest';
+import { isSemver } from './is-semver';
+
+describe('isSemver', () => {
+ it('returns whether a value is Semver', () => {
+ expect(isSemver('<1.2.3')).toBeTrue();
+ expect(isSemver('<=1.2.3')).toBeTrue();
+ expect(isSemver('1.2.3')).toBeTrue();
+ expect(isSemver('1.x.x')).toBeTrue();
+ expect(isSemver('1.2.x')).toBeTrue();
+ expect(isSemver('~1.2.3')).toBeTrue();
+ expect(isSemver('^1.2.3')).toBeTrue();
+ expect(isSemver('>=1.2.3')).toBeTrue();
+ expect(isSemver('>1.2.3')).toBeTrue();
+ expect(isSemver('*')).toBeFalse();
+ expect(isSemver('>=16.8.0 <17.0.0')).toBeFalse();
+ expect(isSemver('https://github.com/npm/npm.git')).toBeFalse();
+ });
+});
diff --git a/src/commands/lib/is-semver.ts b/src/commands/lib/is-semver.ts
new file mode 100644
index 00000000..bbcdae7a
--- /dev/null
+++ b/src/commands/lib/is-semver.ts
@@ -0,0 +1,28 @@
+import {
+ RANGE_EXACT,
+ RANGE_GT,
+ RANGE_GTE,
+ RANGE_LOOSE,
+ RANGE_LT,
+ RANGE_LTE,
+ RANGE_MINOR,
+ RANGE_PATCH,
+} from '../../constants';
+
+export const isValidSemverRange = (range: string) =>
+ range === RANGE_EXACT ||
+ range === RANGE_GT ||
+ range === RANGE_GTE ||
+ range === RANGE_LOOSE ||
+ range === RANGE_LT ||
+ range === RANGE_LTE ||
+ range === RANGE_MINOR ||
+ range === RANGE_PATCH;
+
+export const isSemver = (version: string) => {
+ return version.search(/^(~|\^|>=|>|<=|<|)?[0-9]+\.[0-9x]+\.[0-9x]+/) !== -1 && version.indexOf(' ') === -1;
+};
+
+export const isLooseSemver = (version: string) => {
+ return isSemver(version) && version.search(/\.x(\.|$)/) !== -1;
+};
diff --git a/src/commands/lib/log.ts b/src/commands/lib/log.ts
new file mode 100644
index 00000000..4a791c4d
--- /dev/null
+++ b/src/commands/lib/log.ts
@@ -0,0 +1 @@
+export const log = (...args: any[]) => console.log(...args);
diff --git a/src/commands/lib/write-if-changed.ts b/src/commands/lib/write-if-changed.ts
new file mode 100644
index 00000000..ae116777
--- /dev/null
+++ b/src/commands/lib/write-if-changed.ts
@@ -0,0 +1,20 @@
+import chalk from 'chalk';
+import { writeFileSync } from 'fs-extra';
+import { EOL } from 'os';
+import { relative } from 'path';
+import { SourceWrapper } from './get-wrappers';
+import { log } from './log';
+
+export const writeIfChanged = (indent: string, wrapper: SourceWrapper, mutateContents: (...args: any[]) => any) => {
+ const toJson = () => `${JSON.stringify(wrapper.contents, null, indent)}${EOL}`;
+ const shortPath = relative(process.cwd(), wrapper.filePath);
+ const before = toJson();
+ mutateContents();
+ const after = toJson();
+ if (before !== after) {
+ writeFileSync(wrapper.filePath, after);
+ log(chalk.green('✓'), shortPath);
+ } else {
+ log(chalk.dim('-', shortPath));
+ }
+};
diff --git a/src/commands/list-mismatches.spec.ts b/src/commands/list-mismatches.spec.ts
new file mode 100644
index 00000000..2a2fec5d
--- /dev/null
+++ b/src/commands/list-mismatches.spec.ts
@@ -0,0 +1,22 @@
+import * as mock from '../../test/mock';
+
+describe('listMismatches', () => {
+ let listMismatches: any;
+ let log: any;
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ beforeEach(() => {
+ jest.mock('./lib/log', () => ({ log: jest.fn() }));
+ listMismatches = require('./list-mismatches').listMismatches;
+ log = require('./lib/log').log;
+ });
+
+ it('outputs all dependencies installed with different versions', () => {
+ const wrappers = [mock.wrapper('a', ['foo@0.1.0']), mock.wrapper('b', ['foo@0.2.0'])];
+ listMismatches(['dependencies'], /./, wrappers);
+ expect(log.mock.calls).toMatchSnapshot();
+ });
+});
diff --git a/src/commands/list-mismatches.ts b/src/commands/list-mismatches.ts
new file mode 100644
index 00000000..00acebd5
--- /dev/null
+++ b/src/commands/list-mismatches.ts
@@ -0,0 +1,42 @@
+import chalk from 'chalk';
+import { DependencyType } from '../constants';
+import { getDependencyTypes } from './lib/get-dependency-types';
+import { getMismatchedDependencies, InstalledPackage, sortByName } from './lib/get-installations';
+import { getWrappers, SourceWrapper } from './lib/get-wrappers';
+import { log } from './lib/log';
+
+interface Options {
+ dev: boolean;
+ filter: RegExp;
+ peer: boolean;
+ prod: boolean;
+ source: string[];
+}
+
+export const listMismatches = (
+ dependencyTypes: DependencyType[],
+ filter: RegExp,
+ wrappers: SourceWrapper[],
+): InstalledPackage[] => {
+ const iterator = getMismatchedDependencies(dependencyTypes, wrappers);
+ const mismatches = Array.from(iterator).filter(({ name }) => name.search(filter) !== -1);
+
+ mismatches.sort(sortByName).forEach(({ name, installations }) => {
+ log(chalk`{red ✕ ${name}}`);
+ installations.forEach(({ source, type, version }) => {
+ log(chalk`{dim -} ${version} {dim in ${type} of ${source.contents.name}}`);
+ });
+ });
+
+ return mismatches;
+};
+
+export const listMismatchesFromDisk = ({ dev, filter, peer, prod, source }: Options) => {
+ const dependencyTypes = getDependencyTypes({ dev, peer, prod });
+ const wrappers = getWrappers({ source });
+ const mismatches = listMismatches(dependencyTypes, filter, wrappers);
+
+ if (mismatches.length > 0) {
+ process.exit(1);
+ }
+};
diff --git a/src/commands/list.spec.ts b/src/commands/list.spec.ts
new file mode 100644
index 00000000..65dff61d
--- /dev/null
+++ b/src/commands/list.spec.ts
@@ -0,0 +1,22 @@
+import * as mock from '../../test/mock';
+
+describe('list', () => {
+ let list: any;
+ let log: any;
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ beforeEach(() => {
+ jest.mock('./lib/log', () => ({ log: jest.fn() }));
+ list = require('./list').list;
+ log = require('./lib/log').log;
+ });
+
+ it('outputs all dependencies', () => {
+ const wrappers = [mock.wrapper('a', ['foo@0.1.0']), mock.wrapper('b', ['foo@0.2.0'])];
+ list(['dependencies'], /./, wrappers);
+ expect(log.mock.calls).toMatchSnapshot();
+ });
+});
diff --git a/src/commands/list.ts b/src/commands/list.ts
new file mode 100644
index 00000000..34c1eaed
--- /dev/null
+++ b/src/commands/list.ts
@@ -0,0 +1,37 @@
+import chalk from 'chalk';
+import { DependencyType } from '../constants';
+import { getDependencyTypes } from './lib/get-dependency-types';
+import { getDependencies, sortByName } from './lib/get-installations';
+import { getWrappers, SourceWrapper } from './lib/get-wrappers';
+import { log } from './lib/log';
+
+interface Options {
+ dev: boolean;
+ filter: RegExp;
+ peer: boolean;
+ prod: boolean;
+ source: string[];
+}
+
+export const list = (dependencyTypes: DependencyType[], filter: RegExp, wrappers: SourceWrapper[]) => {
+ const iterator = getDependencies(dependencyTypes, wrappers);
+ const packages = Array.from(iterator).filter(({ name }) => name.search(filter) !== -1);
+
+ packages.sort(sortByName).forEach(({ name, installations }) => {
+ const versions = installations.map(({ version }) => version);
+ const uniques = Array.from(new Set(versions));
+ const hasMismatches = uniques.length > 1;
+ const uniquesList = uniques.sort().join(', ');
+ const message = hasMismatches
+ ? chalk`{red ✕ ${name}} {dim.red ${uniquesList}}`
+ : chalk`{dim -} {white ${name}} {dim ${uniquesList}}`;
+ log(message);
+ });
+};
+
+export const listFromDisk = ({ dev, filter, peer, prod, source }: Options) => {
+ const dependencyTypes = getDependencyTypes({ dev, peer, prod });
+ const wrappers = getWrappers({ source });
+
+ list(dependencyTypes, filter, wrappers);
+};
diff --git a/src/commands/set-semver-ranges.spec.ts b/src/commands/set-semver-ranges.spec.ts
new file mode 100644
index 00000000..85a30169
--- /dev/null
+++ b/src/commands/set-semver-ranges.spec.ts
@@ -0,0 +1,78 @@
+import 'expect-more-jest';
+import * as mock from '../../test/mock';
+import { setSemverRange, setSemverRanges } from './set-semver-ranges';
+
+describe('setSemverRanges', () => {
+ it('sets all versions to use the supplied range', () => {
+ const range = '~';
+ const wrapper = mock.wrapper('a', ['foo@0.1.0', 'bar@2.0.0']);
+ setSemverRanges(range, ['dependencies'], /./, wrapper);
+ expect(wrapper).toEqual(mock.wrapper('a', ['foo@~0.1.0', 'bar@~2.0.0']));
+ });
+});
+
+describe('setSemverRange', () => {
+ describe('when the current value is Semver', () => {
+ it('sets its semver range to the given range', () => {
+ [
+ ['', '1.2.3'],
+ ['>', '>1.2.3'],
+ ['>=', '>=1.2.3'],
+ ['.x', '1.x.x'],
+ ['<', '<1.2.3'],
+ ['<=', '<=1.2.3'],
+ ['^', '^1.2.3'],
+ ['~', '~1.2.3'],
+ ].forEach(([range, expected]) => {
+ expect(setSemverRange(range, '<1.2.3')).toEqual(expected);
+ expect(setSemverRange(range, '<=1.2.3')).toEqual(expected);
+ expect(setSemverRange(range, '1.2.3')).toEqual(expected);
+ expect(setSemverRange(range, '~1.2.3')).toEqual(expected);
+ expect(setSemverRange(range, '^1.2.3')).toEqual(expected);
+ expect(setSemverRange(range, '>=1.2.3')).toEqual(expected);
+ expect(setSemverRange(range, '>1.2.3')).toEqual(expected);
+ expect(setSemverRange(range, '*')).toEqual('*');
+ expect(setSemverRange(range, 'https://github.com/npm/npm.git')).toEqual('https://github.com/npm/npm.git');
+ });
+ });
+ });
+ describe('when the current value contains a wildcard patch', () => {
+ it('sets its semver range to the given range', () => {
+ const current = '1.2.x';
+ expect(setSemverRange('', current)).toEqual('1.2.0');
+ expect(setSemverRange('>', current)).toEqual('>1.2.0');
+ expect(setSemverRange('>=', current)).toEqual('>=1.2.0');
+ expect(setSemverRange('.x', current)).toEqual('1.x.x');
+ expect(setSemverRange('<', current)).toEqual('<1.2.0');
+ expect(setSemverRange('<=', current)).toEqual('<=1.2.0');
+ expect(setSemverRange('^', current)).toEqual('^1.2.0');
+ expect(setSemverRange('~', current)).toEqual('~1.2.0');
+ });
+ });
+ describe('when the current value contains a wildcard minor and patch', () => {
+ it('sets its semver range to the given range', () => {
+ const current = '1.x.x';
+ expect(setSemverRange('', current)).toEqual('1.0.0');
+ expect(setSemverRange('>', current)).toEqual('>1.0.0');
+ expect(setSemverRange('>=', current)).toEqual('>=1.0.0');
+ expect(setSemverRange('.x', current)).toEqual(current);
+ expect(setSemverRange('<', current)).toEqual('<1.0.0');
+ expect(setSemverRange('<=', current)).toEqual('<=1.0.0');
+ expect(setSemverRange('^', current)).toEqual('^1.0.0');
+ expect(setSemverRange('~', current)).toEqual('~1.0.0');
+ });
+ });
+ describe('when the current value contains multiple versions', () => {
+ it('leaves the version unchanged', () => {
+ const current = '>=16.8.0 <17.0.0';
+ expect(setSemverRange('', current)).toEqual(current);
+ expect(setSemverRange('>', current)).toEqual(current);
+ expect(setSemverRange('>=', current)).toEqual(current);
+ expect(setSemverRange('.x', current)).toEqual(current);
+ expect(setSemverRange('<', current)).toEqual(current);
+ expect(setSemverRange('<=', current)).toEqual(current);
+ expect(setSemverRange('^', current)).toEqual(current);
+ expect(setSemverRange('~', current)).toEqual(current);
+ });
+ });
+});
diff --git a/src/commands/set-semver-ranges.ts b/src/commands/set-semver-ranges.ts
new file mode 100644
index 00000000..bef67245
--- /dev/null
+++ b/src/commands/set-semver-ranges.ts
@@ -0,0 +1,54 @@
+import { DependencyType, RANGE_LOOSE } from '../constants';
+import { getDependencyTypes } from './lib/get-dependency-types';
+import { getDependencies } from './lib/get-installations';
+import { getWrappers, SourceWrapper } from './lib/get-wrappers';
+import { isLooseSemver, isSemver, isValidSemverRange } from './lib/is-semver';
+import { writeIfChanged } from './lib/write-if-changed';
+
+interface Options {
+ dev: boolean;
+ filter: RegExp;
+ indent: string;
+ peer: boolean;
+ prod: boolean;
+ semverRange: string;
+ source: string[];
+}
+
+export const setSemverRange = (range: string, version: string) => {
+ if (!isSemver(version) || !isValidSemverRange(range)) {
+ return version;
+ }
+ const nextVersion = isLooseSemver(version) ? version.replace(/\.x/g, '.0') : version;
+ const from1stNumber = nextVersion.search(/[0-9]/);
+ const from1stDot = nextVersion.indexOf('.');
+ return range === RANGE_LOOSE
+ ? `${nextVersion.slice(from1stNumber, from1stDot)}.x.x`
+ : `${range}${nextVersion.slice(from1stNumber)}`;
+};
+
+export const setSemverRanges = (
+ semverRange: string,
+ dependencyTypes: DependencyType[],
+ filter: RegExp,
+ wrapper: SourceWrapper,
+) => {
+ const iterator = getDependencies(dependencyTypes, [wrapper]);
+ for (const installedPackage of iterator) {
+ if (installedPackage.name.search(filter) !== -1) {
+ for (const installation of installedPackage.installations) {
+ const { name, type, version } = installation;
+ installation.source.contents[type][name] = setSemverRange(semverRange, version);
+ }
+ }
+ }
+};
+
+export const setSemverRangesToDisk = ({ dev, filter, indent, peer, prod, semverRange, source }: Options) => {
+ const dependencyTypes = getDependencyTypes({ dev, peer, prod });
+ getWrappers({ source }).forEach((wrapper) => {
+ writeIfChanged(indent, wrapper, () => {
+ setSemverRanges(semverRange, dependencyTypes, filter, wrapper);
+ });
+ });
+};
diff --git a/src/constants.ts b/src/constants.ts
index fb13d879..2ac13d3b 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,10 +1,5 @@
-import { IManifestKey } from './typings';
-
-export const DEPENDENCY_TYPES: IManifestKey[] = [
- 'dependencies',
- 'devDependencies',
- 'peerDependencies',
-];
+export type DependencyType = 'dependencies' | 'devDependencies' | 'peerDependencies';
+export const DEPENDENCY_TYPES: DependencyType[] = ['dependencies', 'devDependencies', 'peerDependencies'];
export const SORT_AZ = [
'contributors',
@@ -17,10 +12,11 @@ export const SORT_AZ = [
];
export const SORT_FIRST = ['name', 'description', 'version', 'author'];
-export const VERSION = require('../package.json').version;
export const GREATER = 1;
export const LESSER = -1;
export const SAME = 0;
+
+export type ValidRange = '*' | '' | '>' | '>=' | '.x' | '<' | '<=' | '^' | '~';
export const RANGE_ANY = '*';
export const RANGE_EXACT = '';
export const RANGE_GT = '>';
@@ -31,7 +27,7 @@ export const RANGE_LTE = '<=';
export const RANGE_MINOR = '^';
export const RANGE_PATCH = '~';
-export const SEMVER_ORDER = [
+export const SEMVER_ORDER: ValidRange[] = [
RANGE_LT,
RANGE_LTE,
RANGE_EXACT,
@@ -46,71 +42,30 @@ const DEFAULT_INDENT = ' ';
const DEFAULT_SEMVER_RANGE = RANGE_EXACT;
const MONOREPO_PATTERN = 'package.json';
const PACKAGES_PATTERN = 'packages/*/package.json';
-const ALL_PATTERNS = [MONOREPO_PATTERN, PACKAGES_PATTERN];
-
-export const FIX_MISMATCHES = {
- command: 'fix-mismatches',
- description:
- 'set dependencies used with different versions to the same version',
-};
-
-export const FORMAT = {
- command: 'format',
- description: 'sort and shorten properties according to a convention',
-};
-
-export const LIST = {
- command: 'list',
- description: 'list every dependency used in your packages',
-};
-
-export const LIST_MISMATCHES = {
- command: 'list-mismatches',
- description:
- 'list every dependency used with different versions in your packages',
-};
-
-export const SET_SEMVER_RANGES = {
- command: 'set-semver-ranges',
- description: 'set semver ranges to the given format',
-};
-
-export const OPTION_SEMVER_RANGE = {
- default: DEFAULT_SEMVER_RANGE,
- description:
- `${RANGE_LT}, ${RANGE_LTE}, "${RANGE_EXACT}", ${RANGE_PATCH}, ${RANGE_MINOR}, ` +
- `${RANGE_GTE}, ${RANGE_GT}, or ${RANGE_ANY}. defaults to "${DEFAULT_SEMVER_RANGE}"`,
- spec: '-r, --semver-range ',
-};
-
-export const OPTION_SOURCES = {
- default: ALL_PATTERNS,
- description: 'glob pattern for package.json files to read from',
- spec: '-s, --source [pattern]',
-};
-
-export const OPTIONS_PROD = {
- description: 'include dependencies',
- spec: '-p, --prod',
-};
-
-export const OPTIONS_DEV = {
- description: 'include devDependencies',
- spec: '-d, --dev',
-};
-
-export const OPTIONS_PEER = {
- description: 'include peerDependencies',
- spec: '-P, --peer',
-};
-
-export const OPTIONS_FILTER_DEPENDENCIES = {
- description: 'regex for depdendency filter',
- spec: '-f, --filter [pattern]',
-};
-
-export const OPTION_INDENT = {
- default: DEFAULT_INDENT,
- description: `override indentation. defaults to "${DEFAULT_INDENT}"`,
- spec: '-i, --indent [value]',
+export const ALL_PATTERNS = [MONOREPO_PATTERN, PACKAGES_PATTERN];
+
+interface OptionsByName {
+ dev: [string, string];
+ filter: [string, string];
+ indent: [string, string];
+ peer: [string, string];
+ prod: [string, string];
+ semverRange: [string, string];
+ source: [string, string, (...args: any) => any];
+}
+
+export const option: OptionsByName = {
+ dev: ['-d, --dev', 'include devDependencies'],
+ filter: ['-f, --filter [pattern]', 'regex for dependency filter'],
+ indent: ['-i, --indent [value]', `override indentation. defaults to "${DEFAULT_INDENT}"`],
+ peer: ['-P, --peer', 'include peerDependencies'],
+ prod: ['-p, --prod', 'include dependencies'],
+ semverRange: ['-r, --semver-range ', `see supported ranges below. defaults to "${DEFAULT_SEMVER_RANGE}"`],
+ source: [
+ '-s, --source [pattern]',
+ 'glob pattern for package.json files to read from',
+ function collect(value: string, values: string[] = []) {
+ return [...values, value];
+ },
+ ],
};
diff --git a/src/fix-mismatches.spec.ts b/src/fix-mismatches.spec.ts
deleted file mode 100644
index 749d112f..00000000
--- a/src/fix-mismatches.spec.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { readJsonSync } from 'fs-extra';
-import { getFixture, getMockCommander } from '../test/helpers';
-import { run } from './fix-mismatches';
-import { IManifest } from './typings';
-import mock = require('mock-fs');
-
-describe('fix-mismatches', () => {
- let spyConsole: any;
-
- afterAll(() => {
- mock.restore();
- });
-
- beforeAll(async () => {
- const [one, two, three] = getFixture('exact').data as IManifest[];
- const sources = [
- '/path/1/package.json',
- '/path/2/package.json',
- '/path/3/package.json',
- ];
- const program = getMockCommander(sources, '^((?!ignore).)*$');
- mock({
- '/path/1/package.json': JSON.stringify(one),
- '/path/2/package.json': JSON.stringify(two),
- '/path/3/package.json': JSON.stringify(three),
- });
- const noop = () => undefined;
- spyConsole = jest.spyOn(console, 'log').mockImplementation(noop);
- await run(program);
- });
-
- it('sets the version of dependencies with different versions to the newest of those versions found', () => {
- expect(readJsonSync('/path/1/package.json')).toEqual(
- expect.objectContaining({
- dependencies: { chalk: '2.3.0', commander: '2.13.0', ignore: '1.0.0' },
- devDependencies: {
- jest: '22.1.4',
- prettier: '1.10.2',
- rimraf: '2.6.2',
- },
- peerDependencies: { gulp: '*' },
- }),
- );
- expect(readJsonSync('/path/2/package.json')).toEqual(
- expect.objectContaining({
- dependencies: { chalk: '2.3.0', ignore: '2.0.0' },
- devDependencies: { jest: '22.1.4' },
- }),
- );
- expect(readJsonSync('/path/3/package.json')).toEqual(
- expect.objectContaining({
- devDependencies: {
- npm: 'https://github.com/npm/npm.git',
- prettier: '1.10.2',
- },
- peerDependencies: { gulp: '*' },
- }),
- );
- });
-});
diff --git a/src/fix-mismatches.ts b/src/fix-mismatches.ts
deleted file mode 100644
index 8b7504fb..00000000
--- a/src/fix-mismatches.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import chalk from 'chalk';
-import { writeJson } from 'fs-extra';
-import _ = require('lodash');
-import { relative } from 'path';
-import {
- OPTION_INDENT,
- OPTION_SOURCES,
- OPTIONS_DEV,
- OPTIONS_FILTER_DEPENDENCIES,
- OPTIONS_PEER,
- OPTIONS_PROD,
-} from './constants';
-import { collect } from './lib/collect';
-import { getDependencyTypes } from './lib/get-dependency-types';
-import { getIndent } from './lib/get-indent';
-import { getPackages } from './lib/get-packages';
-import { getMismatchedVersionsByName } from './lib/get-versions-by-name';
-import { getNewest } from './lib/version';
-import { CommanderApi, IManifest } from './typings';
-
-export const run = async (program: CommanderApi) => {
- program
- .option(OPTION_SOURCES.spec, OPTION_SOURCES.description, collect)
- .option(OPTIONS_PROD.spec, OPTIONS_PROD.description)
- .option(OPTIONS_DEV.spec, OPTIONS_DEV.description)
- .option(OPTIONS_PEER.spec, OPTIONS_PEER.description)
- .option(OPTION_INDENT.spec, OPTION_INDENT.description)
- .option(
- OPTIONS_FILTER_DEPENDENCIES.spec,
- OPTIONS_FILTER_DEPENDENCIES.description,
- )
- .parse(process.argv);
-
- const pkgs = getPackages(program);
- const dependencyTypes = getDependencyTypes(program);
- const indent = getIndent(program);
- const mismatchedVersionsByName = getMismatchedVersionsByName(
- dependencyTypes,
- pkgs,
- program.filter,
- );
-
- await Promise.all(
- pkgs.map(({ data, path }) => {
- const nextData: IManifest = JSON.parse(JSON.stringify(data));
- const shortPath = `./${relative('.', path)}`;
- const changes: string[][] = [];
- Object.entries(mismatchedVersionsByName).forEach(([name, versions]) => {
- const newest = getNewest(versions);
- dependencyTypes.forEach((type) => {
- if (
- nextData[type] &&
- nextData[type][name] &&
- nextData[type][name] !== newest
- ) {
- changes.push([name, nextData[type][name], newest]);
- nextData[type][name] = newest;
- }
- });
- });
- if (changes.length > 0) {
- changes.forEach(([name, from, to]) => {
- console.log(
- chalk.bgYellow.black(' FIXED '),
- chalk.blue(shortPath),
- name,
- chalk.red(from),
- '→',
- chalk.green(to),
- );
- });
- return writeJson(path, nextData, { spaces: indent });
- }
- console.log(chalk.bgGreen.black(' VALID '), chalk.blue(shortPath));
- }),
- );
-};
diff --git a/src/format.spec.ts b/src/format.spec.ts
deleted file mode 100644
index 005ba411..00000000
--- a/src/format.spec.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { readJsonSync } from 'fs-extra';
-import { getFixture, getMockCommander, shuffleObject } from '../test/helpers';
-import { SORT_FIRST } from './constants';
-import { run } from './format';
-import { IManifest } from './typings';
-import mock = require('mock-fs');
-
-describe('format', () => {
- let minimal: IManifest;
- let untidy: IManifest;
-
- beforeAll(async () => {
- const program = getMockCommander([
- '/minimal/package.json',
- '/untidy/package.json',
- ]);
- mock({
- '/minimal/package.json': JSON.stringify({
- name: 'minimal',
- version: '0.0.0',
- }),
- '/untidy/package.json': getFixture('untidy', shuffleObject).json,
- });
- await run(program);
- minimal = readJsonSync('/minimal/package.json');
- untidy = readJsonSync('/untidy/package.json');
- mock.restore();
- });
-
- it('sorts specified keys to the top of package.json', () => {
- expect(Object.keys(untidy).slice(0, SORT_FIRST.length)).toEqual(SORT_FIRST);
- });
-
- it('sorts remaining keys alphabetically', () => {
- expect(Object.keys(untidy).slice(SORT_FIRST.length - 1)).toEqual([
- 'author',
- 'bin',
- 'bugs',
- 'dependencies',
- 'devDependencies',
- 'files',
- 'homepage',
- 'keywords',
- 'license',
- 'main',
- 'peerDependencies',
- 'repository',
- 'scripts',
- ]);
- });
-
- it('sorts "dependencies" alphabetically', () => {
- expect(minimal).not.toHaveProperty('dependencies');
- expect(untidy.dependencies).toEqual({
- arnold: '5.0.0',
- dog: '2.13.0',
- guybrush: '7.1.1',
- mango: '2.3.0',
- });
- });
-
- it('sorts "devDependencies" alphabetically', () => {
- expect(minimal).not.toHaveProperty('devDependencies');
- expect(untidy.devDependencies).toEqual({
- stroopwafel: '4.4.2',
- waldorf: '22.1.4',
- });
- });
-
- it('sorts "files" alphabetically', () => {
- expect(minimal).not.toHaveProperty('files');
- expect(untidy.files).toEqual(['assets', 'dist']);
- });
-
- it('sorts "keywords" alphabetically', () => {
- expect(minimal).not.toHaveProperty('keywords');
- expect(untidy.keywords).toEqual(['thing', 'those', 'whatsits']);
- });
-
- it('sorts "peerDependencies" alphabetically', () => {
- expect(minimal).not.toHaveProperty('peerDependencies');
- expect(untidy.peerDependencies).toEqual({
- giftwrap: '0.1.2',
- jambalaya: '6.1.4',
- zoolander: '1.4.25',
- });
- });
-
- it('sorts "scripts" alphabetically', () => {
- expect(minimal).not.toHaveProperty('scripts');
- expect(untidy.scripts).toEqual({
- build: 'tsc',
- format: 'prettier',
- lint: 'tslint',
- test: 'jest',
- });
- });
-
- it('uses shorthand "bugs"', () => {
- expect(minimal).not.toHaveProperty('bugs');
- expect(untidy.bugs).toEqual('https://github.com/JaneDoe/do-it/issues');
- });
-
- it('uses shorthand "repository"', () => {
- expect(minimal).not.toHaveProperty('repository');
- expect(untidy.repository).toEqual('JaneDoe/do-it');
- });
-});
diff --git a/src/format.ts b/src/format.ts
deleted file mode 100644
index 03459004..00000000
--- a/src/format.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import chalk from 'chalk';
-import { writeJson } from 'fs-extra';
-import _ = require('lodash');
-import { relative } from 'path';
-import {
- OPTION_INDENT,
- OPTION_SOURCES,
- SORT_AZ,
- SORT_FIRST,
-} from './constants';
-import { collect } from './lib/collect';
-import { getIndent } from './lib/get-indent';
-import { getPackages } from './lib/get-packages';
-import { CommanderApi, IManifest } from './typings';
-
-export const run = async (program: CommanderApi) => {
- const shortenBugs = (manifest: IManifest): IManifest => {
- const bugsUrl = _.get(manifest, 'bugs.url') as string;
- return bugsUrl ? { ...manifest, bugs: bugsUrl } : manifest;
- };
-
- const shortenRepository = (manifest: IManifest): IManifest => {
- const repoUrl = _.get(manifest, 'repository.url', '') as string;
- return repoUrl.includes('github.com')
- ? { ...manifest, repository: repoUrl.split('github.com/')[1] }
- : manifest;
- };
-
- const sortObject = (obj: object) =>
- _(obj)
- .entries()
- .sortBy('0')
- .reduce((next, [key, value]) => ({ ...next, [key]: value }), {});
-
- const sortValue = (value: any) =>
- _.isArray(value)
- ? value.slice(0).sort()
- : _.isObject(value)
- ? sortObject(value)
- : value;
-
- const sortManifest = (manifest: IManifest): IManifest => {
- const [first, rest] = _(manifest)
- .entries()
- .sortBy('0')
- .partition(([key, value]) => SORT_FIRST.indexOf(key) !== -1)
- .value();
-
- const firstSorted = [...first].sort(
- ([keyA], [keyB]) => SORT_FIRST.indexOf(keyA) - SORT_FIRST.indexOf(keyB),
- );
-
- const restSorted = _(rest)
- .map(([key, value]) => [
- key,
- SORT_AZ.indexOf(key) !== -1 ? sortValue(value) : value,
- ])
- .value();
-
- return _([...firstSorted, ...restSorted]).reduce(
- (obj, [key, value]) => ({ ...obj, [key]: value }),
- {} as IManifest,
- );
- };
-
- program
- .option(OPTION_SOURCES.spec, OPTION_SOURCES.description, collect)
- .option(OPTION_INDENT.spec, OPTION_INDENT.description)
- .parse(process.argv);
-
- const pkgs = getPackages(program);
- const indent = getIndent(program);
-
- await Promise.all(
- pkgs.map(({ data, path }) => {
- const nextData = sortManifest(shortenBugs(shortenRepository(data)));
- const hasChanged = JSON.stringify(nextData) !== JSON.stringify(data);
- const shortPath = `./${relative('.', path)}`;
- if (hasChanged) {
- console.log(chalk.bgYellow.black(' FIXED '), chalk.blue(shortPath));
- return writeJson(path, nextData, { spaces: indent });
- }
- console.log(chalk.bgGreen.black(' VALID '), chalk.blue(shortPath));
- }),
- );
-};
diff --git a/src/lib/collect.spec.ts b/src/lib/collect.spec.ts
deleted file mode 100644
index 59574434..00000000
--- a/src/lib/collect.spec.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { collect } from './collect';
-
-describe('collect', () => {
- it('concatenates values', () => {
- expect(collect('baz', ['foo', 'bar'])).toEqual(['foo', 'bar', 'baz']);
- });
-});
diff --git a/src/lib/collect.ts b/src/lib/collect.ts
deleted file mode 100644
index 1e84093c..00000000
--- a/src/lib/collect.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const collect = (value: string, values: string[] = []) => [
- ...values,
- value,
-];
diff --git a/src/lib/get-dependency-types.spec.ts b/src/lib/get-dependency-types.spec.ts
deleted file mode 100644
index 2f4abc0e..00000000
--- a/src/lib/get-dependency-types.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommanderApi } from '../typings';
-import { getDependencyTypes } from './get-dependency-types';
-
-describe('getDependencyTypes', () => {
- it('returns all if none are set or true if any are set', () => {
- [
- [
- { dev: false, peer: false, prod: false },
- ['dependencies', 'devDependencies', 'peerDependencies'],
- ],
- [{ dev: true, peer: false, prod: false }, ['devDependencies']],
- [{ dev: false, peer: true, prod: false }, ['peerDependencies']],
- [{ dev: false, peer: false, prod: true }, ['dependencies']],
- ].forEach(([program, expected]) => {
- expect(getDependencyTypes((program as unknown) as CommanderApi)).toEqual(
- expected,
- );
- });
- });
-});
diff --git a/src/lib/get-indent.ts b/src/lib/get-indent.ts
deleted file mode 100644
index 7e163c12..00000000
--- a/src/lib/get-indent.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { OPTION_INDENT } from '../constants';
-import { CommanderApi } from '../typings';
-
-export type GetIndent = (program: CommanderApi) => string;
-
-export const getIndent: GetIndent = (program) =>
- program.indent || OPTION_INDENT.default;
diff --git a/src/lib/get-packages.ts b/src/lib/get-packages.ts
deleted file mode 100644
index 302c5046..00000000
--- a/src/lib/get-packages.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { readJsonSync } from 'fs-extra';
-import { sync } from 'glob';
-import { join, resolve } from 'path';
-import { OPTION_SOURCES } from '../constants';
-import { CommanderApi, IManifestDescriptor } from '../typings';
-
-const getYarnWorkspaces = (): string[] | null => {
- const rootPackageJson = resolve(process.cwd(), 'package.json');
- const pkgJson = readJsonSync(rootPackageJson, { throws: false });
- if (pkgJson && pkgJson.workspaces && pkgJson.workspaces.length) {
- return pkgJson.workspaces.map((glob: string) => join(glob, 'package.json'));
- }
- return null;
-};
-
-const getLernaPackages = (): string[] | null => {
- const lernaPath = resolve(process.cwd(), 'lerna.json');
- const lerna = readJsonSync(lernaPath, { throws: false });
- if (lerna && lerna.packages && lerna.packages.length) {
- return lerna.packages.map((glob: string) => join(glob, 'package.json'));
- }
- return null;
-};
-
-export const getSources = (program: CommanderApi): string[] => {
- if (program.source && program.source.length) {
- return program.source;
- }
- return getYarnWorkspaces() || getLernaPackages() || OPTION_SOURCES.default;
-};
-
-export const getPackages = (program: CommanderApi): IManifestDescriptor[] =>
- getSources(program)
- .reduce(
- (filePaths, pattern) => filePaths.concat(sync(pattern)),
- [],
- )
- .map((filePath) => ({
- data: readJsonSync(filePath),
- path: filePath,
- }));
diff --git a/src/lib/get-versions-by-name.ts b/src/lib/get-versions-by-name.ts
deleted file mode 100644
index 63093e7a..00000000
--- a/src/lib/get-versions-by-name.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { IManifestDescriptor, IManifestKey } from '../typings';
-
-export interface IVersionsByName {
- [name: string]: string[];
-}
-
-export type GetVersionsByName = (
- dependencyTypes: IManifestKey[],
- pkgs: IManifestDescriptor[],
- dependencyFilterPattern?: string,
-) => IVersionsByName;
-
-export const getDependencyFilter = (dependencyFilterPattern?: string) => {
- if (!dependencyFilterPattern) {
- return () => true;
- }
- const dependencyMatcher = new RegExp(dependencyFilterPattern);
-
- return (dependencyName: string) => dependencyMatcher.test(dependencyName);
-};
-
-export const getVersionsByName: GetVersionsByName = (
- dependencyTypes,
- pkgs,
- dependencyFilterPattern,
-) => {
- const dependencyFilter = getDependencyFilter(dependencyFilterPattern);
- const versionsByName: IVersionsByName = {};
- for (const type of dependencyTypes) {
- for (const pkg of pkgs) {
- const dependencies = pkg.data[type];
- if (dependencies) {
- for (const name in dependencies) {
- if (!dependencyFilter(name)) {
- continue;
- }
- if (dependencies.hasOwnProperty(name)) {
- const version = dependencies[name];
- versionsByName[name] = versionsByName[name] || [];
- if (!versionsByName[name].includes(version)) {
- versionsByName[name].push(version);
- }
- }
- }
- }
- }
- }
- return versionsByName;
-};
-
-export const getMismatchedVersionsByName: GetVersionsByName = (
- dependencyTypes,
- pkgs,
- dependencyFilterPattern,
-) => {
- const dependencyFilter = getDependencyFilter(dependencyFilterPattern);
- const mismatchedVersionsByName: IVersionsByName = {};
- const versionsByName = getVersionsByName(
- dependencyTypes,
- pkgs,
- dependencyFilterPattern,
- );
- for (const type of dependencyTypes) {
- for (const pkg of pkgs) {
- const dependencies = pkg.data[type];
- if (dependencies) {
- for (const name in dependencies) {
- if (!dependencyFilter(name)) {
- continue;
- }
- if (dependencies.hasOwnProperty(name)) {
- if (versionsByName[name].length > 1) {
- mismatchedVersionsByName[name] = versionsByName[name];
- }
- }
- }
- }
- }
- }
- return mismatchedVersionsByName;
-};
diff --git a/src/lib/version.spec.ts b/src/lib/version.spec.ts
deleted file mode 100644
index 3059cef2..00000000
--- a/src/lib/version.spec.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import _ = require('lodash');
-import { getNewest, getVersionRange, sortBySemver } from './version';
-
-describe('getNewest', () => {
- it('returns the newest version from an array of versions', () => {
- const a = ['<1.0.0'];
- const b = _.shuffle([...a, '<=1.0.0']);
- const c = _.shuffle([...b, '1.0.0']);
- const d = _.shuffle([...c, '~1.0.0']);
- const e = _.shuffle([...d, '1.x.x']);
- const f = _.shuffle([...e, '^1.0.0']);
- const g = _.shuffle([...f, '>=1.0.0']);
- const h = _.shuffle([...g, '>1.0.0']);
- const i = _.shuffle([...h, '*']);
- const j = _.shuffle([...i, 'http://asdf.com/asdf.tar.gz']);
- const k = _.shuffle([...j, 'file:../foo/bar']);
- const l = _.shuffle([...k, 'latest']);
- const m = _.shuffle([...l, 'git+ssh://git@github.com:npm/cli.git#v1.0.27']);
- const n = _.shuffle([...m, 'git+ssh://git@github.com:npm/cli#semver:^5.0']);
- const o = _.shuffle([...n, 'git+https://isaacs@github.com/npm/cli.git']);
- const p = _.shuffle([...o, 'git://github.com/npm/cli.git#v1.0.27']);
- const q = _.shuffle([...p, 'expressjs/express']);
- const r = _.shuffle([...q, 'mochajs/mocha#4727d357ea']);
- const s = _.shuffle([...r, 'user/repo#feature/branch']);
- // valid semver
- expect(getNewest(a)).toEqual('<1.0.0');
- expect(getNewest(b)).toEqual('<=1.0.0');
- expect(getNewest(c)).toEqual('1.0.0');
- expect(getNewest(d)).toEqual('~1.0.0');
- expect(getNewest(e)).toEqual('1.x.x');
- expect(getNewest(f)).toEqual('^1.0.0');
- expect(getNewest(g)).toEqual('>=1.0.0');
- expect(getNewest(h)).toEqual('>1.0.0');
- expect(getNewest(i)).toEqual('*');
- // invalid semver
- expect(getNewest(j)).toEqual('*');
- expect(getNewest(k)).toEqual('*');
- expect(getNewest(l)).toEqual('*');
- expect(getNewest(m)).toEqual('*');
- expect(getNewest(n)).toEqual('*');
- expect(getNewest(o)).toEqual('*');
- expect(getNewest(p)).toEqual('*');
- expect(getNewest(q)).toEqual('*');
- expect(getNewest(r)).toEqual('*');
- expect(getNewest(s)).toEqual('*');
- });
-});
-
-describe('getVersionRange', () => {
- it('gets the version range from a semver dependency', () => {
- [
- ['*', '*'],
- ['>1.0.0', '>'],
- ['>=1.0.0', '>='],
- ['^1.0.0', '^'],
- ['~1.0.0', '~'],
- ['1.x.x', '.x'],
- ['1.0.0', ''],
- ['<=1.0.0', '<='],
- ['<1.0.0', '<'],
- ['http://asdf.com/asdf.tar.gz', ''],
- ['file:../foo/bar', ''],
- ['latest', ''],
- ['git+ssh://git@github.com:npm/cli.git#v1.0.27', ''],
- ['git+ssh://git@github.com:npm/cli#semver:^5.0', ''],
- ['git+https://isaacs@github.com/npm/cli.git', ''],
- ['git://github.com/npm/cli.git#v1.0.27', ''],
- ['expressjs/express"', ''],
- ['mochajs/mocha#4727d357ea"', ''],
- ['user/repo#feature/branch', ''],
- ].forEach(([version, expected]) => {
- expect(getVersionRange(version)).toEqual(expected);
- });
- });
-});
-
-describe('sortBySemver', () => {
- it('returns an ordered array of semver versions', () => {
- _.times(10, () => {
- const unordered = [
- 'http://asdf.com/asdf.tar.gz',
- 'file:../foo/bar',
- 'latest',
- 'git+ssh://git@github.com:npm/cli.git#v1.0.27',
- 'git+ssh://git@github.com:npm/cli#semver:^5.0',
- 'git+https://isaacs@github.com/npm/cli.git',
- 'git://github.com/npm/cli.git#v1.0.27',
- 'expressjs/express',
- 'mochajs/mocha#4727d357ea',
- 'user/repo#feature/branch',
- '^1.0.0',
- '^1.0.1',
- '^1.0.0',
- '1.0.0',
- '1.0.1',
- '1.0.0',
- '*',
- '1.x.x',
- '*',
- '<=1.0.0',
- '<=1.0.1',
- '<=1.0.0',
- '>1.0.0',
- '>1.0.1',
- '>1.0.0',
- '>=1.0.0',
- '>=1.0.1',
- '>=1.0.0',
- '<1.0.0',
- '<1.0.1',
- '<1.0.0',
- '~1.0.0',
- '~1.0.1',
- '~1.0.0',
- ];
- const expectedOrder = [
- '<1.0.0',
- '<1.0.0',
- '<=1.0.0',
- '<=1.0.0',
- '1.0.0',
- '1.0.0',
- '~1.0.0',
- '~1.0.0',
- '^1.0.0',
- '^1.0.0',
- '1.x.x',
- '>=1.0.0',
- '>=1.0.0',
- '>1.0.0',
- '>1.0.0',
- '<1.0.1',
- '<=1.0.1',
- '1.0.1',
- '~1.0.1',
- '^1.0.1',
- '>=1.0.1',
- '>1.0.1',
- '*',
- '*',
- ];
- const shuffled = _.shuffle(unordered);
- expect(sortBySemver(shuffled)).toEqual(expectedOrder);
- });
- });
-});
diff --git a/src/lib/version.ts b/src/lib/version.ts
deleted file mode 100644
index 61ad3604..00000000
--- a/src/lib/version.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import _ = require('lodash');
-import * as semver from 'semver';
-import { GREATER, LESSER, SAME, SEMVER_ORDER } from '../constants';
-
-const isSemver = (version: string) =>
- version === '*' ||
- version.search(/^(<|<=|~|\^|>=|>)?([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)/) !== -1;
-
-export const getNewest = (versions: string[]): string => {
- const sorted = sortBySemver(versions);
- return sorted[sorted.length - 1];
-};
-
-export const getVersionNumber = (version: string): string =>
- version.slice(version.search(/[0-9]/), version.length);
-
-export const getVersionRange = (version: string): string => {
- if (isSemver(version)) {
- return version.includes('.x') ? '.x' : version.split(/[0-9]/)[0];
- }
- return '';
-};
-
-export const sortBySemver = (versions: string[]): string[] =>
- versions
- .concat()
- .filter(isSemver)
- .sort()
- .sort((a: string, b: string) => {
- if (a === '*') {
- return GREATER;
- }
- if (b === '*') {
- return LESSER;
- }
- if (a === b) {
- return SAME;
- }
- let aRange = getVersionRange(a);
- let bRange = getVersionRange(b);
- let aNumber = getVersionNumber(a);
- let bNumber = getVersionNumber(b);
- if (aNumber.indexOf('.x') !== -1) {
- aNumber = aNumber.split('.x').join('.0');
- aRange = '^';
- }
- if (bNumber.indexOf('.x') !== -1) {
- bNumber = bNumber.split('.x').join('.0');
- bRange = '^';
- }
- if (semver.gt(aNumber, bNumber)) {
- return GREATER;
- }
- if (semver.lt(aNumber, bNumber)) {
- return LESSER;
- }
- if (SEMVER_ORDER.indexOf(aRange) > SEMVER_ORDER.indexOf(bRange)) {
- return GREATER;
- }
- if (SEMVER_ORDER.indexOf(aRange) < SEMVER_ORDER.indexOf(bRange)) {
- return LESSER;
- }
- return SAME;
- });
diff --git a/src/list-mismatches.spec.ts b/src/list-mismatches.spec.ts
deleted file mode 100644
index b8a0bb98..00000000
--- a/src/list-mismatches.spec.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import mock = require('mock-fs');
-import { getFixture, getMockCommander } from '../test/helpers';
-import { run } from './list-mismatches';
-import { IManifest } from './typings';
-
-describe('list-mismatches', () => {
- let spyConsole: any;
- let spyProcess: any;
-
- beforeAll(async () => {
- const [one, two, three] = getFixture('exact').data as IManifest[];
- const program = getMockCommander(
- ['/path/1/package.json', '/path/2/package.json', '/path/3/package.json'],
- '^((?!ignore).)*$',
- );
- mock({
- '/path/1/package.json': JSON.stringify(one),
- '/path/2/package.json': JSON.stringify(two),
- '/path/3/package.json': JSON.stringify(three),
- });
- const noop = () => undefined;
- spyConsole = jest.spyOn(console, 'log').mockImplementation(noop);
- spyProcess = jest.spyOn(process, 'exit').mockImplementation(noop as any);
- await run(program);
- mock.restore();
- });
-
- it('returns an index of dependencies used with different versions', () => {
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('gulp'),
- expect.stringContaining('0.9.1, *'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('chalk'),
- expect.stringContaining('2.3.0, 1.0.0'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('jest'),
- expect.stringContaining('22.1.3, 22.1.4'),
- );
- expect(spyConsole).not.toHaveBeenCalledWith(
- expect.stringContaining('ignore'),
- );
- });
-
- it('returns an error exit code when mismatches are found', () => {
- expect(spyProcess).toHaveBeenCalledWith(1);
- });
-
- it('returns exit code 0 when no mismatches are found', async () => {
- const program = getMockCommander([
- '/bar/package.json',
- '/foo/package.json',
- ]);
- mock({
- '/bar/package.json': JSON.stringify({
- dependencies: { bar: '1.0.0' },
- name: 'bar',
- }),
- '/foo/package.json': JSON.stringify({
- dependencies: { bar: '1.0.0' },
- name: 'foo',
- }),
- });
- spyProcess.mockClear();
- await run(program);
- mock.restore();
- expect(spyProcess).not.toHaveBeenCalled();
- });
-});
diff --git a/src/list-mismatches.ts b/src/list-mismatches.ts
deleted file mode 100644
index 18df8f62..00000000
--- a/src/list-mismatches.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import chalk from 'chalk';
-import _ = require('lodash');
-import {
- OPTION_SOURCES,
- OPTIONS_DEV,
- OPTIONS_FILTER_DEPENDENCIES,
- OPTIONS_PEER,
- OPTIONS_PROD,
-} from './constants';
-import { collect } from './lib/collect';
-import { getDependencyTypes } from './lib/get-dependency-types';
-import { getPackages } from './lib/get-packages';
-import { getMismatchedVersionsByName } from './lib/get-versions-by-name';
-import { CommanderApi } from './typings';
-
-export const run = async (program: CommanderApi) => {
- program
- .option(OPTION_SOURCES.spec, OPTION_SOURCES.description, collect)
- .option(OPTIONS_PROD.spec, OPTIONS_PROD.description)
- .option(OPTIONS_DEV.spec, OPTIONS_DEV.description)
- .option(OPTIONS_PEER.spec, OPTIONS_PEER.description)
- .option(
- OPTIONS_FILTER_DEPENDENCIES.spec,
- OPTIONS_FILTER_DEPENDENCIES.description,
- )
- .parse(process.argv);
- const dependencyTypes = getDependencyTypes(program);
- const pkgs = getPackages(program);
- const mismatchedVersionsByName = getMismatchedVersionsByName(
- dependencyTypes,
- pkgs,
- program.filter,
- );
-
- _.each(mismatchedVersionsByName, (versions, name) => {
- console.log(chalk.yellow(name), chalk.dim(versions.join(', ')));
- });
-
- if (Object.keys(mismatchedVersionsByName).length) {
- process.exit(1);
- }
-};
diff --git a/src/list.spec.ts b/src/list.spec.ts
deleted file mode 100644
index ee42b7c1..00000000
--- a/src/list.spec.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import mock = require('mock-fs');
-import { getFixture, getMockCommander } from '../test/helpers';
-import { run } from './list';
-import { IManifest } from './typings';
-
-describe('list', () => {
- let spyConsole: any;
-
- beforeAll(async () => {
- const [one, two, three] = getFixture('exact').data as IManifest[];
- const program = getMockCommander(
- ['/path/1/package.json', '/path/2/package.json', '/path/3/package.json'],
- '^((?!ignore).)*$',
- );
- mock({
- '/path/1/package.json': JSON.stringify(one),
- '/path/2/package.json': JSON.stringify(two),
- '/path/3/package.json': JSON.stringify(three),
- });
- const noop = () => undefined;
- spyConsole = jest.spyOn(console, 'log').mockImplementation(noop);
- await run(program);
- mock.restore();
- });
-
- it('returns an index of dependencies used with different versions', () => {
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('chalk'),
- expect.stringContaining('2.3.0, 1.0.0'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('commander'),
- expect.stringContaining('2.13.0'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('jest'),
- expect.stringContaining('22.1.3, 22.1.4'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('prettier'),
- expect.stringContaining('1.10.2'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('rimraf'),
- expect.stringContaining('2.6.2'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('npm'),
- expect.stringContaining('https://github.com/npm/npm.git'),
- );
- expect(spyConsole).toHaveBeenCalledWith(
- expect.stringContaining('gulp'),
- expect.stringContaining('0.9.1, *'),
- );
- expect(spyConsole).not.toHaveBeenCalledWith(
- expect.stringContaining('ignore'),
- );
- });
-});
diff --git a/src/list.ts b/src/list.ts
deleted file mode 100644
index 3b57314f..00000000
--- a/src/list.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import chalk from 'chalk';
-import _ = require('lodash');
-import {
- OPTION_SOURCES,
- OPTIONS_DEV,
- OPTIONS_FILTER_DEPENDENCIES,
- OPTIONS_PEER,
- OPTIONS_PROD,
-} from './constants';
-import { collect } from './lib/collect';
-import { getDependencyTypes } from './lib/get-dependency-types';
-import { getPackages } from './lib/get-packages';
-import { getVersionsByName } from './lib/get-versions-by-name';
-import { CommanderApi } from './typings';
-
-export const run = async (program: CommanderApi) => {
- program
- .option(OPTION_SOURCES.spec, OPTION_SOURCES.description, collect)
- .option(OPTIONS_PROD.spec, OPTIONS_PROD.description)
- .option(OPTIONS_DEV.spec, OPTIONS_DEV.description)
- .option(OPTIONS_PEER.spec, OPTIONS_PEER.description)
- .option(
- OPTIONS_FILTER_DEPENDENCIES.spec,
- OPTIONS_FILTER_DEPENDENCIES.description,
- )
- .parse(process.argv);
-
- const dependencyTypes = getDependencyTypes(program);
- const pkgs = getPackages(program);
- const versionsByName = getVersionsByName(
- dependencyTypes,
- pkgs,
- program.filter,
- );
-
- _(versionsByName)
- .entries()
- .sortBy('0')
- .each(([name, versions]) => {
- if (versions.length > 1) {
- console.log(chalk.yellow(name), chalk.dim(versions.join(', ')));
- } else {
- console.log(chalk.blue(name), chalk.dim(versions[0]));
- }
- });
-};
diff --git a/src/set-semver-ranges.spec.ts b/src/set-semver-ranges.spec.ts
deleted file mode 100644
index 9cdd6d38..00000000
--- a/src/set-semver-ranges.spec.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { readJsonSync } from 'fs-extra';
-import { getFixture, getMockCommander } from '../test/helpers';
-import {
- RANGE_ANY,
- RANGE_EXACT,
- RANGE_GT,
- RANGE_GTE,
- RANGE_LOOSE,
- RANGE_LT,
- RANGE_LTE,
- RANGE_MINOR,
- RANGE_PATCH,
-} from './constants';
-import { run } from './set-semver-ranges';
-import mock = require('mock-fs');
-
-describe('set-semver-ranges', () => {
- const ranges = [
- [RANGE_ANY, 'any'],
- [RANGE_EXACT, 'exact'],
- [RANGE_GT, 'gt'],
- [RANGE_GTE, 'gte'],
- [RANGE_LOOSE, 'loose'],
- [RANGE_LT, 'lt'],
- [RANGE_LTE, 'lte'],
- [RANGE_MINOR, 'minor'],
- [RANGE_PATCH, 'patch'],
- ].map(([range, name]) => ({
- data: getFixture(name).data[0],
- filePath: `/path/${name}/package.json`,
- range,
- }));
- const unsupported = [RANGE_ANY, RANGE_LOOSE];
- const sources = ranges.map(({ filePath }) => filePath);
- const filesystem = ranges.reduce(
- (obj, { filePath, data }) => ({ ...obj, [filePath]: JSON.stringify(data) }),
- {},
- );
-
- ranges.forEach(({ data: expectedData, range: targetRange }) => {
- ranges
- .filter(({ range: sourceRange }) => sourceRange !== targetRange)
- .filter(({ range: sourceRange }) => !unsupported.includes(sourceRange))
- .forEach(({ filePath, range: sourceRange }) => {
- it(`sets "${sourceRange}" semver ranges to the "${targetRange}" format`, async () => {
- const program = getMockCommander(sources, '^((?!ignore).)*$');
- program.semverRange = targetRange;
- mock(filesystem);
- const noop = () => undefined;
- const spyConsole = jest
- .spyOn(console, 'log')
- .mockImplementation(noop);
- await run(program);
- expect(readJsonSync(filePath)).toEqual(expectedData);
- mock.restore();
- });
- });
- });
-});
diff --git a/src/set-semver-ranges.ts b/src/set-semver-ranges.ts
deleted file mode 100644
index 1ff68d7e..00000000
--- a/src/set-semver-ranges.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import chalk from 'chalk';
-import { writeJson } from 'fs-extra';
-import _ = require('lodash');
-import { relative } from 'path';
-import * as semver from 'semver';
-import {
- OPTION_INDENT,
- OPTION_SEMVER_RANGE,
- OPTION_SOURCES,
- OPTIONS_DEV,
- OPTIONS_FILTER_DEPENDENCIES,
- OPTIONS_PEER,
- OPTIONS_PROD,
- RANGE_ANY,
- RANGE_LOOSE,
-} from './constants';
-import { collect } from './lib/collect';
-import { getDependencyTypes } from './lib/get-dependency-types';
-import { getIndent } from './lib/get-indent';
-import { getPackages } from './lib/get-packages';
-import { getDependencyFilter } from './lib/get-versions-by-name';
-import { getVersionNumber } from './lib/version';
-import { CommanderApi } from './typings';
-
-export const run = async (program: CommanderApi) => {
- program
- .option(OPTION_SEMVER_RANGE.spec, OPTION_SEMVER_RANGE.description)
- .option(OPTION_SOURCES.spec, OPTION_SOURCES.description, collect)
- .option(OPTIONS_PROD.spec, OPTIONS_PROD.description)
- .option(OPTIONS_DEV.spec, OPTIONS_DEV.description)
- .option(OPTIONS_PEER.spec, OPTIONS_PEER.description)
- .option(
- OPTIONS_FILTER_DEPENDENCIES.spec,
- OPTIONS_FILTER_DEPENDENCIES.description,
- )
- .option(OPTION_INDENT.spec, OPTION_INDENT.description)
- .parse(process.argv);
-
- const semverRange: string =
- program.semverRange || OPTION_SEMVER_RANGE.default;
-
- const pkgs = getPackages(program);
- const dependencyTypes = getDependencyTypes(program);
- const indent = getIndent(program);
- const dependencyFilter = getDependencyFilter(program.filter);
- _(pkgs).each((pkg) =>
- _(dependencyTypes)
- .map((property) => pkg.data[property])
- .filter(Boolean)
- .each((dependencies) => {
- _(dependencies).each((version, name) => {
- if (!dependencyFilter(name)) {
- return;
- }
- const versionNumber = getVersionNumber(version)
- .split('.x')
- .join('.0');
- if (version !== '*' && semver.validRange(version)) {
- if (semverRange === RANGE_ANY) {
- dependencies[name] = '*';
- } else if (semverRange === RANGE_LOOSE) {
- dependencies[name] =
- semver.major(versionNumber) === 0
- ? `${semver.major(versionNumber)}.${semver.minor(
- versionNumber,
- )}.x`
- : `${semver.major(versionNumber)}.x.x`;
- } else {
- dependencies[name] = `${semverRange}${versionNumber}`;
- }
- }
- });
- }),
- );
-
- await Promise.all(
- pkgs.map(({ data, path }) => writeJson(path, data, { spaces: indent })),
- );
-
- _.each(pkgs, (pkg) => {
- console.log(chalk.blue(`./${relative('.', pkg.path)}`));
- });
-};
diff --git a/src/syncpack.spec.ts b/src/syncpack.spec.ts
deleted file mode 100644
index b244add8..00000000
--- a/src/syncpack.spec.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { sync } from 'glob';
-import { getMockCommander } from '../test/helpers';
-import { run } from './syncpack';
-
-it('registers each command', () => {
- const commands = [
- ...sync('bin-*.ts', { cwd: __dirname }),
- ...sync('!*.spec.ts', { cwd: __dirname }),
- ];
- const program = getMockCommander([]);
- const spy = jest.spyOn(program, 'command');
- const commandNames = commands.map((basename) =>
- basename.replace(/bin\-|\.ts/g, ''),
- );
-
- run(program);
- expect(commands.length).toBeGreaterThan(0);
- expect(spy).toHaveBeenCalledTimes(commands.length);
-
- commandNames.forEach((name) => {
- expect(spy.mock.calls).toEqual(
- expect.arrayContaining([
- expect.arrayContaining([name, expect.any(String)]),
- ]),
- );
- });
-});
diff --git a/src/syncpack.ts b/src/syncpack.ts
deleted file mode 100644
index 19b61edb..00000000
--- a/src/syncpack.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import {
- FIX_MISMATCHES,
- FORMAT,
- LIST,
- LIST_MISMATCHES,
- SET_SEMVER_RANGES,
- VERSION,
-} from './constants';
-import { CommanderApi } from './typings';
-
-export const run = (program: CommanderApi) => {
- program
- .version(VERSION)
- .command(FIX_MISMATCHES.command, FIX_MISMATCHES.description)
- .command(FORMAT.command, FORMAT.description)
- .command(LIST.command, LIST.description, { isDefault: true })
- .command(LIST_MISMATCHES.command, LIST_MISMATCHES.description)
- .command(SET_SEMVER_RANGES.command, SET_SEMVER_RANGES.description);
-
- program.parse(process.argv);
-};
diff --git a/src/typings.ts b/src/typings.ts
deleted file mode 100644
index 7e2f02c8..00000000
--- a/src/typings.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { CommanderStatic } from 'commander';
-
-export interface IDictionary {
- [key: string]: T;
-}
-
-export type IManifestKey =
- | 'dependencies'
- | 'devDependencies'
- | 'peerDependencies';
-
-export interface IManifest {
- dependencies: IDictionary;
- devDependencies: IDictionary;
- peerDependencies: IDictionary;
- [otherProps: string]: string | IDictionary;
-}
-
-export interface IFileDescriptor {
- path: string;
- data: object;
-}
-
-export interface IManifestDescriptor {
- path: string;
- data: IManifest;
-}
-
-// export interface IMockCommander {
-// command: (...args: any[]) => jest.SpyInstance;
-// option: (...args: any[]) => jest.SpyInstance;
-// parse: (...args: any[]) => jest.SpyInstance;
-// source: string[];
-// }
-
-export type CommanderApi = CommanderStatic; // | IMockCommander;
diff --git a/test/fixtures/any.json b/test/fixtures/any.json
deleted file mode 100644
index a5291077..00000000
--- a/test/fixtures/any.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": "*",
- "commander": "*",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": "*",
- "prettier": "*",
- "rimraf": "*"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": "*"
- }
- },
- {
- "dependencies": {
- "chalk": "*"
- },
- "devDependencies": {
- "jest": "*"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": "*"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/exact.json b/test/fixtures/exact.json
deleted file mode 100644
index 9d848c36..00000000
--- a/test/fixtures/exact.json
+++ /dev/null
@@ -1,38 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": "2.3.0",
- "commander": "2.13.0",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": "22.1.3",
- "prettier": "1.10.2",
- "rimraf": "2.6.2"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": "0.9.1"
- }
- },
- {
- "dependencies": {
- "chalk": "1.0.0",
- "ignore": "2.0.0"
- },
- "devDependencies": {
- "jest": "22.1.4"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": "1.10.2"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
diff --git a/test/fixtures/gt.json b/test/fixtures/gt.json
deleted file mode 100644
index dea67074..00000000
--- a/test/fixtures/gt.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": ">2.3.0",
- "commander": ">2.13.0",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": ">22.1.3",
- "prettier": ">1.10.2",
- "rimraf": ">2.6.2"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": ">0.9.1"
- }
- },
- {
- "dependencies": {
- "chalk": ">1.0.0"
- },
- "devDependencies": {
- "jest": ">22.1.4"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": ">1.10.2"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/gte.json b/test/fixtures/gte.json
deleted file mode 100644
index 00aeb744..00000000
--- a/test/fixtures/gte.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": ">=2.3.0",
- "commander": ">=2.13.0",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": ">=22.1.3",
- "prettier": ">=1.10.2",
- "rimraf": ">=2.6.2"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": ">=0.9.1"
- }
- },
- {
- "dependencies": {
- "chalk": ">=1.0.0"
- },
- "devDependencies": {
- "jest": ">=22.1.4"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": ">=1.10.2"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/loose.json b/test/fixtures/loose.json
deleted file mode 100644
index ee5ea62d..00000000
--- a/test/fixtures/loose.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": "2.x.x",
- "commander": "2.x.x",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": "22.x.x",
- "prettier": "1.x.x",
- "rimraf": "2.x.x"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": "0.9.x"
- }
- },
- {
- "dependencies": {
- "chalk": "1.x.x"
- },
- "devDependencies": {
- "jest": "22.x.x"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": "1.x.x"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/lt.json b/test/fixtures/lt.json
deleted file mode 100644
index 0b9a6867..00000000
--- a/test/fixtures/lt.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": "<2.3.0",
- "commander": "<2.13.0",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": "<22.1.3",
- "prettier": "<1.10.2",
- "rimraf": "<2.6.2"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": "<0.9.1"
- }
- },
- {
- "dependencies": {
- "chalk": "<1.0.0"
- },
- "devDependencies": {
- "jest": "<22.1.4"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": "<1.10.2"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/lte.json b/test/fixtures/lte.json
deleted file mode 100644
index 285da84d..00000000
--- a/test/fixtures/lte.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": "<=2.3.0",
- "commander": "<=2.13.0",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": "<=22.1.3",
- "prettier": "<=1.10.2",
- "rimraf": "<=2.6.2"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": "<=0.9.1"
- }
- },
- {
- "dependencies": {
- "chalk": "<=1.0.0"
- },
- "devDependencies": {
- "jest": "<=22.1.4"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": "<=1.10.2"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/minor.json b/test/fixtures/minor.json
deleted file mode 100644
index f15ffad4..00000000
--- a/test/fixtures/minor.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": "^2.3.0",
- "commander": "^2.13.0",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": "^22.1.3",
- "prettier": "^1.10.2",
- "rimraf": "^2.6.2"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": "^0.9.1"
- }
- },
- {
- "dependencies": {
- "chalk": "^1.0.0"
- },
- "devDependencies": {
- "jest": "^22.1.4"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": "^1.10.2"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/patch.json b/test/fixtures/patch.json
deleted file mode 100644
index a657684b..00000000
--- a/test/fixtures/patch.json
+++ /dev/null
@@ -1,37 +0,0 @@
-[
- {
- "dependencies": {
- "chalk": "~2.3.0",
- "commander": "~2.13.0",
- "ignore": "1.0.0"
- },
- "devDependencies": {
- "jest": "~22.1.3",
- "prettier": "~1.10.2",
- "rimraf": "~2.6.2"
- },
- "name": "foo",
- "peerDependencies": {
- "gulp": "~0.9.1"
- }
- },
- {
- "dependencies": {
- "chalk": "~1.0.0"
- },
- "devDependencies": {
- "jest": "~22.1.4"
- },
- "name": "bar"
- },
- {
- "devDependencies": {
- "npm": "https://github.com/npm/npm.git",
- "prettier": "~1.10.2"
- },
- "name": "baz",
- "peerDependencies": {
- "gulp": "*"
- }
- }
-]
\ No newline at end of file
diff --git a/test/fixtures/untidy.json b/test/fixtures/untidy.json
deleted file mode 100644
index ae035321..00000000
--- a/test/fixtures/untidy.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{
- "files": [
- "assets",
- "dist"
- ],
- "bugs": {
- "url": "https://github.com/JaneDoe/do-it/issues"
- },
- "author": "Jane Doe ",
- "devDependencies": {
- "waldorf": "22.1.4",
- "stroopwafel": "4.4.2"
- },
- "scripts": {
- "test": "jest",
- "build": "tsc",
- "lint": "tslint",
- "format": "prettier"
- },
- "version": "1.0.2",
- "main": "do-it",
- "license": "MIT",
- "description": "Does the thing",
- "homepage": "https://github.com/JaneDoe/do-it#readme",
- "dependencies": {
- "guybrush": "7.1.1",
- "arnold": "5.0.0",
- "dog": "2.13.0",
- "mango": "2.3.0"
- },
- "name": "do-it",
- "repository": {
- "url": "git://github.com/JaneDoe/do-it",
- "type": "git"
- },
- "keywords": [
- "those",
- "whatsits",
- "thing"
- ],
- "bin": {
- "zoo": "dist/zoo.js",
- "moose": "dist/moose.js",
- "apple": "dist/apple.js"
- },
- "peerDependencies": {
- "jambalaya": "6.1.4",
- "giftwrap": "0.1.2",
- "zoolander": "1.4.25"
- }
-}
\ No newline at end of file
diff --git a/test/helpers.ts b/test/helpers.ts
deleted file mode 100644
index bb7ea9a0..00000000
--- a/test/helpers.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { readJsonSync } from 'fs-extra';
-import _ = require('lodash');
-import { CommanderApi } from '../src/typings';
-
-const shuffle = (value: any): typeof value =>
- _.isArray(value)
- ? _.shuffle(value)
- : _.isObject(value)
- ? shuffleObject(value)
- : value;
-
-export const shuffleObject = (obj: object): object =>
- _(obj)
- .entries()
- .map(([key, value]) => [key, shuffle(value)])
- .shuffle()
- .reduce((next, [key, value]) => ({ ...next, [key]: value }), {});
-
-export const getFixture = (name: string, transform = (a: any | any[]) => a) => {
- const fixturePath = `${__dirname}/fixtures/${name}.json`;
- const data = transform(readJsonSync(fixturePath));
- const json = JSON.stringify(data, null, 2);
- return { data, json };
-};
-
-export const getMockCommander = (
- source: string[],
- dependencyFilterString?: string,
-) => {
- const program = ({
- command: () => program,
- filter: dependencyFilterString,
- option: () => program,
- parse: () => program,
- source,
- version: () => program,
- } as unknown) as CommanderApi;
- return program;
-};
diff --git a/test/mock.ts b/test/mock.ts
new file mode 100644
index 00000000..30acf6f8
--- /dev/null
+++ b/test/mock.ts
@@ -0,0 +1,19 @@
+import { SourceWrapper } from '../src/commands/lib/get-wrappers';
+
+const toObject = (identifiers: string[]) =>
+ identifiers.reduce((memo: any, dep) => {
+ const [name, version] = dep.split('@');
+ memo[name] = version;
+ return memo;
+ }, {});
+
+export const wrapper = (dirName: string, deps: string[], devDeps?: string[], peerDeps?: string[]): SourceWrapper => {
+ return {
+ contents: {
+ ...(deps ? { dependencies: toObject(deps) } : {}),
+ ...(devDeps ? { devDependencies: toObject(devDeps) } : {}),
+ ...(peerDeps ? { peerDependencies: toObject(peerDeps) } : {}),
+ },
+ filePath: `/${dirName}/package.json`,
+ };
+};
diff --git a/test/setup.ts b/test/setup.ts
deleted file mode 100644
index fd44bc53..00000000
--- a/test/setup.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import 'expect-more-jest';
-
-console.log('https://github.com/tschaub/mock-fs/issues/234');
diff --git a/tsconfig.json b/tsconfig.json
index b9407d43..fe60e919 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"declaration": true,
+ "downlevelIteration": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es2017", "es6"],
diff --git a/tslint.json b/tslint.json
index 4ea4eaf5..4f886300 100644
--- a/tslint.json
+++ b/tslint.json
@@ -1,6 +1,8 @@
{
"extends": "tslint:recommended",
"rules": {
+ "forin": [false],
+ "interface-name": [false],
"no-console": [false],
"no-var-requires": [false],
"quotemark": [true, "single"],