Skip to content

Commit

Permalink
feat(repair): add lerna repair command (#3302)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry committed Aug 23, 2022
1 parent a248165 commit aae1a2b
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 79 deletions.
2 changes: 1 addition & 1 deletion commands/info/README.md
Expand Up @@ -6,7 +6,7 @@ Install [lerna](https://www.npmjs.com/package/lerna) for access to the `lerna` C

## Usage

The `info` prints local environment information that proves to be useful especially while submitting bug reports.
The `info` command prints local environment information that proves to be useful especially while submitting bug reports.

`lerna info`

Expand Down
18 changes: 18 additions & 0 deletions core/lerna/__tests__/repair-command.test.js
@@ -0,0 +1,18 @@
// @ts-check

"use strict";

const path = require("path");
const { loggingOutput } = require("@lerna-test/helpers/logging-output");

// file under test
const lernaRepair = require("@lerna-test/helpers").commandRunner(require("../commands/repair/command"));

describe("repair", () => {
it("should output the result of running migrations", async () => {
// project fixture is irrelevant, no actual changes are made
await lernaRepair(path.resolve(__dirname, "../../.."))();

expect(loggingOutput("info")).toContain("No changes were necessary. This workspace is up to date!");
});
});
15 changes: 15 additions & 0 deletions core/lerna/commands/repair/command.js
@@ -0,0 +1,15 @@
// @ts-check

"use strict";

/**
* @see https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module
*/
exports.command = "repair";

exports.describe = "Runs automated migrations to repair the state of a lerna repo";

exports.handler = function handler(argv) {
// eslint-disable-next-line global-require
return require(".")(argv);
};
51 changes: 51 additions & 0 deletions core/lerna/commands/repair/index.js
@@ -0,0 +1,51 @@
// @ts-check

"use strict";

const { Command } = require("@lerna/command");
const log = require("npmlog");
const { executeMigrations } = require("nx/src/command-line/migrate");
const migrationsJson = require("../../migrations.json");

module.exports = factory;

function factory(argv) {
return new RepairCommand(argv);
}

class RepairCommand extends Command {
// eslint-disable-next-line class-methods-use-this
initialize() {}

async execute() {
const verbose = log.level === "verbose";

const lernaMigrations = Object.entries(migrationsJson.generators).map(([name, migration]) => {
return /** @type {const} */ ({
package: "lerna",
cli: "nx",
name,
description: migration.description,
version: migration.version,
});
});

const migrationsThatMadeNoChanges = await executeMigrations(
process.cwd(),
lernaMigrations,
verbose,
false,
""
);

if (migrationsThatMadeNoChanges.length < lernaMigrations.length) {
// @ts-ignore
this.logger.info("repair", `Successfully repaired your configuration. This workspace is up to date!`);
} else {
// @ts-ignore
this.logger.info("repair", `No changes were necessary. This workspace is up to date!`);
}
}
}

module.exports.RepairCommand = RepairCommand;
6 changes: 6 additions & 0 deletions core/lerna/index.js
@@ -1,3 +1,5 @@
// @ts-check

"use strict";

const cli = require("@lerna/cli");
Expand All @@ -18,6 +20,8 @@ const publishCmd = require("@lerna/publish/command");
const runCmd = require("@lerna/run/command");
const versionCmd = require("@lerna/version/command");

const repairCmd = require("./commands/repair/command");

const pkg = require("./package.json");

module.exports = main;
Expand All @@ -27,6 +31,7 @@ function main(argv) {
lernaVersion: pkg.version,
};

// @ts-ignore
return cli()
.command(addCmd)
.command(bootstrapCmd)
Expand All @@ -41,6 +46,7 @@ function main(argv) {
.command(linkCmd)
.command(listCmd)
.command(publishCmd)
.command(repairCmd)
.command(runCmd)
.command(versionCmd)
.parse(argv, context);
Expand Down
10 changes: 10 additions & 0 deletions core/lerna/migrations.json
@@ -0,0 +1,10 @@
{
"generators": {
"noop": {
"cli": "nx",
"version": "5.3.0",
"description": "Noop example migration, can be removed once a real migration is created",
"implementation": "./migrations/noop/noop"
}
}
}
6 changes: 6 additions & 0 deletions core/lerna/migrations/noop/noop.js
@@ -0,0 +1,6 @@
// @ts-check

// eslint-disable-next-line no-unused-vars
exports.default = async function generator(tree) {
// This is a noop migration just to show how one would be written until the first real implementation is in place.
};
16 changes: 16 additions & 0 deletions core/lerna/migrations/noop/noop.test.js
@@ -0,0 +1,16 @@
// @ts-check

const { createTreeWithEmptyWorkspace } = require("@nrwl/devkit/testing");
const { default: noopMigration } = require("./noop");

describe("noop migration", () => {
let tree;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});

it("should be runnable and not throw", async () => {
await expect(noopMigration(tree)).resolves.toBeUndefined();
});
});
12 changes: 10 additions & 2 deletions core/lerna/package.json
Expand Up @@ -19,7 +19,10 @@
"files": [
"index.js",
"cli.js",
"schemas/lerna-schema.json"
"schemas/lerna-schema.json",
"migrations",
"commands",
"migrations.json"
],
"engines": {
"node": "^14.15.0 || >=16.0.0"
Expand All @@ -32,12 +35,16 @@
"url": "git+https://github.com/lerna/lerna.git",
"directory": "core/lerna"
},
"nx-migrations": {
"migrations": "./migrations.json"
},
"scripts": {
"test": "echo \"Run tests from root\" && exit 1"
},
"dependencies": {
"@lerna/add": "file:../../commands/add",
"@lerna/bootstrap": "file:../../commands/bootstrap",
"@lerna/command": "file:../command",
"@lerna/changed": "file:../../commands/changed",
"@lerna/clean": "file:../../commands/clean",
"@lerna/cli": "file:../cli",
Expand All @@ -52,8 +59,9 @@
"@lerna/publish": "file:../../commands/publish",
"@lerna/run": "file:../../commands/run",
"@lerna/version": "file:../../commands/version",
"@nrwl/devkit": ">=14.5.8 < 16",
"import-local": "^3.0.2",
"npmlog": "^6.0.2",
"nx": ">=14.5.4 < 16"
"nx": ">=14.5.8 < 16"
}
}
36 changes: 36 additions & 0 deletions e2e/tests/lerna-repair/lerna-repair.spec.ts
@@ -0,0 +1,36 @@
import { Fixture } from "../../utils/fixture";
import { normalizeEnvironment } from "../../utils/snapshot-serializer-utils";

expect.addSnapshotSerializer({
serialize(str) {
return normalizeEnvironment(str);
},
test(val) {
return val != null && typeof val === "string";
},
});

describe("lerna-repair", () => {
let fixture: Fixture;

beforeAll(async () => {
fixture = await Fixture.create({
name: "lerna-repair",
packageManager: "npm",
initializeGit: true,
runLernaInit: true,
installDependencies: true,
});
});
afterAll(() => fixture.destroy());

it("should run any existing migrations", async () => {
const output = await fixture.lerna("repair");

expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info repair No changes were necessary. This workspace is up to date!
`);
});
});

0 comments on commit aae1a2b

Please sign in to comment.