@sboudrias/package 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2025 Simon Boudrias
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @sboudrias/package
2
+
3
+ Package metadata tools for JavaScript packages and monorepos.
4
+
5
+ # Installation
6
+
7
+ <table>
8
+ <tr>
9
+ <th>npm</th>
10
+ <th>yarn</th>
11
+ <th>pnpm</th>
12
+ <th>bun</th>
13
+ </tr>
14
+ <tr>
15
+ <td>
16
+
17
+ ```sh
18
+ npm install @sboudrias/package --save-dev
19
+ ```
20
+
21
+ </td>
22
+ <td>
23
+
24
+ ```sh
25
+ yarn add @sboudrias/package --dev
26
+ ```
27
+
28
+ </td>
29
+ <td>
30
+
31
+ ```sh
32
+ pnpm add @sboudrias/package --save-dev
33
+ ```
34
+
35
+ </td>
36
+ <td>
37
+
38
+ ```sh
39
+ bun add @sboudrias/package --dev
40
+ ```
41
+
42
+ </td>
43
+ </tr>
44
+ </table>
45
+
46
+ # Usage
47
+
48
+ ```bash
49
+ package lint
50
+ ```
51
+
52
+ `package lint` validates public workspace packages and fixes safe package metadata issues in place.
53
+
54
+ ```bash
55
+ package lint --check
56
+ ```
57
+
58
+ `package lint --check` runs the same validation without writing files. It exits non-zero when any package needs a fix or has a manual conflict.
59
+
60
+ # Lint Rules
61
+
62
+ ## Valid Peer Dependencies
63
+
64
+ Runtime dependencies can declare their own peer dependencies. `package lint` makes those peer requirements visible on the package that uses the runtime dependency.
65
+
66
+ It adds missing peers to `peerDependencies` and copies matching `peerDependenciesMeta` entries so optional peers stay optional.
67
+
68
+ ## Matching engines
69
+
70
+ Packages should only advertise Node.js support that their runtime dependencies can also support.
71
+
72
+ `package lint` sets missing, invalid, or out-of-root-range `engines.node` values to the root package `engines.node` range. It fails when a runtime dependency supports a narrower Node.js range than the package.
73
+
74
+ That failure is intentional. Bumping a dependency can raise the minimum supported Node.js version and break dependants that still install the package under the previous `engines.node` range. In that case, manually narrow the package engine range or choose a compatible dependency version.
75
+
76
+ ## Ensure package.json is exposed
77
+
78
+ Packages should expose their manifest for tools that inspect package metadata at runtime.
79
+
80
+ `package lint` ensures public packages expose `"./package.json": "./package.json"` in `exports`.
81
+
82
+ # Workspace Discovery
83
+
84
+ The CLI discovers workspaces from `package.json` `workspaces` fields and `pnpm-workspace.yaml` files.
85
+
86
+ If no workspaces are configured, the root `package.json` is linted as a single-package project.
87
+
88
+ Private packages are ignored by default.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { type BaseContext } from 'clipanion';
3
+ export declare function main(argv?: string[], context?: Partial<BaseContext>): Promise<number>;
package/dist/bin.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Builtins, Cli, Command, Option } from 'clipanion';
5
+ import { lintPackages } from "./lint.js";
6
+ class LintCommand extends Command {
7
+ static paths = [['lint']];
8
+ static usage = Command.Usage({
9
+ description: 'Validate and fix package metadata.',
10
+ examples: [
11
+ ['Fix package metadata', '$0 lint'],
12
+ ['Validate package metadata without writing files', '$0 lint --check'],
13
+ ],
14
+ });
15
+ check = Option.Boolean('--check', false, {
16
+ description: 'Validate package metadata without writing files.',
17
+ });
18
+ async execute() {
19
+ try {
20
+ const result = await lintPackages({ check: this.check });
21
+ for (const issue of result.issues) {
22
+ this.context.stderr.write(`[${issue.status}] ${issue.message} (${issue.packagePath})\n`);
23
+ }
24
+ return result.hasFailures ? 1 : 0;
25
+ }
26
+ catch (error) {
27
+ this.context.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
28
+ return 1;
29
+ }
30
+ }
31
+ }
32
+ const cli = Cli.from([LintCommand, Builtins.HelpCommand], {
33
+ binaryLabel: 'Package',
34
+ binaryName: 'package',
35
+ enableCapture: false,
36
+ });
37
+ export async function main(argv = process.argv.slice(2), context = {}) {
38
+ return cli.run(argv, {
39
+ ...Cli.defaultContext,
40
+ ...context,
41
+ });
42
+ }
43
+ if (process.argv[1] != null &&
44
+ path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
45
+ process.exitCode = await main();
46
+ }
@@ -0,0 +1 @@
1
+ export { lintPackages } from './lint.ts';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { lintPackages } from "./lint.js";
package/dist/lint.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export declare function lintPackages(options?: {
2
+ cwd?: string;
3
+ check?: boolean;
4
+ }): Promise<{
5
+ issues: {
6
+ packagePath: string;
7
+ message: string;
8
+ status: "error" | "fixed" | "would fix";
9
+ }[];
10
+ hasFailures: boolean;
11
+ }>;
package/dist/lint.js ADDED
@@ -0,0 +1,120 @@
1
+ import path from 'node:path';
2
+ import { subset, validRange } from 'semver';
3
+ import { applyPeerDependencyHoists, getPeerDependencyHoists, } from "./peer-dependencies.js";
4
+ import { resolveDependencyPackageJson, writePackageJsonFile } from "./package-json.js";
5
+ import { readWorkspaceProject } from "./workspaces.js";
6
+ function nodeEngine(pkg) {
7
+ const node = pkg.engines?.['node'];
8
+ return typeof node === 'string' ? node : undefined;
9
+ }
10
+ export async function lintPackages(options = {}) {
11
+ const cwd = options.cwd ?? process.cwd();
12
+ const check = options.check ?? false;
13
+ const { rootPackageJson, packages } = await readWorkspaceProject(cwd);
14
+ const rootNodeRange = nodeEngine(rootPackageJson);
15
+ if (rootNodeRange == null || validRange(rootNodeRange) == null) {
16
+ throw new Error('The root package.json must define a valid engines.node range.');
17
+ }
18
+ const publicPackages = packages.filter(({ packageJson }) => packageJson.private !== true);
19
+ const packagesByName = new Map(packages.flatMap(({ packageJson }) => packageJson.name == null ? [] : [[packageJson.name, packageJson]]));
20
+ const resolvePackageJson = (dependencyName, packageDir) => packagesByName.get(dependencyName) ??
21
+ resolveDependencyPackageJson(dependencyName, packageDir);
22
+ const originalPackages = new Map(publicPackages.map(({ packageJson, packagePath }) => [
23
+ packagePath,
24
+ JSON.stringify(packageJson),
25
+ ]));
26
+ const issues = [];
27
+ const report = (packagePath, message, fixable) => {
28
+ issues.push({
29
+ packagePath,
30
+ message,
31
+ status: fixable ? (check ? 'would fix' : 'fixed') : 'error',
32
+ });
33
+ };
34
+ for (const { packageDir, packageJson, packagePath } of publicPackages) {
35
+ const name = packageJson.name ?? packagePath;
36
+ const { hoists } = getPeerDependencyHoists(packageJson, packageDir, resolvePackageJson);
37
+ for (const hoist of hoists) {
38
+ report(packagePath, hoist.kind === 'peer-dependency'
39
+ ? `${name} must hoist ${hoist.peerName} from ${hoist.dependencyName}.`
40
+ : `${name} must copy peer dependency metadata for ${hoist.peerName} from ${hoist.dependencyName}.`, true);
41
+ }
42
+ if (!check)
43
+ applyPeerDependencyHoists(packageJson, hoists);
44
+ let packageRange = nodeEngine(packageJson);
45
+ if (packageRange == null ||
46
+ validRange(packageRange) == null ||
47
+ !subset(packageRange, rootNodeRange)) {
48
+ report(packagePath, `${name} must define an engines.node range within the root range "${rootNodeRange}".`, true);
49
+ if (check) {
50
+ packageRange = undefined;
51
+ }
52
+ else {
53
+ packageJson.engines ??= {};
54
+ packageJson.engines['node'] = rootNodeRange;
55
+ packageRange = rootNodeRange;
56
+ }
57
+ }
58
+ if (packageRange != null) {
59
+ for (const dependencyName of new Set([
60
+ ...Object.keys(packageJson.dependencies ?? {}),
61
+ ...Object.keys(packageJson.optionalDependencies ?? {}),
62
+ ])) {
63
+ const dependencyPackage = resolvePackageJson(dependencyName, packageDir);
64
+ if (dependencyPackage == null) {
65
+ report(packagePath, `${name} depends on ${dependencyName}, but its package.json could not be resolved.`, false);
66
+ continue;
67
+ }
68
+ const dependencyRange = nodeEngine(dependencyPackage);
69
+ if (dependencyRange == null)
70
+ continue;
71
+ if (validRange(dependencyRange) == null) {
72
+ report(packagePath, `${name} depends on ${dependencyName}, which has an invalid engines.node range: ${dependencyRange}`, false);
73
+ }
74
+ else if (!subset(packageRange, dependencyRange)) {
75
+ report(packagePath, `${name} supports node "${packageRange}", but ${dependencyName} supports "${dependencyRange}".`, false);
76
+ }
77
+ }
78
+ }
79
+ const currentExports = packageJson.exports;
80
+ const currentExportsObject = currentExports != null &&
81
+ typeof currentExports === 'object' &&
82
+ !Array.isArray(currentExports)
83
+ ? currentExports
84
+ : undefined;
85
+ if (currentExportsObject != null &&
86
+ Reflect.get(currentExportsObject, './package.json') === './package.json') {
87
+ continue;
88
+ }
89
+ if (currentExports == null ||
90
+ typeof currentExports === 'string' ||
91
+ currentExportsObject != null) {
92
+ report(packagePath, `${name} must export ./package.json.`, true);
93
+ if (!check) {
94
+ if (currentExports == null) {
95
+ packageJson.exports = { './package.json': './package.json' };
96
+ }
97
+ else if (typeof currentExports === 'string') {
98
+ packageJson.exports = {
99
+ '.': currentExports,
100
+ './package.json': './package.json',
101
+ };
102
+ }
103
+ else if (currentExportsObject != null) {
104
+ Reflect.set(currentExportsObject, './package.json', './package.json');
105
+ }
106
+ }
107
+ }
108
+ else {
109
+ report(packagePath, `${name} uses an unsupported exports shape; add ./package.json manually.`, false);
110
+ }
111
+ }
112
+ const changedPackages = publicPackages.filter(({ packageJson, packagePath }) => JSON.stringify(packageJson) !== originalPackages.get(packagePath));
113
+ if (!check) {
114
+ await Promise.all(changedPackages.map(({ packageJson, packagePath }) => writePackageJsonFile(path.join(cwd, packagePath), packageJson)));
115
+ }
116
+ return {
117
+ issues,
118
+ hasFailures: issues.some((issue) => issue.status !== 'fixed'),
119
+ };
120
+ }
@@ -0,0 +1,4 @@
1
+ import type { PackageJson } from 'type-fest';
2
+ export declare function readPackageJsonFile(filepath: string): PackageJson;
3
+ export declare function writePackageJsonFile(filepath: string, packageJson: PackageJson): Promise<void>;
4
+ export declare function resolveDependencyPackageJson(name: string, fromDirectory?: string): PackageJson | undefined;
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs';
2
+ import fsPromises from 'node:fs/promises';
3
+ import Module from 'node:module';
4
+ import path from 'node:path';
5
+ import { parse as parseJsonc } from 'jsonc-parser';
6
+ function asPackageJson(value, source) {
7
+ if (value == null || typeof value !== 'object' || Array.isArray(value)) {
8
+ throw new TypeError(`Expected ${source} to contain a package.json object`);
9
+ }
10
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
11
+ return value;
12
+ }
13
+ function errorCode(error) {
14
+ return error && typeof error === 'object' && 'code' in error
15
+ ? String(error.code)
16
+ : undefined;
17
+ }
18
+ function createRequireFrom(fromDirectory) {
19
+ return Module.createRequire(path.join(fromDirectory, 'package.json'));
20
+ }
21
+ function findPackageJson(fromDirectory) {
22
+ let directory = fromDirectory;
23
+ for (;;) {
24
+ const packageJsonPath = path.join(directory, 'package.json');
25
+ if (fs.existsSync(packageJsonPath)) {
26
+ return packageJsonPath;
27
+ }
28
+ const parent = path.dirname(directory);
29
+ if (parent === directory) {
30
+ return undefined;
31
+ }
32
+ directory = parent;
33
+ }
34
+ }
35
+ function findNodeModulesPackageJson(name, fromDirectory) {
36
+ let directory = fromDirectory;
37
+ for (;;) {
38
+ const packageJsonPath = path.join(directory, 'node_modules', name, 'package.json');
39
+ if (fs.existsSync(packageJsonPath)) {
40
+ return packageJsonPath;
41
+ }
42
+ const parent = path.dirname(directory);
43
+ if (parent === directory) {
44
+ return undefined;
45
+ }
46
+ directory = parent;
47
+ }
48
+ }
49
+ export function readPackageJsonFile(filepath) {
50
+ return asPackageJson(parseJsonc(fs.readFileSync(filepath, 'utf8')), filepath);
51
+ }
52
+ export async function writePackageJsonFile(filepath, packageJson) {
53
+ await fsPromises.writeFile(filepath, JSON.stringify(packageJson, null, 2) + '\n');
54
+ }
55
+ export function resolveDependencyPackageJson(name, fromDirectory = process.cwd()) {
56
+ const requireFromDirectory = createRequireFrom(fromDirectory);
57
+ try {
58
+ const packageJson = requireFromDirectory(`${name}/package.json`);
59
+ return asPackageJson(packageJson, `${name}/package.json`);
60
+ }
61
+ catch (error) {
62
+ const code = errorCode(error);
63
+ if (code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && code !== 'MODULE_NOT_FOUND') {
64
+ throw error;
65
+ }
66
+ }
67
+ try {
68
+ const entrypoint = requireFromDirectory.resolve(name);
69
+ const entrypointPackageJsonPath = findPackageJson(path.dirname(entrypoint));
70
+ if (entrypointPackageJsonPath != null) {
71
+ return readPackageJsonFile(entrypointPackageJsonPath);
72
+ }
73
+ }
74
+ catch (error) {
75
+ const code = errorCode(error);
76
+ if (code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && code !== 'MODULE_NOT_FOUND') {
77
+ throw error;
78
+ }
79
+ }
80
+ const packageJsonPath = findNodeModulesPackageJson(name, fromDirectory);
81
+ if (packageJsonPath != null) {
82
+ return readPackageJsonFile(packageJsonPath);
83
+ }
84
+ return undefined;
85
+ }
@@ -0,0 +1,20 @@
1
+ import type { PackageJson } from 'type-fest';
2
+ export type ResolvePackageJson = (dependencyName: string, fromDirectory: string) => PackageJson | undefined;
3
+ export type PeerDependencyHoist = {
4
+ kind: 'peer-dependency';
5
+ dependencyName: string;
6
+ peerName: string;
7
+ peerVersion: string;
8
+ } | {
9
+ kind: 'peer-dependency-meta';
10
+ dependencyName: string;
11
+ peerName: string;
12
+ peerMeta: NonNullable<PackageJson['peerDependenciesMeta']>[string];
13
+ };
14
+ export type PeerDependencyHoistResult = {
15
+ hoists: PeerDependencyHoist[];
16
+ missingDependencies: string[];
17
+ };
18
+ export declare function getPeerDependencyHoists(pkg: PackageJson, packageDir: string, resolvePackageJson?: ResolvePackageJson): PeerDependencyHoistResult;
19
+ export declare function applyPeerDependencyHoists(pkg: PackageJson, hoists: PeerDependencyHoist[]): void;
20
+ export declare function hoistPeerDependencies(pkg: PackageJson, packageDir: string, resolvePackageJson?: ResolvePackageJson): boolean;
@@ -0,0 +1,66 @@
1
+ import { resolveDependencyPackageJson } from "./package-json.js";
2
+ function runtimeDependencyNames(pkg) {
3
+ return new Set([
4
+ ...Object.keys(pkg.dependencies ?? {}),
5
+ ...Object.keys(pkg.optionalDependencies ?? {}),
6
+ ]);
7
+ }
8
+ function hasRuntimeDependency(pkg, dependencyName) {
9
+ return (pkg.dependencies?.[dependencyName] != null ||
10
+ pkg.optionalDependencies?.[dependencyName] != null);
11
+ }
12
+ export function getPeerDependencyHoists(pkg, packageDir, resolvePackageJson = resolveDependencyPackageJson) {
13
+ const hoists = [];
14
+ const missingDependencies = [];
15
+ for (const dependencyName of runtimeDependencyNames(pkg)) {
16
+ const dependencyPackage = resolvePackageJson(dependencyName, packageDir);
17
+ if (dependencyPackage == null) {
18
+ missingDependencies.push(dependencyName);
19
+ continue;
20
+ }
21
+ for (const peerName of Object.keys(dependencyPackage.peerDependencies ?? {})) {
22
+ const peerVersion = dependencyPackage.peerDependencies?.[peerName];
23
+ if (typeof peerVersion !== 'string')
24
+ continue;
25
+ let packageShouldHavePeer = !hasRuntimeDependency(pkg, peerName) && pkg.peerDependencies?.[peerName] == null;
26
+ if (packageShouldHavePeer) {
27
+ hoists.push({
28
+ kind: 'peer-dependency',
29
+ dependencyName,
30
+ peerName,
31
+ peerVersion,
32
+ });
33
+ }
34
+ packageShouldHavePeer ||= pkg.peerDependencies?.[peerName] != null;
35
+ const peerMeta = dependencyPackage.peerDependenciesMeta?.[peerName];
36
+ if (packageShouldHavePeer &&
37
+ peerMeta != null &&
38
+ pkg.peerDependenciesMeta?.[peerName] == null) {
39
+ hoists.push({
40
+ kind: 'peer-dependency-meta',
41
+ dependencyName,
42
+ peerName,
43
+ peerMeta,
44
+ });
45
+ }
46
+ }
47
+ }
48
+ return { hoists, missingDependencies };
49
+ }
50
+ export function applyPeerDependencyHoists(pkg, hoists) {
51
+ for (const hoist of hoists) {
52
+ if (hoist.kind === 'peer-dependency') {
53
+ pkg.peerDependencies ??= {};
54
+ pkg.peerDependencies[hoist.peerName] = hoist.peerVersion;
55
+ }
56
+ else {
57
+ pkg.peerDependenciesMeta ??= {};
58
+ pkg.peerDependenciesMeta[hoist.peerName] = hoist.peerMeta;
59
+ }
60
+ }
61
+ }
62
+ export function hoistPeerDependencies(pkg, packageDir, resolvePackageJson) {
63
+ const { hoists } = getPeerDependencyHoists(pkg, packageDir, resolvePackageJson);
64
+ applyPeerDependencyHoists(pkg, hoists);
65
+ return hoists.length > 0;
66
+ }
@@ -0,0 +1,12 @@
1
+ import type { PackageJson } from 'type-fest';
2
+ export type WorkspacePackage = {
3
+ packageJson: PackageJson;
4
+ packagePath: string;
5
+ packageDir: string;
6
+ };
7
+ export type WorkspaceProject = {
8
+ rootPackageJson: PackageJson;
9
+ rootPackagePath: string;
10
+ packages: WorkspacePackage[];
11
+ };
12
+ export declare function readWorkspaceProject(cwd?: string): Promise<WorkspaceProject>;
@@ -0,0 +1,86 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { globby } from 'globby';
4
+ import { parse as parseYaml } from 'yaml';
5
+ import { readPackageJsonFile } from "./package-json.js";
6
+ function errorCode(error) {
7
+ return error && typeof error === 'object' && 'code' in error
8
+ ? String(error.code)
9
+ : undefined;
10
+ }
11
+ function workspacePackagePattern(pattern) {
12
+ const isNegated = pattern.startsWith('!');
13
+ const workspacePattern = isNegated ? pattern.slice(1) : pattern;
14
+ const packageJsonPattern = workspacePattern.endsWith('/package.json')
15
+ ? workspacePattern
16
+ : `${workspacePattern.replace(/\/$/, '')}/package.json`;
17
+ return isNegated ? `!${packageJsonPattern}` : packageJsonPattern;
18
+ }
19
+ function packageJsonWorkspacePatterns(rootPackageJson) {
20
+ const workspaces = rootPackageJson.workspaces;
21
+ if (Array.isArray(workspaces)) {
22
+ return workspaces.filter((workspace) => typeof workspace === 'string');
23
+ }
24
+ if (workspaces != null && typeof workspaces === 'object') {
25
+ const packages = 'packages' in workspaces ? workspaces.packages : undefined;
26
+ if (Array.isArray(packages)) {
27
+ return packages.filter((workspace) => typeof workspace === 'string');
28
+ }
29
+ }
30
+ return [];
31
+ }
32
+ function pnpmWorkspacePatternsFrom(value) {
33
+ if (value == null || typeof value !== 'object' || Array.isArray(value)) {
34
+ return [];
35
+ }
36
+ const packages = 'packages' in value ? value.packages : undefined;
37
+ if (!Array.isArray(packages)) {
38
+ return [];
39
+ }
40
+ return packages.filter((workspace) => typeof workspace === 'string');
41
+ }
42
+ async function readPnpmWorkspacePatterns(cwd) {
43
+ for (const filename of ['pnpm-workspace.yaml', 'pnpm-workspace.yml']) {
44
+ try {
45
+ const content = await fs.readFile(path.join(cwd, filename), 'utf8');
46
+ return pnpmWorkspacePatternsFrom(parseYaml(content));
47
+ }
48
+ catch (error) {
49
+ if (errorCode(error) !== 'ENOENT') {
50
+ throw error;
51
+ }
52
+ }
53
+ }
54
+ return [];
55
+ }
56
+ export async function readWorkspaceProject(cwd = process.cwd()) {
57
+ const rootPackagePath = path.join(cwd, 'package.json');
58
+ const rootPackageJson = readPackageJsonFile(rootPackagePath);
59
+ const workspacePatterns = [
60
+ ...packageJsonWorkspacePatterns(rootPackageJson),
61
+ ...(await readPnpmWorkspacePatterns(cwd)),
62
+ ];
63
+ if (workspacePatterns.length === 0) {
64
+ return {
65
+ rootPackageJson,
66
+ rootPackagePath,
67
+ packages: [
68
+ {
69
+ packageJson: rootPackageJson,
70
+ packagePath: 'package.json',
71
+ packageDir: cwd,
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ const packagePaths = await globby([...new Set(workspacePatterns.map(workspacePackagePattern)), '!**/node_modules'], { cwd });
77
+ const packages = packagePaths.toSorted().map((packagePath) => {
78
+ const absolutePackagePath = path.join(cwd, packagePath);
79
+ return {
80
+ packageJson: readPackageJsonFile(absolutePackagePath),
81
+ packagePath,
82
+ packageDir: path.dirname(absolutePackagePath),
83
+ };
84
+ });
85
+ return { rootPackageJson, rootPackagePath, packages };
86
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@sboudrias/package",
3
+ "version": "1.0.0",
4
+ "keywords": [
5
+ "cli",
6
+ "lint",
7
+ "monorepo",
8
+ "node",
9
+ "npm",
10
+ "package-json",
11
+ "pnpm",
12
+ "workspace",
13
+ "yarn"
14
+ ],
15
+ "homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/tools/package/README.md",
16
+ "license": "MIT",
17
+ "author": "Simon Boudrias <admin@simonboudrias.com>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/SBoudrias/Inquirer.js.git"
21
+ },
22
+ "bin": "./dist/bin.js",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "type": "module",
27
+ "sideEffects": false,
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "tsc": "tsc && chmod +x ./dist/bin.js"
40
+ },
41
+ "dependencies": {
42
+ "clipanion": "^4.0.0-rc.4",
43
+ "globby": "^16.2.0",
44
+ "jsonc-parser": "^3.3.1",
45
+ "semver": "^7.7.4",
46
+ "typanion": "^3.14.0",
47
+ "yaml": "^2.9.0"
48
+ },
49
+ "devDependencies": {
50
+ "@repo/tsconfig": "workspace:*",
51
+ "@types/node": "^25.5.2",
52
+ "@types/semver": "^7.7.1",
53
+ "type-fest": "^5.5.0",
54
+ "typescript": "^6.0.2",
55
+ "vitest": "^4.1.2"
56
+ },
57
+ "engines": {
58
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
59
+ },
60
+ "main": "./dist/index.js",
61
+ "types": "./dist/index.d.ts",
62
+ "gitHead": "bfd8710229e3d3c9784bef3bbfbd84c7bd09bb9e"
63
+ }