Skip to content

Commit 247eee7

Browse files
committed
its done?
1 parent fcea281 commit 247eee7

19 files changed

+491
-104
lines changed

.claude/settings.local.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,16 @@
2222
"Bash(node --experimental-vm-modules node_modules/jest/bin/jest.js src/__tests__/selfOverwrite.test.ts --runInBand --verbose 2>&1 | grep -E '\\(✓|✕|PASS|FAIL|Tests:|✓|×|√|●|RUNS|✗|passed|failed| \\)')",
2323
"Bash(node --experimental-vm-modules node_modules/jest/bin/jest.js src/__tests__/selfOverwrite.test.ts --runInBand --verbose 2>&1 | grep -E '\\(PASS|FAIL|Tests:|✓|✕|✗|×|√\\)' | tail -5)",
2424
"Bash(node --experimental-vm-modules node_modules/jest/bin/jest.js \"src/__tests__/\" --runInBand 2>&1 | grep -E '\\(FAIL |● \\)' | head -20)",
25-
"Bash(node --experimental-vm-modules node_modules/jest/bin/jest.js src/__tests__/metadataFormatting.integration.test.ts src/__tests__/converterWorker.paths.test.ts --runInBand 2>&1 | grep -B1 -A5 '●.*should escape\\\\|●.*uses runtime\\\\|●.*throws' | head -40)"
25+
"Bash(node --experimental-vm-modules node_modules/jest/bin/jest.js src/__tests__/metadataFormatting.integration.test.ts src/__tests__/converterWorker.paths.test.ts --runInBand 2>&1 | grep -B1 -A5 '●.*should escape\\\\|●.*uses runtime\\\\|●.*throws' | head -40)",
26+
"Bash(node -e \"const {version} = require\\('dotenv/package.json'\\); console.log\\(version\\)\" 2>/dev/null || grep '\"dotenv\"' package.json)",
27+
"Bash(node -e \"const dotenv = require\\('dotenv'\\); console.log\\(Object.keys\\(dotenv\\)\\)\")",
28+
"Bash(node -e \"require\\('dotenv'\\).config\\({ quiet: true }\\)\" 2>&1)",
29+
"Bash(node -e \"const dotenv = require\\('dotenv'\\); const r = dotenv.config.__proto__; console.log\\(dotenv.config.toString\\(\\).slice\\(0, 500\\)\\)\")",
30+
"Bash(node -e \"require\\('dotenv'\\).configDotenv\\(\\)\" 2>&1)",
31+
"Bash(DOTENV_LOG_LEVEL=error node -e \"require\\('dotenv'\\).config\\(\\)\" 2>&1)",
32+
"Bash(node -e \"require\\('dotenv'\\).config\\({ debug: false, silent: true, logLevel: 'error' }\\)\" 2>&1)",
33+
"Bash(npm run build 2>&1 | tail -5)",
34+
"Bash(wc -l /mnt/c/Users/Darren/Ez-Game-Audio-Conversion/node_modules/cfonts/fonts/*.json)"
2635
],
2736
"deny": [],
2837
"ask": []

.github/workflows/RELEASE-GUIDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Each platform creates a separate release archive:
6767
- `add_context_menu.bat` - Windows context menu integration
6868
- `remove_context_menu.bat` - Uninstall context menu
6969

70+
Note: For Windows builds, the packaged `.exe` icon + version metadata is applied automatically during `npm run package`. To disable this (rare), set `POST_SEA_SKIP_ICON=1`.
71+
7072
### Linux (`EZ-Game-Audio-Linux.zip`)
7173
- `ez-game-audio` - Single executable application
7274
- `ffmpeg` - Audio conversion binary

.jest-latest.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ npm run clean # Remove dist/ and release/ folders
4747

4848
**Icon/Metadata (Optional):**
4949
- Icon exists at `media/ico/icon.ico`
50-
- Manual icon application script at `src/ico/icon.js` (Windows only, requires `resedit` package)
51-
- Not currently integrated into automated build process
50+
- Icon + version metadata is applied automatically for Windows builds during `npm run package` via `src/ico/icon.js` (uses `resedit`)
51+
- To skip icon injection: set `POST_SEA_SKIP_ICON=1`
5252

