diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75c332705..9f8b8e4a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -146,13 +146,17 @@ Releases are done with GitHub actions: ## Upgrading Dependencies -Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo. +WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules. ```sh -pnpm dlx taze -r +pnpm tsx scripts/upgrade-deps.ts ``` -Configuration is in [`taze.config.ts`](./taze.config.ts). +To see all the options, run: + +```sh +pnpm tsx scripts/upgrade-deps.ts --help +``` ## Install Unreleased Versions diff --git a/package.json b/package.json index 804f90882..9b61b5341 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@commitlint/config-conventional": "^19.8.1", "@commitlint/types": "^19.8.1", "@types/fs-extra": "^11.0.4", + "@types/semver": "^7.7.1", "@vitest/coverage-v8": "^3.2.4", "changelogen": "^0.6.2", "consola": "^3.4.2", @@ -33,6 +34,7 @@ "markdownlint-cli": "^0.45.0", "nano-spawn": "^1.0.2", "prettier": "^3.6.2", + "semver": "^7.7.2", "simple-git-hooks": "^2.13.1", "tsx": "4.19.4", "typedoc": "^0.25.4", diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 113d39097..3b4018dbf 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -51,7 +51,7 @@ "wxt": ">=0.20.0" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "@types/ua-parser-js": "^0.7.39", "publint": "^0.3.12", "typescript": "^5.9.2", diff --git a/packages/auto-icons/package.json b/packages/auto-icons/package.json index ae5741dfd..a503a7990 100644 --- a/packages/auto-icons/package.json +++ b/packages/auto-icons/package.json @@ -46,11 +46,11 @@ }, "dependencies": { "defu": "^6.1.4", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "sharp": "^0.34.1" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "oxlint": "^1.14.0", "publint": "^0.3.12", "typescript": "^5.9.2", diff --git a/packages/browser/package.json b/packages/browser/package.json index 62c2b9c8f..7c71672ca 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -24,7 +24,7 @@ ], "devDependencies": { "@types/chrome": "0.1.4", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "nano-spawn": "^1.0.2", "tsx": "4.19.4", "typescript": "^5.9.2", diff --git a/packages/browser/templates/package.json b/packages/browser/templates/package.json index b4e2bebda..baab99b6e 100644 --- a/packages/browser/templates/package.json +++ b/packages/browser/templates/package.json @@ -24,7 +24,7 @@ ], "devDependencies": { "@types/chrome": "{{chromeTypesVersion}}", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "nano-spawn": "^1.0.2", "tsx": "4.19.4", "typescript": "^5.9.2", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 296e8342e..c674b54d7 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -40,7 +40,7 @@ } }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "@types/node": "^20.17.6", "oxlint": "^1.14.0", "publint": "^0.3.12", diff --git a/packages/module-react/package.json b/packages/module-react/package.json index 8f0f1bb39..fe5ebfbd1 100644 --- a/packages/module-react/package.json +++ b/packages/module-react/package.json @@ -51,7 +51,7 @@ "@vitejs/plugin-react": "^4.4.1" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.3", "publint": "^0.3.12", diff --git a/packages/module-solid/package.json b/packages/module-solid/package.json index 48b1aac2e..512289461 100644 --- a/packages/module-solid/package.json +++ b/packages/module-solid/package.json @@ -51,7 +51,7 @@ "vite-plugin-solid": "^2.11.6" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "publint": "^0.3.12", "solid-js": "^1.9.6", "typescript": "^5.9.2", diff --git a/packages/module-svelte/package.json b/packages/module-svelte/package.json index 76f4bfa80..099ce1e4f 100644 --- a/packages/module-svelte/package.json +++ b/packages/module-svelte/package.json @@ -50,7 +50,7 @@ "@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "publint": "^0.3.12", "typescript": "^5.9.2", "unbuild": "^3.5.0", diff --git a/packages/module-vue/package.json b/packages/module-vue/package.json index 3d44a324e..5030da3ff 100644 --- a/packages/module-vue/package.json +++ b/packages/module-vue/package.json @@ -49,7 +49,7 @@ "@vitejs/plugin-vue": "^5.2.3 || ^6.0.0" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "publint": "^0.3.12", "typescript": "^5.9.2", "unbuild": "^3.5.0", diff --git a/packages/runner/package.json b/packages/runner/package.json index 5c5387294..49570dd4b 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -29,7 +29,7 @@ }, "dependencies": {}, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "oxlint": "^1.14.0", "publint": "^0.3.12", "tsx": "4.19.4", diff --git a/packages/storage/package.json b/packages/storage/package.json index e2a575c19..16d74c9a0 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -35,7 +35,7 @@ "dequal": "^2.0.3" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "@webext-core/fake-browser": "^1.3.2", "oxlint": "^1.14.0", "publint": "^0.3.12", diff --git a/packages/unocss/package.json b/packages/unocss/package.json index 9d8ad4496..f6a08b579 100644 --- a/packages/unocss/package.json +++ b/packages/unocss/package.json @@ -40,7 +40,7 @@ "wxt": ">=0.19.0" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "oxlint": "^1.14.0", "publint": "^0.3.12", "typescript": "^5.9.2", diff --git a/packages/webextension-polyfill/package.json b/packages/webextension-polyfill/package.json index ab0c827c7..77b3bada9 100644 --- a/packages/webextension-polyfill/package.json +++ b/packages/webextension-polyfill/package.json @@ -45,7 +45,7 @@ "wxt": ">=0.20.0" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "@types/webextension-polyfill": "^0.12.3", "publint": "^0.3.12", "typescript": "^5.9.2", diff --git a/packages/wxt/package.json b/packages/wxt/package.json index 5fdad05c9..d3aa12f61 100644 --- a/packages/wxt/package.json +++ b/packages/wxt/package.json @@ -35,7 +35,7 @@ "esbuild": "^0.25.0", "fast-glob": "^3.3.3", "filesize": "^10.1.6", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "get-port-please": "^3.1.2", "giget": "^1.2.3 || ^2.0.0", "hookable": "^5.5.3", @@ -63,7 +63,7 @@ "web-ext-run": "^0.2.3" }, "devDependencies": { - "@aklinker1/check": "2.0.0", + "@aklinker1/check": "^2.1.0", "@faker-js/faker": "^10.0.0", "@types/fs-extra": "^11.0.4", "@types/lodash.merge": "^4.6.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3dc949a7..3f83bf4cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ importers: '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 + '@types/semver': + specifier: ^7.7.1 + version: 7.7.1 '@vitest/coverage-v8': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.0(@types/debug@4.1.12)(@types/node@20.17.30)(happy-dom@18.0.1)(jiti@2.5.1)(sass@1.89.1)(tsx@4.19.4)(yaml@2.7.0)) @@ -61,6 +64,9 @@ importers: prettier: specifier: ^3.6.2 version: 3.6.2 + semver: + specifier: ^7.7.2 + version: 7.7.2 simple-git-hooks: specifier: ^2.13.1 version: 2.13.1 @@ -111,8 +117,8 @@ importers: version: 1.0.40 devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 '@types/ua-parser-js': specifier: ^0.7.39 version: 0.7.39 @@ -135,15 +141,15 @@ importers: specifier: ^6.1.4 version: 6.1.4 fs-extra: - specifier: ^11.3.0 - version: 11.3.0 + specifier: ^11.3.1 + version: 11.3.1 sharp: specifier: ^0.34.1 version: 0.34.1 devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 oxlint: specifier: ^1.14.0 version: 1.14.0 @@ -173,8 +179,8 @@ importers: specifier: 0.1.4 version: 0.1.4 fs-extra: - specifier: ^11.3.0 - version: 11.3.0 + specifier: ^11.3.1 + version: 11.3.1 nano-spawn: specifier: ^1.0.2 version: 1.0.2 @@ -204,8 +210,8 @@ importers: version: 3.3.3 devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 '@types/node': specifier: ^20.17.6 version: 20.17.30 @@ -238,8 +244,8 @@ importers: version: 4.5.0(vite@6.3.5(@types/node@20.17.30)(jiti@2.5.1)(sass@1.89.1)(tsx@4.19.4)(yaml@2.7.0)) devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 '@types/react': specifier: ^19.1.2 version: 19.1.6 @@ -272,8 +278,8 @@ importers: version: 2.11.6(solid-js@1.9.7)(vite@6.3.5(@types/node@20.17.30)(jiti@2.5.1)(sass@1.89.1)(tsx@4.19.4)(yaml@2.7.0)) devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 publint: specifier: ^0.3.12 version: 0.3.12 @@ -300,8 +306,8 @@ importers: version: 5.1.6 devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 publint: specifier: ^0.3.12 version: 0.3.12 @@ -322,8 +328,8 @@ importers: version: 5.2.3(vite@6.3.5(@types/node@20.17.30)(jiti@2.5.1)(sass@1.89.1)(tsx@4.19.4)(yaml@2.7.0))(vue@3.5.21(typescript@5.9.2)) devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 publint: specifier: ^0.3.12 version: 0.3.12 @@ -340,8 +346,8 @@ importers: packages/runner: devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 oxlint: specifier: ^1.14.0 version: 1.14.0 @@ -374,8 +380,8 @@ importers: version: 2.0.3 devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 '@webext-core/fake-browser': specifier: ^1.3.2 version: 1.3.2 @@ -405,8 +411,8 @@ importers: version: 3.3.3 devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 oxlint: specifier: ^1.14.0 version: 1.14.0 @@ -429,8 +435,8 @@ importers: packages/webextension-polyfill: devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 '@types/webextension-polyfill': specifier: ^0.12.3 version: 0.12.3 @@ -510,8 +516,8 @@ importers: specifier: ^10.1.6 version: 10.1.6 fs-extra: - specifier: ^11.3.0 - version: 11.3.0 + specifier: ^11.3.1 + version: 11.3.1 get-port-please: specifier: ^3.1.2 version: 3.1.2 @@ -589,8 +595,8 @@ importers: version: 0.2.3 devDependencies: '@aklinker1/check': - specifier: 2.0.0 - version: 2.0.0 + specifier: ^2.1.0 + version: 2.1.0 '@faker-js/faker': specifier: ^10.0.0 version: 10.0.0 @@ -689,10 +695,6 @@ packages: resolution: {integrity: sha512-SL8NBNwOiTiCKjQnpOxTA4WfoeiTNhiVy1J6Ztk28xTbZQ7IL0zHbKODnEJAjuy8bfnxlChQT1ZMzjG7kBU4aw==} hasBin: true - '@aklinker1/check@2.0.0': - resolution: {integrity: sha512-itoIc3WlLCtoVEwTIi/f6E4UKXJQJhYyMzbBqbQ73uvIsChgXKfmZHjaUwz4PyBtmkvlO4yhkh1JSQUzy2x5jA==} - hasBin: true - '@aklinker1/check@2.1.0': resolution: {integrity: sha512-x+0vxb0vHV+6ZskcLYobI6FCAFG1jwVGgKuH9pTfHLW3vlzbGSGUKcxbCuMI3Q51WZS8oE4UfB+4v5M2+0uz1g==} hasBin: true @@ -2073,6 +2075,9 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + '@types/ua-parser-js@0.7.39': resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} @@ -3106,10 +3111,6 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs-extra@11.3.0: - resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} - engines: {node: '>=14.14'} - fs-extra@11.3.1: resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} engines: {node: '>=14.14'} @@ -4523,11 +4524,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -5317,12 +5313,6 @@ snapshots: proper-lockfile: 4.1.2 yaml: 2.7.0 - '@aklinker1/check@2.0.0': - dependencies: - '@antfu/utils': 0.7.10 - ci-info: 4.2.0 - citty: 0.1.6 - '@aklinker1/check@2.1.0': dependencies: '@antfu/utils': 0.7.10 @@ -6535,6 +6525,8 @@ snapshots: '@types/resolve@1.20.2': {} + '@types/semver@7.7.1': {} + '@types/ua-parser-js@0.7.39': {} '@types/unist@2.0.11': {} @@ -7767,12 +7759,6 @@ snapshots: fraction.js@4.3.7: {} - fs-extra@11.3.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 - fs-extra@11.3.1: dependencies: graceful-fs: 4.2.11 @@ -8274,7 +8260,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 make-error@1.3.6: {} @@ -8570,7 +8556,7 @@ snapshots: pkg-types: 1.3.1 postcss: 8.5.3 postcss-nested: 7.0.2(postcss@8.5.3) - semver: 7.7.1 + semver: 7.7.2 tinyglobby: 0.2.12 optionalDependencies: sass: 1.89.1 @@ -8631,7 +8617,7 @@ snapshots: dependencies: growly: 1.3.0 is-wsl: 2.2.0 - semver: 7.7.1 + semver: 7.7.2 shellwords: 0.1.1 uuid: 8.3.2 which: 2.0.2 @@ -8762,7 +8748,7 @@ snapshots: ky: 1.8.1 registry-auth-token: 5.0.2 registry-url: 6.0.1 - semver: 7.7.1 + semver: 7.7.2 package-manager-detector@0.2.11: dependencies: @@ -9311,8 +9297,6 @@ snapshots: semver@6.3.1: {} - semver@7.7.1: {} - semver@7.7.2: {} seroval-plugins@1.3.2(seroval@1.3.2): @@ -9332,7 +9316,7 @@ snapshots: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.7.1 + semver: 7.7.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.1 '@img/sharp-darwin-x64': 0.34.1 @@ -9823,7 +9807,7 @@ snapshots: is-npm: 6.0.0 latest-version: 9.0.0 pupa: 3.1.0 - semver: 7.7.1 + semver: 7.7.2 xdg-basedir: 5.1.0 util-deprecate@1.0.2: {} diff --git a/scripts/upgrade-deps.ts b/scripts/upgrade-deps.ts new file mode 100644 index 000000000..2d1cdae9c --- /dev/null +++ b/scripts/upgrade-deps.ts @@ -0,0 +1,360 @@ +import glob from 'fast-glob'; +import fs from 'fs-extra'; +import * as semver from 'semver'; +import { dirname } from 'node:path'; +import consola from 'consola'; +import spawn from 'nano-spawn'; + +const HELP_MESSAGE = ` +Upgrades dependencies throughout WXT using custom rules. + +Usage: + pnpm tsx scripts/upgrade-deps.ts [options] + +Options: + --write, -w Write changes to package.json files + --major, -m Allow major version upgrades + --dev-only, -d Only check/upgrade dev dependencies + --prod-only, -p Only check/upgrade production dependencies + --help, -h Display this help message +`.slice(1, -1); + +const IGNORED_PACKAGES = [ + // Very touchy, don't change: + 'typedoc', + 'typedoc-plugin-markdown', + 'typedoc-vitepress-theme', + // Manually manage version so a single version is used: + 'esbuild', + // Maintained manually to match min-node version + '@types/node', + // License changed in newer versions + 'ua-parser-js', +]; + +await main(); + +async function main(): Promise { + const args = process.argv.slice(2); + const isHelp = ['-h', '--help'].some((arg) => args.includes(arg)); + const isWrite = ['-w', '--write'].some((arg) => args.includes(arg)); + const isDevOnly = ['-d', '--dev-only'].some((arg) => args.includes(arg)); + const isProdOnly = ['-p', '--prod-only'].some((arg) => args.includes(arg)); + + if (isHelp) { + console.log(HELP_MESSAGE); + process.exit(0); + } + + const { packageJsonFiles, dependencyVersionsMap } = + await getPackageJsonDependencies(isDevOnly, isProdOnly); + const dependencyVersionMap = validateNoMultipleVersions( + dependencyVersionsMap, + ); + + consola.start( + `Fetching ${Object.keys(dependencyVersionsMap).length} dependencies...`, + ); + const dependencies = await fetchAllPackageInfos(dependencyVersionMap); + consola.success('Done!'); + + const isMajor = ['-m', '--major'].some((arg) => args.includes(arg)); + const upgrades = await detectUpgrades(dependencies, isMajor); + + if (upgrades.length === 0) { + console.log(); + consola.info("No upgrades found, you're up to date!"); + console.log(); + process.exit(0); + } + + printUpgrades(upgrades); + + if (!isWrite) { + consola.info('Run with `-w` to write changes to package.json files'); + console.log(); + process.exit(0); + } + + consola.start('Writing new versions to package.json files...'); + await writeUpgrades(packageJsonFiles, upgrades); + consola.success('Done!'); + console.log(); + consola.info('Run `pnpm i` to install new dependencies'); + console.log(); + process.exit(0); +} + +type DependencyVersionsMap = Record>; +type PackageJsonData = { + content: any; + path: string; + folder: string; +}; + +async function getPackageJsonDependencies( + isDevOnly: boolean, + isProdOnly: boolean, +): Promise<{ + packageJsonFiles: string[]; + dependencyVersionsMap: DependencyVersionsMap; +}> { + const packageJsonFiles = await glob( + ['package.json', '*/*/package.json', '!**/node_modules'], + { onlyFiles: true }, + ); + const packageJsons: PackageJsonData[] = await Promise.all( + packageJsonFiles.map(async (path) => ({ + content: await fs.readJson(path), + path, + folder: dirname(path), + })), + ); + + const dependencyVersionsMap = packageJsons.reduce( + (map, { content }) => { + const addToMap = ([name, version]: [string, any]) => { + if ( + name === 'wxt' || + version.startsWith('workspace:') || + IGNORED_PACKAGES.includes(name) + ) + return; + + map[name] ||= new Set(); + map[name].add(version as string); + }; + if (!isDevOnly) + Object.entries(content.dependencies || {}).forEach(addToMap); + if (!isProdOnly) + Object.entries(content.devDependencies || {}).forEach(addToMap); + return map; + }, + {}, + ); + + return { + packageJsonFiles, + dependencyVersionsMap, + }; +} + +type DependencyVersionMap = Record; + +function validateNoMultipleVersions( + dependencyVersionsMap: DependencyVersionsMap, +): DependencyVersionMap { + const depsWithMultipleVersions = Object.entries(dependencyVersionsMap).filter( + ([_, versions]) => versions.size > 1, + ); + if (depsWithMultipleVersions.length === 0) { + return Object.fromEntries( + Object.entries(dependencyVersionsMap).map(([name, versions]) => [ + name, + [...versions][0], + ]), + ); + } + + const maxWidth = Math.max( + ...depsWithMultipleVersions.map(([name]) => name.length), + ); + console.log(maxWidth); + const addCyan = (text: string) => `\x1b[36m${text}\x1b[0m`; + + consola.info('Found multiple versions of:'); + for (const [name, versions] of depsWithMultipleVersions) { + console.log( + ` \x1b[35m${name.padEnd(maxWidth)}\x1b[0m ${Array.from(versions) + .map(addCyan) + .join('\t')}`, + ); + } + consola.error(`${depsWithMultipleVersions.length} problem(s) found`); + process.exit(1); +} + +async function fetchPackageInfo(name: string): Promise { + // Use PNPM instead of API in case dependencies don't come from NPM + const res = await spawn('pnpm', ['view', name, '--json']); + return JSON.parse(res.output); +} + +type PackageInfo = { + name: string; + 'dist-tags': { + latest: string; + [tag: string]: string; + }; + versions: string[]; + time: { + created: string; + modified: string; + [version: string]: string; + }; +}; + +type DependencyInfo = { + name: string; + currentVersionRange: string; + info: PackageInfo; +}; + +async function fetchAllPackageInfos( + deps: DependencyVersionMap, +): Promise { + const infos = await Promise.all( + Object.entries(deps).map(async ([name, currentVersionRange]) => ({ + name, + currentVersionRange, + info: await fetchPackageInfo(name), + })), + ); + return infos.toSorted((a, b) => a.name.localeCompare(b.name)); +} + +type UpgradeDetails = { + name: string; + rangePrefix: string; + currentVersion: string; + currentVersionReleasedAt: Date; + currentRange: string; + upgradeToVersion: string; + upgradeToVersionReleasedAt: Date; + upgradeToRange: string; + diff: semver.ReleaseType | null; + latestVersion: string; + latestVersionReleasedAt: Date; +}; + +async function detectUpgrades( + deps: DependencyInfo[], + isMajor: boolean, +): Promise { + const results: UpgradeDetails[] = []; + + for (const dep of deps) { + const currentRange = dep.currentVersionRange; + if (currentRange === '*') continue; + + const parts = currentRange.split(' || ').map((part) => part.trim()); + const lastRange = parts[parts.length - 1]; + const isUnion = parts.length > 1; + + const rangePrefix = lastRange.match(/^(.?)(\d+\.\d+\.\d+)/)?.[1] ?? ''; + + const currentVersion = semver.minVersion(lastRange)?.version; + if (currentVersion == null) + throw Error(`Invalid version specifier: ${dep.name}@${currentRange}`); + + const currentVersionReleasedAt = new Date(dep.info.time[currentVersion]); + const isPre1 = currentVersion.startsWith('0.'); + + const latestVersion = dep.info['dist-tags'].latest; + const latestVersionReleasedAt = new Date(dep.info.time[latestVersion]); + + semver.RELEASE_TYPES; + const upgradeToVersion = isMajor + ? // Always use the latest version for major upgrades + latestVersion + : // Otherwise use the last stable version greater than the current version that is not a major release + (dep.info.versions.findLast( + (v) => + semver.gt(v, currentVersion) && + (isPre1 ? ['patch'] : ['patch', 'minor']).includes( + semver.diff(v, currentVersion)!, + ) && + semver.prerelease(v) == null, + ) ?? currentVersion); + const upgradeToVersionReleasedAt = new Date( + dep.info.time[upgradeToVersion], + ); + + let upgradeToRange = `${rangePrefix}${upgradeToVersion}`; + upgradeToRange = isUnion + ? semver.satisfies(upgradeToVersion, currentRange) + ? currentRange + : `${currentRange} || ${upgradeToRange}` + : upgradeToRange; + + let diff = semver.diff(currentVersion, upgradeToVersion); + if (isPre1) { + if (diff === 'minor') diff = 'major'; + if (diff === 'patch') diff = 'minor'; + } + + if (upgradeToRange === currentRange) continue; + // if (currentVersion === latestVersion) continue; + + results.push({ + name: dep.name, + rangePrefix, + diff, + currentVersion, + currentVersionReleasedAt, + currentRange, + upgradeToVersion, + upgradeToVersionReleasedAt, + upgradeToRange, + latestVersion, + latestVersionReleasedAt, + }); + } + + return results; +} + +function printUpgrades(upgrades: UpgradeDetails[]): void { + const namePadding = Math.max(...upgrades.map((u) => u.name.length)); + const currentVersionPadding = Math.max( + ...upgrades.map((u) => u.currentRange.length), + ); + const upgradeToVersionPadding = Math.max( + ...upgrades.map((u) => u.upgradeToRange.length), + ); + const numberPadding = String(upgrades.length + 1).length + 1; + + consola.info(`Found ${upgrades.length} upgrades:`); + console.log(); + for (let i = 0; i < upgrades.length; i++) { + const upgrade = upgrades[i]; + const num = `\x1b[2m${(i + 1).toString().padStart(numberPadding)}.\x1b[0m`; + const name = `\x1b[35m${upgrade.name.padEnd(namePadding)}\x1b[0m`; + const color = + upgrade.diff == null + ? '\x1b[2m' + : upgrade.diff === 'patch' + ? '\x1b[32m' + : upgrade.diff === 'minor' + ? '\x1b[33m' + : '\x1b[31m'; + const currentVersion = `\x1b[2m${upgrade.currentRange.padEnd(currentVersionPadding)}\x1b[0m`; + const upgradeToVersion = `${color}${upgrade.upgradeToRange.padEnd(upgradeToVersionPadding)}\x1b[0m`; + const latest = + upgrade.latestVersion !== upgrade.upgradeToVersion + ? ` \x1b[2m\x1b[31m(${upgrade.latestVersion} available)\x1b[0m` + : ''; + console.log( + ` ${num} ${name} ${currentVersion} \x1b[2m→\x1b[0m ${upgradeToVersion}${latest}`, + ); + } + console.log(); +} + +async function writeUpgrades( + packageJsonFiles: string[], + upgrades: UpgradeDetails[], +) { + for (const packageJsonFile of packageJsonFiles) { + const oldText = await fs.readFile(packageJsonFile, 'utf8'); + let newText = oldText; + for (const upgrade of upgrades) { + const search = `"${upgrade.name}": "${upgrade.currentRange}"`; + const replace = `"${upgrade.name}": "${upgrade.upgradeToRange}"`; + newText = newText.replaceAll(search, replace); + } + if (newText !== oldText) { + await fs.writeFile(packageJsonFile, newText, 'utf8'); + } + } +} diff --git a/taze.config.ts b/taze.config.ts deleted file mode 100644 index 5ea3b3903..000000000 --- a/taze.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -// https://github.com/antfu-collective/taze?tab=readme-ov-file#config-file -export default { - exclude: [ - // Very touchy, don't change: - 'typedoc', - 'typedoc-plugin-markdown', - 'typedoc-vitepress-theme', - // Manually manage version so a single version is used: - 'esbuild', - // Maintained manually to match min-node version - '@types/node', - // License changed in newer versions - 'ua-parser-js', - ], -}; diff --git a/templates/vue/package.json b/templates/vue/package.json index 79860ae2e..3c04e679b 100644 --- a/templates/vue/package.json +++ b/templates/vue/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@wxt-dev/module-vue": "^1.0.2", - "typescript": "5.6.3", + "typescript": "^5.9.2", "vue-tsc": "^2.2.10", "wxt": "^0.20.6" }