Skip to content

Commit 51c8c78

Browse files
committed
fix: include runtime node module entrypoints in packages
1 parent 4192341 commit 51c8c78

6 files changed

Lines changed: 95 additions & 6 deletions

File tree

.github/workflows/release.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ jobs:
115115
- name: Build split-architecture macOS artifacts
116116
run: npm run dist:mac:split
117117

118+
- name: Verify packaged runtime dependencies
119+
run: npm run release:verify-runtime-deps
120+
118121
- name: Verify macOS update metadata
119122
run: npm run release:verify-mac-updates
120123

@@ -157,6 +160,9 @@ jobs:
157160
- name: Build Windows x64 artifacts
158161
run: npm run dist:win:x64
159162

163+
- name: Verify packaged runtime dependencies
164+
run: npm run release:verify-runtime-deps
165+
160166
- name: Restore Electron native ABI
161167
if: always()
162168
run: npm run rebuild:native:force

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ This project follows a pre-1.0 release flow. Minor versions may include breaking
66

77
## Unreleased
88

9+
## 0.3.1
10+
11+
### Fixed
12+
13+
- Fixed packaged macOS and Windows apps missing runtime `node_modules/*/src` JavaScript entry files required by transitive dependencies such as `@opentelemetry/api`.
14+
15+
### Added
16+
17+
- Added a packaged runtime dependency verification step to release builds so missing `app.asar` runtime entries fail before artifacts are uploaded.
18+
19+
## 0.3.0
20+
921
### Added
1022

1123
- Open-source project governance files and release hardening checklist.

package-lock.json

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "funplay",
3-
"version": "0.3.0",
3+
"version": "0.3.1",
44
"private": true,
55
"license": "MIT",
66
"description": "AI game development workbench for multi-engine game creation.",
@@ -29,6 +29,7 @@
2929
"runtime:maturity-gate:live": "node scripts/runtime-maturity-gate.mjs --require-live",
3030
"release:audit": "node scripts/release-config-audit.mjs",
3131
"release:gate": "node scripts/release-gate.mjs",
32+
"release:verify-runtime-deps": "node scripts/verify-packaged-runtime-deps.mjs",
3233
"release:verify-mac-updates": "node scripts/merge-mac-update-metadata.mjs --verify release/latest-mac.yml --check-artifacts",
3334
"runtime:pptx:check": "node scripts/prepare-pptx-runtime.mjs --check",
3435
"runtime:pptx:prepare": "node scripts/prepare-pptx-runtime.mjs",
@@ -74,7 +75,6 @@
7475
"!node_modules/**/*.d.ts",
7576
"!node_modules/**/*.d.mts",
7677
"!node_modules/**/*.d.cts",
77-
"!node_modules/**/src/**/*",
7878
"!node_modules/**/docs/**/*",
7979
"!node_modules/**/doc/**/*",
8080
"!node_modules/**/test/**/*",
@@ -134,6 +134,7 @@
134134
"zod": "4.3.6"
135135
},
136136
"devDependencies": {
137+
"@electron/asar": "^3.4.1",
137138
"@stylistic/eslint-plugin": "^5.10.0",
138139
"@types/node": "25.6.0",
139140
"@types/react": "19.2.14",

scripts/release-config-audit.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ if (publish?.releaseType !== 'release') {
7777
}
7878

7979
const scripts = packageJson.scripts ?? {};
80-
for (const scriptName of ['release:audit', 'release:gate', 'dist:mac:split', 'dist:win:x64', 'release:verify-mac-updates']) {
80+
for (const scriptName of ['release:audit', 'release:gate', 'dist:mac:split', 'dist:win:x64', 'release:verify-runtime-deps', 'release:verify-mac-updates']) {
8181
if (!scripts[scriptName]) {
8282
fail(`Release audit failed: missing package script ${scriptName}.`);
8383
}
@@ -124,6 +124,9 @@ for (const [path, source] of searchable) {
124124
if (!workflow.includes('gh release') || !workflow.includes('dist:mac:split') || !workflow.includes('dist:win:x64')) {
125125
fail('Release audit failed: release workflow must build macOS split artifacts, Windows x64, and publish through gh release.');
126126
}
127+
if (!workflow.includes('release:verify-runtime-deps')) {
128+
fail('Release audit failed: release workflow must verify packaged runtime dependencies before uploading artifacts.');
129+
}
127130
if (!workflow.includes('Import macOS signing certificate') || !workflow.includes('Developer ID Application')) {
128131
fail('Release audit failed: release workflow must import and validate a Developer ID Application certificate before notarization.');
129132
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { readdir, stat } from 'node:fs/promises';
2+
import { join, resolve, sep } from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
import { createRequire } from 'node:module';
5+
6+
const require = createRequire(import.meta.url);
7+
const { listPackage } = require('@electron/asar');
8+
9+
const repoRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
10+
const inputRoots = process.argv.slice(2).map((input) => resolve(repoRoot, input));
11+
const scanRoots = inputRoots.length ? inputRoots : [resolve(repoRoot, 'release')];
12+
13+
const requiredAsarEntries = [
14+
'node_modules/ai/dist/index.mjs',
15+
'node_modules/@opentelemetry/api/package.json',
16+
'node_modules/@opentelemetry/api/build/src/index.js'
17+
];
18+
19+
async function collectAsars(directory, found = []) {
20+
let entries;
21+
try {
22+
entries = await readdir(directory, { withFileTypes: true });
23+
} catch {
24+
return found;
25+
}
26+
27+
for (const entry of entries) {
28+
const path = join(directory, entry.name);
29+
if (entry.isDirectory()) {
30+
await collectAsars(path, found);
31+
} else if (entry.isFile() && entry.name === 'app.asar') {
32+
found.push(path);
33+
}
34+
}
35+
return found;
36+
}
37+
38+
function normalizeEntry(entry) {
39+
return entry.replace(/^\/+/, '').split(sep).join('/');
40+
}
41+
42+
async function verifyAsar(asarPath) {
43+
const entries = new Set(listPackage(asarPath).map(normalizeEntry));
44+
const missing = requiredAsarEntries.filter((entry) => !entries.has(entry));
45+
if (missing.length) {
46+
throw new Error(`${asarPath} is missing runtime dependency entries:\n${missing.map((entry) => `- ${entry}`).join('\n')}`);
47+
}
48+
}
49+
50+
const asars = [];
51+
for (const scanRoot of scanRoots) {
52+
const scanStats = await stat(scanRoot).catch(() => null);
53+
if (!scanStats?.isDirectory()) {
54+
throw new Error(`${scanRoot} does not exist or is not a directory. Build packaged artifacts before verifying runtime dependencies.`);
55+
}
56+
await collectAsars(scanRoot, asars);
57+
}
58+
if (!asars.length) {
59+
throw new Error('No app.asar files found under release/. Build packaged artifacts before verifying runtime dependencies.');
60+
}
61+
62+
for (const asarPath of asars) {
63+
await verifyAsar(asarPath);
64+
}
65+
66+
console.log(`Packaged runtime dependency verification passed for ${asars.length} app.asar file(s).`);

0 commit comments

Comments
 (0)