Skip to content

Commit

Permalink
feat(plugin-npm-cli): yarn npm info (yarnpkg#881)
Browse files Browse the repository at this point in the history
* feat(plugin-info): yarn info

* chore(release-workflow): set releases

* fix(dependencies):  fix dependency versions

* refactor: move `yarn info` to plugin-npm-cli as `yarn npm info`

* fix(dependencies): fix @types/semver version

* fix(dependencies): fix @types/semver range

* Some tweaks

* feat(npm-plugin-cli/npm-info): make `name` always be the first field

* fix(npm-plugin-cli/npm-info): print warning if version is invalid

* feat(npm-plugin-cli/npm-info): change version specifier to range specifier

* Small tweaks

Co-authored-by: Maël Nison <[email protected]>
  • Loading branch information
paul-soporan and arcanis authored Feb 4, 2020
1 parent 79b70c0 commit 364fed0
Show file tree
Hide file tree
Showing 25 changed files with 50,534 additions and 50,035 deletions.
100,156 changes: 50,164 additions & 49,992 deletions .pnp.js

Large diffs are not rendered by default.

Binary file added .yarn/cache/@npm-types-npm-1.0.1-3d959856a7-2.zip
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/@types-semver-npm-6.0.2-69b6c6df20-2.zip
Binary file not shown.
Binary file not shown.
Binary file added .yarn/cache/json5-npm-2.1.1-bbee3956d5-2.zip
Binary file not shown.
Binary file added .yarn/cache/semver-npm-7.1.2-04062a75e3-2.zip
Binary file not shown.
26 changes: 26 additions & 0 deletions .yarn/versions/54251b67.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
releases:
"@yarnpkg/cli": prerelease
"@yarnpkg/plugin-npm-cli": prerelease

declined:
- "@yarnpkg/builder"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-essentials"
- "@yarnpkg/plugin-git"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-node-modules"
- "@yarnpkg/plugin-npm"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/core"
- "@yarnpkg/doctor"
- "@yarnpkg/pnp"
- "@yarnpkg/pnpify"

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Those plugins typically come bundled with Yarn. You don't need to do anything sp
- [★ plugin-init](packages/plugin-init) adds support for the [`yarn init`](https://yarnpkg.com/cli/init) command.
- [★ plugin-link](packages/plugin-link) adds support for using `link:` and `portal:` references as dependencies.
- [★ plugin-npm](packages/plugin-npm) adds support for using [semver ranges](https://semver.org) as dependencies, resolving them to an NPM-like registry.
- [★ plugin-npm-cli](packages/plugin-npm-cli) adds support for the NPM-specific commands ([`yarn npm login`](https://yarnpkg.com/cli/npm/login), [`yarn npm publish`](https://yarnpkg.com/cli/npm/publish), ...).
- [★ plugin-npm-cli](packages/plugin-npm-cli) adds support for the NPM-specific commands ([`yarn npm info`](https://yarnpkg.com/cli/npm/info), [`yarn npm login`](https://yarnpkg.com/cli/npm/login), [`yarn npm publish`](https://yarnpkg.com/cli/npm/publish), ...).
- [★ plugin-pack](packages/plugin-pack) adds support for the [`yarn pack`](https://yarnpkg.com/cli/pack) command.
- [★ plugin-pnp](packages/plugin-pnp) adds support for installing Javascript dependencies through the [Plug'n'Play](https://yarnpkg.com/features/pnp) specification.

Expand Down
4 changes: 2 additions & 2 deletions packages/acceptance-tests/pkg-tests-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@types/invariant": "^2.2.30",
"@types/klaw": "^2.0.0",
"@types/minimatch": "^3.0.3",
"@types/semver": "^6.0.2",
"@types/semver": "^7.1.0",
"@types/tar-fs": "^1.16.1",
"@types/tmp": "^0.0.33",
"@yarnpkg/cli": "workspace:^2.0.0-rc.28",
Expand All @@ -17,7 +17,7 @@
"klaw": "^2.1.1",
"minimatch": "^3.0.4",
"pkg-tests-fixtures": "workspace:0.0.0",
"semver": "^5.6.0",
"semver": "^7.1.2",
"serve-static": "^1.14.1",
"super-resolve": "^1.0.0",
"tar-fs": "^1.16.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/acceptance-tests/pkg-tests-specs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@yarnpkg/parsers": "workspace:^2.0.0-rc.9",
"fs-extra": "^7.0.1",
"pkg-tests-core": "workspace:0.0.0",
"semver": "^5.6.0"
"semver": "^7.1.2"
},
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-essentials/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@yarnpkg/parsers": "workspace:^2.0.0-rc.9",
"clipanion": "^2.1.5",
"inquirer": "^6.2.0",
"semver": "^5.6.0",
"semver": "^7.1.2",
"treeify": "^1.1.0",
"yup": "^0.27.0"
},
Expand All @@ -18,7 +18,7 @@
},
"devDependencies": {
"@types/inquirer": "^0.0.43",
"@types/semver": "^6.0.2",
"@types/semver": "^7.1.0",
"@types/treeify": "^1.0.0",
"@yarnpkg/cli": "workspace:^2.0.0-rc.28",
"@yarnpkg/core": "workspace:^2.0.0-rc.22"
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"version": "2.0.0-rc.16",
"main": "./sources/index.ts",
"dependencies": {
"@types/semver": "^6.0.2",
"@types/semver": "^7.1.0",
"@yarnpkg/fslib": "workspace:^2.0.0-rc.15",
"git-url-parse": "11.1.2",
"semver": "^5.6.0",
"semver": "^7.1.2",
"tmp": "^0.1.0"
},
"peerDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-interactive-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"diff": "^4.0.1",
"ink": "^2.3.0",
"react": "^16.8.4",
"semver": "^5.6.0"
"semver": "^7.1.2"
},
"peerDependencies": {
"@yarnpkg/cli": "^2.0.0-rc.28",
Expand All @@ -18,7 +18,7 @@
"devDependencies": {
"@types/diff": "^4.0.2",
"@types/react": "^16.8.0",
"@types/semver": "^6.0.2",
"@types/semver": "^7.1.0",
"@yarnpkg/builder": "workspace:^2.0.0-rc.18",
"@yarnpkg/cli": "workspace:^2.0.0-rc.28",
"@yarnpkg/core": "workspace:^2.0.0-rc.22",
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-npm-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This plugin is included by default in Yarn.

## Commands

- [`yarn npm info`](https://yarnpkg.com/cli/npm/info)
- [`yarn npm login`](https://yarnpkg.com/cli/npm/login)
- [`yarn npm publish`](https://yarnpkg.com/cli/npm/publish)
- [`yarn npm whoami`](https://yarnpkg.com/cli/npm/whoami)
5 changes: 5 additions & 0 deletions packages/plugin-npm-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"@yarnpkg/fslib": "workspace:^2.0.0-rc.15",
"clipanion": "^2.1.5",
"inquirer": "^6.2.0",
"json5": "^2.1.1",
"semver": "^7.1.2",
"ssri": "^6.0.1",
"yup": "^0.27.0"
},
Expand All @@ -16,7 +18,10 @@
"@yarnpkg/plugin-pack": "^2.0.0-rc.17"
},
"devDependencies": {
"@npm/types": "^1.0.1",
"@types/inquirer": "^0.0.43",
"@types/json5": "0.0.30",
"@types/semver": "^7.1.0",
"@types/ssri": "^6.0.1",
"@yarnpkg/cli": "workspace:^2.0.0-rc.28",
"@yarnpkg/core": "workspace:^2.0.0-rc.22",
Expand Down
252 changes: 252 additions & 0 deletions packages/plugin-npm-cli/sources/commands/npm/info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import * as npm from '@npm/types';
import {BaseCommand} from '@yarnpkg/cli';
import {Project, Configuration, structUtils, ReportError, Descriptor} from '@yarnpkg/core';
import {StreamReport, MessageName} from '@yarnpkg/core';
import {npmHttpUtils} from '@yarnpkg/plugin-npm';
import {Command, Usage, UsageError} from 'clipanion';
import path from 'path';
import semver from 'semver';
import {inspect} from 'util';

declare module '@npm/types' {
/*
* Add missing property `users` on interface `Packument`
*/
interface Packument {
users: Record<string, boolean>[];
}
}

/**
* The combined type of `Packument` (without the `versions` field) and `PackumentVersion`
*/
type CombinedPackument = Omit<npm.Packument, 'versions'> & npm.PackumentVersion;

/**
* `CombinedPackument` with a `versions` field that is an array of tags
*/
interface PackageInformation extends CombinedPackument {
versions: string[];
}

// eslint-disable-next-line arca/no-default-export
export default class InfoCommand extends BaseCommand {
@Command.Rest()
packages!: string;

@Command.String(`-f,--fields`)
fields?: string;

@Command.Boolean(`--json`)
json: boolean = false;

static usage: Usage = Command.Usage({
description: `show information about a package`,
details: `
This command will fetch information about a package from the npm registry, and prints it in a tree format.
The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).
Append \`@<range>\` to the package argument to provide information specific to the latest version that satisfies the range. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.
If the \`-f,--fields\` option is set, it's a comma-separated list of fields which will be used to only display part of the package informations.
By default, this command won't return the \`dist\`, \`readme\`, and \`users\` fields, since they are often very long. To explicitly request those fields, explicitly list them with the \`--fields\` flag or request the output in JSON mode.
If the \`--json\` flag is set the output will follow a JSON-stream output also known as NDJSON (https://github.com/ndjson/ndjson-spec).
`,
examples: [[
`Show all available information about react (except the \`dist\`, \`readme\`, and \`users\` fields)`,
`yarn npm info react`,
], [
`Show all available information about react as valid JSON (including the \`dist\`, \`readme\`, and \`users\` fields)`,
`yarn npm info react --json`,
], [
`Show all available information about react 16.12.0`,
`yarn npm info [email protected]`,
], [
`Show the description of react`,
`yarn npm info react --fields description`,
], [
`Show all available versions of react`,
`yarn npm info react --fields versions`,
], [
`Show the readme of react`,
`yarn npm info react --fields readme`,
], [
`Show a few fields of react`,
`yarn npm info react --fields homepage,repository`,
]],
});

@Command.Path(`npm`, `info`)
async execute() {
const configuration = await Configuration.find(this.context.cwd, this.context.plugins);
const {project} = await Project.find(configuration, this.context.cwd);

const fields = typeof this.fields !== `undefined`
? new Set([`name`, ...this.fields.split(/\s*,\s*/)])
: null;

const infos: Array<PackageInformation> = [];
let leadWithSeparator = false;

const report = await StreamReport.start({
configuration,
includeFooter: false,
json: this.json,
stdout: this.context.stdout,
}, async report => {
for (const identStr of this.packages) {
let descriptor: Descriptor;
if (identStr === `.`) {
const workspace = project.topLevelWorkspace;
if (!workspace.manifest.name)
throw new UsageError(`Missing 'name' field in ${path.join(workspace.cwd, `package.json`)}`);

descriptor = structUtils.makeDescriptor(workspace.manifest.name, `unknown`);
} else {
descriptor = structUtils.parseDescriptor(identStr);
}

const identUrl = npmHttpUtils.getIdentUrl(descriptor);

let result: npm.Packument;
try {
// The information from `registry.npmjs.org/<package>`
result = clean(await npmHttpUtils.get(identUrl, {
configuration,
ident: descriptor,
json: true,
})) as npm.Packument;
} catch (err) {
if (err.name !== `HTTPError`) {
throw err;
} else if (err.response.statusCode === 404) {
throw new ReportError(MessageName.EXCEPTION, `Package not found`);
} else {
throw new ReportError(MessageName.EXCEPTION, err.toString());
}
}

const versions = Object.keys(result.versions).sort(semver.compareLoose);
const fallbackVersion = result[`dist-tags`].latest || versions[versions.length - 1];

// The latest version that satisfies `descriptor.range` (if it is a valid range), else `fallbackVersion`
let version: string = fallbackVersion;
if (semver.validRange(descriptor.range)) {
const maxSatisfyingVersion = semver.maxSatisfying(versions, descriptor.range);

if (maxSatisfyingVersion !== null) {
version = maxSatisfyingVersion;
} else {
report.reportWarning(MessageName.UNNAMED, `Unmet range ${structUtils.prettyRange(configuration, descriptor.range)}; falling back to the latest version`);
leadWithSeparator = true;
}
} else if (descriptor.range !== `unknown`) {
report.reportWarning(MessageName.UNNAMED, `Invalid range ${structUtils.prettyRange(configuration, descriptor.range)}; falling back to the latest version`);
leadWithSeparator = true;
}

const release = result.versions[version];

/**
* The merging of
* @see `result` - The information from `registry.npmjs.org/<package>`
* @see `release` - The release corresponding to `version`
* @see `version` - The latest version that satisfies `descriptor.range` (if it is a valid range), else `fallbackVersion`
* @see `versions` - All version tags of a package, sorted in ascending order
*/
const packageInformation: PackageInformation = {
...result,
...release,
version,
versions,
};

let serialized: any;
if (fields !== null) {
serialized = {};

for (const field of fields) {
// @ts-ignore
const value = packageInformation[field];

if (typeof value !== `undefined`) {
serialized[field] = value;
} else {
report.reportWarning(MessageName.EXCEPTION, `The '${field}' field doesn't exist inside ${structUtils.prettyIdent(configuration, descriptor)}'s informations`);
leadWithSeparator = true;
continue;
}
}
} else {
// Remove long fields
if (!this.json) {
delete packageInformation.dist;
delete packageInformation.readme;
delete packageInformation.users;
}

serialized = packageInformation;
}

report.reportJson(serialized);

if (!this.json) {
infos.push(serialized);
}
}
});

inspect.styles.name = `cyan`;

for (const serialized of infos) {
if (serialized !== infos[0] || leadWithSeparator)
this.context.stdout.write(`\n`);

this.context.stdout.write(`${inspect(serialized, {
depth: Infinity,
colors: true,
compact: false,
})}\n`);
}

return report.exitCode();
}
}

// Remove hidden properties recursively
function clean(value: unknown): unknown {
if (Array.isArray(value)) {
const result: unknown[] = [];

for (let item of value) {
item = clean(item);

if (item) {
result.push(item);
}
}

return result;
} else if (typeof value === `object` && value !== null) {
const result: any = {};

for (const key of Object.keys(value)) {
if (key.startsWith(`_`))
continue;

const item = clean(value[key as keyof typeof value]);
if (item) {
result[key] = item;
}
}

return result;
} else if (value) {
return value;
} else {
return null;
}
}
Loading

0 comments on commit 364fed0

Please sign in to comment.