Skip to content

Commit bae1133

Browse files
committed
feat(core): add format command
1 parent 2a3ce5d commit bae1133

12 files changed

Lines changed: 281 additions & 8 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"author": "Jamie Mason <jamie@foldleft.io> (https://github.com/JamieMason)",
66
"bin": {
77
"syncpack": "dist/bin.js",
8+
"syncpack-format": "dist/bin-format.js",
89
"syncpack-fix-mismatches": "dist/bin-fix-mismatches.js",
910
"syncpack-list-mismatches": "dist/bin-list-mismatches.js",
1011
"syncpack-list": "dist/bin-list.js"

src/bin-format.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env node
2+
3+
import chalk from 'chalk';
4+
import * as program from 'commander';
5+
import * as _ from 'lodash';
6+
import { relative } from 'path';
7+
import { DEFAULT_PATTERN, OPTION_PACKAGES } from './constants';
8+
import { format } from './manifests';
9+
10+
program.option(OPTION_PACKAGES.spec, OPTION_PACKAGES.description).parse(process.argv);
11+
12+
const { packages = DEFAULT_PATTERN } = program;
13+
14+
format(packages).then((descriptors) => {
15+
_.each(descriptors, (descriptor) => {
16+
console.log(chalk.blue(`./${relative('.', descriptor.path)}`));
17+
});
18+
});

src/bin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/usr/bin/env node
22

33
import * as program from 'commander';
4-
import { FIX_MISMATCHES, LIST, LIST_MISMATCHES } from './constants';
4+
import { FIX_MISMATCHES, FORMAT, LIST, LIST_MISMATCHES } from './constants';
55

66
program
77
.version('TODO')
88
.command(FIX_MISMATCHES.name, FIX_MISMATCHES.description)
9+
.command(FORMAT.name, FORMAT.description)
910
.command(LIST.name, LIST.description, { isDefault: true })
1011
.command(LIST_MISMATCHES.name, LIST_MISMATCHES.description);
1112

src/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export const FIX_MISMATCHES = {
44
description: 'set dependencies used with different versions to the same version',
55
name: 'fix-mismatches'
66
};
7+
export const FORMAT = {
8+
description: 'sort and shorten properties according to a convention',
9+
name: 'format'
10+
};
711
export const LIST = {
812
description: 'list every dependency used in your packages',
913
name: 'list'
@@ -22,3 +26,6 @@ export const OPTION_PACKAGES = {
2226
};
2327
export const SAME = 0;
2428
export const SEMVER_ORDER = ['<', '<=', '', '~', '^', '>=', '>', '*'];
29+
export const SORT_AZ = ['dependencies', 'devDependencies', 'files', 'keywords', 'peerDependencies', 'scripts'];
30+
export const SORT_FIRST = ['name', 'description', 'version', 'author'];
31+
export const VERSION = execSync(`npm view ${__dirname} version`, { encoding: 'utf8' });

src/manifests/index.spec.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { readFileSync } from 'fs';
22
import * as mock from 'mock-fs';
33
import { createFile, createManifest, createMockDescriptor, createMockFs } from '../../test/helpers';
44
import { IManifest, IManifestDescriptor } from '../typings';
5-
import { getMismatchedVersions, getVersions, setVersion, setVersionRange, setVersionsToNewestMismatch } from './index';
5+
import {
6+
format,
7+
getMismatchedVersions,
8+
getVersions,
9+
setVersion,
10+
setVersionRange,
11+
setVersionsToNewestMismatch
12+
} from './index';
613

714
const pattern = '/Users/you/Dev/monorepo/packages/*/package.json';
815

@@ -23,6 +30,15 @@ beforeEach(() => {
2330
});
2431
});
2532

