Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ inputs:
description: 'Threshold for warning about packages with multiple versions'
required: false
default: '1'
detect-replacements:
description: 'Detect modules which have community suggested alternatives'
required: false
default: true

runs:
using: node24
main: main.js
main: build/main.js
166 changes: 146 additions & 20 deletions main.js → build/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fileURLToPath } from 'node:url';
import { createRequire as topLevelCreateRequire } from 'node:module';
import { createRequire as topLevelCreateRequire } from 'node:module';
import { dirname as topLevelDirname } from 'path';
const require = topLevelCreateRequire(import.meta.url);
var __create = Object.create;
Expand Down Expand Up @@ -19702,7 +19701,7 @@ var require_core = __commonJS({
return inputs.map((input) => input.trim());
}
exports.getMultilineInput = getMultilineInput;
function getBooleanInput(name, options) {
function getBooleanInput2(name, options) {
const trueValue = ["true", "True", "TRUE"];
const falseValue = ["false", "False", "FALSE"];
const val = getInput3(name, options);
Expand All @@ -19713,7 +19712,7 @@ var require_core = __commonJS({
throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}
Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
}
exports.getBooleanInput = getBooleanInput;
exports.getBooleanInput = getBooleanInput2;
function setOutput(name, value) {
const filePath = process.env["GITHUB_OUTPUT"] || "";
if (filePath) {
Expand Down Expand Up @@ -24143,6 +24142,17 @@ function getFileFromRef(ref, filePath, cwd2) {
return null;
}
}
function tryGetJSONFromRef(ref, filePath, cwd2) {
const content = getFileFromRef(ref, filePath, cwd2);
if (content) {
try {
return JSON.parse(content);
} catch {
return null;
}
}
return null;
}
function getBaseRef() {
const inputBaseRef = core.getInput("base-ref");
if (inputBaseRef) {
Expand Down Expand Up @@ -24251,6 +24261,28 @@ async function calculateTotalDependencySizeIncrease(newVersions) {
}
return { totalSize, packageSizes };
}
var dependencyTypeMap = {
prod: "dependencies",
dev: "devDependencies",
optional: "optionalDependencies",
peer: "peerDependencies"
};
function getDependenciesFromPackageJson(pkg, types) {
const result = /* @__PURE__ */ new Map();
for (const type of types) {
const value = pkg[dependencyTypeMap[type]];
if (value === void 0) {
continue;
}
for (const [name, version] of Object.entries(value)) {
if (typeof version !== "string") {
continue;
}
result.set(name, version);
}
}
return result;
}

// src/packs.ts
var core3 = __toESM(require_core(), 1);
Expand Down Expand Up @@ -24374,6 +24406,70 @@ function comparePackSizes(basePacks, sourcePacks, threshold) {
};
}

// src/replacements.ts
import nativeManifest from "./native-O77SEK3D.json" with { type: "json" };
import microUtilsManifest from "./micro-utilities-74AZJTCK.json" with { type: "json" };
import preferredManifest from "./preferred-UDJHBJAQ.json" with { type: "json" };
var allReplacements = [
...nativeManifest.moduleReplacements,
...microUtilsManifest.moduleReplacements,
...preferredManifest.moduleReplacements
];
function scanForReplacements(messages, baseDependencies, currentDependencies) {
const replacementMessages = [];
for (const [name] of currentDependencies) {
if (!baseDependencies.has(name)) {
const replacement = allReplacements.find(
(modReplacement) => modReplacement.moduleName === name
);
if (replacement) {
switch (replacement.type) {
case "none":
replacementMessages.push(
`| ${name} | This package is no longer necessary |`
);
break;
case "native": {
const mdnUrl = replacement.mdnPath ? `https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/${replacement.mdnPath}` : "";
const nativeReplacement = mdnUrl ? `[${replacement.replacement}](${mdnUrl})` : replacement.replacement;
replacementMessages.push(`| ${name} | Use ${nativeReplacement} |`);
break;
}
case "simple":
replacementMessages.push(
`| ${name} | ${replacement.replacement} |`
);
break;
case "documented": {
const docUrl = `https://github.com/es-tooling/module-replacements/blob/main/docs/modules/${replacement.docPath}.md`;
replacementMessages.push(
`| ${name} | [See documentation](${docUrl}) |`
);
break;
}
}
}
}
}
if (replacementMessages.length > 0) {
messages.push(
`## \u26A0\uFE0F Recommended Package Replacements

The following new packages or versions have community recommended replacements:

| \u{1F4E6} Package | \u{1F4A1} Recommendation |
| --- | --- |
${replacementMessages.join("\n")}

> [!NOTE]
> These recommendations have been defined by the [e18e](https://e18e.dev) community.
> They may not always be a straightforward migration, so please review carefully
> and use the exclusion feature if you want to ignore any of them in future.
`
);
}
}

// src/main.ts
function formatBytes(bytes) {
if (bytes === 0) return "0 B";
Expand Down Expand Up @@ -24406,6 +24502,23 @@ async function run() {
const lockfilePath = detectLockfile(workspacePath);
const token = core4.getInput("github-token", { required: true });
const prNumber = parseInt(core4.getInput("pr-number", { required: true }), 10);
const detectReplacements = core4.getBooleanInput("detect-replacements");
const dependencyThreshold = parseInt(
core4.getInput("dependency-threshold") || "10",
10
);
const sizeThreshold = parseInt(
core4.getInput("size-threshold") || "100000",
10
);
const duplicateThreshold = parseInt(
core4.getInput("duplicate-threshold") || "1",
10
);
const packSizeThreshold = parseInt(
core4.getInput("pack-size-threshold") || "50000",
10
);
if (Number.isNaN(prNumber) || prNumber < 1) {
core4.info("No valid pull request number was found. Skipping.");
return;
Expand Down Expand Up @@ -24435,24 +24548,18 @@ async function run() {
core4.info("No package-lock.json found in current ref");
return;
}
const currentDeps = parseLockfile(lockfilePath, currentPackageLock);
const baseDeps = parseLockfile(lockfilePath, basePackageLock);
const dependencyThreshold = parseInt(
core4.getInput("dependency-threshold") || "10",
10
);
const sizeThreshold = parseInt(
core4.getInput("size-threshold") || "100000",
10
);
const duplicateThreshold = parseInt(
core4.getInput("duplicate-threshold") || "1",
10
const basePackageJson = tryGetJSONFromRef(
baseRef,
"package.json",
workspacePath
);
const packSizeThreshold = parseInt(
core4.getInput("pack-size-threshold") || "50000",
10
const currentPackageJson = tryGetJSONFromRef(
currentRef,
"package.json",
workspacePath
);
const currentDeps = parseLockfile(lockfilePath, currentPackageLock);
const baseDeps = parseLockfile(lockfilePath, basePackageLock);
core4.info(`Dependency threshold set to ${dependencyThreshold}`);
core4.info(`Size threshold set to ${formatBytes(sizeThreshold)}`);
core4.info(`Duplicate threshold set to ${duplicateThreshold}`);
Expand Down Expand Up @@ -24619,6 +24726,25 @@ ${packRows}`
core4.info(`Failed to compare pack sizes: ${err}`);
}
}
if (detectReplacements) {
if (!basePackageJson || !currentPackageJson) {
core4.setFailed(
"detect-replacements requires both base and current package.json to be present"
);
return;
}
const baseDependencies = getDependenciesFromPackageJson(basePackageJson, [
"optional",
"peer",
"dev",
"prod"
]);
const currentDependencies = getDependenciesFromPackageJson(
currentPackageJson,
["optional", "peer", "dev", "prod"]
);
scanForReplacements(messages, baseDependencies, currentDependencies);
}
if (messages.length === 0) {
core4.info("No dependency warnings found. Skipping comment creation.");
return;
Expand Down
136 changes: 136 additions & 0 deletions build/micro-utilities-74AZJTCK.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"moduleReplacements": [
{
"type": "simple",
"moduleName": "call-bind",
"replacement": "Use Function.call.bind(v)",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "clone-regexp",
"replacement": "Use new RegExp(regexpToCopy)",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "es-get-iterator",
"replacement": "Use v[Symbol.iterator]?.()",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "es-set-tostringtag",
"replacement": "Use Object.defineProperty(target, Symbol.toStringTag, { value, configurable: true })",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-array-buffer",
"replacement": "Use v instanceof ArrayBuffer, or if cross-realm, use Object.prototype.toString.call(v) === \"[object ArrayBuffer]\"",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-boolean-object",
"replacement": "Use Object.prototype.toString.call(v) === \"[object Boolean]\"",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-date-object",
"replacement": "Use v instanceof Date, or if cross-realm, use Object.prototype.toString.call(v) === \"[object Date]\"",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-even",
"replacement": "Use (n % 2) === 0",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-negative",
"replacement": "Use (n) => n < 0",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-negative-zero",
"replacement": "Use Object.is(v, -0)",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-npm",
"replacement": "Use process.env.npm_config_user_agent?.startsWith(\"npm\")",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-number",
"replacement": "Use typeof v === \"number\" || (typeof v === \"string\" && Number.isFinite(+v))",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-number-object",
"replacement": "Use Object.prototype.toString.call(v) === \"[object Number]\"",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-odd",
"replacement": "Use (n % 2) === 1",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-plain-object",
"replacement": "Use v && typeof v === \"object\" && (Object.getPrototypeOf(v) === null || Object.getPrototypeOf(v) === Object.prototype)",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-primitive",
"replacement": "Use v === null || (typeof v !== \"function\" && typeof v !== \"object\")",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-regexp",
"replacement": "Use v instanceof RegExp, or if cross-realm, use Object.prototype.toString.call(v) === \"[object RegExp]\"",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-string",
"replacement": "Use typeof str === \"string\"",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-travis",
"replacement": "Use (\"TRAVIS\" in process.env)",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-whitespace",
"replacement": "Use str.trim() === \"\" or /^\\s*$/.test(str)",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "is-windows",
"replacement": "Use process.platform === \"win32\"",
"category": "micro-utilities"
},
{
"type": "simple",
"moduleName": "split-lines",
"replacement": "Use str.split(/\\r?\\n/)",
"category": "micro-utilities"
}
]
}
Loading