Skip to content

Commit

Permalink
feat(cli): add prompt to fix unsupported mismatches
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMason committed Jun 4, 2023
1 parent 96d6c6d commit 296fad5
Show file tree
Hide file tree
Showing 37 changed files with 631 additions and 259 deletions.
27 changes: 16 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"syncpack-lint-semver-ranges": "dist/bin-lint-semver-ranges/index.js",
"syncpack-list": "dist/bin-list/index.js",
"syncpack-list-mismatches": "dist/bin-list-mismatches/index.js",
"syncpack-prompt": "dist/bin-prompt/index.js",
"syncpack-set-semver-ranges": "dist/bin-set-semver-ranges/index.js"
},
"bugs": "https://github.com/JamieMason/syncpack/issues",
Expand All @@ -32,33 +33,34 @@
"chalk": "4.1.2",
"commander": "10.0.1",
"cosmiconfig": "8.1.3",
"enquirer": "2.3.6",
"fs-extra": "11.1.1",
"glob": "8.1.0",
"minimatch": "6.2.0",
"glob": "10.2.6",
"minimatch": "9.0.1",
"read-yaml-file": "2.1.0",
"semver": "7.5.0",
"semver": "7.5.1",
"tightrope": "0.1.0",
"ts-toolbelt": "9.6.0"
},
"devDependencies": {
"@tsconfig/node14": "1.0.3",
"@types/fs-extra": "11.0.1",
"@types/glob": "8.1.0",
"@types/jest": "29.5.1",
"@types/jest": "29.5.2",
"@types/node": "14.18.36",
"@types/semver": "7.3.13",
"@typescript-eslint/eslint-plugin": "5.59.0",
"@typescript-eslint/parser": "5.59.0",
"eslint": "8.39.0",
"@types/semver": "7.5.0",
"@typescript-eslint/eslint-plugin": "5.59.8",
"@typescript-eslint/parser": "5.59.8",
"eslint": "8.42.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jest": "27.2.1",
"expect-more-jest": "5.5.0",
"jest": "29.5.0",
"prettier": "2.8.8",
"rimraf": "4.4.1",
"rimraf": "5.0.1",
"ts-jest": "29.1.0",
"ts-node": "10.9.1",
"typescript": "5.0.4"
"typescript": "5.1.3"
},
"engines": {
"node": ">=14"
Expand Down Expand Up @@ -91,7 +93,10 @@
"main": "dist/index.js",
"repository": "JamieMason/syncpack",
"resolutions": {
"chalk": "4.1.2"
"chalk": "4.1.2",
"string-width": "<5.0.0",
"strip-ansi": "<7.0.0",
"wrap-ansi": "<8.0.0"
},
"scripts": {
"build": "rm -rf ./dist && tsc --project .",
Expand Down
34 changes: 34 additions & 0 deletions site/docs/prompt.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
id: prompt
title: prompt
---

Displays a series of prompts to fix mismatches which syncpack cannot fix
automatically.

## CLI Options

```
-s, --source [pattern] glob pattern for package.json files to read from
-f, --filter [pattern] only include dependencies whose name matches this regex
-c, --config <path> path to a syncpack config file
-t, --types <names> only include dependencies matching these types (eg. types=dev,prod,myCustomType)
-h, --help display help for command
```

## Examples

```bash
# uses defaults for resolving packages
syncpack prompt
# uses packages defined by --source when provided
syncpack prompt --source "apps/*/package.json"
# multiple globs can be provided like this
syncpack prompt --source "apps/*/package.json" --source "core/*/package.json"
# uses dependencies regular expression defined by --filter when provided
syncpack prompt --filter "typescript|tslint"
# only inspect "devDependencies"
syncpack prompt --types dev
# only inspect "devDependencies" and "peerDependencies"
syncpack prompt --types dev,peer
```
1 change: 1 addition & 0 deletions site/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const sidebars = {
'lint',
'list-mismatches',
'list',
'prompt',
'set-semver-ranges',
],
'CLI Options': [
Expand Down
10 changes: 8 additions & 2 deletions src/bin-list/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ICON } from '../constants';
import type { Context } from '../get-context';
import type { Instance } from '../get-package-json-files/instance';
import { getVersionGroups } from '../get-version-groups';
import { getUniqueVersions } from '../get-version-groups/lib/get-unique-versions';
import { isSupported } from '../lib/is-semver';
import * as log from '../lib/log';
import { sortByName } from '../lib/sort-by-name';

Expand Down Expand Up @@ -70,8 +72,12 @@ export function list(ctx: Context): Context {
chalk`{red %s %s} %s`,
ICON.cross,
report.name,
report.instances
.map((instance) => chalk.red(instance.version))
getUniqueVersions(report.instances)
.map((version) =>
isSupported(version)
? chalk.red(version)
: chalk.yellow(version),
)
.join(chalk.dim(', ')),
);
break;
Expand Down
62 changes: 62 additions & 0 deletions src/bin-prompt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env node

import chalk from 'chalk';
import { program } from 'commander';
import { disk } from '../lib/disk';
import { showHelpOnError } from '../lib/show-help-on-error';
import { option } from '../option';
import { promptCli } from './prompt-cli';

program.description(
' displays a series of prompts to fix mismatches which syncpack cannot fix automatically',
);

program.on('--help', () => {
console.log(chalk`
Examples:
{dim # uses defaults for resolving packages}
syncpack prompt
{dim # uses packages defined by --source when provided}
syncpack prompt --source {yellow "apps/*/package.json"}
{dim # multiple globs can be provided like this}
syncpack prompt --source {yellow "apps/*/package.json"} --source {yellow "core/*/package.json"}
{dim # uses dependencies regular expression defined by --filter when provided}
syncpack prompt --filter {yellow "typescript|tslint"}
{dim # only inspect "devDependencies"}
syncpack prompt --types dev
{dim # only inspect "devDependencies" and "peerDependencies"}
syncpack prompt --types dev,peer
Resolving Packages:
1. If {yellow --source} globs are provided, use those.
2. If using Pnpm Workspaces, read {yellow packages} from {yellow pnpm-workspace.yaml} in the root of the project.
3. If using Yarn Workspaces, read {yellow workspaces} from {yellow package.json}.
4. If using Lerna, read {yellow packages} from {yellow lerna.json}.
5. 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}
Pnpm Workspaces {blue.underline https://pnpm.js.org/en/workspaces}
`);
});

showHelpOnError(program);

program
.option(...option.source)
.option(...option.filter)
.option(...option.config)
.option(...option.types)
.parse(process.argv);

promptCli(
{
configPath: program.opts().config,
filter: program.opts().filter,
source: program.opts().source,
types: program.opts().types,
},
disk,
);
51 changes: 51 additions & 0 deletions src/bin-prompt/prompt-cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import chalk from 'chalk';

import type { CliConfig } from '../config/types';
import { getContext } from '../get-context';
import { getVersionGroups } from '../get-version-groups';
import { getUniqueVersions } from '../get-version-groups/lib/get-unique-versions';
import type { Disk } from '../lib/disk';
import { sortByName } from '../lib/sort-by-name';
import { writeIfChanged } from '../lib/write-if-changed';

export async function promptCli(
input: Partial<CliConfig>,
disk: Disk,
): Promise<void> {
const ctx = getContext(input, disk);
const versionGroups = getVersionGroups(ctx);

for (const versionGroup of versionGroups) {
const reports = versionGroup.inspect().sort(sortByName);
for (const report of reports) {
switch (report.status) {
case 'SAME_RANGE_MISMATCH':
case 'UNSUPPORTED_MISMATCH': {
const OTHER = chalk.dim('Other');
const SKIP = chalk.dim('Skip this dependency');
const chosenVersion = await disk.askForChoice({
message: chalk`${report.name} {dim Choose a version to replace the others}`,
choices: [...getUniqueVersions(report.instances), OTHER, SKIP],
});

if (chosenVersion === SKIP) {
continue;
} else if (chosenVersion === OTHER) {
const newVersion = await disk.askForInput({
message: chalk`${report.name} {dim Enter a new version to replace the others}`,
});
report.instances.forEach((instance) => {
instance.setVersion(newVersion);
});
} else {
report.instances.forEach((instance) => {
instance.setVersion(chosenVersion);
});
}
}
}
}
}

writeIfChanged(ctx);
}
3 changes: 3 additions & 0 deletions src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ program
executableFile: './bin-list/index.js',
isDefault: true,
})
.command('prompt', 'fix mismatches which syncpack cannot fix automatically', {
executableFile: './bin-prompt/index.js',
})
.command('set-semver-ranges', 'set semver ranges to the given format', {
executableFile: './bin-set-semver-ranges/index.js',
})
Expand Down
2 changes: 1 addition & 1 deletion src/get-semver-groups/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import minimatch from 'minimatch';
import { minimatch } from 'minimatch';
import { isArrayOfStrings } from 'tightrope/guard/is-array-of-strings';
import { isNonEmptyArray } from 'tightrope/guard/is-non-empty-array';
import { isNonEmptyString } from 'tightrope/guard/is-non-empty-string';
Expand Down
2 changes: 1 addition & 1 deletion src/get-version-groups/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import minimatch from 'minimatch';
import { minimatch } from 'minimatch';
import { isArrayOfStrings } from 'tightrope/guard/is-array-of-strings';
import { isNonEmptyArray } from 'tightrope/guard/is-non-empty-array';
import { isNonEmptyString } from 'tightrope/guard/is-non-empty-string';
Expand Down
1 change: 0 additions & 1 deletion src/get-version-groups/standard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export class StandardVersionGroup {
const wsFile = wsInstance?.packageJsonFile;
const wsVersion = wsFile?.contents?.version;
const isWorkspacePackage = wsInstance && wsVersion;

if (isWorkspacePackage) {
const nonWsInstances = getNonWorkspaceInstances(instances);
if (!hasMismatch(nonWsInstances)) {
Expand Down
39 changes: 27 additions & 12 deletions src/lib/disk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { cosmiconfigSync } from 'cosmiconfig';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Select *does* exist
import { Input, Select } from 'enquirer';
import {
readFileSync,
readJsonSync,
Expand All @@ -12,14 +15,18 @@ import { isNonEmptyObject } from 'tightrope/guard/is-non-empty-object';
import type { O } from 'ts-toolbelt';
import type { RcConfig } from '../config/types';
import { CWD } from '../constants';
import type { Context } from '../get-context';
import { verbose } from './log';

export type Disk = {
askForChoice: (opts: {
message: string;
choices: string[];
}) => Promise<string>;
askForInput: (opts: { message: string }) => Promise<string>;
globSync: (pattern: string) => string[];
process: {
exit: (code: number) => void;
};
globSync: (pattern: string) => string[];
readConfigFileSync: (configPath?: string) => O.Partial<RcConfig, 'deep'>;
readFileSync: (filePath: string) => string;
readYamlFileSync: <T = unknown>(filePath: string) => T;
Expand All @@ -30,21 +37,29 @@ export type Disk = {
const client = cosmiconfigSync('syncpack');

export const disk: Disk = {
process: {
exit(code: number): void {
verbose('exit(', code, ')');
process.exit(code);
},
askForChoice({ message, choices }) {
return new Select({ name: 'choice', message, choices })
.run()
.catch(console.error);
},
globSync(pattern: string): string[] {
askForInput({ message }) {
return new Input({ message }).run().catch(console.error);
},
globSync(pattern) {
verbose('globSync(', pattern, ')');
return globSync(pattern, {
ignore: '**/node_modules/**',
absolute: true,
cwd: CWD,
});
},
readConfigFileSync(configPath?: string): Context['config']['rcFile'] {
process: {
exit(code) {
verbose('exit(', code, ')');
process.exit(code);
},
},
readConfigFileSync(configPath) {
verbose('readConfigFileSync(', configPath, ')');
try {
const result = configPath ? client.load(configPath) : client.search();
Expand All @@ -66,19 +81,19 @@ export const disk: Disk = {
return {};
}
},
readFileSync(filePath: string): string {
readFileSync(filePath) {
verbose('readFileSync(', filePath, ')');
return readFileSync(filePath, { encoding: 'utf8' });
},
readYamlFileSync<T = unknown>(filePath: string): T {
verbose('readYamlFileSync(', filePath, ')');
return readYamlSync<T>(filePath);
},
removeSync(filePath: string): void {
removeSync(filePath) {
verbose('removeSync(', filePath, ')');
removeSync(filePath);
},
writeFileSync(filePath: string, contents: string): void {
writeFileSync(filePath, contents) {
verbose('writeFileSync(', filePath, contents, ')');
writeFileSync(filePath, contents);
},
Expand Down
4 changes: 4 additions & 0 deletions test/mock-disk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export interface MockDisk {
readonly askForChoice: jest.Mock<any, any>;
readonly askForInput: jest.Mock<any, any>;
readonly globSync: jest.Mock<any, any>;
readonly process: {
exit: jest.Mock<any, any>;
Expand All @@ -12,6 +14,8 @@ export interface MockDisk {

export function mockDisk(): MockDisk {
return {
askForChoice: jest.fn(() => Promise.resolve()),
askForInput: jest.fn(() => Promise.resolve()),
globSync: jest.fn(() => []),
process: {
exit: jest.fn(() => []),
Expand Down
2 changes: 1 addition & 1 deletion test/scenarios/lib/create-scenario.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import minimatch from 'minimatch';
import { minimatch } from 'minimatch';
import { join, normalize } from 'path';
import { CWD } from '../../../src/constants';
import type { Context } from '../../../src/get-context';
Expand Down

0 comments on commit 296fad5

Please sign in to comment.