From bf6e5ef2a4fb759cf5a99e33252e7a65e2174dfe Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Tue, 28 Mar 2023 09:40:02 -0700 Subject: [PATCH] chore: update mono-repo-publish to leave a tarball So we can generate SBOMs and attestations for the tarball. --- packages/mono-repo-publish/package-lock.json | 251 +++++++++++++++++++ packages/mono-repo-publish/package.json | 1 + packages/mono-repo-publish/src/main.ts | 44 +++- packages/mono-repo-publish/test/cli.ts | 203 ++++++++------- packages/mono-repo-publish/test/main.test.ts | 117 +++++---- packages/mono-repo-publish/test/util.ts | 38 +++ 6 files changed, 518 insertions(+), 136 deletions(-) create mode 100644 packages/mono-repo-publish/test/util.ts diff --git a/packages/mono-repo-publish/package-lock.json b/packages/mono-repo-publish/package-lock.json index 2e8b850bcff..0106bd7a946 100644 --- a/packages/mono-repo-publish/package-lock.json +++ b/packages/mono-repo-publish/package-lock.json @@ -31,6 +31,7 @@ "nock": "^13.2.9", "semistandard": "^16.0.1", "sinon": "^15.0.0", + "ts-node": "^10.9.1", "typescript": "^4.8.2" }, "engines": { @@ -149,6 +150,28 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", @@ -671,6 +694,30 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "node_modules/@types/btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz", @@ -978,6 +1025,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1065,6 +1121,12 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1429,6 +1491,12 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -3466,6 +3534,12 @@ "semver": "bin/semver.js" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -5477,6 +5551,58 @@ "node": ">=8" } }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -5612,6 +5738,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", @@ -5834,6 +5966,15 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5938,6 +6079,27 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@eslint/eslintrc": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", @@ -6380,6 +6542,30 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, "@types/btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz", @@ -6590,6 +6776,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -6648,6 +6840,12 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -6932,6 +7130,12 @@ } } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -8450,6 +8654,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -9900,6 +10110,35 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -10006,6 +10245,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "v8-to-istanbul": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", @@ -10175,6 +10420,12 @@ } } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/packages/mono-repo-publish/package.json b/packages/mono-repo-publish/package.json index feda1928ab8..9b9f1682d4e 100644 --- a/packages/mono-repo-publish/package.json +++ b/packages/mono-repo-publish/package.json @@ -33,6 +33,7 @@ "nock": "^13.2.9", "semistandard": "^16.0.1", "sinon": "^15.0.0", + "ts-node": "^10.9.1", "typescript": "^4.8.2" }, "dependencies": { diff --git a/packages/mono-repo-publish/src/main.ts b/packages/mono-repo-publish/src/main.ts index bd49cbce086..72d36bba57f 100644 --- a/packages/mono-repo-publish/src/main.ts +++ b/packages/mono-repo-publish/src/main.ts @@ -137,6 +137,7 @@ function publish( : 'i'; const output: string[] = []; try { + // Install dependencies. output.push( execSync(`npm ${installCommand} --registry=https://registry.npmjs.org`, { cwd: directory, @@ -144,13 +145,35 @@ function publish( stdio: 'inherit', }) ); + + // Pack a tarball and leave it behind so we can archive what we've + // published. output.push( - execSync(`npm publish --access=public${dryRun ? ' --dry-run' : ''}`, { + execSync('npm pack .', { cwd: directory, encoding: 'utf-8', stdio: 'inherit', }) ); + + // npm pack creates a tarball, but its name is unpredictable. So we have to + // find the most recent tarball in the directory and assume that's the one + // created by npm pack. + const tarball = findMostRecentTarBall(directory); + + // Publish the tarball to npmjs.org. + output.push( + execSync( + `npm publish --access=public${dryRun ? ' --dry-run' : ''} ${tarball}`, + { + cwd: directory, + encoding: 'utf-8', + stdio: 'inherit', + } + ) + ); + + // Clean out the node_modules directory. rmSync(join(directory, 'node_modules'), { recursive: true, force: true, @@ -167,6 +190,25 @@ function publish( }; } +/// Finds the most recently created file in the directory with extension .tgz. +function findMostRecentTarBall(directory: string): string | undefined { + return ( + fs + // Collect tarballs. + .readdirSync(directory) + .filter(fname => fname.endsWith('.tgz')) + // Lookup each tarball's creation time. + .map(fname => { + return { + fname, + stats: fs.statSync(join(directory, fname)), + }; + }) + // Choose the tarball with the most recent creation time. + .reduce((a, b) => (a.stats.ctimeMs > b.stats.ctimeMs ? a : b))?.fname + ); +} + export function publishSubmodules(directories: string[], dryRun: boolean) { console.log(`Directories to publish: ${directories}`); const execSync = methodOverrides.execSyncOverride || childProcess.execSync; diff --git a/packages/mono-repo-publish/test/cli.ts b/packages/mono-repo-publish/test/cli.ts index 1b8e652d9b5..34ffdb3df76 100644 --- a/packages/mono-repo-publish/test/cli.ts +++ b/packages/mono-repo-publish/test/cli.ts @@ -16,6 +16,7 @@ import {parser} from '../src/bin/mono-repo-publish'; import * as core from '../src/main'; import {describe, it, afterEach, beforeEach} from 'mocha'; import * as sinon from 'sinon'; +import {makeTempDirWithTarballs} from './util'; const sandbox = sinon.createSandbox(); @@ -31,97 +32,131 @@ describe('CLI', () => { getFilesStub = sandbox.stub(core, 'getsPRFiles'); execSync = sandbox.stub(core.methodOverrides, 'execSyncOverride'); }); - describe('default', () => { - it('handles explicit credentials', async () => { - getOctokitStub.resolves(sandbox.spy()); - getFilesStub.resolves(['packages/pkg1/package.json']); - await parser.parseAsync( - '--pr-url=https://github.com/testOwner/testRepo/pull/1234 --app-id-path=./test/fixtures/app-id --installation-id-path=./test/fixtures/installation-id --private-key-path=./test/fixtures/private-key' - ); - sinon.assert.calledOnceWithExactly( - getOctokitStub, - './test/fixtures/app-id', - './test/fixtures/private-key', - './test/fixtures/installation-id' - ); - sinon.assert.calledOnce(getFilesStub); - sinon.assert.calledTwice(execSync); - sinon.assert.calledWith( - execSync.firstCall, - 'npm i --registry=https://registry.npmjs.org', - sinon.match({cwd: 'packages/pkg1'}) - ); - sinon.assert.calledWith( - execSync.secondCall, - 'npm publish --access=public', - sinon.match({cwd: 'packages/pkg1'}) - ); + describe('with temp package dir', () => { + let tmpDir; + let runDir: string; + + before(async () => { + tmpDir = await makeTempDirWithTarballs('packages/pkg1'); + runDir = process.cwd(); + process.chdir(tmpDir); }); - it('handles credentials from the environment', async () => { - getOctokitStub.resolves(sandbox.spy()); - getFilesStub.resolves(['packages/pkg1/package.json']); - sandbox.stub(process, 'env').value({ - APP_ID_PATH: './test/fixtures/app-id', - INSTALLATION_ID_PATH: './test/fixtures/installation-id', - GITHUB_PRIVATE_KEY_PATH: './test/fixtures/private-key', - }); - await parser.parseAsync( - '--pr-url=https://github.com/testOwner/testRepo/pull/1234' - ); - sinon.assert.calledOnceWithExactly( - getOctokitStub, - './test/fixtures/app-id', - './test/fixtures/private-key', - './test/fixtures/installation-id' - ); - sinon.assert.calledOnce(getFilesStub); - sinon.assert.calledWith( - execSync.firstCall, - 'npm i --registry=https://registry.npmjs.org', - sinon.match({cwd: 'packages/pkg1'}) - ); - sinon.assert.calledWith( - execSync.secondCall, - 'npm publish --access=public', - sinon.match({cwd: 'packages/pkg1'}) - ); + after(() => { + process.chdir(runDir); }); - it('uses executes a dry run', async () => { - getOctokitStub.resolves(sandbox.spy()); - getFilesStub.resolves([ - 'packages/pkg1/package.json', - 'packages/pkg1/package-lock.json', - ]); - sandbox.stub(process, 'env').value({ - APP_ID_PATH: './test/fixtures/app-id', - INSTALLATION_ID_PATH: './test/fixtures/installation-id', - GITHUB_PRIVATE_KEY_PATH: './test/fixtures/private-key', + + describe('default', () => { + it('handles explicit credentials', async () => { + getOctokitStub.resolves(sandbox.spy()); + getFilesStub.resolves(['packages/pkg1/package.json']); + await parser.parseAsync( + '--pr-url=https://github.com/testOwner/testRepo/pull/1234 --app-id-path=./test/fixtures/app-id --installation-id-path=./test/fixtures/installation-id --private-key-path=./test/fixtures/private-key' + ); + + sinon.assert.calledOnceWithExactly( + getOctokitStub, + './test/fixtures/app-id', + './test/fixtures/private-key', + './test/fixtures/installation-id' + ); + sinon.assert.calledOnce(getFilesStub); + sinon.assert.calledThrice(execSync); + sinon.assert.calledWith( + execSync.firstCall, + 'npm i --registry=https://registry.npmjs.org', + sinon.match({cwd: 'packages/pkg1'}) + ); + sinon.assert.calledWith( + execSync.secondCall, + 'npm pack .', + sinon.match({cwd: 'packages/pkg1'}) + ); + sinon.assert.calledWith( + execSync.thirdCall, + 'npm publish --access=public newer.tgz', + sinon.match({cwd: 'packages/pkg1'}) + ); }); - await parser.parseAsync( - '--pr-url=https://github.com/testOwner/testRepo/pull/1234 --dry-run' - ); - sinon.assert.calledOnceWithExactly( - getOctokitStub, - './test/fixtures/app-id', - './test/fixtures/private-key', - './test/fixtures/installation-id' - ); - sinon.assert.calledOnce(getFilesStub); - sinon.assert.calledTwice(execSync); - sinon.assert.calledWith( - execSync.firstCall, - 'npm i --registry=https://registry.npmjs.org', - sinon.match({cwd: 'packages/pkg1'}) - ); - sinon.assert.calledWith( - execSync.secondCall, - 'npm publish --access=public --dry-run', - sinon.match({cwd: 'packages/pkg1'}) - ); + it('handles credentials from the environment', async () => { + getOctokitStub.resolves(sandbox.spy()); + getFilesStub.resolves(['packages/pkg1/package.json']); + sandbox.stub(process, 'env').value({ + APP_ID_PATH: './test/fixtures/app-id', + INSTALLATION_ID_PATH: './test/fixtures/installation-id', + GITHUB_PRIVATE_KEY_PATH: './test/fixtures/private-key', + }); + await parser.parseAsync( + '--pr-url=https://github.com/testOwner/testRepo/pull/1234' + ); + + sinon.assert.calledOnceWithExactly( + getOctokitStub, + './test/fixtures/app-id', + './test/fixtures/private-key', + './test/fixtures/installation-id' + ); + sinon.assert.calledOnce(getFilesStub); + sinon.assert.calledWith( + execSync.firstCall, + 'npm i --registry=https://registry.npmjs.org', + sinon.match({cwd: 'packages/pkg1'}) + ); + sinon.assert.calledWith( + execSync.secondCall, + 'npm pack .', + sinon.match({cwd: 'packages/pkg1'}) + ); + sinon.assert.calledWith( + execSync.thirdCall, + 'npm publish --access=public newer.tgz', + sinon.match({cwd: 'packages/pkg1'}) + ); + }); + + it('uses executes a dry run', async () => { + getOctokitStub.resolves(sandbox.spy()); + getFilesStub.resolves([ + 'packages/pkg1/package.json', + 'packages/pkg1/package-lock.json', + ]); + sandbox.stub(process, 'env').value({ + APP_ID_PATH: './test/fixtures/app-id', + INSTALLATION_ID_PATH: './test/fixtures/installation-id', + GITHUB_PRIVATE_KEY_PATH: './test/fixtures/private-key', + }); + await parser.parseAsync( + '--pr-url=https://github.com/testOwner/testRepo/pull/1234 --dry-run' + ); + + sinon.assert.calledOnceWithExactly( + getOctokitStub, + './test/fixtures/app-id', + './test/fixtures/private-key', + './test/fixtures/installation-id' + ); + sinon.assert.calledOnce(getFilesStub); + sinon.assert.calledThrice(execSync); + sinon.assert.calledWith( + execSync.firstCall, + 'npm i --registry=https://registry.npmjs.org', + sinon.match({cwd: 'packages/pkg1'}) + ); + sinon.assert.calledWith( + execSync.secondCall, + 'npm pack .', + sinon.match({cwd: 'packages/pkg1'}) + ); + sinon.assert.calledWith( + execSync.thirdCall, + 'npm publish --access=public --dry-run newer.tgz', + sinon.match({cwd: 'packages/pkg1'}) + ); + }); }); + it('excludes files', async () => { getOctokitStub.resolves(sandbox.spy()); getFilesStub.resolves([ diff --git a/packages/mono-repo-publish/test/main.test.ts b/packages/mono-repo-publish/test/main.test.ts index ee9d1d41a48..d3afe0931e8 100644 --- a/packages/mono-repo-publish/test/main.test.ts +++ b/packages/mono-repo-publish/test/main.test.ts @@ -19,6 +19,7 @@ import nock from 'nock'; import * as sinon from 'sinon'; import * as path from 'path'; import {Octokit} from '@octokit/rest'; +import {makeTempDirWithTarballs} from './util'; const sandbox = sinon.createSandbox(); @@ -96,57 +97,75 @@ describe('mono-repo publish', () => { assert.deepStrictEqual(submodules, ['packages/secondPackage', '.']); }); - it('passes in the right arguments for npm publish', () => { - core.publishSubmodules(['foo'], false); - sandbox.assert.calledWith( - execSync.firstCall, - 'npm i --registry=https://registry.npmjs.org' - ); - sandbox.assert.calledWith( - execSync.secondCall, - 'npm publish --access=public' - ); - }); + describe('with temp dir', () => { + let tmpDir; + let runDir: string; - it('passes in --dry-run option', () => { - core.publishSubmodules(['foo'], true); - sandbox.assert.calledWith( - execSync.firstCall, - 'npm i --registry=https://registry.npmjs.org' - ); - sandbox.assert.calledWith( - execSync.secondCall, - 'npm publish --access=public --dry-run' - ); - }); + before(async () => { + tmpDir = await makeTempDirWithTarballs('foo'); + runDir = process.cwd(); + process.chdir(tmpDir); + }); - // A node_modules folder existing in the root directory was preventing - // google-api-nodejs-client from publishing. - it('it removes node_modules after publish', () => { - const errors = core.publishSubmodules(['foo'], true); - sandbox.assert.calledWith( - execSync.firstCall, - 'npm i --registry=https://registry.npmjs.org' - ); - sandbox.assert.calledWith( - execSync.secondCall, - 'npm publish --access=public --dry-run' - ); - sandbox.assert.calledWith(rmdirSync, 'foo/node_modules', { - force: true, - recursive: true, + after(() => { + process.chdir(runDir); }); - assert.strictEqual(errors.length, 0); - }); - it('returns array of errors after attempting all publications', () => { - execSync.throws(Error('publish fail')); - const errors = core.publishSubmodules(['foo'], true); - sandbox.assert.calledWith( - execSync.firstCall, - 'npm i --registry=https://registry.npmjs.org' - ); - assert.strictEqual(errors.length, 1); + it('passes in the right arguments for npm publish', () => { + core.publishSubmodules(['foo'], false); + sandbox.assert.calledWith( + execSync.firstCall, + 'npm i --registry=https://registry.npmjs.org' + ); + sandbox.assert.calledWith(execSync.secondCall, 'npm pack .'); + sandbox.assert.calledWith( + execSync.thirdCall, + 'npm publish --access=public newer.tgz' + ); + }); + + it('passes in --dry-run option', () => { + core.publishSubmodules(['foo'], true); + sandbox.assert.calledWith( + execSync.firstCall, + 'npm i --registry=https://registry.npmjs.org' + ); + sandbox.assert.calledWith(execSync.secondCall, 'npm pack .'); + sandbox.assert.calledWith( + execSync.thirdCall, + 'npm publish --access=public --dry-run newer.tgz' + ); + }); + + // A node_modules folder existing in the root directory was preventing + // google-api-nodejs-client from publishing. + it('it removes node_modules after publish', () => { + const errors = core.publishSubmodules(['foo'], true); + sandbox.assert.calledWith( + execSync.firstCall, + 'npm i --registry=https://registry.npmjs.org' + ); + sandbox.assert.calledWith(execSync.secondCall, 'npm pack .'); + sandbox.assert.calledWith( + execSync.thirdCall, + 'npm publish --access=public --dry-run newer.tgz' + ); + sandbox.assert.calledWith(rmdirSync, 'foo/node_modules', { + force: true, + recursive: true, + }); + assert.strictEqual(errors.length, 0); + }); + + it('returns array of errors after attempting all publications', () => { + execSync.throws(Error('publish fail')); + const errors = core.publishSubmodules(['foo'], true); + sandbox.assert.calledWith( + execSync.firstCall, + 'npm i --registry=https://registry.npmjs.org' + ); + assert.strictEqual(errors.length, 1); + }); }); it('uses npm ci, if package-lock.json exists', () => { @@ -155,10 +174,6 @@ describe('mono-repo publish', () => { execSync.firstCall, 'npm ci --registry=https://registry.npmjs.org' ); - sandbox.assert.calledWith( - execSync.secondCall, - 'npm publish --access=public' - ); }); it('skips package if package is set to private', () => { diff --git a/packages/mono-repo-publish/test/util.ts b/packages/mono-repo-publish/test/util.ts new file mode 100644 index 00000000000..a487e36d478 --- /dev/null +++ b/packages/mono-repo-publish/test/util.ts @@ -0,0 +1,38 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as os from 'os'; +import * as fs from 'fs'; +import * as path from 'path'; + +/// Makes a temporary directory that contains: +/// /subdirName +/// /subdirName/older.tgz +/// /subdirName/newer.tgz +/// /subdirName/newest.tar +export async function makeTempDirWithTarballs( + subdirName: string +): Promise { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test')); + // Create the foo directory and some files. + fs.mkdirSync(path.join(tmpDir, subdirName), {recursive: true}); + // The newer tarball should be picked up. + fs.writeFileSync(path.join(tmpDir, subdirName, 'older.tgz'), 'older'); + await new Promise(resolve => setTimeout(resolve, 10)); + fs.writeFileSync(path.join(tmpDir, subdirName, 'newer.tgz'), 'newer'); + // Ignored because it doesn't have extension .tgz. + await new Promise(resolve => setTimeout(resolve, 10)); + fs.writeFileSync(path.join(tmpDir, subdirName, 'newest.tar'), 'newest'); + return tmpDir; +}