Compare commits

..
Author SHA1 Message Date
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> b8c13c0370 chore(deps): update dependency github/gh-aw to v1.2.2 (#8620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-11-11 04:02:03 +00:00
10 changed files with 11 additions and 333 deletions
+3 -9
View File
@@ -191,9 +191,7 @@ jobs:
pnpm run prebuild ${{ matrix.target }}
- name: Release ${{ env.TAG_CHANNEL }} Version
run: |
pnpm release-version autobuild-latest
pnpm polyfills:generate
run: pnpm release-version autobuild-latest
- name: Tauri build for Windows-macOS-Linux
uses: tauri-apps/tauri-action@v1
@@ -273,9 +271,7 @@ jobs:
pnpm run prebuild ${{ matrix.target }}
- name: Release ${{ env.TAG_CHANNEL }} Version
run: |
pnpm release-version autobuild-latest
pnpm polyfills:generate
run: pnpm release-version autobuild-latest
- name: 'Setup for linux'
run: |-
@@ -408,9 +404,7 @@ jobs:
pnpm run prebuild ${{ matrix.target }}
- name: Release ${{ env.TAG_CHANNEL }} Version
run: |
pnpm release-version autobuild-latest
pnpm polyfills:generate
run: pnpm release-version autobuild-latest
- name: Download WebView2 Runtime
run: |
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Install gh-aw extension
uses: github/gh-aw-actions/setup-cli@53d0749e19424a918acd82b4d140264ce48f2f14 # v1.2.2
with:
version: v1.0.1
version: v1.2.2
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
with:
+1 -3
View File
@@ -115,9 +115,7 @@ jobs:
- name: Release ${{ env.TAG_CHANNEL }} Version
if: github.event.inputs[matrix.input] == 'true'
run: |
pnpm release-version ${{ env.TAG_NAME }}
pnpm polyfills:generate
run: pnpm release-version ${{ env.TAG_NAME }}
- name: Add Rust Target
if: github.event.inputs[matrix.input] == 'true'
-3
View File
@@ -58,9 +58,6 @@ jobs:
- name: Run TypeScript typecheck
run: pnpm typecheck
- name: Verify modern polyfill inventory against automatic discovery
run: pnpm polyfills:check
- name: Run tests
run: pnpm test
-11
View File
@@ -101,17 +101,6 @@ For frontend-only development, use `pnpm web:dev`. It does not start the Rust ba
### Build the Project
`pnpm web:build` uses a generated modern-polyfill inventory and rejects changed
emitted module inputs, output, targets, or locked dependencies before writing
output. Inputs include dependency code and code later removed by minification.
When it reports a stale inventory, run `pnpm polyfills:generate` and commit
`scripts/modern-polyfills.json`. Generation preserves automatic discovery's import
order and requires byte-identical automatic/explicit outputs for the macOS, Linux,
and Windows production defines. CI repeats this comparison with
`pnpm polyfills:check`; browser targets and worker configuration stay unchanged.
Release build jobs regenerate after rewriting versions because version data is
also emitted in frontend modules.
Standard build:
```bash
-2
View File
@@ -15,8 +15,6 @@
"tauri": "tauri",
"web:dev": "vite",
"web:build": "tsrs -p . --noEmit && vite build",
"polyfills:generate": "node scripts/generate-modern-polyfills.mjs --write",
"polyfills:check": "node scripts/generate-modern-polyfills.mjs --check",
"web:serve": "vite preview",
"prebuild": "node scripts/prebuild.mjs",
"updater": "node scripts/updater.mjs",
-80
View File
@@ -1,80 +0,0 @@
import assert from 'node:assert/strict'
import { createHash } from 'node:crypto'
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { build } from 'vite'
const [command, ...extra] = process.argv.slice(2)
if (!['--write', '--check'].includes(command) || extra.length) {
throw new Error(
'Usage: node scripts/generate-modern-polyfills.mjs --write|--check',
)
}
const inventoryPath = 'scripts/modern-polyfills.json'
const staleMessage =
'Modern polyfill inventory is stale; run pnpm polyfills:generate and commit scripts/modern-polyfills.json'
function outputHashes(result) {
assert(
!Array.isArray(result) && 'output' in result,
'Expected one app output',
)
return result.output
.map((file) => [
file.fileName,
createHash('sha256')
.update(file.type === 'chunk' ? file.code : file.source)
.digest('hex'),
])
.sort(([a], [b]) => a.localeCompare(b))
}
const temporary = mkdtempSync(path.join(os.tmpdir(), 'modern-polyfills-'))
const capture = path.join(temporary, 'capture.json')
const candidate = path.join(temporary, 'inventory.json')
const snapshots = []
const fingerprints = []
let imports
try {
process.env.MODERN_POLYFILLS_CAPTURE = capture
// OS_PLATFORM changes emitted code; certify each production define without
// launching a native app or normalizing away code the detector actually sees.
for (const platform of ['darwin', 'linux', 'win32']) {
const config = {
logLevel: 'error',
define: { OS_PLATFORM: JSON.stringify(platform) },
build: { write: false },
}
const automatic = await build(config)
const detected = JSON.parse(readFileSync(capture, 'utf8'))
if (imports) assert.deepEqual(detected.imports, imports)
imports = detected.imports
fingerprints.push(detected.fingerprint)
snapshots.push({ config, hashes: outputHashes(automatic) })
}
const content = `${JSON.stringify({ imports, fingerprints: [...new Set(fingerprints)].sort() }, null, 2)}\n`
writeFileSync(candidate, content)
delete process.env.MODERN_POLYFILLS_CAPTURE
process.env.MODERN_POLYFILLS_INVENTORY = candidate
for (const { config, hashes } of snapshots) {
assert.deepEqual(
outputHashes(await build(config)),
hashes,
'Explicit polyfills must be byte-identical to automatic discovery',
)
}
if (command === '--check') {
assert.equal(readFileSync(inventoryPath, 'utf8'), content, staleMessage)
} else {
writeFileSync(inventoryPath, content)
}
console.log(
`Modern polyfills ${command === '--check' ? 'verified' : 'generated'}: ${imports.length} imports; all three OS outputs byte-identical to automatic discovery`,
)
} finally {
delete process.env.MODERN_POLYFILLS_CAPTURE
delete process.env.MODERN_POLYFILLS_INVENTORY
rmSync(temporary, { recursive: true })
}
-72
View File
@@ -1,72 +0,0 @@
{
"imports": [
"core-js/modules/es.error.cause.js",
"core-js/modules/es.array.at.js",
"core-js/modules/es.array.includes.js",
"core-js/modules/es.array.push.js",
"core-js/modules/es.array.unshift.js",
"core-js/modules/es.array-buffer.detached.js",
"core-js/modules/es.array-buffer.transfer.js",
"core-js/modules/es.array-buffer.transfer-to-fixed-length.js",
"core-js/modules/es.iterator.constructor.js",
"core-js/modules/es.iterator.every.js",
"core-js/modules/es.iterator.filter.js",
"core-js/modules/es.iterator.find.js",
"core-js/modules/es.iterator.flat-map.js",
"core-js/modules/es.iterator.for-each.js",
"core-js/modules/es.iterator.map.js",
"core-js/modules/es.iterator.reduce.js",
"core-js/modules/es.iterator.some.js",
"core-js/modules/es.iterator.to-array.js",
"core-js/modules/es.json.parse.js",
"core-js/modules/es.json.stringify.js",
"core-js/modules/es.map.get-or-insert.js",
"core-js/modules/es.map.get-or-insert-computed.js",
"core-js/modules/es.regexp.flags.js",
"core-js/modules/es.set.difference.v2.js",
"core-js/modules/es.set.intersection.v2.js",
"core-js/modules/es.set.is-disjoint-from.v2.js",
"core-js/modules/es.set.is-subset-of.v2.js",
"core-js/modules/es.set.is-superset-of.v2.js",
"core-js/modules/es.set.symmetric-difference.v2.js",
"core-js/modules/es.set.union.v2.js",
"core-js/modules/es.string.at-alternative.js",
"core-js/modules/es.typed-array.at.js",
"core-js/modules/es.typed-array.fill.js",
"core-js/modules/es.typed-array.find-last.js",
"core-js/modules/es.typed-array.find-last-index.js",
"core-js/modules/es.typed-array.set.js",
"core-js/modules/es.typed-array.sort.js",
"core-js/modules/es.typed-array.to-reversed.js",
"core-js/modules/es.typed-array.to-sorted.js",
"core-js/modules/es.typed-array.with.js",
"core-js/modules/es.uint8-array.set-from-base64.js",
"core-js/modules/es.uint8-array.set-from-hex.js",
"core-js/modules/es.uint8-array.to-base64.js",
"core-js/modules/es.uint8-array.to-hex.js",
"core-js/modules/es.weak-map.get-or-insert.js",
"core-js/modules/es.weak-map.get-or-insert-computed.js",
"core-js/modules/esnext.array.group.js",
"core-js/modules/web.dom-exception.stack.js",
"core-js/modules/web.immediate.js",
"core-js/modules/web.url-search-params.delete.js",
"core-js/modules/web.url-search-params.has.js",
"core-js/modules/web.url-search-params.size.js",
"core-js/modules/es.object.has-own.js",
"core-js/modules/es.array.with.js",
"core-js/modules/es.symbol.async-dispose.js",
"core-js/modules/es.async-iterator.async-dispose.js",
"core-js/modules/es.symbol.dispose.js",
"core-js/modules/es.iterator.dispose.js",
"core-js/modules/es.aggregate-error.cause.js",
"core-js/modules/es.array.find-last.js",
"core-js/modules/es.iterator.drop.js",
"core-js/modules/es.iterator.take.js",
"core-js/modules/web.structured-clone.js"
],
"fingerprints": [
"30dd6a7f16e7065fa9803260e11aa2f2db56cabb69664257c940ab05bae8505b",
"5630fe8a486687073b6d9894dd39735b77d5d8177432a4ada81947a429ff6991",
"ddbb980f9ad3fe92ee7c30248d46ed4cdeb421c714266e627b825b59042c30bc"
]
}
-139
View File
@@ -1,139 +0,0 @@
import { createHash } from 'node:crypto'
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import legacy, { detectPolyfills } from '@vitejs/plugin-legacy'
const digest = (value) => createHash('sha256').update(value).digest('hex')
const inventoryPath = 'scripts/modern-polyfills.json'
const staleMessage =
'Modern polyfill inventory is stale; run pnpm polyfills:generate and commit scripts/modern-polyfills.json'
// Bind complete emitted module inputs as well as output: minification can hide APIs.
// Generation double-checks the exported detector against legacy's automatic build.
export function guardedLegacy(options) {
const capturePath = process.env.MODERN_POLYFILLS_CAPTURE
const inventory = capturePath
? null
: JSON.parse(
readFileSync(
process.env.MODERN_POLYFILLS_INVENTORY || inventoryPath,
'utf8',
),
)
const plugins = legacy({
...options,
modernPolyfills: Boolean(capturePath),
additionalModernPolyfills: [
...options.additionalModernPolyfills,
...(capturePath ? [] : inventory.imports),
],
})
const postProcess = plugins.find(
(plugin) => plugin.name === 'vite:legacy-post-process',
)
if (!postProcess?.renderChunk) {
throw new Error('plugin-legacy must provide its post-process render hook')
}
const original = postProcess.renderChunk
const render = typeof original === 'function' ? original : original.handler
const discovered = new Map()
let chunkNames
let context
if (capturePath) {
postProcess.renderChunk = async function (raw, chunk, output, meta) {
chunkNames ??= Object.keys(meta.chunks)
const imports = new Set()
await detectPolyfills(
raw,
options.modernTargets,
options.assumptions || {},
imports,
)
discovered.set(chunk.fileName, imports)
return render.call(this, raw, chunk, output, meta)
}
}
return [
...plugins,
{
name: 'guard-modern-polyfills',
enforce: 'post',
apply: 'build',
configResolved(config) {
if (config.build.ssr || options.renderLegacyChunks !== false) {
throw new Error(
'Modern polyfill guard requires a modern-only app build',
)
}
// Custom import paths must not bind the inventory to a checkout location.
context = [
{
...options,
additionalModernPolyfills: options.additionalModernPolyfills.map(
(file) =>
path.relative(process.cwd(), file).split(path.sep).join('/'),
),
},
config.build.target,
config.mode,
...[
'package.json',
'pnpm-lock.yaml',
'vite.config.mts',
'scripts/modern-polyfills.mjs',
'scripts/generate-modern-polyfills.mjs',
].map((file) => digest(readFileSync(file))),
...[
'.browserslistrc',
'browserslist',
'src/.browserslistrc',
'src/browserslist',
].map((file) =>
existsSync(file) ? digest(readFileSync(file)) : null,
),
digest(
readFileSync(new URL(import.meta.resolve('@vitejs/plugin-legacy'))),
),
]
},
generateBundle(_output, bundle) {
const imports = capturePath
? [
...new Set(
chunkNames.flatMap((name) => [...discovered.get(name)]),
),
]
: inventory.imports
const chunks = Object.values(bundle).filter(
(file) => file.type === 'chunk',
)
const moduleIds = new Set(
chunks.flatMap((chunk) => Object.keys(chunk.modules)),
)
const inputs = [...moduleIds].map((id) => {
// Rolldown's runtime is generated, not a transformed input module.
if (id === '\0rolldown/runtime.js') return null
// Vite's HTML entry code embeds an absolute import to the main module.
if (id.endsWith('.html')) return digest(readFileSync(id))
const code = this.getModuleInfo(id)?.code
if (code == null) this.error(`Missing emitted module input: ${id}`)
return digest(code)
})
const fingerprint = digest(
JSON.stringify([
context,
inputs.sort(),
chunks.map((chunk) => digest(chunk.code)).sort(),
imports,
]),
)
if (capturePath) {
writeFileSync(capturePath, JSON.stringify({ fingerprint, imports }))
} else if (!inventory.fingerprints.includes(fingerprint)) {
this.error(staleMessage)
}
},
},
]
}
+6 -13
View File
@@ -1,29 +1,22 @@
import path from 'node:path'
import legacy from '@vitejs/plugin-legacy'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import svgr from 'vite-plugin-svgr'
import { guardedLegacy } from './scripts/modern-polyfills.mjs'
const svgPlugin = svgr()
const svgLoad = svgPlugin.load
if (!svgLoad) throw new Error('vite-plugin-svgr must provide a load hook')
// SVGR's internal include filter still crosses the JS bridge for every module.
svgPlugin.load = {
filter: { id: /\.svg\?react$/ },
handler: typeof svgLoad === 'function' ? svgLoad : svgLoad.handler,
}
export default defineConfig({
root: 'src',
server: { port: 3000 },
plugins: [
svgPlugin,
svgr(),
react(),
guardedLegacy({
legacy({
modernTargets: ['edge>=109', 'safari>=14'],
renderLegacyChunks: false,
// Auto-detected from the emitted bundle, dependencies included,
// so post-Safari-14 APIs never depend on extending a manual list.
modernPolyfills: true,
additionalModernPolyfills: [
path.resolve('./src/polyfills/matchMedia.js'),
path.resolve('./src/polyfills/WeakRef.js'),