@pinnacle0/pnpm-single-version 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/README.md +74 -0
- package/dist/checker/error-message.js +47 -0
- package/dist/checker/index.js +33 -0
- package/dist/command/checkDeps.js +62 -0
- package/dist/command/index.js +2 -0
- package/dist/command/install.js +51 -0
- package/dist/command/pnpmfile/.pnpmfile.cjs +7 -0
- package/dist/command/pnpmfile/hook.js +2 -0
- package/dist/command/setup.js +13 -0
- package/dist/constant.js +2 -0
- package/dist/error.js +37 -0
- package/dist/hook-bundle.js +29 -0
- package/dist/hook.js +55 -0
- package/dist/index.js +2 -0
- package/dist/parser-option/find-project-root.js +54 -0
- package/dist/parser-option/parse-option.js +62 -0
- package/dist/type.js +1 -0
- package/dist/util/array-value-map-Helper.js +18 -0
- package/dist/util/glob-util.js +16 -0
- package/dist/util/logger.js +19 -0
- package/dist/util/pipe.js +3 -0
- package/dist/util/pnpm-logger.js +15 -0
- package/package.json +47 -0
- package/src/checker/error-message.ts +58 -0
- package/src/checker/index.ts +46 -0
- package/src/command/checkDeps.ts +27 -0
- package/src/command/index.ts +2 -0
- package/src/command/install.ts +17 -0
- package/src/command/pnpmfile/.pnpmfile.cjs +7 -0
- package/src/command/pnpmfile/hook.ts +3 -0
- package/src/command/setup.ts +13 -0
- package/src/constant.ts +2 -0
- package/src/error.ts +43 -0
- package/src/hook.ts +23 -0
- package/src/index.ts +3 -0
- package/src/parser-option/find-project-root.ts +22 -0
- package/src/parser-option/parse-option.ts +38 -0
- package/src/type.ts +17 -0
- package/src/util/array-value-map-Helper.ts +19 -0
- package/src/util/glob-util.ts +25 -0
- package/src/util/logger.ts +24 -0
- package/src/util/pipe.ts +43 -0
- package/src/util/pnpm-logger.ts +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# pnpm single version
|
|
2
|
+
|
|
3
|
+
Enforce Single version of dependencies on pnpm workspace.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D pnpm-single-version
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Add following options to `package.json` in project root
|
|
14
|
+
|
|
15
|
+
```json5
|
|
16
|
+
"pnpmSingleVersion": {
|
|
17
|
+
"includes": [
|
|
18
|
+
// Place all the single version dependencies here
|
|
19
|
+
"@babel/core",
|
|
20
|
+
"esbuild",
|
|
21
|
+
// glob is also supported
|
|
22
|
+
"eslint-plugin-*",
|
|
23
|
+
"*-plugin",
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Maunal Checking using CLI
|
|
29
|
+
|
|
30
|
+
You can `pnpm-single-version` in Terminal
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm pnpm-single-version
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
or
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm psv
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Automatic resolve (Recommanded)
|
|
43
|
+
|
|
44
|
+
Apart from manual checking, checking can also be done when `pnpm-lock.yaml` is resolved, where pnpm detected dependencies changes running `pnpm install` , `pnpm update` and `pnpm removed`. This is much effective.
|
|
45
|
+
|
|
46
|
+
By using `afterAllResolved` hook in `.pnpmfile.cjs`, installation process can be interrupted when non-single version dependencies is detected.
|
|
47
|
+
|
|
48
|
+
To setup it up,
|
|
49
|
+
|
|
50
|
+
1. First, install checker via
|
|
51
|
+
|
|
52
|
+
```shell
|
|
53
|
+
pnpm pnpm-single-version install
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
this command will generate a checker file inside `.psv` directory of the root directory of workspace.
|
|
57
|
+
|
|
58
|
+
2. Then you should create a `.pnpmfile.cjs` and add following code
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const hook = require("./.psv/hook");
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
hooks: {
|
|
65
|
+
afterAllResolved: hook,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Now, when you call `pnpm install` and `pnpm update`, checking is going to be involve automatically only when have dependenices changes.
|
|
71
|
+
|
|
72
|
+
**PS: You may need to run `psv install` every time you update `pnpm-single-version`**
|
|
73
|
+
|
|
74
|
+
More about `.pnpmfile.cjs` at https://pnpm.io/pnpmfile
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
const Emoji = {
|
|
3
|
+
sad: "😔",
|
|
4
|
+
cross: "❌",
|
|
5
|
+
happy: "🤗"
|
|
6
|
+
};
|
|
7
|
+
const headerMessage = (num)=>`${Emoji.cross} Found ${chalk.redBright(num)} Non-single version dependencies\n`;
|
|
8
|
+
const listVersionMessage = (name, infos)=>`
|
|
9
|
+
- ${chalk.blueBright(name)} has ${chalk.redBright(infos.length)} distinct versions
|
|
10
|
+
${infos.map((_)=>{
|
|
11
|
+
var __peersSuffix;
|
|
12
|
+
return _.version + ((__peersSuffix = _.peersSuffix) !== null && __peersSuffix !== void 0 ? __peersSuffix : "");
|
|
13
|
+
}).join(", ")}
|
|
14
|
+
`;
|
|
15
|
+
const hintMessage = `
|
|
16
|
+
Well, here is what you can try to do:
|
|
17
|
+
Run ${chalk.blueBright(`$ pnpm why <dependency> -r `)} on project root to figure out the dependencies that contain conflicted version.
|
|
18
|
+
If the output is too long, try to redirect output, like ${chalk.blueBright(`$ pnpm why <dependency> -r > example.txt`)}
|
|
19
|
+
more about ${chalk.blueBright(`$ pnpm why`)} command, check out ${chalk.greenBright("https://pnpm.io/cli/why")}
|
|
20
|
+
`;
|
|
21
|
+
export function createErrorMessage(nonSingleVersionDeps) {
|
|
22
|
+
if (nonSingleVersionDeps.size === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
let message = headerMessage(nonSingleVersionDeps.size);
|
|
26
|
+
for (const [name, infos] of nonSingleVersionDeps.entries()){
|
|
27
|
+
message += listVersionMessage(name, infos);
|
|
28
|
+
}
|
|
29
|
+
message += hintMessage;
|
|
30
|
+
return message;
|
|
31
|
+
}
|
|
32
|
+
export const logInstallationInterruptedMessage = (logger)=>{
|
|
33
|
+
logger.message(`${Emoji.sad} ${chalk.red("Installation Process interrupted.")}`);
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param {string | null} message
|
|
38
|
+
* @returns {boolean} Any Error message logged
|
|
39
|
+
*/ export const logErrorMessage = (logger)=>(message)=>{
|
|
40
|
+
if (!message) {
|
|
41
|
+
logger.message(`${Emoji.happy} All Passed!!!`);
|
|
42
|
+
return false;
|
|
43
|
+
} else {
|
|
44
|
+
logger.message(message);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { nameVerFromPkgSnapshot } from "@pnpm/lockfile-utils";
|
|
2
|
+
import { pipe } from "../util/pipe.js";
|
|
3
|
+
import { ArrayValueMapHelper } from "../util/array-value-map-Helper.js";
|
|
4
|
+
import { LockfilePackagesMissingError } from "../error.js";
|
|
5
|
+
import { globUtil } from "../util/glob-util.js";
|
|
6
|
+
import { createErrorMessage, logErrorMessage } from "./error-message.js";
|
|
7
|
+
export const filterNonSingleVersionDependencies = (packageInfoMap)=>{
|
|
8
|
+
const nonSingleVersionPackageInfo = new Map();
|
|
9
|
+
for (const [path, snapshots] of packageInfoMap.entries()){
|
|
10
|
+
if (snapshots.length > 1) {
|
|
11
|
+
nonSingleVersionPackageInfo.set(path, snapshots);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return nonSingleVersionPackageInfo;
|
|
15
|
+
};
|
|
16
|
+
const filterSnapshotNeededForChecking = (snapshots)=>(depsPattern)=>{
|
|
17
|
+
const matcher = globUtil.toRegex(depsPattern);
|
|
18
|
+
const packageInfos = ArrayValueMapHelper.create();
|
|
19
|
+
for (const [path, snapshot] of Object.entries(snapshots)){
|
|
20
|
+
const nameVersionInfo = nameVerFromPkgSnapshot(path, snapshot);
|
|
21
|
+
if (matcher.test(nameVersionInfo.name)) {
|
|
22
|
+
ArrayValueMapHelper.add(packageInfos, nameVersionInfo.name, nameVersionInfo);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return packageInfos;
|
|
26
|
+
};
|
|
27
|
+
export const checker = (lockfile, options, logger)=>{
|
|
28
|
+
if (!lockfile.packages) {
|
|
29
|
+
throw new LockfilePackagesMissingError();
|
|
30
|
+
}
|
|
31
|
+
logger.message("Verify single version dependencies...");
|
|
32
|
+
return pipe(filterSnapshotNeededForChecking(lockfile.packages), filterNonSingleVersionDependencies, createErrorMessage, logErrorMessage(logger))(options.includes);
|
|
33
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
2
|
+
try {
|
|
3
|
+
var info = gen[key](arg);
|
|
4
|
+
var value = info.value;
|
|
5
|
+
} catch (error) {
|
|
6
|
+
reject(error);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (info.done) {
|
|
10
|
+
resolve(value);
|
|
11
|
+
} else {
|
|
12
|
+
Promise.resolve(value).then(_next, _throw);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function _async_to_generator(fn) {
|
|
16
|
+
return function() {
|
|
17
|
+
var self = this, args = arguments;
|
|
18
|
+
return new Promise(function(resolve, reject) {
|
|
19
|
+
var gen = fn.apply(self, args);
|
|
20
|
+
function _next(value) {
|
|
21
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
22
|
+
}
|
|
23
|
+
function _throw(err) {
|
|
24
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
25
|
+
}
|
|
26
|
+
_next(undefined);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
import { readWantedLockfile } from "@pnpm/lockfile-file";
|
|
31
|
+
import { Logger } from "../util/logger.js";
|
|
32
|
+
import isCI from "is-ci";
|
|
33
|
+
import { checker } from "../checker/index.js";
|
|
34
|
+
import { parseOptions } from "../parser-option/parse-option.js";
|
|
35
|
+
import { findProjectRoot } from "../parser-option/find-project-root.js";
|
|
36
|
+
import chalk from "chalk";
|
|
37
|
+
import { logInstallationInterruptedMessage } from "../checker/error-message.js";
|
|
38
|
+
export const checkDeps = function() {
|
|
39
|
+
var _ref = _async_to_generator(function*() {
|
|
40
|
+
try {
|
|
41
|
+
const projectRoot = yield findProjectRoot();
|
|
42
|
+
const options = yield parseOptions(projectRoot);
|
|
43
|
+
const lockfile = yield readWantedLockfile(projectRoot, {
|
|
44
|
+
ignoreIncompatible: false
|
|
45
|
+
});
|
|
46
|
+
if (!lockfile) {
|
|
47
|
+
Logger.message(`Cannot find ${chalk.blueBright("pnpm-lock.yaml")}, try to run ${chalk.blueBright("$ pnpm install")} first`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
if (checker(lockfile, options, Logger) && options.failOnCI && isCI) {
|
|
51
|
+
logInstallationInterruptedMessage(Logger);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
error instanceof Error ? Logger.error(error) : console.error("Unexpected error", error);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return function checkDeps() {
|
|
60
|
+
return _ref.apply(this, arguments);
|
|
61
|
+
};
|
|
62
|
+
}();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
2
|
+
try {
|
|
3
|
+
var info = gen[key](arg);
|
|
4
|
+
var value = info.value;
|
|
5
|
+
} catch (error) {
|
|
6
|
+
reject(error);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (info.done) {
|
|
10
|
+
resolve(value);
|
|
11
|
+
} else {
|
|
12
|
+
Promise.resolve(value).then(_next, _throw);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function _async_to_generator(fn) {
|
|
16
|
+
return function() {
|
|
17
|
+
var self = this, args = arguments;
|
|
18
|
+
return new Promise(function(resolve, reject) {
|
|
19
|
+
var gen = fn.apply(self, args);
|
|
20
|
+
function _next(value) {
|
|
21
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
22
|
+
}
|
|
23
|
+
function _throw(err) {
|
|
24
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
25
|
+
}
|
|
26
|
+
_next(undefined);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
import fs from "fs";
|
|
31
|
+
import path from "path";
|
|
32
|
+
import { findProjectRoot } from "../parser-option/find-project-root.js";
|
|
33
|
+
import { Logger } from "../util/logger.js";
|
|
34
|
+
export const install = function() {
|
|
35
|
+
var _ref = _async_to_generator(function*() {
|
|
36
|
+
const root = yield findProjectRoot();
|
|
37
|
+
const psvDotFolderPath = path.join(root, ".psv");
|
|
38
|
+
if (fs.existsSync(psvDotFolderPath)) {
|
|
39
|
+
fs.rmSync(psvDotFolderPath, {
|
|
40
|
+
force: true,
|
|
41
|
+
recursive: true
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
fs.mkdirSync(psvDotFolderPath);
|
|
45
|
+
fs.copyFileSync(path.join(import.meta.dirname, "../hook-bundle.js"), path.join(psvDotFolderPath, "hook.js"));
|
|
46
|
+
Logger.message("installed .psv");
|
|
47
|
+
});
|
|
48
|
+
return function install() {
|
|
49
|
+
return _ref.apply(this, arguments);
|
|
50
|
+
};
|
|
51
|
+
}();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { checkDeps } from "./checkDeps.js";
|
|
3
|
+
import { install } from "./install.js";
|
|
4
|
+
import packageJSON from "../../package.json" with {
|
|
5
|
+
type: "json"
|
|
6
|
+
};
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program.name("pnpm-single-version").alias("psv").version(packageJSON.version);
|
|
9
|
+
program.command("check", {
|
|
10
|
+
isDefault: true
|
|
11
|
+
}).description("Verify single version dependencies").action(checkDeps);
|
|
12
|
+
program.command("install").description("Install the checker file into .psv").action(install);
|
|
13
|
+
program.parse();
|
package/dist/constant.js
ADDED
package/dist/error.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { PACKAGE_JSON_OPTIONS_KEY } from "./constant.js";
|
|
3
|
+
export class ProjectRootError extends Error {
|
|
4
|
+
constructor(){
|
|
5
|
+
super("Can not find project root");
|
|
6
|
+
this.name = "ProjectRootError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class PackageJsonNotFoundError extends Error {
|
|
10
|
+
constructor(){
|
|
11
|
+
super(`Unable to read package.json of root project `);
|
|
12
|
+
this.name = "PackageJsonNotFoundError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class OptionNotFoundError extends Error {
|
|
16
|
+
constructor(){
|
|
17
|
+
super(`Unable to find option ${chalk.blueBright(`'${PACKAGE_JSON_OPTIONS_KEY}'`)} in package.json of root project `);
|
|
18
|
+
this.name = "OptionsNotFound";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class OptionSyntaxError extends Error {
|
|
22
|
+
constructor(message){
|
|
23
|
+
super(`Invalid option: ${message}`);
|
|
24
|
+
this.name = "OptionSyntaxError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class LockFileError extends Error {
|
|
28
|
+
constructor(){
|
|
29
|
+
super("Unable to read lock file");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class LockfilePackagesMissingError extends Error {
|
|
33
|
+
constructor(){
|
|
34
|
+
super(`'packages' in lockfile is missing`);
|
|
35
|
+
this.name = "LockfilePackagesMissingError";
|
|
36
|
+
}
|
|
37
|
+
}
|