fix(js): show lifecycle script contents in publish executor, scrub version in dry-run (#23850)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> ## Current Behavior <!-- This is the behavior we have today --> Lifecycle scripts related to publishing change the output of `npm publish`, which mixes JSON with non-JSON content despite the `--json` flag being set. Currently, we will error when attempting to parse the whole thing as JSON. The lifecycle scripts contents themselves are not shown to the user. Additionally, during dry-run we have no choice but to print the version that currently exists on disk, which can be confusing. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> We extract and parse the JSON from the `npm publish` output, even when it is mixed with other output. We also show the lifecycle script outputs to the user, where applicable. During dry-run, we replace the version in the publish output with a placeholder to avoid confusion around what would be published. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes https://github.com/nrwl/nx/issues/22925
This commit is contained in:
parent
4cbb0f0988
commit
aa2519ff62
@ -637,7 +637,7 @@ describe('nx release - independent projects', () => {
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@999.9.9-version-git-operations-test.3
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXXB CHANGELOG.md
|
XXXB CHANGELOG.md
|
||||||
@ -646,8 +646,8 @@ describe('nx release - independent projects', () => {
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 999.9.9-version-git-operations-test.3
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-999.9.9-version-git-operations-test.3.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
@ -686,7 +686,7 @@ describe('nx release - independent projects', () => {
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@999.9.9-version-git-operations-test.3
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXXB CHANGELOG.md
|
XXXB CHANGELOG.md
|
||||||
@ -695,8 +695,8 @@ describe('nx release - independent projects', () => {
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 999.9.9-version-git-operations-test.3
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-999.9.9-version-git-operations-test.3.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
@ -723,7 +723,7 @@ describe('nx release - independent projects', () => {
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@999.9.9-version-git-operations-test.3
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXXB CHANGELOG.md
|
XXXB CHANGELOG.md
|
||||||
@ -732,8 +732,8 @@ describe('nx release - independent projects', () => {
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 999.9.9-version-git-operations-test.3
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-999.9.9-version-git-operations-test.3.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
@ -768,7 +768,7 @@ describe('nx release - independent projects', () => {
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@999.9.9-version-git-operations-test.3
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXXB CHANGELOG.md
|
XXXB CHANGELOG.md
|
||||||
@ -777,8 +777,8 @@ describe('nx release - independent projects', () => {
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 999.9.9-version-git-operations-test.3
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-999.9.9-version-git-operations-test.3.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
@ -790,7 +790,7 @@ describe('nx release - independent projects', () => {
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@999.9.9-version-git-operations-test.3
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXXB CHANGELOG.md
|
XXXB CHANGELOG.md
|
||||||
@ -799,8 +799,8 @@ describe('nx release - independent projects', () => {
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 999.9.9-version-git-operations-test.3
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-999.9.9-version-git-operations-test.3.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
@ -832,7 +832,7 @@ describe('nx release - independent projects', () => {
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@999.9.9-version-git-operations-test.3
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXXB CHANGELOG.md
|
XXXB CHANGELOG.md
|
||||||
@ -841,8 +841,8 @@ describe('nx release - independent projects', () => {
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 999.9.9-version-git-operations-test.3
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-999.9.9-version-git-operations-test.3.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
|
|||||||
@ -407,7 +407,7 @@ ${JSON.stringify(
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@1000.0.0-next.0
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXB index.js
|
XXB index.js
|
||||||
@ -415,8 +415,8 @@ ${JSON.stringify(
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 1000.0.0-next.0
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-1000.0.0-next.0.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
@ -428,7 +428,7 @@ ${JSON.stringify(
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@1000.0.0-next.0
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXB index.js
|
XXB index.js
|
||||||
@ -436,8 +436,8 @@ ${JSON.stringify(
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 1000.0.0-next.0
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-1000.0.0-next.0.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
@ -449,7 +449,7 @@ ${JSON.stringify(
|
|||||||
> nx run {project-name}:nx-release-publish
|
> nx run {project-name}:nx-release-publish
|
||||||
|
|
||||||
|
|
||||||
📦 @proj/{project-name}@1000.0.0-next.0
|
📦 @proj/{project-name}@X.X.X-dry-run
|
||||||
=== Tarball Contents ===
|
=== Tarball Contents ===
|
||||||
|
|
||||||
XXB index.js
|
XXB index.js
|
||||||
@ -457,8 +457,8 @@ ${JSON.stringify(
|
|||||||
XXB project.json
|
XXB project.json
|
||||||
=== Tarball Details ===
|
=== Tarball Details ===
|
||||||
name: @proj/{project-name}
|
name: @proj/{project-name}
|
||||||
version: 1000.0.0-next.0
|
version: X.X.X-dry-run
|
||||||
filename: proj-{project-name}-1000.0.0-next.0.tgz
|
filename: proj-{project-name}-X.X.X-dry-run.tgz
|
||||||
package size: XXXB
|
package size: XXXB
|
||||||
unpacked size: XXXB
|
unpacked size: XXXB
|
||||||
shasum: {SHASUM}
|
shasum: {SHASUM}
|
||||||
|
|||||||
@ -0,0 +1,348 @@
|
|||||||
|
import { extractNpmPublishJsonData } from './extract-npm-publish-json-data';
|
||||||
|
|
||||||
|
describe('extractNpmPublishJsonData()', () => {
|
||||||
|
describe('only unrelated JSON data', () => {
|
||||||
|
// Does not match expected npm publish JSON data
|
||||||
|
const data = {
|
||||||
|
foo: true,
|
||||||
|
bar: [1, 2, 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should safely ignore unrelated formatted JSON data', () => {
|
||||||
|
const formattedJsonStr = JSON.stringify(data, null, 2);
|
||||||
|
const res = extractNpmPublishJsonData(formattedJsonStr);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
"foo": true,
|
||||||
|
"bar": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
expect(res.jsonData).toEqual(null);
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`""`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely ignore unrelated unformatted JSON data', () => {
|
||||||
|
const unformattedJsonStr = JSON.stringify(data);
|
||||||
|
const res = extractNpmPublishJsonData(unformattedJsonStr);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(
|
||||||
|
`"{"foo":true,"bar":[1,2,3]}"`
|
||||||
|
);
|
||||||
|
expect(res.jsonData).toEqual(null);
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`""`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mixed unrelated JSON and non-JSON data', () => {
|
||||||
|
// Does not match expected npm publish JSON data
|
||||||
|
const data = {
|
||||||
|
foo: true,
|
||||||
|
bar: [1, 2, 3],
|
||||||
|
};
|
||||||
|
const extraContentBefore = 'Some random text';
|
||||||
|
const extraContentAfter = 'More random text';
|
||||||
|
|
||||||
|
it('should safely ignore unrelated mixed data containing formatted JSON', () => {
|
||||||
|
const formattedJsonStr = JSON.stringify(data, null, 2);
|
||||||
|
const res = extractNpmPublishJsonData(`${extraContentBefore}
|
||||||
|
${formattedJsonStr}
|
||||||
|
${extraContentAfter}`);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(`
|
||||||
|
"Some random text
|
||||||
|
{
|
||||||
|
"foo": true,
|
||||||
|
"bar": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
}
|
||||||
|
More random text"
|
||||||
|
`);
|
||||||
|
expect(res.jsonData).toEqual(null);
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`""`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely ignore unrelated mixed data containing unformatted JSON', () => {
|
||||||
|
const unformattedJsonStr = JSON.stringify(data);
|
||||||
|
const res = extractNpmPublishJsonData(`${extraContentBefore}
|
||||||
|
${unformattedJsonStr}
|
||||||
|
${extraContentAfter}`);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(`
|
||||||
|
"Some random text
|
||||||
|
{"foo":true,"bar":[1,2,3]}
|
||||||
|
More random text"
|
||||||
|
`);
|
||||||
|
expect(res.jsonData).toEqual(null);
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`""`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('output containing npm publish JSON data', () => {
|
||||||
|
it('should extract the relevant JSON data from a simple publish output string containing only the data', () => {
|
||||||
|
const commandOutput = `{
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"name": "package-a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"size": 251,
|
||||||
|
"unpackedSize": 233,
|
||||||
|
"shasum": "cf4a6657f230ddf5375102bafc8f5184002a620a",
|
||||||
|
"integrity": "sha512-Qra/YIkAxVavs3tumB/svugHLY5CISujdeUcMd2FfvtVkjEEsVAEYbqZTq0ixnkvjVrLr27mAvH94GjjMKWzIg==",
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 233,
|
||||||
|
"mode": 420
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entryCount": 1,
|
||||||
|
"bundled": []
|
||||||
|
}`;
|
||||||
|
const res = extractNpmPublishJsonData(commandOutput);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(`""`);
|
||||||
|
expect(res.jsonData).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"bundled": [],
|
||||||
|
"entryCount": 1,
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"mode": 420,
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 233,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"integrity": "sha512-Qra/YIkAxVavs3tumB/svugHLY5CISujdeUcMd2FfvtVkjEEsVAEYbqZTq0ixnkvjVrLr27mAvH94GjjMKWzIg==",
|
||||||
|
"name": "package-a",
|
||||||
|
"shasum": "cf4a6657f230ddf5375102bafc8f5184002a620a",
|
||||||
|
"size": 251,
|
||||||
|
"unpackedSize": 233,
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`""`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract the relevant JSON data from a publish output string containing lifecycle script outputs', () => {
|
||||||
|
const exampleCommandOutputWithLifecycleScripts = `
|
||||||
|
> package-a@1.0.0 prepublishOnly
|
||||||
|
> echo 'prepublishOnly from package-a'
|
||||||
|
|
||||||
|
prepublishOnly from package-a
|
||||||
|
{
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"name": "package-a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"size": 206,
|
||||||
|
"unpackedSize": 179,
|
||||||
|
"shasum": "f01c6f5c8d72ed33e70c1c1b1258f46c92360e57",
|
||||||
|
"integrity": "sha512-24/pgfxiTiNB/dw7ZbBZ+I1vidq09KU6n/QgXCtx1y4+ezYpEBSncdrEpDxuMD6YaP8twg3H8zQBLoG8xwygcA==",
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 179,
|
||||||
|
"mode": 420
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entryCount": 1,
|
||||||
|
"bundled": []
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const res = extractNpmPublishJsonData(
|
||||||
|
exampleCommandOutputWithLifecycleScripts
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
> package-a@1.0.0 prepublishOnly
|
||||||
|
> echo 'prepublishOnly from package-a'
|
||||||
|
|
||||||
|
prepublishOnly from package-a
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(res.jsonData).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"bundled": [],
|
||||||
|
"entryCount": 1,
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"mode": 420,
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 179,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"integrity": "sha512-24/pgfxiTiNB/dw7ZbBZ+I1vidq09KU6n/QgXCtx1y4+ezYpEBSncdrEpDxuMD6YaP8twg3H8zQBLoG8xwygcA==",
|
||||||
|
"name": "package-a",
|
||||||
|
"shasum": "f01c6f5c8d72ed33e70c1c1b1258f46c92360e57",
|
||||||
|
"size": 206,
|
||||||
|
"unpackedSize": 179,
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work when a user lifecycle script adds custom, unformatted JSON data to the output', () => {
|
||||||
|
const exampleCommandOutputWithLifecycleScripts = `
|
||||||
|
> package-a@1.0.0 prepublishOnly
|
||||||
|
> node -e 'console.log(JSON.stringify({"name": "package-a", "version": "1.0.0"}));'
|
||||||
|
|
||||||
|
{"name":"package-a","version":"1.0.0"}
|
||||||
|
{
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"name": "package-a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"size": 249,
|
||||||
|
"unpackedSize": 232,
|
||||||
|
"shasum": "63caa58603b8f9b76a5151ad4e965c3ac0b83c71",
|
||||||
|
"integrity": "sha512-mXgusXuPfyvqNpnHY3F0TwLiitKzt98hcAxgEq6/uueEM53haisRQx+tf5FEE6uNRhE+9U0A2y9//KD2OPnSBQ==",
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 232,
|
||||||
|
"mode": 420
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entryCount": 1,
|
||||||
|
"bundled": []
|
||||||
|
}`;
|
||||||
|
const res = extractNpmPublishJsonData(
|
||||||
|
exampleCommandOutputWithLifecycleScripts
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
> package-a@1.0.0 prepublishOnly
|
||||||
|
> node -e 'console.log(JSON.stringify({"name": "package-a", "version": "1.0.0"}));'
|
||||||
|
|
||||||
|
{"name":"package-a","version":"1.0.0"}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(res.jsonData).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"bundled": [],
|
||||||
|
"entryCount": 1,
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"mode": 420,
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 232,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"integrity": "sha512-mXgusXuPfyvqNpnHY3F0TwLiitKzt98hcAxgEq6/uueEM53haisRQx+tf5FEE6uNRhE+9U0A2y9//KD2OPnSBQ==",
|
||||||
|
"name": "package-a",
|
||||||
|
"shasum": "63caa58603b8f9b76a5151ad4e965c3ac0b83c71",
|
||||||
|
"size": 249,
|
||||||
|
"unpackedSize": 232,
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`""`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract the relevant JSON data when formatted JSON data is present alongside the expected npm publish JSON data', () => {
|
||||||
|
const exampleCommandOutputWithFormattedJSON = `
|
||||||
|
{
|
||||||
|
"unrelated": true,
|
||||||
|
"data": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"name": "package-a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"size": 249,
|
||||||
|
"unpackedSize": 232,
|
||||||
|
"shasum": "63caa58603b8f9b76a5151ad4e965c3ac0b83c71",
|
||||||
|
"integrity": "sha512-mXgusXuPfyvqNpnHY3F0TwLiitKzt98hcAxgEq6/uueEM53haisRQx+tf5FEE6uNRhE+9U0A2y9//KD2OPnSBQ==",
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 232,
|
||||||
|
"mode": 420
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entryCount": 1,
|
||||||
|
"bundled": []
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"extra": "data",
|
||||||
|
"foo": "bar"
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const res = extractNpmPublishJsonData(
|
||||||
|
exampleCommandOutputWithFormattedJSON
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res.beforeJsonData).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
{
|
||||||
|
"unrelated": true,
|
||||||
|
"data": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(res.jsonData).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"bundled": [],
|
||||||
|
"entryCount": 1,
|
||||||
|
"filename": "package-a-1.0.0.tgz",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"mode": 420,
|
||||||
|
"path": "package.json",
|
||||||
|
"size": 232,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"id": "package-a@1.0.0",
|
||||||
|
"integrity": "sha512-mXgusXuPfyvqNpnHY3F0TwLiitKzt98hcAxgEq6/uueEM53haisRQx+tf5FEE6uNRhE+9U0A2y9//KD2OPnSBQ==",
|
||||||
|
"name": "package-a",
|
||||||
|
"shasum": "63caa58603b8f9b76a5151ad4e965c3ac0b83c71",
|
||||||
|
"size": 249,
|
||||||
|
"unpackedSize": 232,
|
||||||
|
"version": "1.0.0",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(res.afterJsonData).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
{
|
||||||
|
"extra": "data",
|
||||||
|
"foo": "bar"
|
||||||
|
}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
const expectedNpmPublishJsonKeys = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'version',
|
||||||
|
'size',
|
||||||
|
'filename',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Regular expression to match JSON-like objects, including nested objects (which the expected npm publish output will have, e.g. in its "files" array)
|
||||||
|
// /{(?:[^{}]|{[^{}]*})*}/g
|
||||||
|
// /{ : Matches the opening brace of a JSON object
|
||||||
|
// (?: ) : Non-capturing group to apply quantifiers
|
||||||
|
// [^{}] : Matches any character except for braces
|
||||||
|
// | : OR
|
||||||
|
// {[^{}]*} : Matches nested JSON objects
|
||||||
|
// * : The non-capturing group (i.e. any character except for braces OR nested JSON objects) can repeat zero or more times
|
||||||
|
// } : Matches the closing brace of a JSON object
|
||||||
|
// /g : Global flag to match all occurrences in the string
|
||||||
|
const jsonRegex = /{(?:[^{}]|{[^{}]*})*}/g;
|
||||||
|
|
||||||
|
export function extractNpmPublishJsonData(str: string): {
|
||||||
|
beforeJsonData: string;
|
||||||
|
jsonData: Record<string, unknown> | null;
|
||||||
|
afterJsonData: string;
|
||||||
|
} {
|
||||||
|
const jsonMatches = str.match(jsonRegex);
|
||||||
|
if (jsonMatches) {
|
||||||
|
for (const match of jsonMatches) {
|
||||||
|
// Cheap upfront check to see if the stringified JSON data has the expected keys as substrings
|
||||||
|
if (!expectedNpmPublishJsonKeys.every((key) => str.includes(key))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Full JSON parsing to identify the JSON object
|
||||||
|
try {
|
||||||
|
const parsedJson = JSON.parse(match);
|
||||||
|
if (
|
||||||
|
!expectedNpmPublishJsonKeys.every(
|
||||||
|
(key) => parsedJson[key] !== undefined
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const jsonStartIndex = str.indexOf(match);
|
||||||
|
return {
|
||||||
|
beforeJsonData: str.slice(0, jsonStartIndex),
|
||||||
|
jsonData: parsedJson,
|
||||||
|
afterJsonData: str.slice(jsonStartIndex + match.length),
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
// Ignore parsing errors for unrelated JSON blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No applicable jsonData detected, the whole contents is the beforeJsonData
|
||||||
|
return {
|
||||||
|
beforeJsonData: str,
|
||||||
|
jsonData: null,
|
||||||
|
afterJsonData: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import { parseRegistryOptions } from '../../utils/npm-config';
|
|||||||
import { logTar } from './log-tar';
|
import { logTar } from './log-tar';
|
||||||
import { PublishExecutorSchema } from './schema';
|
import { PublishExecutorSchema } from './schema';
|
||||||
import chalk = require('chalk');
|
import chalk = require('chalk');
|
||||||
|
import { extractNpmPublishJsonData } from './extract-npm-publish-json-data';
|
||||||
|
|
||||||
const LARGE_BUFFER = 1024 * 1000000;
|
const LARGE_BUFFER = 1024 * 1000000;
|
||||||
|
|
||||||
@ -200,6 +201,11 @@ export default async function runExecutor(
|
|||||||
console.log('Skipped npm view because --first-release was set');
|
console.log('Skipped npm view because --first-release was set');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: If this is ever changed away from running the command at the workspace root and pointing at the package root (e.g. back
|
||||||
|
* to running from the package root directly), then special attention should be paid to the fact that npm publish will nest its
|
||||||
|
* JSON output under the name of the package in that case (and it would need to be handled below).
|
||||||
|
*/
|
||||||
const npmPublishCommandSegments = [
|
const npmPublishCommandSegments = [
|
||||||
`npm publish "${packageRoot}" --json --"${registryConfigKey}=${registry}" --tag=${tag}`,
|
`npm publish "${packageRoot}" --json --"${registryConfigKey}=${registry}" --tag=${tag}`,
|
||||||
];
|
];
|
||||||
@ -220,11 +226,47 @@ export default async function runExecutor(
|
|||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const stdoutData = JSON.parse(output.toString());
|
/**
|
||||||
|
* We cannot JSON.parse the output directly because if the user is using lifecycle scripts, npm will mix its publish output with the JSON output all on stdout.
|
||||||
|
* Additionally, we want to capture and show the lifecycle script outputs as beforeJsonData and afterJsonData and print them accordingly below.
|
||||||
|
*/
|
||||||
|
const { beforeJsonData, jsonData, afterJsonData } =
|
||||||
|
extractNpmPublishJsonData(output.toString());
|
||||||
|
if (!jsonData) {
|
||||||
|
console.error(
|
||||||
|
'The npm publish output data could not be extracted. Please report this issue on https://github.com/nrwl/nx'
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// If npm workspaces are in use, the publish output will nest the data under the package name, so we normalize it first
|
// If in dry-run mode, the version on disk will not represent the version that would be published, so we scrub it from the output to avoid confusion.
|
||||||
const normalizedStdoutData = stdoutData[packageName] ?? stdoutData;
|
const dryRunVersionPlaceholder = 'X.X.X-dry-run';
|
||||||
logTar(normalizedStdoutData);
|
if (isDryRun) {
|
||||||
|
for (const [key, val] of Object.entries(jsonData)) {
|
||||||
|
if (typeof val !== 'string') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
jsonData[key] = val.replace(
|
||||||
|
new RegExp(packageJson.version, 'g'),
|
||||||
|
dryRunVersionPlaceholder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof beforeJsonData === 'string' &&
|
||||||
|
beforeJsonData.trim().length > 0
|
||||||
|
) {
|
||||||
|
console.log(beforeJsonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
logTar(jsonData);
|
||||||
|
|
||||||
|
if (typeof afterJsonData === 'string' && afterJsonData.trim().length > 0) {
|
||||||
|
console.log(afterJsonData);
|
||||||
|
}
|
||||||
|
|
||||||
if (isDryRun) {
|
if (isDryRun) {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user