Skip to content

Commit 432496a

Browse files
authored
feat: add module replacements (#37)
* feat: add module replacements * fix: move json imports to copied files * fix: use relative path
1 parent 32b0adb commit 432496a

File tree

13 files changed

+3896
-44
lines changed

13 files changed

+3896
-44
lines changed

action.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ inputs:
4040
description: 'Threshold for warning about packages with multiple versions'
4141
required: false
4242
default: '1'
43+
detect-replacements:
44+
description: 'Detect modules which have community suggested alternatives'
45+
required: false
46+
default: true
4347

4448
runs:
4549
using: node24
46-
main: main.js
50+
main: build/main.js

main.js renamed to build/main.js

Lines changed: 146 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { fileURLToPath } from 'node:url';
2-
import { createRequire as topLevelCreateRequire } from 'node:module';
1+
import { createRequire as topLevelCreateRequire } from 'node:module';
32
import { dirname as topLevelDirname } from 'path';
43
const require = topLevelCreateRequire(import.meta.url);
54
var __create = Object.create;
@@ -19702,7 +19701,7 @@ var require_core = __commonJS({
1970219701
return inputs.map((input) => input.trim());
1970319702
}
1970419703
exports.getMultilineInput = getMultilineInput;
19705-
function getBooleanInput(name, options) {
19704+
function getBooleanInput2(name, options) {
1970619705
const trueValue = ["true", "True", "TRUE"];
1970719706
const falseValue = ["false", "False", "FALSE"];
1970819707
const val = getInput3(name, options);
@@ -19713,7 +19712,7 @@ var require_core = __commonJS({
1971319712
throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}
1971419713
Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
1971519714
}
19716-
exports.getBooleanInput = getBooleanInput;
19715+
exports.getBooleanInput = getBooleanInput2;
1971719716
function setOutput(name, value) {
1971819717
const filePath = process.env["GITHUB_OUTPUT"] || "";
1971919718
if (filePath) {
@@ -24143,6 +24142,17 @@ function getFileFromRef(ref, filePath, cwd2) {
2414324142
return null;
2414424143
}
2414524144
}
24145+
function tryGetJSONFromRef(ref, filePath, cwd2) {
24146+
const content = getFileFromRef(ref, filePath, cwd2);
24147+
if (content) {
24148+
try {
24149+
return JSON.parse(content);
24150+
} catch {
24151+
return null;
24152+
}
24153+
}
24154+
return null;
24155+
}
2414624156
function getBaseRef() {
2414724157
const inputBaseRef = core.getInput("base-ref");
2414824158
if (inputBaseRef) {
@@ -24251,6 +24261,28 @@ async function calculateTotalDependencySizeIncrease(newVersions) {
2425124261
}
2425224262
return { totalSize, packageSizes };
2425324263
}
24264+
var dependencyTypeMap = {
24265+
prod: "dependencies",
24266+
dev: "devDependencies",
24267+
optional: "optionalDependencies",
24268+
peer: "peerDependencies"
24269+
};
24270+
function getDependenciesFromPackageJson(pkg, types) {
24271+
const result = /* @__PURE__ */ new Map();
24272+
for (const type of types) {
24273+
const value = pkg[dependencyTypeMap[type]];
24274+
if (value === void 0) {
24275+
continue;
24276+
}
24277+
for (const [name, version] of Object.entries(value)) {
24278+
if (typeof version !== "string") {
24279+
continue;
24280+
}
24281+
result.set(name, version);
24282+
}
24283+
}
24284+
return result;
24285+
}
2425424286

2425524287
// src/packs.ts
2425624288
var core3 = __toESM(require_core(), 1);
@@ -24374,6 +24406,70 @@ function comparePackSizes(basePacks, sourcePacks, threshold) {
2437424406
};
2437524407
}
2437624408

24409+
// src/replacements.ts
24410+
import nativeManifest from "./native-O77SEK3D.json" with { type: "json" };
24411+
import microUtilsManifest from "./micro-utilities-74AZJTCK.json" with { type: "json" };
24412+
import preferredManifest from "./preferred-UDJHBJAQ.json" with { type: "json" };
24413+
var allReplacements = [
24414+
...nativeManifest.moduleReplacements,
24415+
...microUtilsManifest.moduleReplacements,
24416+
...preferredManifest.moduleReplacements
24417+
];
24418+
function scanForReplacements(messages, baseDependencies, currentDependencies) {
24419+
const replacementMessages = [];
24420+
for (const [name] of currentDependencies) {
24421+
if (!baseDependencies.has(name)) {
24422+
const replacement = allReplacements.find(
24423+
(modReplacement) => modReplacement.moduleName === name
24424+
);
24425+
if (replacement) {
24426+
switch (replacement.type) {
24427+
case "none":
24428+
replacementMessages.push(
24429+
`| ${name} | This package is no longer necessary |`
24430+
);
24431+
break;
24432+
case "native": {
24433+
const mdnUrl = replacement.mdnPath ? `https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/${replacement.mdnPath}` : "";
24434+
const nativeReplacement = mdnUrl ? `[${replacement.replacement}](${mdnUrl})` : replacement.replacement;
24435+
replacementMessages.push(`| ${name} | Use ${nativeReplacement} |`);
24436+
break;
24437+
}
24438+
case "simple":
24439+
replacementMessages.push(
24440+
`| ${name} | ${replacement.replacement} |`
24441+
);
24442+
break;
24443+
case "documented": {
24444+
const docUrl = `https://github.com/es-tooling/module-replacements/blob/main/docs/modules/${replacement.docPath}.md`;
24445+
replacementMessages.push(
24446+
`| ${name} | [See documentation](${docUrl}) |`
24447+
);
24448+
break;
24449+
}
24450+
}
24451+
}
24452+
}
24453+
}
24454+
if (replacementMessages.length > 0) {
24455+
messages.push(
24456+
`## \u26A0\uFE0F Recommended Package Replacements
24457+
24458+
The following new packages or versions have community recommended replacements:
24459+
24460+
| \u{1F4E6} Package | \u{1F4A1} Recommendation |
24461+
| --- | --- |
24462+
${replacementMessages.join("\n")}
24463+
24464+
> [!NOTE]
24465+
> These recommendations have been defined by the [e18e](https://e18e.dev) community.
24466+
> They may not always be a straightforward migration, so please review carefully
24467+
> and use the exclusion feature if you want to ignore any of them in future.
24468+
`
24469+
);
24470+
}
24471+
}
24472+
2437724473
// src/main.ts
2437824474
function formatBytes(bytes) {
2437924475
if (bytes === 0) return "0 B";
@@ -24406,6 +24502,23 @@ async function run() {
2440624502
const lockfilePath = detectLockfile(workspacePath);
2440724503
const token = core4.getInput("github-token", { required: true });
2440824504
const prNumber = parseInt(core4.getInput("pr-number", { required: true }), 10);
24505+
const detectReplacements = core4.getBooleanInput("detect-replacements");
24506+
const dependencyThreshold = parseInt(
24507+
core4.getInput("dependency-threshold") || "10",
24508+
10
24509+
);
24510+
const sizeThreshold = parseInt(
24511+
core4.getInput("size-threshold") || "100000",
24512+
10
24513+
);
24514+
const duplicateThreshold = parseInt(
24515+
core4.getInput("duplicate-threshold") || "1",
24516+
10
24517+
);
24518+
const packSizeThreshold = parseInt(
24519+
core4.getInput("pack-size-threshold") || "50000",
24520+
10
24521+
);
2440924522
if (Number.isNaN(prNumber) || prNumber < 1) {
2441024523
core4.info("No valid pull request number was found. Skipping.");
2441124524
return;
@@ -24435,24 +24548,18 @@ async function run() {
2443524548
core4.info("No package-lock.json found in current ref");
2443624549
return;
2443724550
}
24438-
const currentDeps = parseLockfile(lockfilePath, currentPackageLock);
24439-
const baseDeps = parseLockfile(lockfilePath, basePackageLock);
24440-
const dependencyThreshold = parseInt(
24441-
core4.getInput("dependency-threshold") || "10",
24442-
10
24443-
);
24444-
const sizeThreshold = parseInt(
24445-
core4.getInput("size-threshold") || "100000",
24446-
10
24447-
);
24448-
const duplicateThreshold = parseInt(
24449-
core4.getInput("duplicate-threshold") || "1",
24450-
10
24551+
const basePackageJson = tryGetJSONFromRef(
24552+
baseRef,
24553+
"package.json",
24554+
workspacePath
2445124555
);
24452-
const packSizeThreshold = parseInt(
24453-
core4.getInput("pack-size-threshold") || "50000",
24454-
10
24556+
const currentPackageJson = tryGetJSONFromRef(
24557+
currentRef,
24558+
"package.json",
24559+
workspacePath
2445524560
);
24561+
const currentDeps = parseLockfile(lockfilePath, currentPackageLock);
24562+
const baseDeps = parseLockfile(lockfilePath, basePackageLock);
2445624563
core4.info(`Dependency threshold set to ${dependencyThreshold}`);
2445724564
core4.info(`Size threshold set to ${formatBytes(sizeThreshold)}`);
2445824565
core4.info(`Duplicate threshold set to ${duplicateThreshold}`);
@@ -24619,6 +24726,25 @@ ${packRows}`
2461924726
core4.info(`Failed to compare pack sizes: ${err}`);
2462024727
}
2462124728
}
24729+
if (detectReplacements) {
24730+
if (!basePackageJson || !currentPackageJson) {
24731+
core4.setFailed(
24732+
"detect-replacements requires both base and current package.json to be present"
24733+
);
24734+
return;
24735+
}
24736+
const baseDependencies = getDependenciesFromPackageJson(basePackageJson, [
24737+
"optional",
24738+
"peer",
24739+
"dev",
24740+
"prod"
24741+
]);
24742+
const currentDependencies = getDependenciesFromPackageJson(
24743+
currentPackageJson,
24744+
["optional", "peer", "dev", "prod"]
24745+
);
24746+
scanForReplacements(messages, baseDependencies, currentDependencies);
24747+
}
2462224748
if (messages.length === 0) {
2462324749
core4.info("No dependency warnings found. Skipping comment creation.");
2462424750
return;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
{
2+
"moduleReplacements": [
3+
{
4+
"type": "simple",
5+
"moduleName": "call-bind",
6+
"replacement": "Use Function.call.bind(v)",
7+
"category": "micro-utilities"
8+
},
9+
{
10+
"type": "simple",
11+
"moduleName": "clone-regexp",
12+
"replacement": "Use new RegExp(regexpToCopy)",
13+
"category": "micro-utilities"
14+
},
15+
{
16+
"type": "simple",
17+
"moduleName": "es-get-iterator",
18+
"replacement": "Use v[Symbol.iterator]?.()",
19+
"category": "micro-utilities"
20+
},
21+
{
22+
"type": "simple",
23+
"moduleName": "es-set-tostringtag",
24+
"replacement": "Use Object.defineProperty(target, Symbol.toStringTag, { value, configurable: true })",
25+
"category": "micro-utilities"
26+
},
27+
{
28+
"type": "simple",
29+
"moduleName": "is-array-buffer",
30+
"replacement": "Use v instanceof ArrayBuffer, or if cross-realm, use Object.prototype.toString.call(v) === \"[object ArrayBuffer]\"",
31+
"category": "micro-utilities"
32+
},
33+
{
34+
"type": "simple",
35+
"moduleName": "is-boolean-object",
36+
"replacement": "Use Object.prototype.toString.call(v) === \"[object Boolean]\"",
37+
"category": "micro-utilities"
38+
},
39+
{
40+
"type": "simple",
41+
"moduleName": "is-date-object",
42+
"replacement": "Use v instanceof Date, or if cross-realm, use Object.prototype.toString.call(v) === \"[object Date]\"",
43+
"category": "micro-utilities"
44+
},
45+
{
46+
"type": "simple",
47+
"moduleName": "is-even",
48+
"replacement": "Use (n % 2) === 0",
49+
"category": "micro-utilities"
50+
},
51+
{
52+
"type": "simple",
53+
"moduleName": "is-negative",
54+
"replacement": "Use (n) => n < 0",
55+
"category": "micro-utilities"
56+
},
57+
{
58+
"type": "simple",
59+
"moduleName": "is-negative-zero",
60+
"replacement": "Use Object.is(v, -0)",
61+
"category": "micro-utilities"
62+
},
63+
{
64+
"type": "simple",
65+
"moduleName": "is-npm",
66+
"replacement": "Use process.env.npm_config_user_agent?.startsWith(\"npm\")",
67+
"category": "micro-utilities"
68+
},
69+
{
70+
"type": "simple",
71+
"moduleName": "is-number",
72+
"replacement": "Use typeof v === \"number\" || (typeof v === \"string\" && Number.isFinite(+v))",
73+
"category": "micro-utilities"
74+
},
75+
{
76+
"type": "simple",
77+
"moduleName": "is-number-object",
78+
"replacement": "Use Object.prototype.toString.call(v) === \"[object Number]\"",
79+
"category": "micro-utilities"
80+
},
81+
{
82+
"type": "simple",
83+
"moduleName": "is-odd",
84+
"replacement": "Use (n % 2) === 1",
85+
"category": "micro-utilities"
86+
},
87+
{
88+
"type": "simple",
89+
"moduleName": "is-plain-object",
90+
"replacement": "Use v && typeof v === \"object\" && (Object.getPrototypeOf(v) === null || Object.getPrototypeOf(v) === Object.prototype)",
91+
"category": "micro-utilities"
92+
},
93+
{
94+
"type": "simple",
95+
"moduleName": "is-primitive",
96+
"replacement": "Use v === null || (typeof v !== \"function\" && typeof v !== \"object\")",
97+
"category": "micro-utilities"
98+
},
99+
{
100+
"type": "simple",
101+
"moduleName": "is-regexp",
102+
"replacement": "Use v instanceof RegExp, or if cross-realm, use Object.prototype.toString.call(v) === \"[object RegExp]\"",
103+
"category": "micro-utilities"
104+
},
105+
{
106+
"type": "simple",
107+
"moduleName": "is-string",
108+
"replacement": "Use typeof str === \"string\"",
109+
"category": "micro-utilities"
110+
},
111+
{
112+
"type": "simple",
113+
"moduleName": "is-travis",
114+
"replacement": "Use (\"TRAVIS\" in process.env)",
115+
"category": "micro-utilities"
116+
},
117+
{
118+
"type": "simple",
119+
"moduleName": "is-whitespace",
120+
"replacement": "Use str.trim() === \"\" or /^\\s*$/.test(str)",
121+
"category": "micro-utilities"
122+
},
123+
{
124+
"type": "simple",
125+
"moduleName": "is-windows",
126+
"replacement": "Use process.platform === \"win32\"",
127+
"category": "micro-utilities"
128+
},
129+
{
130+
"type": "simple",
131+
"moduleName": "split-lines",
132+
"replacement": "Use str.split(/\\r?\\n/)",
133+
"category": "micro-utilities"
134+
}
135+
]
136+
}

0 commit comments

Comments
 (0)