@orangemug/oops 0.1.1

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 ADDED
@@ -0,0 +1,26 @@
1
+ # `@orangemug/oops`
2
+ Have I got a compromised pacakge in my npm/pnpm/yarn cache?
3
+
4
+ ## Usage
5
+ Find out with `oops`
6
+
7
+ ```bash
8
+ npx @orangemug/oops --help
9
+ # ./oops <dangerous_package_versions>
10
+ #
11
+ # Example: npx @orangemug/oops @ctrl/tinycolor:4.1.1 @ctrl/tinycolor:4.1.2
12
+ ```
13
+
14
+ You can attach this command to a bug tracker ticket somewhere in your company/organisation
15
+
16
+ > Check for compromised packages with
17
+ >
18
+ > ```bash
19
+ > npx @orangemug/oops \
20
+ > @ctrl/tinycolor:4.1.1 \
21
+ > @ctrl/tinycolor:4.1.2
22
+ > ```
23
+
24
+
25
+ ## Licence
26
+ MIT
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import chalk from 'chalk';
3
+ import { exec as exec$1 } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import semver from 'semver';
6
+ import minimist from 'minimist';
7
+
8
+ const exec = promisify(exec$1);
9
+ async function pnpmDoesPackageExist(pkgName) {
10
+ var _a;
11
+ const result = await exec(`pnpm cache view ${pkgName}`);
12
+ const obj = JSON.parse(result.stdout);
13
+ for (const [_registry, data] of Object.entries(obj)) {
14
+ return (_a = data.cachedVersions) !== null && _a !== void 0 ? _a : [];
15
+ }
16
+ return [];
17
+ }
18
+ async function npmDoesPackageExist(pkgName) {
19
+ const result = await exec(`npm cache ls ${pkgName}`);
20
+ const out = [];
21
+ for (const line of result.stdout.split("\n")) {
22
+ const matches = line.match(/\/(.*)-(.*)\.tgz$/);
23
+ if (matches) {
24
+ out.push(matches[2]);
25
+ }
26
+ }
27
+ return out;
28
+ }
29
+ async function yarnDoesPackageExist(pkgName) {
30
+ const result = await exec(`yarn cache list --pattern ${pkgName}`);
31
+ const items = result.stdout.split("\n").map(line => line.split(/\s+/));
32
+ const out = [];
33
+ for (const item of items) {
34
+ if (item[0] === pkgName) {
35
+ out.push(item[1]);
36
+ }
37
+ }
38
+ return out;
39
+ }
40
+ async function doesPackageExistInCache(pkgName, version) {
41
+ if (!semver.valid(version)) {
42
+ throw new Error(`Invalid version range "${version}"`);
43
+ }
44
+ const effectedVersions = {};
45
+ for (const [managerName, pkgVersions] of [
46
+ ["npm", await npmDoesPackageExist(pkgName)],
47
+ ["pnpm", await pnpmDoesPackageExist(pkgName)],
48
+ ["yarn", await yarnDoesPackageExist(pkgName)],
49
+ ]) {
50
+ const sorted = pkgVersions.sort((v1, v2) => {
51
+ if (semver.lt(v1, v2)) {
52
+ return -1;
53
+ }
54
+ else if (semver.gt(v1, v2)) {
55
+ return 1;
56
+ }
57
+ return 0;
58
+ });
59
+ const filteredVersions = sorted.filter(pkgVersion => {
60
+ return semver.satisfies(pkgVersion, version);
61
+ });
62
+ effectedVersions[managerName] = filteredVersions;
63
+ }
64
+ return effectedVersions;
65
+ }
66
+
67
+ async function run(packages) {
68
+ let hasErrors = false;
69
+ for (const pkg of packages) {
70
+ const [pkgName, version] = pkg.split(":");
71
+ if (pkgName && version) {
72
+ const output = await doesPackageExistInCache(pkgName, version);
73
+ console.log(`${chalk.magenta(pkgName)}:${version}`);
74
+ for (const [manager, versions] of Object.entries(output)) {
75
+ if (versions.length > 0) {
76
+ hasErrors = true;
77
+ console.log(` - ${manager}`);
78
+ for (const version of versions) {
79
+ console.log(` * ${chalk.red(version)}`);
80
+ }
81
+ }
82
+ else {
83
+ console.log(` - ${manager}: none`);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ process.exit(hasErrors ? 1 : 0);
89
+ }
90
+ const HELP_TEXT = `
91
+ ./oops <dangerous_package_versions>
92
+
93
+ Example: ./oops @ctrl/tinycolor:4.1.1 @ctrl/tinycolor:4.1.2
94
+ `.trim();
95
+ const argv = minimist(process.argv.slice(2));
96
+ if (argv._.length < 1 && argv.help || argv.h) {
97
+ console.log(HELP_TEXT);
98
+ process.exit(0);
99
+ }
100
+ await run(argv._);
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ var child_process = require('child_process');
4
+ var util = require('util');
5
+ var semver = require('semver');
6
+
7
+ const exec = util.promisify(child_process.exec);
8
+ async function pnpmDoesPackageExist(pkgName) {
9
+ var _a;
10
+ const result = await exec(`pnpm cache view ${pkgName}`);
11
+ const obj = JSON.parse(result.stdout);
12
+ for (const [_registry, data] of Object.entries(obj)) {
13
+ return (_a = data.cachedVersions) !== null && _a !== void 0 ? _a : [];
14
+ }
15
+ return [];
16
+ }
17
+ async function npmDoesPackageExist(pkgName) {
18
+ const result = await exec(`npm cache ls ${pkgName}`);
19
+ const out = [];
20
+ for (const line of result.stdout.split("\n")) {
21
+ const matches = line.match(/\/(.*)-(.*)\.tgz$/);
22
+ if (matches) {
23
+ out.push(matches[2]);
24
+ }
25
+ }
26
+ return out;
27
+ }
28
+ async function yarnDoesPackageExist(pkgName) {
29
+ const result = await exec(`yarn cache list --pattern ${pkgName}`);
30
+ const items = result.stdout.split("\n").map(line => line.split(/\s+/));
31
+ const out = [];
32
+ for (const item of items) {
33
+ if (item[0] === pkgName) {
34
+ out.push(item[1]);
35
+ }
36
+ }
37
+ return out;
38
+ }
39
+ async function doesPackageExistInCache(pkgName, version) {
40
+ if (!semver.valid(version)) {
41
+ throw new Error(`Invalid version range "${version}"`);
42
+ }
43
+ const effectedVersions = {};
44
+ for (const [managerName, pkgVersions] of [
45
+ ["npm", await npmDoesPackageExist(pkgName)],
46
+ ["pnpm", await pnpmDoesPackageExist(pkgName)],
47
+ ["yarn", await yarnDoesPackageExist(pkgName)],
48
+ ]) {
49
+ const sorted = pkgVersions.sort((v1, v2) => {
50
+ if (semver.lt(v1, v2)) {
51
+ return -1;
52
+ }
53
+ else if (semver.gt(v1, v2)) {
54
+ return 1;
55
+ }
56
+ return 0;
57
+ });
58
+ const filteredVersions = sorted.filter(pkgVersion => {
59
+ return semver.satisfies(pkgVersion, version);
60
+ });
61
+ effectedVersions[managerName] = filteredVersions;
62
+ }
63
+ return effectedVersions;
64
+ }
65
+
66
+ exports.doesPackageExistInCache = doesPackageExistInCache;
@@ -0,0 +1,3 @@
1
+ declare function doesPackageExistInCache(pkgName: string, version: string): Promise<Record<string, string[]>>;
2
+
3
+ export { doesPackageExistInCache };
@@ -0,0 +1,64 @@
1
+ import { exec as exec$1 } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import semver from 'semver';
4
+
5
+ const exec = promisify(exec$1);
6
+ async function pnpmDoesPackageExist(pkgName) {
7
+ var _a;
8
+ const result = await exec(`pnpm cache view ${pkgName}`);
9
+ const obj = JSON.parse(result.stdout);
10
+ for (const [_registry, data] of Object.entries(obj)) {
11
+ return (_a = data.cachedVersions) !== null && _a !== void 0 ? _a : [];
12
+ }
13
+ return [];
14
+ }
15
+ async function npmDoesPackageExist(pkgName) {
16
+ const result = await exec(`npm cache ls ${pkgName}`);
17
+ const out = [];
18
+ for (const line of result.stdout.split("\n")) {
19
+ const matches = line.match(/\/(.*)-(.*)\.tgz$/);
20
+ if (matches) {
21
+ out.push(matches[2]);
22
+ }
23
+ }
24
+ return out;
25
+ }
26
+ async function yarnDoesPackageExist(pkgName) {
27
+ const result = await exec(`yarn cache list --pattern ${pkgName}`);
28
+ const items = result.stdout.split("\n").map(line => line.split(/\s+/));
29
+ const out = [];
30
+ for (const item of items) {
31
+ if (item[0] === pkgName) {
32
+ out.push(item[1]);
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+ async function doesPackageExistInCache(pkgName, version) {
38
+ if (!semver.valid(version)) {
39
+ throw new Error(`Invalid version range "${version}"`);
40
+ }
41
+ const effectedVersions = {};
42
+ for (const [managerName, pkgVersions] of [
43
+ ["npm", await npmDoesPackageExist(pkgName)],
44
+ ["pnpm", await pnpmDoesPackageExist(pkgName)],
45
+ ["yarn", await yarnDoesPackageExist(pkgName)],
46
+ ]) {
47
+ const sorted = pkgVersions.sort((v1, v2) => {
48
+ if (semver.lt(v1, v2)) {
49
+ return -1;
50
+ }
51
+ else if (semver.gt(v1, v2)) {
52
+ return 1;
53
+ }
54
+ return 0;
55
+ });
56
+ const filteredVersions = sorted.filter(pkgVersion => {
57
+ return semver.satisfies(pkgVersion, version);
58
+ });
59
+ effectedVersions[managerName] = filteredVersions;
60
+ }
61
+ return effectedVersions;
62
+ }
63
+
64
+ export { doesPackageExistInCache };
@@ -0,0 +1,3 @@
1
+ declare function doesPackageExistInCache(pkgName: string, version: string): Promise<Record<string, string[]>>;
2
+
3
+ export { doesPackageExistInCache };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@orangemug/oops",
3
+ "description": "Have I got a compromised pacakge in my cache?",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/orangemug/oops"
7
+ },
8
+ "type": "module",
9
+ "scripts": {
10
+ "lint": "npx prettier . --check",
11
+ "test": "vitest run --coverage",
12
+ "test:ci": "vitest run --coverage --reporter=junit --outputFile=test-report.junit.xml",
13
+ "lint:format": "npx prettier . --write",
14
+ "build": "rollup -c rollup.config.ts && chmod +x dist/npm/bin/index.js"
15
+ },
16
+ "bin": "./dist/npm/bin/index.ts",
17
+ "exports": {
18
+ ".": {
19
+ "import": {
20
+ "default": "./dist/npm/es/index.js",
21
+ "types": "./dist/npm/es/types.d.ts"
22
+ },
23
+ "require": {
24
+ "default": "./dist/npm/cjs/index.js",
25
+ "types": "./dist/npm/cjs/types.d.ts"
26
+ }
27
+ }
28
+ },
29
+ "files": [
30
+ "./dist/npm",
31
+ "./package.json",
32
+ "./README.md"
33
+ ],
34
+ "devDependencies": {
35
+ "@rollup/plugin-typescript": "^12.3.0",
36
+ "@tsconfig/node22": "^22.0.5",
37
+ "@types/minimist": "^1.2.5",
38
+ "@types/node": "^25.3.5",
39
+ "@types/semver": "^7.7.1",
40
+ "@types/yargs": "^17.0.35",
41
+ "@vitest/coverage-v8": "^4.1.2",
42
+ "prettier": "^3.8.1",
43
+ "rollup-plugin-dts": "^6.4.1",
44
+ "rollup-plugin-typescript-paths": "^1.5.0",
45
+ "tslib": "^2.8.1",
46
+ "tsx": "^4.21.0",
47
+ "vitest": "^4.0.18"
48
+ },
49
+ "dependencies": {
50
+ "chalk": "^5.6.2",
51
+ "minimist": "^1.2.8",
52
+ "semver": "^7.7.4"
53
+ },
54
+ "version": "0.1.1"
55
+ }