Skip to content

Commit

Permalink
feat(publish): add --include-private option for testing private packa…
Browse files Browse the repository at this point in the history
…ges (#3503)
  • Loading branch information
fahslaj committed Feb 10, 2023
1 parent afde32e commit fa1f490
Show file tree
Hide file tree
Showing 9 changed files with 665 additions and 20 deletions.
2 changes: 1 addition & 1 deletion e2e/publish/src/from-git-recover-from-error.spec.ts
Expand Up @@ -77,7 +77,6 @@ describe("lerna-publish-from-git-recover-from-error", () => {
lerna WARN ENOLICENSE Packages test-1 and test-2 are missing a license.
lerna WARN ENOLICENSE One way to fix this is to add a LICENSE.md file to the root of this repository.
lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.
lerna WARN publish Package is already published: test-1@XX.XX.XX
lerna success published test-2 XX.XX.XX
lerna notice
lerna notice 📦 test-2@XX.XX.XX
Expand All @@ -95,6 +94,7 @@ describe("lerna-publish-from-git-recover-from-error", () => {
lerna notice integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
lerna notice total files: 3
lerna notice
lerna WARN publish Package is already published: test-1@XX.XX.XX
Successfully published:
- test-2@XX.XX.XX
lerna success published 1 package
Expand Down
544 changes: 544 additions & 0 deletions e2e/publish/src/publish-private-packages.spec.ts

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion libs/commands/create/README.md
Expand Up @@ -34,7 +34,8 @@ Command Options:
pkg.homepage [string]
--keywords A list of package keywords [array]
--license The desired package license (SPDX identifier) [default: ISC]
--private Make the new package private, never published
--private Make the new package private, indicating that it
should not be published.
--registry Configure the package's publishConfig.registry [string]
--tag Configure the package's publishConfig.tag [string]
--yes Skip all prompts, accepting default values
Expand Down
28 changes: 26 additions & 2 deletions libs/commands/publish/README.md
Expand Up @@ -20,7 +20,7 @@ When run, this command does one of the following things:
- Publish packages in the latest commit where the version is not present in the registry (`from-package`).
- Publish an unversioned "canary" release of packages (and their dependents) updated in the previous commit.

> Lerna will never publish packages which are marked as private (`"private": true` in the `package.json`).
> Lerna will not publish packages which are marked as private (`"private": true` in the `package.json`). This is consistent with the behavior of `npm publish`. See the [package.json docs](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#private) for more information. To override this behavior, see the [`--include-private` option](#--include-private).
During all publish operations, appropriate [lifecycle scripts](#lifecycle-scripts) are called in the root and per-package (unless disabled by [`--ignore-scripts](#--ignore-scripts)).

Expand Down Expand Up @@ -56,6 +56,7 @@ This is useful when a previous `lerna publish` failed to publish all packages to
- [`--graph-type <all|dependencies>`](#--graph-type-alldependencies)
- [`--ignore-scripts`](#--ignore-scripts)
- [`--ignore-prepublish`](#--ignore-prepublish)
- [`--include-private`](#--include-private)
- [`--legacy-auth`](#--legacy-auth)
- [`--no-git-reset`](#--no-git-reset)
- [`--no-granular-pathspec`](#--no-granular-pathspec)
Expand Down Expand Up @@ -173,6 +174,29 @@ When passed, this flag will disable running [lifecycle scripts](#lifecycle-scrip

When passed, this flag will disable running [deprecated](https://docs.npmjs.com/misc/scripts#prepublish-and-prepare) [`prepublish` scripts](#lifecycle-scripts) during `lerna publish`.

### `--include-private`

Indicates a list of packages marked with `"private": true` that should be published. Since `npm publish` refuses to publish any packages with `"private": true`, Lerna removes the property before publishing.

Note that this is different than [private scoped packages](https://docs.npmjs.com/about-private-packages), which do not have `"private": true` in their `package.json`, are intended to be published, and are included by `lerna publish` by default. See the [npm docs](https://docs.npmjs.com/about-private-packages) for more information about these kind of packages.

> ⚠️ CAUTION ⚠️: Packages marked with `"private": true` are not intended to be published, as detailed in the [npm package.json docs](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#private). The intended use of this option is to allow publishing packages that _will be public in the future_ to a local registry for testing purposes.
Examples:

```sh
lerna publish --include-private my-private-package
lerna publish --include-private my-private-package my-other-private-package
```

The wildcard "\*" may also be provided in place of a list of packages. In this case, all private packages will be published.

```sh
lerna publish --include-private "*"
```

Quotes must be placed around "\*" to prevent the shell from prematurely expanding it.

### `--legacy-auth`

When publishing packages that require authentication but you are working with an internally hosted NPM Registry that only uses the legacy Base64 version of username:password. This is the same as the NPM publish `_auth` flag.
Expand Down Expand Up @@ -357,7 +381,7 @@ To publish packages with a scope (e.g., `@mycompany/rocks`), you must set [`acce
- If this field is set for a package _without_ a scope, it **will** fail.
- If you _want_ your scoped package to remain private (i.e., `"restricted"`), there is no need to set this value.

Note that this is **not** the same as setting `"private": true` in a leaf package; if the `private` field is set, that package will _never_ be published under any circumstances.
Note that this is **not** the same as setting `"private": true` in a leaf package; if the `private` field is set, that package will not be published. For more information, see the [`--include-private` option](#--include-private).

### `publishConfig.registry`

Expand Down
14 changes: 13 additions & 1 deletion libs/commands/publish/src/command.ts
Expand Up @@ -122,6 +122,11 @@ const command: CommandModule = {
"Generate a json summary report after all packages have been successfully published, you can pass an optional path for where to save the file.",
type: "string",
},
"include-private": {
describe:
"Include specified private packages when publishing by temporarily removing the private property from the package manifest. This should only be used for testing private packages that will become public. Private packages should not usually be published. See the npm docs for details (https://docs.npmjs.com/cli/v9/configuring-npm/package-json#private).",
type: "array",
},
};

composeVersionOptions(yargs);
Expand Down Expand Up @@ -178,7 +183,14 @@ const command: CommandModule = {
/* eslint-enable no-param-reassign */

return argv;
});
})
.middleware((args) => {
const { includePrivate } = args;
if (includePrivate && Array.isArray(includePrivate)) {
// eslint-disable-next-line no-param-reassign
args["includePrivate"] = includePrivate.reduce((acc, pkg) => [...acc, ...pkg.split(/[\s,]/)], []);
}
}, true);
},
handler(argv) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand Down
70 changes: 57 additions & 13 deletions libs/commands/publish/src/index.ts
Expand Up @@ -127,6 +127,16 @@ class PublishCommand extends Command {
);
}

if (this.options.includePrivate) {
if (this.options.includePrivate.length === 0) {
throw new ValidationError(
"EINCLPRIV",
"Must specify at least one private package to include with --include-private."
);
}
this.logger.info("publish", `Including private packages %j`, this.options.includePrivate);
}

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 @@ -218,7 +228,7 @@ class PublishCommand extends Command {
}

// (occasionally) redundant private filtering necessary to handle nested VersionCommand
this.updates = result.updates.filter((node) => !node.pkg.private);
this.updates = this.filterPrivatePkgUpdates(result.updates);
this.updatesVersions = new Map(result.updatesVersions);

this.packagesToPublish = this.updates.map((node) => node.pkg);
Expand Down Expand Up @@ -249,6 +259,7 @@ class PublishCommand extends Command {

chain = chain.then(() => this.prepareRegistryActions());
chain = chain.then(() => this.prepareLicenseActions());
chain = chain.then(() => this.preparePrivatePackages());

if (this.options.canary) {
chain = chain.then(() => this.updateCanaryVersions());
Expand All @@ -261,6 +272,10 @@ class PublishCommand extends Command {
chain = chain.then(() => this.packUpdated());
chain = chain.then(() => this.publishPacked());

// restore private: true to published private packages
chain = chain.then(() => this.restorePrivatePackages());
chain = chain.then(() => this.serializeChanges());

if (this.gitReset) {
chain = chain.then(() => this.resetChanges());
}
Expand Down Expand Up @@ -346,8 +361,7 @@ class PublishCommand extends Command {
return getTaggedPackages(this.packageGraph, this.project.rootPath, this.execOpts);
});

// private packages are never published, full stop.
chain = chain.then((updates) => updates.filter((node) => !node.pkg.private));
chain = chain.then((updates) => this.filterPrivatePkgUpdates(updates));

return chain.then((updates) => {
const updatesVersions = updates.map((node) => [node.name, node.version]);
Expand Down Expand Up @@ -378,8 +392,8 @@ class PublishCommand extends Command {
}
});

// private packages are already omitted by getUnpublishedPackages()
chain = chain.then(() => getUnpublishedPackages(this.packageGraph, this.conf.snapshot));
chain = chain.then((updates) => this.filterPrivatePkgUpdates(updates));
chain = chain.then((unpublished) => {
if (!unpublished.length) {
this.logger.notice("from-package", "No unpublished release found");
Expand Down Expand Up @@ -430,14 +444,15 @@ class PublishCommand extends Command {

// find changed packages since last release, if any
chain = chain.then(() =>
collectUpdates(this.packageGraph.rawPackageList, this.packageGraph, this.execOpts, {
bump: "prerelease",
canary: true,
ignoreChanges,
forcePublish,
includeMergedTags,
// private packages are never published, don't bother describing their refs.
}).filter((node) => !node.pkg.private)
this.filterPrivatePkgUpdates(
collectUpdates(this.packageGraph.rawPackageList, this.packageGraph, this.execOpts, {
bump: "prerelease",
canary: true,
ignoreChanges,
forcePublish,
includeMergedTags,
})
)
);

const makeVersion =
Expand Down Expand Up @@ -500,7 +515,7 @@ class PublishCommand extends Command {
confirmPublish() {
const count = this.packagesToPublish.length;
const message = this.packagesToPublish.map(
(pkg) => ` - ${pkg.name} => ${this.updatesVersions.get(pkg.name)}`
(pkg) => ` - ${pkg.name} => ${this.updatesVersions.get(pkg.name)}${pkg.private ? " (private!)" : ""}`
);

output("");
Expand All @@ -516,6 +531,26 @@ class PublishCommand extends Command {
return promptConfirmation("Are you sure you want to publish these packages?");
}

preparePrivatePackages() {
return Promise.resolve().then(() => {
this.privatePackagesToPublish = [];
this.packagesToPublish.forEach((pkg) => {
if (pkg.private) {
pkg.removePrivate();
this.privatePackagesToPublish.push(pkg);
}
});
});
}

restorePrivatePackages() {
return Promise.resolve().then(() => {
this.privatePackagesToPublish.forEach((pkg) => {
pkg.private = true;
});
});
}

prepareLicenseActions() {
return Promise.resolve()
.then(() => getPackagesWithoutLicense(this.project, this.packagesToPublish))
Expand Down Expand Up @@ -951,6 +986,15 @@ class PublishCommand extends Command {
return this.options.preDistTag;
}
}

// filter out private packages, respecting the --include-private option
filterPrivatePkgUpdates(updates) {
const privatePackagesToInclude = new Set(this.options.includePrivate || []);
return updates.filter(
(node) =>
!node.pkg.private || privatePackagesToInclude.has("*") || privatePackagesToInclude.has(node.pkg.name)
);
}
}

module.exports.PublishCommand = PublishCommand;
3 changes: 1 addition & 2 deletions libs/commands/publish/src/lib/get-unpublished-packages.ts
Expand Up @@ -18,8 +18,7 @@ function getUnpublishedPackages(packageGraph, opts) {

let chain = Promise.resolve();

// don't bother attempting to get the packument for private packages
const graphNodesToCheck = Array.from(packageGraph.values()).filter(({ pkg }) => !pkg.private);
const graphNodesToCheck = Array.from(packageGraph.values());

const mapper = (pkg) =>
pacote.packument(pkg.name, opts).then(
Expand Down
11 changes: 11 additions & 0 deletions libs/core/src/lib/package.ts
Expand Up @@ -118,6 +118,10 @@ export class Package {
return Boolean(this[PKG].private);
}

set private(isPrivate) {
this[PKG].private = isPrivate;
}

get resolved() {
return this[_resolved];
}
Expand Down Expand Up @@ -326,4 +330,11 @@ export class Package {
depCollection[depName] = hosted.toString({ noGitPlus: false, noCommittish: false });
}
}

/**
* Remove the private property, effectively making the package public.
*/
removePrivate() {
delete this[PKG].private;
}
}
10 changes: 10 additions & 0 deletions packages/lerna/schemas/lerna-schema.json
Expand Up @@ -822,6 +822,9 @@
"verifyAccess": {
"$ref": "#/$defs/commandOptions/publish/verifyAccess"
},
"includePrivate": {
"$ref": "#/$defs/commandOptions/publish/includePrivate"
},

"npmClient": {
"$ref": "#/$defs/globals/npmClient"
Expand Down Expand Up @@ -1694,6 +1697,13 @@
"verifyAccess": {
"type": "boolean",
"description": "During `lerna publish`, when true, verify package read-write access for current npm user."
},
"includePrivate": {
"type": "array",
"items": {
"type": "string"
},
"description": "During `lerna publish`, include specified private packages when publishing by temporarily removing the private property from the package manifest. This should only be used for testing private packages that will become public. The value \"*\" will include all private packages."
}
},
"run": {
Expand Down

0 comments on commit fa1f490

Please sign in to comment.