Adds npm 7 package-lock.json parser (#82)
* Adds npm 7 package-lock.json parcer * Adds a check for unsupported package-lock version * Updates error message * Adds tests for parseLockFile with npm 7 workspaces * Updates README with npm workspaces * Style cleanup * Changes the error message language * Rewrites types in a different style * Change files * Adds changelog file. Co-authored-by: Ria Carmin <riacarmin@microsoft.com>
This commit is contained in:
Родитель
1c825bab65
Коммит
0d9b3bd5c0
|
@ -2,10 +2,11 @@
|
|||
|
||||
A collection of tools that are useful in a git-controlled monorepo that is managed by one of these software:
|
||||
|
||||
- lerna
|
||||
- npm workspaces
|
||||
- pnpm workspaces
|
||||
- rush
|
||||
- yarn workspaces
|
||||
- pnpm workspaces
|
||||
- lerna
|
||||
|
||||
# Environment Variables
|
||||
|
||||
|
@ -17,7 +18,7 @@ default node.js maxBuffer of 1MB)
|
|||
## PREFERRED_WORKSPACE_MANAGER
|
||||
|
||||
Sometimes multiple package manager files are checked in. It is necessary to hint to `workspace-tools` which manager
|
||||
is used: `yarn`, `pnpm`, `rush`, or `lerna`
|
||||
is used: `npm`, `yarn`, `pnpm`, `rush`, or `lerna`
|
||||
|
||||
# Contributing
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "minor",
|
||||
"comment": "Implements NPM workspaces support to parseLockFile utility.",
|
||||
"packageName": "workspace-tools",
|
||||
"email": "riacarmin@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "monorepo-npm",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 1
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "monorepo-npm",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
}
|
17
src/__fixtures__/monorepo-npm-unsupported/packages/package-a/node_modules/.bin/copy
сгенерированный
поставляемый
Normal file
17
src/__fixtures__/monorepo-npm-unsupported/packages/package-a/node_modules/.bin/copy
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const source = process.argv[2];
|
||||
const destination = process.argv[3];
|
||||
|
||||
const sourceAbsolute = path.join(process.cwd(), source);
|
||||
const destinationAbsolute = path.join(process.cwd(), destination);
|
||||
const destinationAbsoluteDir = path.dirname(destinationAbsolute);
|
||||
|
||||
if (!fs.existsSync(destinationAbsoluteDir)) {
|
||||
fs.mkdirSync(destinationAbsoluteDir);
|
||||
}
|
||||
|
||||
fs.copyFileSync(sourceAbsolute, destinationAbsolute);
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "package-a",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"compile": "node node_modules/.bin/copy src/index.ts lib/index.js",
|
||||
"side-effect": "node node_modules/.bin/copy src/index.ts ../DONE"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
console.log("foo");
|
17
src/__fixtures__/monorepo-npm-unsupported/packages/package-b/node_modules/.bin/copy
сгенерированный
поставляемый
Normal file
17
src/__fixtures__/monorepo-npm-unsupported/packages/package-b/node_modules/.bin/copy
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const source = process.argv[2];
|
||||
const destination = process.argv[3];
|
||||
|
||||
const sourceAbsolute = path.join(process.cwd(), source);
|
||||
const destinationAbsolute = path.join(process.cwd(), destination);
|
||||
const destinationAbsoluteDir = path.dirname(destinationAbsolute);
|
||||
|
||||
if (!fs.existsSync(destinationAbsoluteDir)) {
|
||||
fs.mkdirSync(destinationAbsoluteDir);
|
||||
}
|
||||
|
||||
fs.copyFileSync(sourceAbsolute, destinationAbsolute);
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "package-b",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"compile": "node node_modules/.bin/copy src/index.ts lib/index.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
console.log("foo");
|
24
src/__fixtures__/monorepo-npm/node_modules/.package-lock.json
сгенерированный
поставляемый
Normal file
24
src/__fixtures__/monorepo-npm/node_modules/.package-lock.json
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "monorepo-npm",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/package-a": {
|
||||
"resolved": "packages/package-a",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/package-b": {
|
||||
"resolved": "packages/package-b",
|
||||
"link": true
|
||||
},
|
||||
"packages/package-a": {
|
||||
"version": "0.1.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/package-b": {
|
||||
"version": "0.1.0",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
../packages/package-a
|
|
@ -0,0 +1 @@
|
|||
../packages/package-b
|
|
@ -1,20 +1,40 @@
|
|||
import { setupFixture } from "../helpers/setupFixture";
|
||||
import { parseLockFile } from "../lockfile";
|
||||
|
||||
const ERROR_MESSAGES = {
|
||||
NO_LOCK: "You do not have yarn.lock, pnpm-lock.yaml or package-lock.json. Please use one of these package managers.",
|
||||
UNSUPPORTED:
|
||||
"Your package-lock.json version is not supported: lockfileVersion is 1. You need npm version 7 or above and package-lock version 2 or above. Please, upgrade npm or choose a different package manager.",
|
||||
};
|
||||
|
||||
describe("parseLockFile()", () => {
|
||||
it("parses yarn.lock file when it is found", async () => {
|
||||
const packageRoot = await setupFixture("basic");
|
||||
// General
|
||||
it("throws if it cannot find lock file", async () => {
|
||||
const packageRoot = await setupFixture("basic-without-lock-file");
|
||||
|
||||
await expect(parseLockFile(packageRoot)).rejects.toThrow(ERROR_MESSAGES.NO_LOCK);
|
||||
});
|
||||
|
||||
// NPM
|
||||
it("parses package-lock.json file when it is found", async () => {
|
||||
const packageRoot = await setupFixture("monorepo-npm");
|
||||
const parsedLockeFile = await parseLockFile(packageRoot);
|
||||
|
||||
expect(parsedLockeFile).toHaveProperty("type", "success");
|
||||
});
|
||||
|
||||
it("throws if it cannot find a yarn.lock file", async () => {
|
||||
const packageRoot = await setupFixture("basic-without-lock-file");
|
||||
it("throws if npm version is unsupported", async () => {
|
||||
const packageRoot = await setupFixture("monorepo-npm-unsupported");
|
||||
|
||||
await expect(parseLockFile(packageRoot)).rejects.toThrow(
|
||||
"You do not have either yarn.lock nor pnpm-lock.yaml. Please use one of these package managers"
|
||||
);
|
||||
await expect(parseLockFile(packageRoot)).rejects.toThrow(ERROR_MESSAGES.UNSUPPORTED);
|
||||
});
|
||||
|
||||
// Yarn
|
||||
it("parses yarn.lock file when it is found", async () => {
|
||||
const packageRoot = await setupFixture("basic");
|
||||
const parsedLockeFile = await parseLockFile(packageRoot);
|
||||
|
||||
expect(parsedLockeFile).toHaveProperty("type", "success");
|
||||
});
|
||||
|
||||
it("parses combined ranges in yarn.lock", async () => {
|
||||
|
@ -26,12 +46,11 @@ describe("parseLockFile()", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("parses pnpm-lock.yaml properly", async () => {
|
||||
// PNPM
|
||||
it("parses pnpm-lock.yaml file when it is found", async () => {
|
||||
const packageRoot = await setupFixture("basic-pnpm");
|
||||
const parsedLockeFile = await parseLockFile(packageRoot);
|
||||
|
||||
expect(Object.keys(parsedLockeFile.object["yargs@16.2.0"].dependencies!)).toContain(
|
||||
"cliui"
|
||||
);
|
||||
expect(Object.keys(parsedLockeFile.object["yargs@16.2.0"].dependencies!)).toContain("cliui");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// NOTE: never place the import of lockfile implementation here, as it slows down the library as a whole
|
||||
import path from "path";
|
||||
import findUp from "find-up";
|
||||
import fs from "fs-extra";
|
||||
import { ParsedLock, PnpmLockFile } from "./types";
|
||||
import { ParsedLock, PnpmLockFile, NpmLockFile } from "./types";
|
||||
import readYamlFile from "read-yaml-file";
|
||||
import { nameAtVersion } from "./nameAtVersion";
|
||||
import { parsePnpmLock } from "./parsePnpmLock";
|
||||
import { parseNpmLock } from "./parseNpmLock";
|
||||
|
||||
const memoization: { [path: string]: ParsedLock } = {};
|
||||
|
||||
|
@ -42,9 +42,38 @@ export async function parseLockFile(packageRoot: string): Promise<ParsedLock> {
|
|||
return memoization[pnpmLockPath];
|
||||
}
|
||||
|
||||
throw new Error("You do not have either yarn.lock nor pnpm-lock.yaml. Please use one of these package managers");
|
||||
// Third, try for npm workspaces
|
||||
let npmLockPath = await findUp(["package-lock.json"], { cwd: packageRoot });
|
||||
|
||||
if (npmLockPath) {
|
||||
if (memoization[npmLockPath]) {
|
||||
return memoization[npmLockPath];
|
||||
}
|
||||
|
||||
let npmLockJson;
|
||||
try {
|
||||
npmLockJson = fs.readFileSync(npmLockPath);
|
||||
} catch {
|
||||
throw new Error("Couldn’t parse package-lock.json.");
|
||||
}
|
||||
|
||||
const npmLock: NpmLockFile = JSON.parse(npmLockJson.toString());
|
||||
|
||||
if (!npmLock?.lockfileVersion || npmLock.lockfileVersion < 2) {
|
||||
throw new Error(
|
||||
`Your package-lock.json version is not supported: lockfileVersion is ${npmLock.lockfileVersion}. You need npm version 7 or above and package-lock version 2 or above. Please, upgrade npm or choose a different package manager.`
|
||||
);
|
||||
}
|
||||
|
||||
memoization[npmLockPath] = parseNpmLock(npmLock);
|
||||
return memoization[npmLockPath];
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"You do not have yarn.lock, pnpm-lock.yaml or package-lock.json. Please use one of these package managers."
|
||||
);
|
||||
}
|
||||
|
||||
export { nameAtVersion };
|
||||
export { queryLockFile } from "./queryLockFile";
|
||||
export * from './types';
|
||||
export * from "./types";
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import { ParsedLock, NpmLockFile } from "./types";
|
||||
|
||||
export const parseNpmLock = (lock: NpmLockFile): ParsedLock => ({
|
||||
object: lock.dependencies ?? {},
|
||||
type: "success",
|
||||
});
|
|
@ -15,3 +15,29 @@ export type ParsedLock = {
|
|||
export interface PnpmLockFile {
|
||||
packages: { [name: string]: any };
|
||||
}
|
||||
|
||||
export interface NpmWorkspacesInfo {
|
||||
version: string;
|
||||
workspaces: { packages: string[] };
|
||||
}
|
||||
|
||||
export interface NpmSymlinkInfo {
|
||||
resolved: string; // Where the package is resolved from.
|
||||
link: boolean; // A flag to indicate that this is a symbolic link.
|
||||
integrity?: "sha512" | "sha1";
|
||||
dev?: boolean;
|
||||
optional?: boolean;
|
||||
devOptional?: boolean;
|
||||
dependencies?: { [key: string]: LockDependency };
|
||||
}
|
||||
|
||||
export interface NpmLockFile {
|
||||
name: string;
|
||||
version: string;
|
||||
lockfileVersion?: 1 | 2 | 3; // 1: v5, v6; 2: backwards compatible v7; 3: non-backwards compatible v7
|
||||
requires?: boolean;
|
||||
packages?: {
|
||||
""?: NpmWorkspacesInfo; // Monorepo root
|
||||
} & { [key: string]: NpmSymlinkInfo | LockDependency };
|
||||
dependencies?: { [key: string]: LockDependency };
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@ import {
|
|||
WorkspaceImplementations,
|
||||
} from "./implementations";
|
||||
|
||||
import { getLernaWorkspaces } from "./implementations/lerna";
|
||||
import { getNpmWorkspaces } from "./implementations/npm";
|
||||
import { getPnpmWorkspaces } from "./implementations/pnpm";
|
||||
import { getYarnWorkspaces } from "./implementations/yarn";
|
||||
import { getRushWorkspaces } from "./implementations/rush";
|
||||
import { getYarnWorkspaces } from "./implementations/yarn";
|
||||
|
||||
import { WorkspaceInfo } from "../types/WorkspaceInfo";
|
||||
import { WorkspaceManager } from "./WorkspaceManager";
|
||||
import { getNpmWorkspaces } from "./implementations/npm";
|
||||
import { getLernaWorkspaces } from "./implementations/lerna";
|
||||
|
||||
const workspaceGetter: {
|
||||
[key in WorkspaceImplementations]: (cwd: string) => WorkspaceInfo;
|
||||
|
|
|
@ -12,6 +12,6 @@ export function getNpmWorkspaceRoot(cwd: string): string {
|
|||
}
|
||||
|
||||
export function getNpmWorkspaces(cwd: string): WorkspaceInfo {
|
||||
const yarnWorkspacesRoot = getNpmWorkspaceRoot(cwd);
|
||||
return getWorkspaceInfoFromWorkspaceRoot(yarnWorkspacesRoot);
|
||||
const npmWorkspacesRoot = getNpmWorkspaceRoot(cwd);
|
||||
return getWorkspaceInfoFromWorkspaceRoot(npmWorkspacesRoot);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче