This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new b1144f43c fix(build): hash finalized frontend artifacts in the license
manifest (#5019)
b1144f43c is described below
commit b1144f43c1cc9f5f45dea65e963a09e88fe5fc8f
Author: zmuxuny <[email protected]>
AuthorDate: Fri Oct 9 23:39:21 2026 -0700
fix(build): hash finalized frontend artifacts in the license manifest
(#5019)
The license gate hashed the in-memory chunk code, while Vite's
`buildImportAnalysisPlugin` prepends
`__vite__mapDeps` to the chunk that actually lands on disk - so one
manifest entry always mismatched and
`npm run license:check`, `npm run build` and with them the whole frontend
CI job failed. `writeBundle`
now recomputes the hash from the bytes written. The tamper detection itself
is unchanged.
`npm run license:test` 5/5 green. This was the only surviving fix for that
trunk red light.
---
web/scripts/licenses.mjs | 13 +++++++++++--
web/scripts/licenses.test.mjs | 41 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/web/scripts/licenses.mjs b/web/scripts/licenses.mjs
index d48d68857..42278db04 100644
--- a/web/scripts/licenses.mjs
+++ b/web/scripts/licenses.mjs
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { createHash } from 'node:crypto';
-import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
+import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from
'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -175,6 +175,7 @@ export function collectLicenses(moduleIds, base = root) {
export function distributionLicenses() {
let base;
+ let manifest;
return {
name: 'distribution-licenses',
apply: 'build',
@@ -195,13 +196,21 @@ export function distributionLicenses() {
for (const [name, item] of Object.entries(bundle)) {
outputFiles[name] = sha(item.type === 'chunk' ? item.code :
item.source);
}
- const manifest = { modules: result.modules, components:
result.components, outputFiles, files: {} };
+ manifest = { modules: result.modules, components: result.components,
outputFiles, files: {} };
for (const [name, data] of result.files) {
manifest.files[name] = sha(data);
this.emitFile({ type: 'asset', fileName: name, source: data });
}
this.emitFile({ type: 'asset', fileName: 'legal/manifest.json', source:
`${JSON.stringify(manifest, null, 2)}\n` });
},
+ writeBundle({ dir }) {
+ // Vite may rewrite the entry chunk after this plugin's generateBundle
hook.
+ // Record the bytes that were actually distributed, before the
post-build gate runs.
+ for (const name of Object.keys(manifest.outputFiles)) {
+ manifest.outputFiles[name] = sha(read(path.join(dir, name)));
+ }
+ writeFileSync(path.join(dir, 'legal/manifest.json'),
`${JSON.stringify(manifest, null, 2)}\n`);
+ },
};
}
diff --git a/web/scripts/licenses.test.mjs b/web/scripts/licenses.test.mjs
index 75bb3188d..35c100e87 100644
--- a/web/scripts/licenses.test.mjs
+++ b/web/scripts/licenses.test.mjs
@@ -64,6 +64,7 @@ test('missingOrUnknownLicenseFailsTest', (t) => {
test('vitePackagingAndTamperGateTest', async (t) => {
const directory = temporary(t);
+ const licensePlugin = distributionLicenses();
// Build only fixtures for React, CSS, SVG and small dependencies; do not
build the app or run app tests.
const result = await build({
root,
@@ -77,7 +78,7 @@ test('vitePackagingAndTamperGateTest', async (t) => {
if (id === '\0license-fixture') return `import React from
'${root}node_modules/react/index.js'; import logo from
'${root}src/assets/model-logos/openai.svg'; import '${root}src/index.css';
import toggle from '${root}node_modules/toggle-selection/index.js';
console.log(React, logo, toggle);`;
},
},
- distributionLicenses(),
+ licensePlugin,
],
build: { write: false, minify: false, rollupOptions: { input:
'license-fixture' } },
});
@@ -93,6 +94,44 @@ test('vitePackagingAndTamperGateTest', async (t) => {
assert.throws(() => checkDistribution(directory), /modified or missing/);
write(directory, 'NOTICE', result.output.find((item) => item.fileName ===
'NOTICE').source);
const output = Object.keys(manifest.outputFiles)[0];
+ write(directory, output, Buffer.concat([readFileSync(path.join(directory,
output)), Buffer.from('\n// late build rewrite')]));
+ assert.throws(() => checkDistribution(directory), /build artifact
verification failed/);
+ licensePlugin.writeBundle({ dir: directory });
+ checkDistribution(directory);
write(directory, output, 'tampered');
assert.throws(() => checkDistribution(directory), /build artifact
verification failed/);
});
+
+test('viteWriteLifecycleHashesTheFinalChunkTest', async (t) => {
+ const directory = temporary(t);
+ const source = mkdtempSync(path.join(root, 'src/.license-test-'));
+ t.after(() => rmSync(source, { recursive: true, force: true }));
+ write(source, 'index.html', '<script type="module"
src="./main.js"></script>');
+ write(source, 'main.js', "import React from 'react';
console.log(React.version);");
+ const output = path.join(directory, 'dist');
+
+ await build({
+ root,
+ configFile: false,
+ logLevel: 'error',
+ plugins: [
+ distributionLicenses(),
+ {
+ name: 'late-chunk-rewrite',
+ enforce: 'post',
+ generateBundle(_options, bundle) {
+ const entry = Object.values(bundle).find((item) => item.type ===
'chunk' && item.isEntry);
+ assert(entry, 'fixture must emit an entry chunk');
+ entry.code += '\n// rewritten after the license manifest was
generated\n';
+ },
+ },
+ ],
+ build: { outDir: output, emptyOutDir: false, rollupOptions: { input:
path.join(source, 'index.html') } },
+ });
+
+ checkDistribution(output);
+ const manifest = JSON.parse(readFileSync(path.join(output,
'legal/manifest.json')));
+ const entry = Object.keys(manifest.outputFiles).find((name) =>
name.endsWith('.js'));
+ assert(entry, 'manifest must include the emitted JavaScript');
+ assert.match(readFileSync(path.join(output, entry), 'utf8'), /rewritten
after the license manifest/);
+});