Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(publish): include all dependencies in package graph by default, allow no-sort #3263

Merged
merged 1 commit into from Jul 27, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion commands/publish/__tests__/publish-canary.test.js
Expand Up @@ -79,9 +79,9 @@ test("publish --canary", async () => {
expect(npmPublish.registry).toMatchInlineSnapshot(`
Map {
"package-1" => "canary",
"package-3" => "canary",
"package-4" => "canary",
"package-2" => "canary",
"package-3" => "canary",
}
`);
expect(writePkg.updatedVersions()).toMatchInlineSnapshot(`
Expand Down
67 changes: 60 additions & 7 deletions commands/publish/__tests__/publish-command.test.js
Expand Up @@ -101,24 +101,24 @@ describe("PublishCommand", () => {
expect(packDirectory.registry).toMatchInlineSnapshot(`
Set {
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
}
`);
expect(npmPublish.registry).toMatchInlineSnapshot(`
Map {
"package-1" => "latest",
"package-3" => "latest",
"package-4" => "latest",
"package-2" => "latest",
"package-3" => "latest",
}
`);
expect(npmPublish.order()).toEqual([
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
// package-5 is private
]);
expect(npmDistTag.remove).not.toHaveBeenCalled();
Expand All @@ -142,9 +142,9 @@ Map {

expect(npmPublish.order()).toEqual([
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
// package-5 is private
]);
});
Expand All @@ -169,6 +169,21 @@ Map {
});

describe("--graph-type", () => {
it("produces a topological ordering that _includes_ devDependencies when value is not set", async () => {
const cwd = await initFixture("normal");

await lernaPublish(cwd)();

expect(npmPublish.order()).toEqual([
"package-1",
"package-4",
"package-2",
// package-3 has a peer/devDependency on package-2
"package-3",
// package-5 is private
]);
});

it("produces a topological ordering that _includes_ devDependencies when value is 'all'", async () => {
const cwd = await initFixture("normal");

Expand All @@ -184,6 +199,28 @@ Map {
]);
});

it("produces a topological ordering that _excludes_ devDependencies when value is 'dependencies' (DEPRECATED)", async () => {
const cwd = await initFixture("normal");

await lernaPublish(cwd)("--graph-type", "dependencies");

expect(npmPublish.order()).toEqual([
"package-1",
// package-3 has a peer/devDependency on package-2
"package-3",
"package-4",
"package-2",
// package-5 is private
]);

const logMessages = loggingOutput("warn");
expect(logMessages).toMatchInlineSnapshot(`
Array [
"--graph-type=dependencies is deprecated and will be removed in lerna v6. If you have a use-case you feel requires it please open an issue to discuss: https://github.com/lerna/lerna/issues/new/choose",
]
`);
});

it("throws an error when value is _not_ 'all' or 'dependencies'", async () => {
const testDir = await initFixture("normal");
const command = lernaPublish(testDir)("--graph-type", "poopy-pants");
Expand All @@ -192,6 +229,22 @@ Map {
});
});

describe("--no-sort", () => {
it("produces a lexical ordering when --no-sort is set", async () => {
const cwd = await initFixture("normal");

await lernaPublish(cwd)("--no-sort");

expect(npmPublish.order()).toEqual([
"package-1",
"package-2",
"package-3",
"package-4",
// package-5 is private
]);
});
});

describe("--otp", () => {
getOneTimePassword.mockImplementation(() => Promise.resolve("654321"));

Expand Down Expand Up @@ -317,24 +370,24 @@ Map {
expect(packDirectory.registry).toMatchInlineSnapshot(`
Set {
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
}
`);
expect(npmPublish.registry).toMatchInlineSnapshot(`
Map {
"package-1" => "latest",
"package-3" => "latest",
"package-4" => "latest",
"package-2" => "latest",
"package-3" => "latest",
}
`);
expect(npmPublish.order()).toEqual([
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
// package-5 is private
]);
expect(npmDistTag.remove).not.toHaveBeenCalled();
Expand Down
26 changes: 23 additions & 3 deletions commands/publish/__tests__/publish-from-git.test.js
Expand Up @@ -43,9 +43,29 @@ describe("publish from-git", () => {
expect(output.logged()).toMatch("Found 4 packages to publish:");
expect(npmPublish.order()).toEqual([
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
// package-5 is private
]);
});

it("publishes tagged packages, lexically sorted when --no-sort is present", async () => {
const cwd = await initFixture("normal");

await gitTag(cwd, "v1.0.0");
await lernaPublish(cwd)("from-git", "--no-sort");

// called from chained describeRef()
expect(throwIfUncommitted).toHaveBeenCalled();

expect(promptConfirmation).toHaveBeenLastCalledWith("Are you sure you want to publish these packages?");
expect(output.logged()).toMatch("Found 4 packages to publish:");
expect(npmPublish.order()).toEqual([
"package-1",
"package-2",
"package-3",
"package-4",
// package-5 is private
]);
});
Expand All @@ -64,9 +84,9 @@ describe("publish from-git", () => {

expect(npmPublish.order()).toEqual([
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
// package-5 is private
]);
});
Expand All @@ -79,9 +99,9 @@ describe("publish from-git", () => {

expect(npmPublish.order()).toEqual([
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
// package-5 is private
]);
});
Expand Down
18 changes: 17 additions & 1 deletion commands/publish/__tests__/publish-from-package.test.js
Expand Up @@ -55,9 +55,25 @@ describe("publish from-package", () => {

expect(npmPublish.order()).toEqual([
"package-1",
"package-3",
"package-4",
"package-2",
"package-3",
// package-5 is private
]);
});

it("publishes unpublished independent packages, lexically sorted when --no-sort is present", async () => {
const cwd = await initFixture("independent");

getUnpublishedPackages.mockImplementationOnce((packageGraph) => Array.from(packageGraph.values()));

await lernaPublish(cwd)("from-package", "--no-sort");

expect(npmPublish.order()).toEqual([
"package-1",
"package-2",
"package-3",
"package-4",
// package-5 is private
]);
});
Expand Down
49 changes: 40 additions & 9 deletions commands/publish/index.js
Expand Up @@ -58,6 +58,9 @@ class PublishCommand extends Command {
configureProperties() {
super.configureProperties();

// For publish we want to enable topological sorting by default, but allow users to override with --no-sort
this.toposort = this.options.sort !== false;

// Defaults are necessary here because yargs defaults
// override durable options provided by a config file
const {
Expand Down Expand Up @@ -102,6 +105,13 @@ class PublishCommand extends Command {
);
}

if (this.options.graphType === "dependencies") {
this.logger.warn(
"graph-type",
"--graph-type=dependencies is deprecated and will be removed in lerna v6. If you have a use-case you feel requires it please open an issue to discuss: https://github.com/lerna/lerna/issues/new/choose"
);
}

if (this.options.skipNpm) {
// TODO: remove in next major release
this.logger.warn("deprecated", "Instead of --skip-npm, call `lerna version` directly");
Expand Down Expand Up @@ -626,15 +636,21 @@ class PublishCommand extends Command {
}

topoMapPackages(mapper) {
// we don't respect --no-sort here, sorry
return runTopologically(this.packagesToPublish, mapper, {
concurrency: this.concurrency,
rejectCycles: this.options.rejectCycles,
// By default, do not include devDependencies in the graph because it would
// increase the chance of dependency cycles, causing less-than-ideal order.
// If the user has opted-in to --graph-type=all (or "graphType": "all" in lerna.json),
// devDependencies _will_ be included in the graph construction.
graphType: this.options.graphType === "all" ? "allDependencies" : "dependencies",
/**
* Previously `publish` had unique default behavior for graph creation vs other commands: it would only consider dependencies when finding
* edges by default (i.e. relationships between packages specified via devDependencies would be ignored). It was documented to be the case
* in order to try and reduce the chance of dependency cycles.
*
* We are removing this behavior altogether in v6 because we do not want to have different ways of constructing the graph,
* only different ways of utilizing it (e.g. --no-sort vs topological sort).
*
* Therefore until we remove graphType altogether in v6, we provide a way for users to opt into the old default behavior
* by setting the `graphType` option to `dependencies`.
*/
graphType: this.options.graphType === "dependencies" ? "dependencies" : "allDependencies",
});
}

Expand Down Expand Up @@ -676,7 +692,12 @@ class PublishCommand extends Command {
].filter(Boolean)
);

chain = chain.then(() => this.topoMapPackages(mapper));
chain = chain.then(() => {
if (this.toposort) {
return this.topoMapPackages(mapper);
}
return pMap(this.packagesToPublish, mapper, { concurrency: this.concurrency });
});

chain = chain.then(() => removeTempLicenses(this.packagesToBeLicensed));

Expand Down Expand Up @@ -729,7 +750,12 @@ class PublishCommand extends Command {
].filter(Boolean)
);

chain = chain.then(() => this.topoMapPackages(mapper));
chain = chain.then(() => {
if (this.toposort) {
return this.topoMapPackages(mapper);
}
return pMap(this.packagesToPublish, mapper, { concurrency: this.concurrency });
});

if (!this.hasRootedLeaf) {
// cyclical "publish" lifecycles are automatically skipped
Expand Down Expand Up @@ -772,7 +798,12 @@ class PublishCommand extends Command {
});
};

chain = chain.then(() => this.topoMapPackages(mapper));
chain = chain.then(() => {
if (this.toposort) {
return this.topoMapPackages(mapper);
}
return pMap(this.packagesToPublish, mapper, { concurrency: this.concurrency });
});

return chain.finally(() => tracker.finish());
}
Expand Down
6 changes: 4 additions & 2 deletions core/lerna/schemas/lerna-schema.json
Expand Up @@ -1600,8 +1600,10 @@
},
"graphType": {
"type": "string",
"enum": ["all", "depencencies"],
"description": "For `lerna publish`, the type of dependency to use when determining package hierarchy."
"enum": ["all", "dependencies"],
"description": "DEPRECATED: For `lerna publish`, if you want to ignore devDependencies when considering the package graph you can set this property equal to 'dependencies'. This will be removed in v6 of lerna.",
"default": "all",
"deprecated": true
},
"otp": {
"type": "string",
Expand Down