5353
### Testing
5454

package-lock.json

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

package.json

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@
6464
"markdown-to-html-cli": "^5.0.3",
6565
"md-to-pdf": "^5.2.4",
6666
"nodemon": "^3.1.10",
67+
"pe-library": "^2.0.1",
6768
"postject": "^1.0.0-alpha.6",
6869
"prettier": "^3.6.2",
70+
"resedit": "^3.0.2",
6971
"rimraf": "^6.0.1",
7072
"ts-jest": "^29.4.1",
7173
"ts-node": "^10.9.2",
@@ -87,15 +89,32 @@
8789
"<rootDir>/build/",
8890
"<rootDir>/release/"
8991
],
90-
"extensionsToTreatAsEsm": [".ts"],
92+
"extensionsToTreatAsEsm": [
93+
".ts"
94+
],
9195
"moduleNameMapper": {
9296
"^(\\.{1,2}/.*)\\.js$": "$1"
9397
},
9498
"transform": {
95-
"^.+\\.ts$": ["ts-jest", { "useESM": true }]
99+
"^.+\\.ts$": [
100+
"ts-jest",
101+
{
102+
"useESM": true
103+
}
104+
]
96105
},
97-
"moduleFileExtensions": ["ts", "js", "cjs", "jsx", "json", "node"],
98-
"testMatch": ["**/__tests__/**/*.test.ts", "**/__tests__/**/*.test.js"],
106+
"moduleFileExtensions": [
107+
"ts",
108+
"js",
109+
"cjs",
110+
"jsx",
111+
"json",
112+
"node"
113+
],
114+
"testMatch": [
115+
"**/__tests__/**/*.test.ts",
116+
"**/__tests__/**/*.test.js"
117+
],
99118
"verbose": false,
100119
"forceExit": true,
101120
"testTimeout": 30000