33+
describe('format', () => {
34+
it('sorts and shortens properties according to a convention', async () => {
35+
const result = await format(pattern);
36+
result.forEach((descriptor: IManifestDescriptor) => {
37+
expect(Object.keys(descriptor.data)).toEqual(['name', 'dependencies', 'devDependencies', 'peerDependencies']);
38+
});
39+
});
40+
});
41+
2642
describe('getMismatchedVersions', () => {
2743
it('returns an index of dependencies used with different versions', async () => {
2844
const result = await getMismatchedVersions(pattern);

src/manifests/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { IDictionary, IManifest, IManifestDescriptor } from '../typings';
33
import { getManifests } from './get-manifests';
44
import { manifestData } from './manifest-data';
55

6+
export type Format = (pattern: string) => Promise<IManifestDescriptor[]>;
67
export type GetMismatchedVersions = (pattern: string) => Promise<IDictionary<string[]>>;
78
export type GetVersions = (pattern: string) => Promise<IDictionary<string[]>>;
89
export type SetVersion = (name: string, version: string, pattern: string) => Promise<IManifestDescriptor[]>;
@@ -11,6 +12,20 @@ export type SetVersionsToNewestMismatch = (pattern: string) => Promise<IManifest
1112

1213
const unwrap = (descriptors: IManifestDescriptor[]) => descriptors.map((descriptor) => descriptor.data);
1314

15+
export const format: Format = (pattern) =>
16+
getManifests(pattern)
17+
.then((descriptors) => {
18+
const data = unwrap(descriptors);
19+
const nextData = manifestData.format(data);
20+
return descriptors.map((descriptor, i) => ({
21+
data: nextData[i],
22+
path: descriptor.path
23+
}));
24+
})
25+
.then((descriptors) =>
26+
Promise.all(descriptors.map((descriptor) => writeJson(descriptor.path, descriptor.data))).then(() => descriptors)
27+
);
28+
1429
export const getMismatchedVersions: GetMismatchedVersions = (pattern) =>
1530
getManifests(pattern)
1631
.then(unwrap)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { getUntidyManifest } from '../../../test/fixtures';
2+
import { shuffleObject } from '../../../test/helpers';
3+
import { SORT_FIRST } from '../../constants';
4+
import { IManifest } from '../../typings';
5+
import { manifestData } from './index';
6+
7+
describe('format', () => {
8+
let results: IManifest[];
9+
beforeAll(() => {
10+
results = manifestData.format([shuffleObject(getUntidyManifest()) as IManifest]);
11+
});
12+
13+
it('sorts specified keys to the top of package.json', () => {
14+
results.forEach((result) => {
15+
expect(Object.keys(result).slice(0, SORT_FIRST.length)).toEqual(SORT_FIRST);
16+
});
17+
});
18+
19+
it('sorts remaining keys alphabetically', () => {
20+
results.forEach((result) => {
21+
expect(Object.keys(result).slice(SORT_FIRST.length - 1)).toEqual([
22+
'author',
23+
'bin',
24+
'bugs',
25+
'dependencies',
26+
'devDependencies',
27+
'files',
28+
'homepage',
29+
'keywords',
30+
'license',
31+
'main',
32+
'peerDependencies',
33+
'repository',
34+
'scripts'
35+
]);
36+
});
37+
});
38+
39+
it('sorts "dependencies" alphabetically', () => {
40+
results.forEach((result) => {
41+
expect(result.dependencies).toEqual({
42+
arnold: '5.0.0',
43+
dog: '2.13.0',
44+
guybrush: '7.1.1',
45+
mango: '2.3.0'
46+
});
47+
});
48+
});
49+
50+
it('sorts "devDependencies" alphabetically', () => {
51+
results.forEach((result) => {
52+
expect(result.devDependencies).toEqual({ stroopwafel: '4.4.2', waldorf: '22.1.4' });
53+
});
54+
});
55+
56+
it('sorts "files" alphabetically', () => {
57+
results.forEach((result) => {
58+
expect(result.files).toEqual(['assets', 'dist']);
59+
});
60+
});
61+
62+
it('sorts "keywords" alphabetically', () => {
63+
results.forEach((result) => {
64+
expect(result.keywords).toEqual(['thing', 'those', 'whatsits']);
65+
});
66+
});
67+
68+
it('sorts "peerDependencies" alphabetically', () => {
69+
results.forEach((result) => {
70+
expect(result.peerDependencies).toEqual({ giftwrap: '0.1.2', jambalaya: '6.1.4', zoolander: '1.4.25' });
71+
});
72+
});
73+
74+
it('sorts "scripts" alphabetically', () => {
75+
results.forEach((result) => {
76+
expect(result.scripts).toEqual({ build: 'tsc', format: 'prettier', lint: 'tslint', test: 'jest' });
77+
});
78+
});
79+
80+
it('uses shorthand "bugs"', () => {
81+
results.forEach((result) => {
82+
expect(result.bugs).toEqual('https://github.com/JaneDoe/do-it/issues');
83+
});
84+
});
85+
86+
it('uses shorthand "repository"', () => {
87+
results.forEach((result) => {
88+
expect(result.repository).toEqual('JaneDoe/do-it');
89+
});
90+
});
91+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as _ from 'lodash';
2+
import { SORT_AZ, SORT_FIRST } from '../../constants';
3+
import { IDictionary, IManifest } from '../../typings';
4+
5+
export type Format = (manifests: IManifest[]) => IManifest[];
6+
export type ManifestMapper = (manifest: IManifest) => IManifest;
7+
8+
const shortenBugs: ManifestMapper = (manifest: IManifest) => {
9+
if (manifest.bugs && typeof manifest.bugs === 'object' && manifest.bugs.url) {
10+
return {
11+
...manifest,
12+
bugs: manifest.bugs.url
13+
};
14+
}
15+
return manifest;
16+
};
17+
18+
const shortenRepository: ManifestMapper = (manifest) => {
19+
if (
20+
manifest.repository &&
21+
typeof manifest.repository === 'object' &&
22+
manifest.repository.url &&
23+
manifest.repository.url.indexOf('github.com') !== -1
24+
) {
25+
return {
26+
...manifest,
27+
repository: manifest.repository.url.split('github.com/')[1]
28+
};
29+
}
30+
return manifest;
31+
};
32+
33+
const sortObject = (obj: IManifest) =>
34+
_(obj)
35+
.entries()
36+
.sortBy('0')
37+
.reduce((next, [key, value]) => ({ ...next, [key]: value }), {});
38+
39+
const sortValue = (value: any) =>
40+
_.isArray(value) ? value.slice(0).sort() : _.isObject(value) ? sortObject(value) : value;
41+
42+
const sortManifest: ManifestMapper = (manifest) => {
43+
const [first, rest] = _(manifest)
44+
.entries()
45+
.sortBy('0')
46+
.partition(([key, value]) => SORT_FIRST.indexOf(key) !== -1)
47+
.value();
48+
const firstSorted = [...first].sort(([keyA], [keyB]) => SORT_FIRST.indexOf(keyA) - SORT_FIRST.indexOf(keyB));
49+
const restSorted = _(rest)
50+
.map(([key, value]) => [key, SORT_AZ.indexOf(key) !== -1 ? sortValue(value) : value])
51+
.value();
52+
return _([...firstSorted, ...restSorted]).reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {} as IManifest);
53+
};
54+
55+
export const format: Format = (manifests) =>
56+
_.map(manifests, (manifest) => sortManifest(shortenBugs(shortenRepository(manifest))));
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { createManifest } from '../../test/helpers';
2-
import { IManifest } from '../typings';
3-
import { manifestData } from './manifest-data';
1+
import { createManifest } from '../../../test/helpers';
2+
import { IManifest } from '../../typings';
3+
import { manifestData } from './index';
44

55
const { getMismatchedVersions, getVersions, setVersion, setVersionRange, setVersionsToNewestMismatch } = manifestData;
66

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import * as _ from 'lodash';
22
import * as semver from 'semver';
3-
import { DEPENDENCY_TYPES } from '../constants';
4-
import { IDictionary, IManifest } from '../typings';
5-
import { getNewest, isValid } from '../version';
3+
import { DEPENDENCY_TYPES } from '../../constants';
4+
import { IDictionary, IManifest } from '../../typings';
5+
import { getNewest, isValid } from '../../version';
6+
import { format } from './format';
67

78
export type GetMismatchedVersions = (manifests: IManifest[]) => IDictionary<string[]>;
89
export type GetVersions = (manifests: IManifest[]) => IDictionary<string[]>;
@@ -94,6 +95,7 @@ const setVersionsToNewestMismatch: SetVersionsToNewestMismatch = (manifests) =>
9495
};
9596

9697
export const manifestData = {
98+
format,
9799
getMismatchedVersions,
98100
getVersions,
99101
isManifest,

0 commit comments

Comments
 (0)