Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/shaggy-berries-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"modular-scripts": patch
"@modular-scripts/modular-types": minor
"@modular-scripts/workspace-resolver": minor
---

Modular type as an additional property in workspace resolver
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "non-modular-workspace-1",
"version": "1.0.0",
"author": "App Frameworks team",
"license": "MIT",
"private": true,
"workspaces": [
"packages/**"
],
"modular": {
"type": "root"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "app-one",
"private": true,
"modular": {
"type": "app"
},
"dependencies": {
"package-one": "1.0.0",
"package-two": "1.0.0"
},
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "package-extraneous-1",
"private": true,
"main": "./src/index.ts",
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "package-extraneous-2",
"private": true,
"modular": {
"anotherProperty": "value"
},
"main": "./src/index.ts",
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "package-extraneous-3",
"private": true,
"modular": {
"type": ""
},
"main": "./src/index.ts",
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "package-one",
"private": true,
"modular": {
"type": "package"
},
"main": "./src/index.ts",
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand All @@ -29,6 +30,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand All @@ -43,6 +45,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand Down Expand Up @@ -90,6 +93,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand All @@ -104,6 +108,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand Down Expand Up @@ -139,6 +144,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand All @@ -153,6 +159,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand All @@ -167,6 +174,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand Down Expand Up @@ -215,6 +223,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'root',
},
type: 'root',
children: [],
parent: null,
dependencies: undefined,
Expand All @@ -229,6 +238,7 @@ describe('matchWorkspaces', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand Down
5 changes: 3 additions & 2 deletions packages/modular-types/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ export type ModularWorkspacePackage = {
name: string;
version: string;
workspace: boolean;
modular: {
type: ModularType;
modular?: {
type: ModularType | undefined;
};
type: ModularType | undefined;
children: ModularWorkspacePackage[];
parent: ModularWorkspacePackage | null;
dependencies: Record<string, string> | undefined;
Expand Down
4 changes: 3 additions & 1 deletion packages/workspace-resolver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
This package encapsulates two functions:

1. `resolveWorkspace` - Searches the filesystem (at a given modular root) for
workspace packages, returning a flat map of all packages found. An optional
workspace packages, returning a flat map of all packages found, with an
additional `type` field containing the modular type (if present). An optional
2nd argument of `target` can be passed, which sets the working directory that
workspaces should be resolved from. This can be useful when the modular root
needs to be different to the current working directory, such as when modular
Expand Down Expand Up @@ -36,6 +37,7 @@ Map {
modular: {
type: 'package'
},
type: 'package',
children: [],
parent: null,
dependencies: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('resolve-dependencies', () => {
modular: {
type: 'package',
},
type: 'package',
children: [],
parent: null,
dependencies: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,35 @@ describe('@modular-scripts/workspace-resolver', () => {
'The package "app-one" has an invalid version. Modular requires workspace packages to have a version.',
);
});

it('supports type property', async () => {
const projectRoot = path.join(fixturesPath, 'non-modular-workspace-1');
const [allPackages] = await resolveWorkspace(projectRoot, projectRoot);
expect(allPackages.has('non-modular-workspace-1')).toEqual(true);
expect(allPackages.has('app-one')).toEqual(true);
expect(allPackages.get('app-one')?.modular).toEqual({
type: 'app',
});
expect(allPackages.get('app-one')?.type).toBe('app');
expect(allPackages.has('package-one')).toEqual(true);
expect(allPackages.get('package-one')?.modular).toEqual({
type: 'package',
});
expect(allPackages.get('package-one')?.type).toBe('package');
expect(allPackages.has('package-extraneous-1')).toEqual(true);
expect(allPackages.get('package-extraneous-1')?.modular).toBeUndefined();
expect(allPackages.get('package-extraneous-1')?.type).toBeUndefined();
expect(allPackages.has('package-extraneous-2')).toEqual(true);
expect(allPackages.get('package-extraneous-2')?.modular).toEqual({
anotherProperty: 'value',
});
expect(allPackages.get('package-extraneous-2')?.type).toBeUndefined();
expect(allPackages.has('package-extraneous-3')).toEqual(true);
expect(allPackages.get('package-extraneous-3')?.modular).toEqual({
type: '',
});
expect(allPackages.get('package-extraneous-3')?.type).toBeFalsy();
});
});

describe('analyzeWorkspaceDependencies', () => {
Expand Down
99 changes: 47 additions & 52 deletions packages/workspace-resolver/src/resolve-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export async function resolveWorkspace(
const pkgPath = packageJsonPath(root);

const json = await readPackageJson(isRoot, workingDirToUse, pkgPath);
const isModularRoot = json.modular?.type === 'root';
const type = json.modular?.type;
const isModularRoot = type === 'root';

if (!json.name) {
throw new Error(
Expand All @@ -89,10 +90,8 @@ export async function resolveWorkspace(
workspace: !!json.workspaces,
children: [],
parent,
modular: {
type: 'unknown',
...json.modular,
},
modular: json.modular,
type,
// Like yarn classic `workspaces info`, we include all except peerDependencies
dependencies: {
...json.optionalDependencies,
Expand Down Expand Up @@ -150,55 +149,51 @@ export function analyzeWorkspaceDependencies(
const exhaustivePackageNameList = Array.from(workspacePackages.keys());
const allPackages = Array.from(workspacePackages.entries());

// Exclude the root when analyzing package inter-dependencies
const packagesWithoutRoot = Array.from(workspacePackages.entries()).filter(
([, pkg]) => {
return pkg.modular.type !== 'root';
},
);

// Calculate deps and mismatches a-la Yarn classic `workspaces info`
packagesWithoutRoot.forEach(([pkgName, pkg]) => {
const packageDepNames = Object.keys(pkg.dependencies || {}).filter(
(dep) => {
return exhaustivePackageNameList.includes(dep);
},
);
const packageDeps = allPackages.filter(([, pkg]) =>
packageDepNames.includes(pkg.name),
);
Array.from(workspacePackages.entries()).forEach(([pkgName, pkg]) => {
// Exclude the root when analyzing package inter-dependencies
if (pkg.type !== 'root') {
const packageDepNames = Object.keys(pkg.dependencies || {}).filter(
(dep) => {
return exhaustivePackageNameList.includes(dep);
},
);
const packageDeps = allPackages.filter(([, pkg]) =>
packageDepNames.includes(pkg.name),
);

// Mismatched = version in packages/<package>/package.json does not satisfy the dependent's range
const mismatchedWorkspaceDependencies = Object.entries(
pkg.dependencies || {},
)
.filter(([dep, range]) => {
const matchingPackage = packageDeps.find(
([matchingPackageName]) => dep === matchingPackageName,
);
if (!matchingPackage) {
return false;
}

const [, match] = matchingPackage;

// Account for use of Yarn Workspace Ranges
// Note: we do not support the unstable project-relative path flavour syntax
const rangeToUse = range.includes(YARN_WORKSPACE_RANGE_PREFIX)
? range.replace(YARN_WORKSPACE_RANGE_PREFIX, '')
: range;

return !semver.satisfies(match.version, rangeToUse);
})
.flatMap(([dep]) => dep);

mappedDeps.set(pkgName, {
location: path.dirname(pkg.path),
workspaceDependencies: packageDepNames.filter(
(depName) => !mismatchedWorkspaceDependencies.includes(depName),
),
mismatchedWorkspaceDependencies,
});
// Mismatched = version in packages/<package>/package.json does not satisfy the dependent's range
const mismatchedWorkspaceDependencies = Object.entries(
pkg.dependencies || {},
)
.filter(([dep, range]) => {
const matchingPackage = packageDeps.find(
([matchingPackageName]) => dep === matchingPackageName,
);
if (!matchingPackage) {
return false;
}

const [, match] = matchingPackage;

// Account for use of Yarn Workspace Ranges
// Note: we do not support the unstable project-relative path flavour syntax
const rangeToUse = range.includes(YARN_WORKSPACE_RANGE_PREFIX)
? range.replace(YARN_WORKSPACE_RANGE_PREFIX, '')
: range;

return !semver.satisfies(match.version, rangeToUse);
})
.flatMap(([dep]) => dep);

mappedDeps.set(pkgName, {
location: path.dirname(pkg.path),
workspaceDependencies: packageDepNames.filter(
(depName) => !mismatchedWorkspaceDependencies.includes(depName),
),
mismatchedWorkspaceDependencies,
});
}
});

return Object.fromEntries(mappedDeps);
Expand Down