scripts/build-sea.js

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import { execSync } from 'child_process';
11-
import { copyFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'fs';
11+
import { writeFileSync, mkdirSync, existsSync, rmSync } from 'fs';
1212
import { join } from 'path';
1313
import { platform } from 'os';
1414

@@ -20,6 +20,30 @@ const SEA_CONFIG_PATH = 'sea-config.json';
2020
const SEA_BLOB_PATH = join(RELEASE_DIR, 'sea-prep.blob');
2121
const OUTPUT_EXE = join(RELEASE_DIR, isWindows ? `${APP_NAME}.exe` : APP_NAME);
2222

23+
function tryApplyWindowsIcon() {
24+
if (!isWindows) return;
25+
if (process.env.POST_SEA_SKIP_ICON === '1') {
26+
console.log('[SEA] Skipping icon injection (POST_SEA_SKIP_ICON=1)');
27+
return;
28+
}
29+
30+
const iconScript = join('src', 'ico', 'icon.js');
31+
if (!existsSync(iconScript)) {
32+
console.warn('[SEA] Icon script not found; skipping icon injection');
33+
return;
34+
}
35+
36+
console.log('[SEA] Applying Windows icon + version metadata...');
37+
try {
38+
execSync(`node "${iconScript}" "${OUTPUT_EXE}"`, { stdio: 'inherit' });
39+
} catch (error) {
40+
console.warn(
41+
'[SEA] Warning: icon injection failed (continuing):',
42+
error.message
43+
);
44+
}
45+
}
46+
2347
console.log('[SEA] Building Single Executable Application...\n');
2448

2549
// Step 1: Ensure release directory exists
@@ -29,10 +53,18 @@ mkdirSync(RELEASE_DIR, { recursive: true });
2953
// Step 1.5: Bundle ESM to CJS for SEA compatibility
3054
// The code handles import.meta being empty by falling back to CJS __filename
3155
// Exclude cfonts entirely - it uses dynamic require for fonts that can't work in SEA
56+
console.log('[SEA] Injecting banner snapshot...');
57+
try {
58+
execSync('node scripts/gen-banner.js', { stdio: 'inherit' });
59+
} catch (error) {
60+
console.error('[SEA] Failed to generate banner snapshot:', error.message);
61+
process.exit(1);
62+
}
63+
3264
console.log('[SEA] Bundling application with esbuild...');
3365
try {
3466
execSync(
35-
`npx esbuild dist/app.js --bundle --platform=node --format=cjs --outfile=${BUNDLED_APP} --external:worker_threads --external:cfonts --log-override:empty-import-meta=silent`,
67+
`npx esbuild dist/app.js --bundle --platform=node --format=cjs --minify --tree-shaking=true --legal-comments=none --outfile=${BUNDLED_APP} --external:worker_threads --external:cfonts --define:__SEA_BUILD__=true --log-override:empty-import-meta=silent`,
3668
{
3769
stdio: 'inherit',
3870
}
@@ -61,7 +93,7 @@ const WORKER_BUNDLE = join(RELEASE_DIR, 'dist', 'converterWorker.cjs');
6193
mkdirSync(join(RELEASE_DIR, 'dist'), { recursive: true });
6294
try {
6395
execSync(
64-
`npx esbuild dist/converterWorker.js --bundle --platform=node --format=cjs --outfile=${WORKER_BUNDLE} --log-override:empty-import-meta=silent`,
96+
`npx esbuild dist/converterWorker.js --bundle --platform=node --format=cjs --minify --tree-shaking=true --legal-comments=none --outfile=${WORKER_BUNDLE} --log-override:empty-import-meta=silent`,
6597
{
6698
stdio: 'inherit',
6799
}
@@ -85,7 +117,12 @@ try {
85117
// Step 4: Copy Node.js executable
86118
console.log('[SEA] Copying Node.js executable...');
87119
try {
88-
copyFileSync(process.execPath, OUTPUT_EXE);
120+
// Use system copy command instead of Node API to avoid file lock issues
121+
// when npm itself holds the Node.exe lock during package builds
122+
const copyCmd = isWindows
123+
? `cmd /c copy /Y "${process.execPath}" "${OUTPUT_EXE}"`
124+
: `cp "${process.execPath}" "${OUTPUT_EXE}"`;
125+
execSync(copyCmd, { stdio: 'inherit' });
89126
} catch (error) {
90127
console.error('[SEA] Failed to copy Node.js executable:', error.message);
91128
process.exit(1);
@@ -116,6 +153,9 @@ try {
116153
process.exit(1);
117154
}
118155

156+
// Step 5.5: Inject custom icon/version info (best-effort)
157+
tryApplyWindowsIcon();
158+
119159
// Step 6: Cleanup temporary files
120160
console.log('[SEA] Cleaning up temporary files...');
121161
try {

scripts/gen-banner.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Captures the exact cfonts ANSI output and injects it into dist/banner.js,
4+
* replacing the '__SEA_BANNER__' sentinel before esbuild bundles the app.
5+
* Must run after `tsc` and before `esbuild`.
6+
*/
7+
8+
import { readFileSync, writeFileSync } from 'fs';
9+
10+
// Force chalk to output full ANSI colour codes even when stdout is not a TTY.
11+
process.env.FORCE_COLOR = '3';
12+
// Render left-aligned; runtime centering will adapt to actual terminal width.
13+
14+
const { default: cfonts } = await import('cfonts');
15+
const { render } = cfonts;
16+
17+
const result = render('|||EZ Game|Audio', {
18+
font: 'huge',
19+
align: 'left',
20+
gradient: ['green', '#f80'],
21+
background: 'black',
22+
independentGradient: true,
23+
transitionGradient: false,
24+
env: 'node',
25+
});
26+
27+
if (!result) {
28+
console.error('[gen-banner] cfonts render() returned false — aborting.');
29+
process.exit(1);
30+
}
31+
32+
const bannerPath = 'dist/banner.js';
33+
let src = readFileSync(bannerPath, 'utf8');
34+
35+
const sentinel = "const BANNER_SNAPSHOT = '__SEA_BANNER__'";
36+
if (!src.includes(sentinel)) {
37+
console.error(`[gen-banner] Sentinel ${sentinel} not found in ${bannerPath}`);
38+
process.exit(1);
39+
}
40+
41+
src = src.replace(
42+
"const BANNER_SNAPSHOT = '__SEA_BANNER__'",
43+
`const BANNER_SNAPSHOT = ${JSON.stringify(result.string)}`
44+
);
45+
writeFileSync(bannerPath, src, 'utf8');
46+
47+
console.log('[gen-banner] Banner snapshot injected into dist/banner.js');

scripts/post-sea.js

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,34 @@ function ensureFile(filePath, message) {
195195
}
196196
}
197197

198+
function tryApplyWindowsIcon({ exePath }) {
199+
// Best-effort: icon injection should never fail a release build.
200+
if (!isTargetWindows) return;
201+
if (process.env.POST_SEA_SKIP_ICON === '1') {
202+
console.log('[post-sea] Skipping icon injection (POST_SEA_SKIP_ICON=1)');
203+
return;
204+
}
205+
206+
const iconScript = join(rootDir, 'src', 'ico', 'icon.js');
207+
if (!existsSync(iconScript)) {
208+
console.warn('[post-sea] Icon script missing; skipping:', iconScript);
209+
return;
210+
}
211+
212+
console.log('[post-sea] Applying Windows icon + version info...');
213+
try {
214+
execSync(`node "${iconScript}" "${exePath}"`, {
215+
cwd: rootDir,
216+
stdio: 'inherit',
217+
});
218+
} catch (error) {
219+
console.warn(
220+
'[post-sea] Icon injection failed (continuing):',
221+
error instanceof Error ? error.message : String(error)
222+
);
223+
}
224+
}
225+
198226
function cleanupIntermediateArtifacts() {
199227
console.log('[post-sea] Cleaning up intermediate files...');
200228
// Remove staging/temp artifacts.
@@ -329,6 +357,9 @@ const manifestArtifactPaths = [];
329357
let releaseReady = false;
330358

331359
try {
360+
// Apply Windows icon/version metadata before copying into stage.
361+
tryApplyWindowsIcon({ exePath: builtExePath });
362+
332363
// Stage directory structure
333364
console.log('[post-sea] Staging files...');
334365
emptyDir(stageDir);
@@ -426,9 +457,33 @@ try {
426457
}
427458

428459
// Ensure README assets are available for both ZIP and PDF rendering.
460+
// Only copy the media files that README.md actually references — not the entire media/ folder.
429461
const repoMediaDir = join(rootDir, 'media');
430462
if (existsSync(repoMediaDir)) {
431-
copyDir(repoMediaDir, join(readmesDir, 'media'));
463+
const readmeSrc = join(rootDir, 'README.md');
464+
const referencedMediaFiles = [];
465+
if (existsSync(readmeSrc)) {
466+
const readmeText = readFileSync(readmeSrc, 'utf8');
467+
// Match Markdown image/link syntax and HTML src/href attributes pointing into media/
468+
const mdRe = /\]\((media\/[^)\s"]+)\)/g;
469+
const htmlRe = /(?:src|href)="(media\/[^"]+)"/g;
470+
for (const re of [mdRe, htmlRe]) {
471+
let m;
472+
while ((m = re.exec(readmeText)) !== null) {
473+
referencedMediaFiles.push(m[1]);
474+
}
475+
}
476+
}
477+
for (const relPath of [...new Set(referencedMediaFiles)]) {
478+
const srcFile = join(rootDir, relPath);
479+
if (!existsSync(srcFile)) {
480+
console.warn(`[post-sea] Referenced media file not found: ${srcFile}`);
481+
continue;
482+
}
483+
const destFile = join(readmesDir, relPath);
484+
mkdirSync(dirname(destFile), { recursive: true });
485+
copyFileSync(srcFile, destFile);
486+
}
432487
}
433488

434489
// Copy existing docs/readmes if present
@@ -440,10 +495,11 @@ try {
440495
for (const entry of entries) {
441496
if (!entry.isFile()) continue;
442497
if (/^README\.(html|pdf)$/i.test(entry.name)) continue;
443-
copyFileSync(
444-
join(legacyDocsDir, entry.name),
445-
join(readmesDir, entry.name)
446-
);
498+
// Place How-to-start.txt at the archive root for easy first-run discovery.
499+
const dest = /^how-to-start\.txt$/i.test(entry.name)
500+
? join(stageDir, entry.name)
501+
: join(readmesDir, entry.name);
502+
copyFileSync(join(legacyDocsDir, entry.name), dest);
447503
}
448504
}
449505

0 commit comments

Comments
 (0)