Skip to content

Commit

Permalink
feat(depTypes): handle '!peer' and '**'
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMason committed Aug 14, 2023
1 parent fa85cda commit 06f2e88
Show file tree
Hide file tree
Showing 40 changed files with 312 additions and 70 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module.exports = {
coverageReporters: ['html', 'lcov'],
coverageThreshold: {
global: {
branches: 79,
branches: 77,
functions: 79,
lines: 86,
statements: 85,
Expand Down
9 changes: 9 additions & 0 deletions site/docs/config/dependency-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ properties of package.json files will be inspected by syncpack:
}
```

Negated types are also supported, so here everything **except** `dependencies` and `devDependencies`
will be inspected:

```json title=".syncpackrc"
{
"dependencyTypes": ["!dev", "!prod"]
}
```

:::tip

Syncpack config files also support
Expand Down
2 changes: 1 addition & 1 deletion site/docs/option/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Running syncpack multiple times with different options to target different parts
package.json files is a lot of work to maintain, so [`semverGroups`](../config/semver-groups.mdx)
exist to make this easier.

The above example would would defined like so:
The above example would be defined like so:

```json
{
Expand Down
16 changes: 16 additions & 0 deletions site/src/partials/version-group-config/dependency-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ as its version number, regardless of what versions of the same dependencies migh
}
```

Negated types are also supported, so here everything **except** `dependencies` and `devDependencies`
will be assigned to this group:

```json title="Negated types"
{
"versionGroups": [
{
"packages": ["**"],
"dependencies": ["**"],
"dependencyTypes": ["!dev", "!prod"],
"isIgnored": true
}
]
}
```

:::tip

Syncpack config files also support
Expand Down
107 changes: 107 additions & 0 deletions src/config/get-enabled-types.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { getEnabledTypes } from './get-enabled-types';
import * as Effect from '@effect/io/Effect';

const prod = expect.objectContaining({ path: 'dependencies' });
const dev = expect.objectContaining({ path: 'devDependencies' });
const local = expect.objectContaining({ path: 'version' });
const overrides = expect.objectContaining({ path: 'overrides' });
const peerDependencies = expect.objectContaining({ path: 'peerDependencies' });
const pnpmOverrides = expect.objectContaining({ path: 'pnpm.overrides' });
const resolutions = expect.objectContaining({ path: 'resolutions' });
// custom
const engines = expect.objectContaining({ path: 'engines' });

it('defaults to all when nothing is provided', () => {
expect(getEnabledTypes({ cli: {}, rcFile: {} })).toEqual(
Effect.succeed([dev, local, overrides, peerDependencies, pnpmOverrides, prod, resolutions]),
);
});

it('uses every type except a negated type such as "!prod"', () => {
expect(getEnabledTypes({ cli: { types: '!prod' }, rcFile: {} })).toEqual(
Effect.succeed([dev, local, overrides, peerDependencies, pnpmOverrides, resolutions]),
);
});

it('handles multiple negated types', () => {
expect(getEnabledTypes({ cli: { types: '!prod,!dev' }, rcFile: {} })).toEqual(
Effect.succeed([local, overrides, peerDependencies, pnpmOverrides, resolutions]),
);
});

it('uses only provided type when defined', () => {
expect(getEnabledTypes({ cli: { types: 'dev' }, rcFile: {} })).toEqual(Effect.succeed([dev]));
});

it('handles multiple types', () => {
expect(getEnabledTypes({ cli: { types: 'dev,peer' }, rcFile: {} })).toEqual(
Effect.succeed([dev, peerDependencies]),
);
});

it('gives precedence to cli options', () => {
expect(getEnabledTypes({ cli: { types: 'dev' }, rcFile: { dependencyTypes: ['peer'] } })).toEqual(
Effect.succeed([dev]),
);
});

it('includes custom types when others are negated', () => {
expect(
getEnabledTypes({
cli: { types: '!dev' },
rcFile: {
customTypes: {
engines: {
path: 'engines',
strategy: 'versionsByName',
},
},
},
}),
).toEqual(
Effect.succeed([local, overrides, peerDependencies, pnpmOverrides, prod, resolutions, engines]),
);
});

it('includes custom types when named', () => {
expect(
getEnabledTypes({
cli: { types: 'dev,engines' },
rcFile: {
customTypes: {
engines: {
path: 'engines',
strategy: 'versionsByName',
},
},
},
}),
).toEqual(Effect.succeed([dev, engines]));
});

it('includes every type when "**" is provided', () => {
expect(
getEnabledTypes({
cli: { types: '**' },
rcFile: {
customTypes: {
engines: {
path: 'engines',
strategy: 'versionsByName',
},
},
},
}),
).toEqual(
Effect.succeed([
dev,
local,
overrides,
peerDependencies,
pnpmOverrides,
prod,
resolutions,
engines,
]),
);
});
105 changes: 64 additions & 41 deletions src/config/get-enabled-types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as Data from '@effect/data/Data';
import { isNonEmptyArray } from '@effect/data/ReadonlyArray';
import * as Effect from '@effect/io/Effect';
import { isArrayOfStrings } from 'tightrope/guard/is-array-of-strings';
import { isBoolean } from 'tightrope/guard/is-boolean';
import { isEmptyArray } from 'tightrope/guard/is-empty-array';
import { isNonEmptyString } from 'tightrope/guard/is-non-empty-string';
import { DEFAULT_CONFIG } from '../constants';
import { INTERNAL_TYPES } from '../constants';
import type { Ctx } from '../get-context';
import { NameAndVersionPropsStrategy } from '../strategy/name-and-version-props';
import { VersionsByNameStrategy } from '../strategy/versions-by-name';
Expand All @@ -19,8 +20,6 @@ export class RenamedWorkspaceTypeError extends Data.TaggedClass('RenamedWorkspac
Record<string, never>
> {}

// @TODO accept `dependencyTypes: ['**']`
// @TODO support `dependencyTypes: ['!dev']`
export function getEnabledTypes({
cli,
rcFile,
Expand All @@ -29,55 +28,79 @@ export function getEnabledTypes({
DeprecatedTypesError | RenamedWorkspaceTypeError,
Strategy.Any[]
> {
const enabledTypes: Strategy.Any[] = [];
const enabledTypeNames = (
isNonEmptyString(cli.types)
const deprecatedTypeProps = getDeprecatedTypeProps();

if (deprecatedTypeProps.length > 0) {
return Effect.fail(new DeprecatedTypesError({ types: deprecatedTypeProps }));
}

const allStrategiesByName: Record<string, Strategy.Any> = Object.fromEntries([
['dev', new VersionsByNameStrategy('dev', 'devDependencies')],
['local', new NameAndVersionPropsStrategy('local', 'version', 'name')],
['overrides', new VersionsByNameStrategy('overrides', 'overrides')],
['peer', new VersionsByNameStrategy('peer', 'peerDependencies')],
['pnpmOverrides', new VersionsByNameStrategy('pnpmOverrides', 'pnpm.overrides')],
['prod', new VersionsByNameStrategy('prod', 'dependencies')],
['resolutions', new VersionsByNameStrategy('resolutions', 'resolutions')],
...getCustomTypes({ cli, rcFile }).map((customType) => [customType.name, customType]),
]);
const allStrategyNames = Object.keys(allStrategiesByName);

const names: Record<'provided' | 'enabled' | 'positive' | 'negative', string[]> = {
provided: (isNonEmptyString(cli.types)
? cli.types.split(',')
: isArrayOfStrings(rcFile.dependencyTypes)
? rcFile.dependencyTypes
: []
).filter(isNonEmptyString);
const useDefaults = isEmptyArray(enabledTypeNames);

const deprecatedTypes = DEFAULT_CONFIG.dependencyTypes.filter((key) =>
isBoolean((rcFile as Record<string, boolean>)[key]),
);
).filter(isNonEmptyString),
enabled: [],
positive: [],
negative: [],
};

if (deprecatedTypes.length > 0) {
return Effect.fail(new DeprecatedTypesError({ types: deprecatedTypes }));
if (isEmptyArray(names.provided) || names.provided.join('') === '**') {
return Effect.succeed(allStrategyNames.map(getStrategyByName));
}

if (enabledTypeNames.includes('workspace')) {
return Effect.fail(new RenamedWorkspaceTypeError({}));
}
names.provided.forEach((name) => {
if (name.startsWith('!')) {
names.negative.push(name.replace('!', ''));
} else {
names.positive.push(name);
}
});

if (useDefaults || enabledTypeNames.includes('dev')) {
enabledTypes.push(new VersionsByNameStrategy('dev', 'devDependencies'));
}
if (useDefaults || enabledTypeNames.includes('overrides')) {
enabledTypes.push(new VersionsByNameStrategy('overrides', 'overrides'));
}
if (useDefaults || enabledTypeNames.includes('peer')) {
enabledTypes.push(new VersionsByNameStrategy('peer', 'peerDependencies'));
if (isNonEmptyArray(names.negative)) {
allStrategyNames.forEach((name) => {
if (!names.negative.includes(name)) {
names.enabled.push(name);
}
});
}
if (useDefaults || enabledTypeNames.includes('pnpmOverrides')) {
enabledTypes.push(new VersionsByNameStrategy('pnpmOverrides', 'pnpm.overrides'));
}
if (useDefaults || enabledTypeNames.includes('prod')) {
enabledTypes.push(new VersionsByNameStrategy('prod', 'dependencies'));
}
if (useDefaults || enabledTypeNames.includes('resolutions')) {
enabledTypes.push(new VersionsByNameStrategy('resolutions', 'resolutions'));

if (isNonEmptyArray(names.positive)) {
names.positive.forEach((name) => {
if (!names.enabled.includes(name)) {
names.enabled.push(name);
}
});
}
if (useDefaults || enabledTypeNames.includes('local')) {
enabledTypes.push(new NameAndVersionPropsStrategy('localPackage', 'version', 'name'));

if (names.enabled.includes('workspace')) {
return Effect.fail(new RenamedWorkspaceTypeError({}));
}

getCustomTypes({ cli, rcFile }).forEach((customType) => {
if (useDefaults || enabledTypeNames.includes(customType.name)) {
enabledTypes.push(customType);
}
});
return Effect.succeed(names.enabled.map(getStrategyByName));

return Effect.succeed(enabledTypes);
function getStrategyByName(type: string): Strategy.Any {
return allStrategiesByName[type] as Strategy.Any;
}

/**
* Look for dependency types defined using the old syntax of `{ prod: true }`
* which was deprecated in syncpack@9.0.0.
*/
function getDeprecatedTypeProps() {
return INTERNAL_TYPES.filter((key) => isBoolean((rcFile as Record<string, boolean>)[key]));
}
}
12 changes: 11 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,18 @@ export const RANGE = {
WORKSPACE: 'workspace:',
} as const;

export const INTERNAL_TYPES = [
'dev',
'local',
'overrides',
'peer',
'pnpmOverrides',
'prod',
'resolutions',
] as const;

export const DEFAULT_CONFIG = {
dependencyTypes: ['dev', 'local', 'overrides', 'peer', 'pnpmOverrides', 'prod', 'resolutions'],
dependencyTypes: ['**'],
filter: '.',
indent: ' ',
semverGroups: [],
Expand Down
2 changes: 1 addition & 1 deletion src/get-semver-groups/filtered-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class FilteredOutSemverGroup extends Data.TaggedClass('FilteredOut')<{
super({
config: {
dependencies: ['**'],
dependencyTypes: [],
dependencyTypes: ['**'],
label: 'Filtered out',
packages: ['**'],
},
Expand Down
2 changes: 1 addition & 1 deletion src/get-semver-groups/with-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class WithRangeSemverGroup extends Data.TaggedClass('WithRange')<{
);
}

const isLocalPackageInstance = instance.strategy.name === 'localPackage';
const isLocalPackageInstance = instance.strategy.name === 'local';
const exactVersion = setSemverRange('', instance.specifier);
const expectedVersion = setSemverRange(this.config.range, instance.specifier);

Expand Down
2 changes: 1 addition & 1 deletion src/get-version-groups/filtered-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class FilteredOutVersionGroup extends Data.TaggedClass('FilteredOut')<{
super({
config: {
dependencies: ['**'],
dependencyTypes: [],
dependencyTypes: ['**'],
label: 'Filtered out',
packages: ['**'],
},
Expand Down
2 changes: 1 addition & 1 deletion src/get-version-groups/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ function createVersionGroups(
const label = isNonEmptyString(config.label) ? config.label : '';
const dependencyTypes = isArrayOfStrings(config.dependencyTypes)
? config.dependencyTypes
: [];
: ['**'];

if (config.isBanned === true) {
versionGroups.push(
Expand Down
2 changes: 1 addition & 1 deletion src/get-version-groups/standard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,5 @@ function hasNonSemverSpecifiers(instances: Instance.Any[]): boolean {
}

function getLocalPackageInstance(instances: Instance.Any[]): Instance.Any | undefined {
return instances.find((instance) => instance.strategy.name === 'localPackage');
return instances.find((instance) => instance.strategy.name === 'local');
}

0 comments on commit 06f2e88

Please sign in to comment.