@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.
Files changed (43) hide show
  1. package/README.md +74 -0
  2. package/dist/checker/error-message.js +47 -0
  3. package/dist/checker/index.js +33 -0
  4. package/dist/command/checkDeps.js +62 -0
  5. package/dist/command/index.js +2 -0
  6. package/dist/command/install.js +51 -0
  7. package/dist/command/pnpmfile/.pnpmfile.cjs +7 -0
  8. package/dist/command/pnpmfile/hook.js +2 -0
  9. package/dist/command/setup.js +13 -0
  10. package/dist/constant.js +2 -0
  11. package/dist/error.js +37 -0
  12. package/dist/hook-bundle.js +29 -0
  13. package/dist/hook.js +55 -0
  14. package/dist/index.js +2 -0
  15. package/dist/parser-option/find-project-root.js +54 -0
  16. package/dist/parser-option/parse-option.js +62 -0
  17. package/dist/type.js +1 -0
  18. package/dist/util/array-value-map-Helper.js +18 -0
  19. package/dist/util/glob-util.js +16 -0
  20. package/dist/util/logger.js +19 -0
  21. package/dist/util/pipe.js +3 -0
  22. package/dist/util/pnpm-logger.js +15 -0
  23. package/package.json +47 -0
  24. package/src/checker/error-message.ts +58 -0
  25. package/src/checker/index.ts +46 -0
  26. package/src/command/checkDeps.ts +27 -0
  27. package/src/command/index.ts +2 -0
  28. package/src/command/install.ts +17 -0
  29. package/src/command/pnpmfile/.pnpmfile.cjs +7 -0
  30. package/src/command/pnpmfile/hook.ts +3 -0
  31. package/src/command/setup.ts +13 -0
  32. package/src/constant.ts +2 -0
  33. package/src/error.ts +43 -0
  34. package/src/hook.ts +23 -0
  35. package/src/index.ts +3 -0
  36. package/src/parser-option/find-project-root.ts +22 -0
  37. package/src/parser-option/parse-option.ts +38 -0
  38. package/src/type.ts +17 -0
  39. package/src/util/array-value-map-Helper.ts +19 -0
  40. package/src/util/glob-util.ts +25 -0
  41. package/src/util/logger.ts +24 -0
  42. package/src/util/pipe.ts +43 -0
  43. 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,2 @@
1
+ #!/usr/bin/env node
2
+ import "./setup.js";
@@ -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,7 @@
1
+ const hook = require('./.psv/hook')
2
+
3
+ module.exports = {
4
+ hooks: {
5
+ afterAllResolved: hook
6
+ }
7
+ }
@@ -0,0 +1,2 @@
1
+ import { hook } from "../../hook.js";
2
+ export default hook;
@@ -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();
@@ -0,0 +1,2 @@
1
+ export const PACKAGE_JSON_OPTIONS_KEY = 'pnpmSingleVersion';
2
+ export const APP_NAME = 'Single Version Checker';
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
+ }