From 9421102e840aa19979f4db71848ef9d8c3d30c3d Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 30 Jan 2023 11:21:39 +0200 Subject: [PATCH 1/3] test: add tests for upgrade --- src/commands/upgrade.ts | 39 +++- test/cli.spec.ts | 219 ++++++++++++++++++ test/fixtures/dependencies-exact/package.json | 8 + .../inner-dep/package.json | 8 + .../dependencies-file-ref/package.json | 8 + .../fixtures/dependencies-pinned/package.json | 9 + .../dependencies-pinned/pleb.config.mjs | 3 + test/fixtures/dependencies/package.json | 10 + .../lerna-workspace/packages/a/package.json | 3 +- .../lerna-workspace/packages/b/package.json | 5 +- .../yarn-workspace/packages/a/package.json | 3 +- .../yarn-workspace/packages/b/package.json | 3 + test/tsconfig.json | 5 +- 13 files changed, 308 insertions(+), 15 deletions(-) create mode 100644 test/fixtures/dependencies-exact/package.json create mode 100644 test/fixtures/dependencies-file-ref/inner-dep/package.json create mode 100644 test/fixtures/dependencies-file-ref/package.json create mode 100644 test/fixtures/dependencies-pinned/package.json create mode 100644 test/fixtures/dependencies-pinned/pleb.config.mjs create mode 100644 test/fixtures/dependencies/package.json diff --git a/src/commands/upgrade.ts b/src/commands/upgrade.ts index 9f4511c7..cbec7d62 100644 --- a/src/commands/upgrade.ts +++ b/src/commands/upgrade.ts @@ -11,13 +11,29 @@ import { normalizePinnedPackages, loadPlebConfig } from '../utils/config.js'; const { gt, coerce } = semver; +type RequiredRegistryApi = Pick; + export interface UpgradeOptions { directoryPath: string; dryRun?: boolean; registryUrl?: string; + createRegistry?: (url: string, token?: string) => RequiredRegistryApi; + log?: (message: unknown) => void; + logError?: (message: unknown) => void; +} + +function createDefaultRegistry(url: string, token?: string) { + return new NpmRegistry(url, token); } -export async function upgrade({ directoryPath, registryUrl, dryRun }: UpgradeOptions): Promise { +export async function upgrade({ + directoryPath, + registryUrl, + dryRun, + createRegistry = createDefaultRegistry, + log = console.log, + logError = console.error, +}: UpgradeOptions): Promise { const directoryContext = resolveDirectoryContext(directoryPath, { ...fs, ...path }); const packages = allPackagesFromContext(directoryContext); const plebConfig = await loadPlebConfig(directoryPath); @@ -25,7 +41,7 @@ export async function upgrade({ directoryPath, registryUrl, dryRun }: UpgradeOpt const npmConfig = await loadEnvNpmConfig({ basePath: directoryPath }); const resolvedRegistryUrl = registryUrl ?? npmConfig['registry'] ?? officialNpmRegistryUrl; const token = npmConfig[`${uriToIdentifier(resolvedRegistryUrl)}:_authToken`]; - const registry = new NpmRegistry(resolvedRegistryUrl, token); + const registry = createRegistry(resolvedRegistryUrl, token); const internalPackageNames = new Set(packages.map(({ packageJson }) => packageJson.name!)); @@ -42,10 +58,11 @@ export async function upgrade({ directoryPath, registryUrl, dryRun }: UpgradeOpt ) ); - console.log(`Getting "latest" version for ${externalPackageNames.size} dependencies...`); + log(`Getting "latest" version for ${externalPackageNames.size} dependencies...`); const packageNameToVersion = await fetchLatestPackageVersions({ packageNames: externalPackageNames, registry, + logError, }); registry.dispose(); @@ -116,36 +133,38 @@ export async function upgrade({ directoryPath, registryUrl, dryRun }: UpgradeOpt } } if (replacements.size) { - console.log('Changes:'); + log('Changes:'); const maxKeyLength = Array.from(replacements.keys()).reduce((acc, key) => Math.max(acc, key.length), 0); for (const [key, { originalValue, newValue }] of replacements) { - console.log(` ${key.padEnd(maxKeyLength + 2)} ${originalValue.padStart(8)} -> ${newValue}`); + log(` ${key.padEnd(maxKeyLength + 2)} ${originalValue.padStart(8)} -> ${newValue}`); } } if (skipped.size) { - console.log('Skipped:'); + log('Skipped:'); const maxKeyLength = Array.from(skipped.keys()).reduce((acc, key) => Math.max(acc, key.length), 0); for (const [key, { originalValue, reason, newValue }] of skipped) { - console.log( + log( ` ${key.padEnd(maxKeyLength + 2)} ${originalValue.padStart(8)} -> ${newValue}` + (reason ? ` (${reason})` : ``) ); } } if (!replacements.size) { - console.log('Nothing to upgrade.'); + log('Nothing to upgrade.'); } } export interface IFetchLatestPackageVersionsOptions { - registry: NpmRegistry; + registry: RequiredRegistryApi; packageNames: Set; + logError: (message: unknown) => void; } export async function fetchLatestPackageVersions({ registry, packageNames, + logError, }: IFetchLatestPackageVersionsOptions): Promise> { const cliProgress = createCliProgressBar(); const packageNameToVersion = new Map(); @@ -162,7 +181,7 @@ export async function fetchLatestPackageVersions({ } packageNameToVersion.set(packageName, latest); } catch (e) { - console.error((e as Error)?.message || e); + logError((e as Error)?.message || e); } cliProgress.update((packageNames.size - fetchQueue.size) / packageNames.size); }); diff --git a/test/cli.spec.ts b/test/cli.spec.ts index 3234af32..9e8ff103 100644 --- a/test/cli.spec.ts +++ b/test/cli.spec.ts @@ -2,6 +2,7 @@ import { join } from 'path'; import { fileURLToPath } from 'url'; import { expect } from 'chai'; import { spawnAsync } from './spawn-async.js'; +import { upgrade } from 'pleb'; const fixturesRoot = fileURLToPath(new URL('../../test/fixtures', import.meta.url)); const cliEntryPath = fileURLToPath(new URL('../../bin/pleb.js', import.meta.url)); @@ -91,4 +92,222 @@ describe('cli', function () { expect(exitCode).to.equal(0); }); }); + + describe('upgrade', () => { + function createMockRegistry(packagesData: Record) { + return { + async fetchDistTags(packageName: string) { + return Promise.resolve(packagesData[packageName]!); + }, + dispose() { + /**/ + }, + }; + } + function createMockOutput() { + const output: { type: 'log' | 'error'; message: unknown }[] = []; + return { + output, + log(this: void, message: unknown) { + output.push({ type: 'log', message }); + }, + logError(this: void, message: unknown) { + output.push({ type: 'error', message }); + }, + }; + } + it('should report nothing to upgrade for an empty project', async () => { + const newPackagePath = join(fixturesRoot, 'new-package'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => createMockRegistry({}), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 0 dependencies...' }, + { type: 'log', message: 'Nothing to upgrade.' }, + ]); + }); + it('should report nothing to upgrade when everything is up to date', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '1.0.0' }, + 'package-b': { latest: '1.0.0' }, + 'package-c': { latest: '1.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 3 dependencies...' }, + { type: 'log', message: 'Nothing to upgrade.' }, + ]); + }); + it('should report on dependencies with newer versions', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '1.0.0' }, + 'package-b': { latest: '2.0.0' }, + 'package-c': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 3 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' package-b ~1.0.0 -> ~2.0.0' }, + { type: 'log', message: ' package-c ^1.0.0 -> ^2.0.0' }, + ]); + }); + it('should force exact version to caret (opinionated)', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies-exact'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '1.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 1 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' package-a 1.0.0 -> ^1.0.0' }, + ]); + }); + it('should not update file reference', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies-file-ref'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '2.0.0' }, + 'package-b': { latest: '1.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 1 dependencies...' }, + { type: 'log', message: 'Nothing to upgrade.' }, + ]); + }); + it('should report from multiple yarn workspaces (only on external packages)', async () => { + const newPackagePath = join(fixturesRoot, 'yarn-workspace'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'yarn-workspace-b': { latest: '2.0.0' }, + 'external-a': { latest: '2.0.0' }, + 'external-b': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' external-b ^1.0.0 -> ^2.0.0' }, + { type: 'log', message: ' external-a ^1.0.0 -> ^2.0.0' }, + ]); + }); + it('should report from multiple lerna workspaces (only on external packages)', async () => { + const newPackagePath = join(fixturesRoot, 'lerna-workspace'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'lerna-workspace-b': { latest: '2.0.0' }, + 'external-a': { latest: '2.0.0' }, + 'external-b': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' external-b ^1.0.0 -> ^2.0.0' }, + { type: 'log', message: ' external-a ^1.0.0 -> ^2.0.0' }, + ]); + }); + it('should skip pinned packages', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies-pinned'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '2.0.0' }, + 'package-b': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' package-a ~1.0.0 -> ~2.0.0' }, + { type: 'log', message: 'Skipped:' }, + { type: 'log', message: ' package-b ~1.0.0 -> ~2.0.0 (broken stuff)' }, + ]); + }); + }); }); diff --git a/test/fixtures/dependencies-exact/package.json b/test/fixtures/dependencies-exact/package.json new file mode 100644 index 00000000..c6048add --- /dev/null +++ b/test/fixtures/dependencies-exact/package.json @@ -0,0 +1,8 @@ +{ + "name": "pleb-dependencies-exact", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "package-a": "1.0.0" + } +} diff --git a/test/fixtures/dependencies-file-ref/inner-dep/package.json b/test/fixtures/dependencies-file-ref/inner-dep/package.json new file mode 100644 index 00000000..66064ba3 --- /dev/null +++ b/test/fixtures/dependencies-file-ref/inner-dep/package.json @@ -0,0 +1,8 @@ +{ + "name": "pleb-dependencies-file-ref-inner", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "package-b": "^1.0.0" + } +} diff --git a/test/fixtures/dependencies-file-ref/package.json b/test/fixtures/dependencies-file-ref/package.json new file mode 100644 index 00000000..b35f1d63 --- /dev/null +++ b/test/fixtures/dependencies-file-ref/package.json @@ -0,0 +1,8 @@ +{ + "name": "pleb-dependencies-file-ref", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "package-a": "file:./inner-dep" + } +} diff --git a/test/fixtures/dependencies-pinned/package.json b/test/fixtures/dependencies-pinned/package.json new file mode 100644 index 00000000..b7f8bbf2 --- /dev/null +++ b/test/fixtures/dependencies-pinned/package.json @@ -0,0 +1,9 @@ +{ + "name": "pleb-dependencies", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "package-a": "~1.0.0", + "package-b": "~1.0.0" + } +} diff --git a/test/fixtures/dependencies-pinned/pleb.config.mjs b/test/fixtures/dependencies-pinned/pleb.config.mjs new file mode 100644 index 00000000..60bba8c6 --- /dev/null +++ b/test/fixtures/dependencies-pinned/pleb.config.mjs @@ -0,0 +1,3 @@ +export default { + pinnedPackages: [{ name: 'package-b', reason: 'broken stuff' }], +}; diff --git a/test/fixtures/dependencies/package.json b/test/fixtures/dependencies/package.json new file mode 100644 index 00000000..d54369fe --- /dev/null +++ b/test/fixtures/dependencies/package.json @@ -0,0 +1,10 @@ +{ + "name": "pleb-dependencies", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "package-a": "~1.0.0", + "package-b": "~1.0.0", + "package-c": "^1.0.0" + } +} diff --git a/test/fixtures/lerna-workspace/packages/a/package.json b/test/fixtures/lerna-workspace/packages/a/package.json index 56341e1c..7eace803 100644 --- a/test/fixtures/lerna-workspace/packages/a/package.json +++ b/test/fixtures/lerna-workspace/packages/a/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "scripts": {}, "dependencies": { - "lerna-workspace-b": "^1.0.0" + "lerna-workspace-b": "^1.0.0", + "external-a": "^1.0.0" } } diff --git a/test/fixtures/lerna-workspace/packages/b/package.json b/test/fixtures/lerna-workspace/packages/b/package.json index 79ae91db..a6d136d7 100644 --- a/test/fixtures/lerna-workspace/packages/b/package.json +++ b/test/fixtures/lerna-workspace/packages/b/package.json @@ -1,5 +1,8 @@ { "name": "lerna-workspace-b", "version": "1.0.0", - "scripts": {} + "scripts": {}, + "dependencies": { + "external-b": "^1.0.0" + } } diff --git a/test/fixtures/yarn-workspace/packages/a/package.json b/test/fixtures/yarn-workspace/packages/a/package.json index 3dca64f4..6b06e1fa 100644 --- a/test/fixtures/yarn-workspace/packages/a/package.json +++ b/test/fixtures/yarn-workspace/packages/a/package.json @@ -5,6 +5,7 @@ "prepack": "echo \"prepack yarn-workspace-a\"" }, "dependencies": { - "yarn-workspace-b": "^1.0.0" + "yarn-workspace-b": "^1.0.0", + "external-a": "^1.0.0" } } diff --git a/test/fixtures/yarn-workspace/packages/b/package.json b/test/fixtures/yarn-workspace/packages/b/package.json index 38913ef3..c7c9c130 100644 --- a/test/fixtures/yarn-workspace/packages/b/package.json +++ b/test/fixtures/yarn-workspace/packages/b/package.json @@ -3,5 +3,8 @@ "version": "1.0.0", "scripts": { "prepack": "echo \"prepack yarn-workspace-b\"" + }, + "dependencies": { + "external-b": "^1.0.0" } } diff --git a/test/tsconfig.json b/test/tsconfig.json index e6315bcf..a90c63ba 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../dist/test", - "types": ["node", "mocha"] - } + "types": ["node", "mocha"], + }, + "references": [{ "path": "../src" }] } From 4ec5d2ba7eb82bc13fed5c7cbcddee6622e68d50 Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Sun, 12 Feb 2023 18:57:30 +0200 Subject: [PATCH 2/3] test: separate publish and upgrade tests also rename extension to prepare for native node test runner --- package.json | 4 +- test/cli.spec.ts | 313 ------------------------------------------- test/publish.test.ts | 92 +++++++++++++ test/upgrade.test.ts | 233 ++++++++++++++++++++++++++++++++ 4 files changed, 327 insertions(+), 315 deletions(-) delete mode 100644 test/cli.spec.ts create mode 100644 test/publish.test.ts create mode 100644 test/upgrade.test.ts diff --git a/package.json b/package.json index 653eeb4a..0deb1f17 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "prebuild": "npm run clean", "build": "tsc --build", "pretest": "npm run lint && npm run build", - "test": "npm run test:spec", - "test:spec": "mocha \"./dist/test/**/*.spec.js\"", + "test": "npm run test:only", + "test:only": "mocha \"./dist/test/**/*.test.js\"", "lint": "eslint .", "prepack": "npm run build", "prettify": "npx prettier . --write" diff --git a/test/cli.spec.ts b/test/cli.spec.ts deleted file mode 100644 index 9e8ff103..00000000 --- a/test/cli.spec.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { join } from 'path'; -import { fileURLToPath } from 'url'; -import { expect } from 'chai'; -import { spawnAsync } from './spawn-async.js'; -import { upgrade } from 'pleb'; - -const fixturesRoot = fileURLToPath(new URL('../../test/fixtures', import.meta.url)); -const cliEntryPath = fileURLToPath(new URL('../../bin/pleb.js', import.meta.url)); - -const runCli = async (cliArgs: string[] = []) => - spawnAsync('node', [cliEntryPath, ...cliArgs, '--dry-run'], { - pipeStreams: true, - }); - -describe('cli', function () { - this.timeout(30_000); - - describe('publish', () => { - it('allows publishing new (not published) packages', async () => { - const newPackagePath = join(fixturesRoot, 'new-package'); - - const { output, exitCode } = await runCli(['publish', newPackagePath]); - - expect(output).to.include('pleb-new-package: package was never published.'); - expect(output).to.include('pleb-new-package: done.'); - expect(exitCode).to.equal(0); - }); - - it('avoids publishing already-published packages', async () => { - const alreadyPublishedPackagePath = join(fixturesRoot, 'already-published'); - - const { output, exitCode } = await runCli(['publish', alreadyPublishedPackagePath]); - - expect(output).to.include('pleb: 1.3.0 is already published. skipping'); - expect(exitCode).to.equal(0); - }); - - it('avoids publishing private packages', async () => { - const privatePackagePath = join(fixturesRoot, 'private-package'); - - const { output, exitCode } = await runCli(['publish', privatePackagePath]); - - expect(output).to.include('private-project: private. skipping'); - expect(exitCode).to.equal(0); - }); - - it('allows specifying a custom dist directory', async () => { - const distDirFixturePath = join(fixturesRoot, 'dist-dir'); - - const { output, exitCode } = await runCli(['publish', distDirFixturePath, '--contents', 'npm']); - - expect(output).to.include('pleb-new-package: package was never published.'); - expect(output).to.include('total files: 2'); - expect(output).to.include('pleb-new-package: done.'); - expect(exitCode).to.equal(0); - }); - - it('publishes workspace packages in correct order (deps first)', async () => { - const distDirFixturePath = join(fixturesRoot, 'yarn-workspace'); - - const { output, exitCode } = await runCli(['publish', distDirFixturePath]); - - expect(output).to.include('prepack yarn-workspace-b'); - expect(output).to.include('prepack yarn-workspace-a'); - expect(output).to.include('yarn-workspace-a: done.'); - expect(output).to.include('yarn-workspace-b: done.'); - expect(output.indexOf('prepack yarn-workspace-b')).to.be.lessThan(output.indexOf('prepack yarn-workspace-a')); - expect(output.indexOf('prepack yarn-workspace-a')).to.be.lessThan(output.indexOf('yarn-workspace-b: done.')); - expect(output.indexOf('yarn-workspace-b: done.')).to.be.lessThan(output.indexOf('yarn-workspace-a: done.')); - expect(exitCode).to.equal(0); - }); - - it('publishes npm-style "file:" linked packages', async () => { - const distDirFixturePath = join(fixturesRoot, 'npm-linked'); - - const { output, exitCode } = await runCli(['publish', distDirFixturePath]); - - expect(output).to.include('npm-linked-a: done.'); - expect(output).to.include('npm-linked-b: done.'); - expect(output.indexOf('npm-linked-b: done.')).to.be.lessThan(output.indexOf('npm-linked-a: done.')); - expect(exitCode).to.equal(0); - }); - - it('publishes lerna workspaces', async () => { - const distDirFixturePath = join(fixturesRoot, 'lerna-workspace'); - - const { output, exitCode } = await runCli(['publish', distDirFixturePath]); - - expect(output).to.include('lerna-workspace-a: done.'); - expect(output).to.include('lerna-workspace-b: done.'); - expect(output.indexOf('lerna-workspace-b: done.')).to.be.lessThan(output.indexOf('lerna-workspace-a: done.')); - expect(exitCode).to.equal(0); - }); - }); - - describe('upgrade', () => { - function createMockRegistry(packagesData: Record) { - return { - async fetchDistTags(packageName: string) { - return Promise.resolve(packagesData[packageName]!); - }, - dispose() { - /**/ - }, - }; - } - function createMockOutput() { - const output: { type: 'log' | 'error'; message: unknown }[] = []; - return { - output, - log(this: void, message: unknown) { - output.push({ type: 'log', message }); - }, - logError(this: void, message: unknown) { - output.push({ type: 'error', message }); - }, - }; - } - it('should report nothing to upgrade for an empty project', async () => { - const newPackagePath = join(fixturesRoot, 'new-package'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => createMockRegistry({}), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 0 dependencies...' }, - { type: 'log', message: 'Nothing to upgrade.' }, - ]); - }); - it('should report nothing to upgrade when everything is up to date', async () => { - const newPackagePath = join(fixturesRoot, 'dependencies'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => - createMockRegistry({ - 'package-a': { latest: '1.0.0' }, - 'package-b': { latest: '1.0.0' }, - 'package-c': { latest: '1.0.0' }, - }), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 3 dependencies...' }, - { type: 'log', message: 'Nothing to upgrade.' }, - ]); - }); - it('should report on dependencies with newer versions', async () => { - const newPackagePath = join(fixturesRoot, 'dependencies'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => - createMockRegistry({ - 'package-a': { latest: '1.0.0' }, - 'package-b': { latest: '2.0.0' }, - 'package-c': { latest: '2.0.0' }, - }), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 3 dependencies...' }, - { type: 'log', message: 'Changes:' }, - { type: 'log', message: ' package-b ~1.0.0 -> ~2.0.0' }, - { type: 'log', message: ' package-c ^1.0.0 -> ^2.0.0' }, - ]); - }); - it('should force exact version to caret (opinionated)', async () => { - const newPackagePath = join(fixturesRoot, 'dependencies-exact'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => - createMockRegistry({ - 'package-a': { latest: '1.0.0' }, - }), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 1 dependencies...' }, - { type: 'log', message: 'Changes:' }, - { type: 'log', message: ' package-a 1.0.0 -> ^1.0.0' }, - ]); - }); - it('should not update file reference', async () => { - const newPackagePath = join(fixturesRoot, 'dependencies-file-ref'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => - createMockRegistry({ - 'package-a': { latest: '2.0.0' }, - 'package-b': { latest: '1.0.0' }, - }), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 1 dependencies...' }, - { type: 'log', message: 'Nothing to upgrade.' }, - ]); - }); - it('should report from multiple yarn workspaces (only on external packages)', async () => { - const newPackagePath = join(fixturesRoot, 'yarn-workspace'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => - createMockRegistry({ - 'yarn-workspace-b': { latest: '2.0.0' }, - 'external-a': { latest: '2.0.0' }, - 'external-b': { latest: '2.0.0' }, - }), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, - { type: 'log', message: 'Changes:' }, - { type: 'log', message: ' external-b ^1.0.0 -> ^2.0.0' }, - { type: 'log', message: ' external-a ^1.0.0 -> ^2.0.0' }, - ]); - }); - it('should report from multiple lerna workspaces (only on external packages)', async () => { - const newPackagePath = join(fixturesRoot, 'lerna-workspace'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => - createMockRegistry({ - 'lerna-workspace-b': { latest: '2.0.0' }, - 'external-a': { latest: '2.0.0' }, - 'external-b': { latest: '2.0.0' }, - }), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, - { type: 'log', message: 'Changes:' }, - { type: 'log', message: ' external-b ^1.0.0 -> ^2.0.0' }, - { type: 'log', message: ' external-a ^1.0.0 -> ^2.0.0' }, - ]); - }); - it('should skip pinned packages', async () => { - const newPackagePath = join(fixturesRoot, 'dependencies-pinned'); - const { log, logError, output } = createMockOutput(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await upgrade({ - dryRun: true, - directoryPath: newPackagePath, - registryUrl: '', - createRegistry: () => - createMockRegistry({ - 'package-a': { latest: '2.0.0' }, - 'package-b': { latest: '2.0.0' }, - }), - log, - logError, - }); - - expect(output).to.eql([ - { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, - { type: 'log', message: 'Changes:' }, - { type: 'log', message: ' package-a ~1.0.0 -> ~2.0.0' }, - { type: 'log', message: 'Skipped:' }, - { type: 'log', message: ' package-b ~1.0.0 -> ~2.0.0 (broken stuff)' }, - ]); - }); - }); -}); diff --git a/test/publish.test.ts b/test/publish.test.ts new file mode 100644 index 00000000..2c24d380 --- /dev/null +++ b/test/publish.test.ts @@ -0,0 +1,92 @@ +import { join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'chai'; +import { spawnAsync } from './spawn-async.js'; + +const fixturesRoot = fileURLToPath(new URL('../../test/fixtures', import.meta.url)); +const cliEntryPath = fileURLToPath(new URL('../../bin/pleb.js', import.meta.url)); + +const runCli = async (cliArgs: string[] = []) => + spawnAsync('node', [cliEntryPath, ...cliArgs, '--dry-run'], { + pipeStreams: true, + }); + +describe('pleb publish', function () { + this.timeout(30_000); + + it('allows publishing new (not published) packages', async () => { + const newPackagePath = join(fixturesRoot, 'new-package'); + + const { output, exitCode } = await runCli(['publish', newPackagePath]); + + expect(output).to.include('pleb-new-package: package was never published.'); + expect(output).to.include('pleb-new-package: done.'); + expect(exitCode).to.equal(0); + }); + + it('avoids publishing already-published packages', async () => { + const alreadyPublishedPackagePath = join(fixturesRoot, 'already-published'); + + const { output, exitCode } = await runCli(['publish', alreadyPublishedPackagePath]); + + expect(output).to.include('pleb: 1.3.0 is already published. skipping'); + expect(exitCode).to.equal(0); + }); + + it('avoids publishing private packages', async () => { + const privatePackagePath = join(fixturesRoot, 'private-package'); + + const { output, exitCode } = await runCli(['publish', privatePackagePath]); + + expect(output).to.include('private-project: private. skipping'); + expect(exitCode).to.equal(0); + }); + + it('allows specifying a custom dist directory', async () => { + const distDirFixturePath = join(fixturesRoot, 'dist-dir'); + + const { output, exitCode } = await runCli(['publish', distDirFixturePath, '--contents', 'npm']); + + expect(output).to.include('pleb-new-package: package was never published.'); + expect(output).to.include('total files: 2'); + expect(output).to.include('pleb-new-package: done.'); + expect(exitCode).to.equal(0); + }); + + it('publishes workspace packages in correct order (deps first)', async () => { + const distDirFixturePath = join(fixturesRoot, 'yarn-workspace'); + + const { output, exitCode } = await runCli(['publish', distDirFixturePath]); + + expect(output).to.include('prepack yarn-workspace-b'); + expect(output).to.include('prepack yarn-workspace-a'); + expect(output).to.include('yarn-workspace-a: done.'); + expect(output).to.include('yarn-workspace-b: done.'); + expect(output.indexOf('prepack yarn-workspace-b')).to.be.lessThan(output.indexOf('prepack yarn-workspace-a')); + expect(output.indexOf('prepack yarn-workspace-a')).to.be.lessThan(output.indexOf('yarn-workspace-b: done.')); + expect(output.indexOf('yarn-workspace-b: done.')).to.be.lessThan(output.indexOf('yarn-workspace-a: done.')); + expect(exitCode).to.equal(0); + }); + + it('publishes npm-style "file:" linked packages', async () => { + const distDirFixturePath = join(fixturesRoot, 'npm-linked'); + + const { output, exitCode } = await runCli(['publish', distDirFixturePath]); + + expect(output).to.include('npm-linked-a: done.'); + expect(output).to.include('npm-linked-b: done.'); + expect(output.indexOf('npm-linked-b: done.')).to.be.lessThan(output.indexOf('npm-linked-a: done.')); + expect(exitCode).to.equal(0); + }); + + it('publishes lerna workspaces', async () => { + const distDirFixturePath = join(fixturesRoot, 'lerna-workspace'); + + const { output, exitCode } = await runCli(['publish', distDirFixturePath]); + + expect(output).to.include('lerna-workspace-a: done.'); + expect(output).to.include('lerna-workspace-b: done.'); + expect(output.indexOf('lerna-workspace-b: done.')).to.be.lessThan(output.indexOf('lerna-workspace-a: done.')); + expect(exitCode).to.equal(0); + }); +}); diff --git a/test/upgrade.test.ts b/test/upgrade.test.ts new file mode 100644 index 00000000..bde70f46 --- /dev/null +++ b/test/upgrade.test.ts @@ -0,0 +1,233 @@ +import { join } from 'path'; +import { fileURLToPath } from 'url'; +import { expect } from 'chai'; +import { upgrade } from 'pleb'; + +const fixturesRoot = fileURLToPath(new URL('../../test/fixtures', import.meta.url)); + +describe('pleb upgrade', () => { + function createMockRegistry(packagesData: Record) { + return { + async fetchDistTags(packageName: string) { + return Promise.resolve(packagesData[packageName]!); + }, + dispose() { + /**/ + }, + }; + } + + function createMockOutput() { + const output: { type: 'log' | 'error'; message: unknown }[] = []; + return { + output, + log(this: void, message: unknown) { + output.push({ type: 'log', message }); + }, + logError(this: void, message: unknown) { + output.push({ type: 'error', message }); + }, + }; + } + + it('should report nothing to upgrade for an empty project', async () => { + const newPackagePath = join(fixturesRoot, 'new-package'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => createMockRegistry({}), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 0 dependencies...' }, + { type: 'log', message: 'Nothing to upgrade.' }, + ]); + }); + + it('should report nothing to upgrade when everything is up to date', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '1.0.0' }, + 'package-b': { latest: '1.0.0' }, + 'package-c': { latest: '1.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 3 dependencies...' }, + { type: 'log', message: 'Nothing to upgrade.' }, + ]); + }); + + it('should report on dependencies with newer versions', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '1.0.0' }, + 'package-b': { latest: '2.0.0' }, + 'package-c': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 3 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' package-b ~1.0.0 -> ~2.0.0' }, + { type: 'log', message: ' package-c ^1.0.0 -> ^2.0.0' }, + ]); + }); + + it('should force exact version to caret (opinionated)', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies-exact'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '1.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 1 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' package-a 1.0.0 -> ^1.0.0' }, + ]); + }); + + it('should not update file reference', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies-file-ref'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '2.0.0' }, + 'package-b': { latest: '1.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 1 dependencies...' }, + { type: 'log', message: 'Nothing to upgrade.' }, + ]); + }); + + it('should report from multiple yarn workspaces (only on external packages)', async () => { + const newPackagePath = join(fixturesRoot, 'yarn-workspace'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'yarn-workspace-b': { latest: '2.0.0' }, + 'external-a': { latest: '2.0.0' }, + 'external-b': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' external-b ^1.0.0 -> ^2.0.0' }, + { type: 'log', message: ' external-a ^1.0.0 -> ^2.0.0' }, + ]); + }); + + it('should report from multiple lerna workspaces (only on external packages)', async () => { + const newPackagePath = join(fixturesRoot, 'lerna-workspace'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'lerna-workspace-b': { latest: '2.0.0' }, + 'external-a': { latest: '2.0.0' }, + 'external-b': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' external-b ^1.0.0 -> ^2.0.0' }, + { type: 'log', message: ' external-a ^1.0.0 -> ^2.0.0' }, + ]); + }); + + it('should skip pinned packages', async () => { + const newPackagePath = join(fixturesRoot, 'dependencies-pinned'); + const { log, logError, output } = createMockOutput(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + await upgrade({ + dryRun: true, + directoryPath: newPackagePath, + registryUrl: '', + createRegistry: () => + createMockRegistry({ + 'package-a': { latest: '2.0.0' }, + 'package-b': { latest: '2.0.0' }, + }), + log, + logError, + }); + + expect(output).to.eql([ + { type: 'log', message: 'Getting "latest" version for 2 dependencies...' }, + { type: 'log', message: 'Changes:' }, + { type: 'log', message: ' package-a ~1.0.0 -> ~2.0.0' }, + { type: 'log', message: 'Skipped:' }, + { type: 'log', message: ' package-b ~1.0.0 -> ~2.0.0 (broken stuff)' }, + ]); + }); +}); From 01ef3041ee846058875305d06b447cabbf89748c Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Mon, 27 Feb 2023 09:50:12 +0200 Subject: [PATCH 3/3] chore: add exp eslint references support - remove ignore comments from upgrade test --- .eslintrc | 3 ++- test/upgrade.test.ts | 8 -------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.eslintrc b/.eslintrc index 5da52806..d18b47d7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,7 +16,8 @@ { "files": ["*.ts", "*.tsx"], "parserOptions": { - "project": "./{src,test}/tsconfig.json" + "project": "./{src,test}/tsconfig.json", + "EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true }, "extends": [ "plugin:@typescript-eslint/recommended", diff --git a/test/upgrade.test.ts b/test/upgrade.test.ts index bde70f46..b547ee8a 100644 --- a/test/upgrade.test.ts +++ b/test/upgrade.test.ts @@ -34,7 +34,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'new-package'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath, @@ -54,7 +53,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'dependencies'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath, @@ -79,7 +77,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'dependencies'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath, @@ -106,7 +103,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'dependencies-exact'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath, @@ -130,7 +126,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'dependencies-file-ref'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath, @@ -154,7 +149,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'yarn-workspace'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath, @@ -181,7 +175,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'lerna-workspace'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath, @@ -208,7 +201,6 @@ describe('pleb upgrade', () => { const newPackagePath = join(fixturesRoot, 'dependencies-pinned'); const { log, logError, output } = createMockOutput(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call await upgrade({ dryRun: true, directoryPath: newPackagePath,