Skip to content

Commit

Permalink
Support yarn v3 (#1112)
Browse files Browse the repository at this point in the history
Co-authored-by: Luke Sheard <LukeSheard@users.noreply.github.com>
  • Loading branch information
joshwooding and LukeSheard committed Jan 14, 2022
1 parent eabac84 commit 03afe78
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 27 deletions.
6 changes: 6 additions & 0 deletions .changeset/clever-dryers-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'modular-scripts': minor
'modular-views.macro': minor
---

Support yarn v3
2 changes: 2 additions & 0 deletions packages/modular-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"jest-runner-eslint": "1.0.0",
"jest-transform-stub": "2.0.0",
"jest-watch-typeahead": "0.6.5",
"js-yaml": "^4.1.0",
"loader-utils": "2.0.0",
"micromatch": "4.0.4",
"mime": "^3.0.0",
Expand Down Expand Up @@ -138,6 +139,7 @@
"devDependencies": {
"@schemastore/package": "0.0.6",
"@schemastore/tsconfig": "0.0.9",
"@types/js-yaml": "^4.0.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"typescript": "4.4.4"
Expand Down
4 changes: 2 additions & 2 deletions packages/modular-scripts/src/__tests__/init.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs-extra';
import { promisify } from 'util';
import { ModularPackageJson } from '../utils/isModularType';
import { getYarnWorkspaceInfo } from '../utils/getAllWorkspaces';
import { getWorkspaceInfo } from '../utils/getAllWorkspaces';

const mktempd = promisify(tmp.dir);

Expand Down Expand Up @@ -46,7 +46,7 @@ describe('Creating a new modular folder', () => {
});

it('should not have any workspace info', async () => {
const workspace = await getYarnWorkspaceInfo(folder);
const workspace = await getWorkspaceInfo(folder, 'yarn');
expect(workspace).toEqual({});
});
});
1 change: 1 addition & 0 deletions packages/modular-scripts/src/check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const CHECKS = [
'verifyPrivateModularRoot',
'verifyWorkspaceDependencies',
'verifyWorkspaceStructure',
'verifyYarnRcYml',
];

