diff --git a/.changeset/shaggy-berries-thank.md b/.changeset/shaggy-berries-thank.md new file mode 100644 index 000000000..295c4710b --- /dev/null +++ b/.changeset/shaggy-berries-thank.md @@ -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 diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/package.json new file mode 100644 index 000000000..b206203cd --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/package.json @@ -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" + } +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/app-one/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/app-one/package.json new file mode 100644 index 000000000..a4f4944f8 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/app-one/package.json @@ -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" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-1/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-1/package.json new file mode 100644 index 000000000..d0d41e9a8 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-1/package.json @@ -0,0 +1,6 @@ +{ + "name": "package-extraneous-1", + "private": true, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-2/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-2/package.json new file mode 100644 index 000000000..502facc63 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-2/package.json @@ -0,0 +1,9 @@ +{ + "name": "package-extraneous-2", + "private": true, + "modular": { + "anotherProperty": "value" + }, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-3/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-3/package.json new file mode 100644 index 000000000..4fac34920 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-3/package.json @@ -0,0 +1,9 @@ +{ + "name": "package-extraneous-3", + "private": true, + "modular": { + "type": "" + }, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-one/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-one/package.json new file mode 100644 index 000000000..ee6acb268 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-one/package.json @@ -0,0 +1,9 @@ +{ + "name": "package-one", + "private": true, + "modular": { + "type": "package" + }, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts b/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts index 7e0e7c5a8..d090b290c 100644 --- a/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts +++ b/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts @@ -15,6 +15,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -29,6 +30,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -43,6 +45,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -90,6 +93,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -104,6 +108,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -139,6 +144,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -153,6 +159,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -167,6 +174,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -215,6 +223,7 @@ describe('matchWorkspaces', () => { modular: { type: 'root', }, + type: 'root', children: [], parent: null, dependencies: undefined, @@ -229,6 +238,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, diff --git a/packages/modular-types/src/types.ts b/packages/modular-types/src/types.ts index 433b5c00b..e40f0dc3e 100644 --- a/packages/modular-types/src/types.ts +++ b/packages/modular-types/src/types.ts @@ -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 | undefined; diff --git a/packages/workspace-resolver/README.md b/packages/workspace-resolver/README.md index 332bee987..9c2fa5a49 100644 --- a/packages/workspace-resolver/README.md +++ b/packages/workspace-resolver/README.md @@ -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 @@ -36,6 +37,7 @@ Map { modular: { type: 'package' }, + type: 'package', children: [], parent: null, dependencies: { diff --git a/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts b/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts index c07ad143d..d456b34c2 100644 --- a/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts +++ b/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts @@ -28,6 +28,7 @@ describe('resolve-dependencies', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, diff --git a/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts b/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts index 0be12864c..cdce124af 100644 --- a/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts +++ b/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts @@ -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', () => { diff --git a/packages/workspace-resolver/src/resolve-workspace.ts b/packages/workspace-resolver/src/resolve-workspace.ts index 7804ee28d..312308501 100644 --- a/packages/workspace-resolver/src/resolve-workspace.ts +++ b/packages/workspace-resolver/src/resolve-workspace.ts @@ -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( @@ -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, @@ -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.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.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);