interface Check {
Expand Down
43 changes: 43 additions & 0 deletions packages/modular-scripts/src/check/verifyYarnRcYml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as yaml from 'js-yaml';

import * as logger from '../utils/logger';
import getModularRoot from '../utils/getModularRoot';

export async function check(): Promise<boolean> {
let valid = true;

const modularRoot = getModularRoot();

const yarnRcPath = path.join(modularRoot, './.yarnrc.yml');

try {
const doc = yaml.load(await fs.readFile(yarnRcPath, 'utf8'), {
onWarning: function (err) {
console.log('YAML parsing error: ', err);
},
json: true, // JSON compat mode will overwrite dupe entries instead of throw an error.
schema: yaml.FAILSAFE_SCHEMA,
}) as Record<string, string>;
valid = doc.nodeLinker === 'node-modules';
if (doc.nodeLinker !== 'node-modules') {
logger.error(
'.yarnrc.yml file found where nodeLinker is not "node-modules"',
);
valid = false;
} else {
logger.debug(
'Valid .yarnrc.yml file found with "nodeLinker: node-modules"',
);
}
} catch (error) {
if (error instanceof Error) {
logger.debug(
`Received error ${error.message} when trying to check .yarnrc.yml file`,
);
}
}

return valid;
}
96 changes: 81 additions & 15 deletions packages/modular-scripts/src/utils/getAllWorkspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,63 @@ import getModularRoot from './getModularRoot';
import * as logger from './logger';
import stripAnsi from 'strip-ansi';

interface YarnWorkspace {
interface WorkspaceObj {
location: string;
workspaceDependencies: string[];
mismatchedWorkspaceDependencies: string[];
}
type WorkspaceMap = Record<string, WorkspaceObj>;

type YarnWorkspaces = Record<string, YarnWorkspace>;
function formatYarn1Workspace(stdout: string): WorkspaceMap {
return JSON.parse(stdout) as WorkspaceMap;
}

interface YarnWorkspaceV3 extends WorkspaceObj {
name: string;
}

function formatNewYarnWorkspace(stdout: string): WorkspaceMap {
return stdout.split(/\r?\n/).reduce((acc, workspaceString) => {
const { name, ...rest } = JSON.parse(workspaceString) as YarnWorkspaceV3;

if (rest.location !== '.') {
acc[name] = rest;
}

export async function getYarnWorkspaceInfo(
return acc;
}, {} as WorkspaceMap);
}

type SupportedPackageManagers = {
[prop: string]: PackageManagerInfo;
};

export interface PackageManagerInfo {
getWorkspaceCommand: string;
formatWorkspaceCommandOutput: (stdout: string) => WorkspaceMap;
}

const supportedPackageManagers: SupportedPackageManagers = {
yarn1: {
getWorkspaceCommand: 'yarnpkg --silent workspaces info',
formatWorkspaceCommandOutput: formatYarn1Workspace,
},
yarn2: {
getWorkspaceCommand: 'yarnpkg workspaces list --json -v',
formatWorkspaceCommandOutput: formatNewYarnWorkspace,
},
yarn3: {
getWorkspaceCommand: 'yarnpkg workspaces list --json -v',
formatWorkspaceCommandOutput: formatNewYarnWorkspace,
},
};

async function getCommandOutput(
cwd: string,
): Promise<YarnWorkspaces> {
const result = await execa('yarnpkg', ['--silent', 'workspaces', 'info'], {
file: string,
args: readonly string[],
): Promise<string> {
const result = await execa(file, args, {
cwd,
reject: false,
cleanup: true,
Expand All @@ -29,21 +74,42 @@ export async function getYarnWorkspaceInfo(
}

// strip out ANSI color codes and escape characters
const strippedStd =
stripAnsi(
stdout,
// there's an edge case where if there are no packages in the current workspace
// then this command returns an empty string and no JSON - so we have to default
// to an empty directory which can be JSON parsed.
) || '{}';
return stripAnsi(stdout);
}

async function getPackageManagerInfo(cwd: string, packageManager: string) {
if (packageManager === 'yarn') {
const yarnVersion = await getCommandOutput(cwd, 'yarnpkg', ['--version']);
if (yarnVersion.startsWith('1.')) {
return supportedPackageManagers.yarn1;
}
if (yarnVersion.startsWith('2.')) {
return supportedPackageManagers.yarn2;
}
if (yarnVersion.startsWith('3.')) {
return supportedPackageManagers.yarn3;
}
}

throw new Error(`${packageManager} is not supported.`);
}

return JSON.parse(strippedStd) as YarnWorkspaces;
export async function getWorkspaceInfo(
cwd: string,
packageManager: string,
): Promise<WorkspaceMap> {
const packageManagerUtils = await getPackageManagerInfo(cwd, packageManager);
const [file, ...args] = packageManagerUtils.getWorkspaceCommand.split(' ');
const workspaceCommandOutput = await getCommandOutput(cwd, file, args);
return packageManagerUtils.formatWorkspaceCommandOutput(
workspaceCommandOutput,
);
}

function _getAllWorkspaces(): Promise<YarnWorkspaces> {
function _getAllWorkspaces(): Promise<WorkspaceMap> {
const modularRoot = getModularRoot();

return getYarnWorkspaceInfo(modularRoot);
return getWorkspaceInfo(modularRoot, 'yarn');
}

export const getAllWorkspaces = memoize(_getAllWorkspaces);
51 changes: 41 additions & 10 deletions packages/modular-views.macro/src/index.macro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,46 @@ type ModularPackageJson = PackageJson & {
};
};

function getWorkspaces(): Array<[string, { location: string }]> {
const { stdout: yarnVersion } = execa.sync('yarnpkg', ['--version']);

if (yarnVersion.startsWith('1.')) {
const output = execa.sync('yarnpkg', ['workspaces', 'info'], {
all: true,
reject: false,
cwd: modularRootDir,
cleanup: true,
});

const workspaces: Array<[string, { location: string }]> = Object.entries(
JSON.parse(output.stdout),
);

return workspaces;
}

const output = execa.sync('yarnpkg', ['workspaces', 'list', '--json'], {
all: true,
reject: false,
cwd: modularRootDir,
cleanup: true,
});

const workspaces = output.stdout
.split(/\r?\n/)
.reduce((acc, workspaceString) => {
const workspaceObject = JSON.parse(workspaceString) as {
name: string;
location: string;
};
acc.push([workspaceObject.name, workspaceObject]);

return acc;
}, [] as Array<[string, { location: string }]>);

return workspaces;
}

const modularRoot = findUp.sync((directory) => {
const packageJsonPath = path.join(directory, 'package.json');
if (fs.existsSync(packageJsonPath)) {
Expand All @@ -35,16 +75,7 @@ if (!modularRoot) {
const packageNames = [];

const modularRootDir = path.dirname(modularRoot);
const output = execa.sync('yarnpkg', ['workspaces', 'info'], {
all: true,
reject: false,
cwd: modularRootDir,
cleanup: true,
});

const workspaces: Array<[string, { location: string }]> = Object.entries(
JSON.parse(output.stdout),
);
const workspaces = getWorkspaces();

for (const [name, { location }] of workspaces) {
const pkgJson = fs.readJSONSync(
Expand Down
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3372,6 +3372,11 @@
jest-diff "^26.0.0"
pretty-format "^26.0.0"

"@types/js-yaml@^4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138"
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==

"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.9"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
Expand Down Expand Up @@ -4141,6 +4146,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"

argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==

aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
Expand Down Expand Up @@ -9419,6 +9429,13 @@ js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.6.1:
argparse "^1.0.7"
esprima "^4.0.0"

js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"

jsdom@^16.4.0:
version "16.7.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
Expand Down

0 comments on commit 03afe78

Please sign in to comment.