From 1a519f3e46d0a9f4d8f5b6f69ff61ac78e7d93ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:44:31 +0000 Subject: [PATCH 1/4] chore: simplify CI workflows and build scripts Agent-Logs-Url: https://github.com/nitrocode/token-deathclock/sessions/d47a2eb0-3a98-427d-8ddb-d539503e61f5 Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- .github/actions/setup/action.yml | 18 ++++ .github/workflows/{unit-tests.yml => ci.yml} | 50 ++++++++--- .github/workflows/deploy.yml | 28 +----- .github/workflows/e2e-tests.yml | 71 --------------- .github/workflows/preview.yml | 16 +--- .github/workflows/weekly-stats-check.yml | 94 ++++++++++++++++++++ scripts/build-bundle.js | 65 ++++++++++++++ scripts/build-css.js | 34 ++----- scripts/build-js.js | 52 ++++------- 9 files changed, 241 insertions(+), 187 deletions(-) create mode 100644 .github/actions/setup/action.yml rename .github/workflows/{unit-tests.yml => ci.yml} (58%) delete mode 100644 .github/workflows/e2e-tests.yml create mode 100644 .github/workflows/weekly-stats-check.yml create mode 100644 scripts/build-bundle.js diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..346745e --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,18 @@ +name: 'Setup Node.js environment' +description: 'Checkout the repository, configure Node.js 22 with npm cache, and install dependencies' + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + shell: bash diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/ci.yml similarity index 58% rename from .github/workflows/unit-tests.yml rename to .github/workflows/ci.yml index 1f4fd11..eeaf5a2 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,8 @@ -name: Unit Tests +name: CI on: + schedule: + - cron: '0 9 * * 1' # Every Monday at 09:00 UTC push: branches: - main @@ -14,13 +16,16 @@ jobs: changes: runs-on: ubuntu-latest outputs: - code: ${{ steps.filter.outputs.code }} + # On scheduled runs there is no diff to compare — always run tests. + # On push/PR, only run when relevant files changed. + code: ${{ github.event_name == 'schedule' || steps.filter.outputs.code == 'true' }} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Detect relevant file changes uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + if: github.event_name != 'schedule' id: filter with: filters: | @@ -36,17 +41,7 @@ jobs: if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci + - uses: ./.github/actions/setup - name: Build generated files needed for tests run: npm run build:milestones && npm run build:js @@ -65,6 +60,22 @@ jobs: verbose: true fail_ci_if_error: false + test-e2e: + needs: changes + if: needs.changes.outputs.code == 'true' + runs-on: ubuntu-latest + steps: + - uses: ./.github/actions/setup + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Build generated files + run: npm run build + + - name: Run E2E tests + run: npm run test:e2e + unit-test-status: needs: [changes, test] if: always() @@ -77,3 +88,16 @@ jobs: exit 1 fi echo "Unit tests passed or were skipped (no relevant files changed)" + + e2e-test-status: + needs: [changes, test-e2e] + if: always() + runs-on: ubuntu-latest + steps: + - name: Report status + run: | + if [[ "${{ needs.test-e2e.result }}" == "failure" ]]; then + echo "E2E tests failed" + exit 1 + fi + echo "E2E tests passed or were skipped (no relevant files changed)" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 94cc553..e30e0e0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,32 +20,10 @@ jobs: name: github-pages url: https://nitrocode.github.io/token-deathclock/ steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: ./.github/actions/setup - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Regenerate milestones-data.js from YAML - run: npm run build:milestones - - - name: Regenerate changelog-data.js from CHANGELOG.md - run: npm run build:changelog - - - name: Regenerate project-stats-data.js from YAML - run: npm run build:project-stats - - - name: Rebuild script.js from src/js/ source files - run: npm run build:js - - - name: Rebuild styles.css from styles/ source files - run: npm run build:css + - name: Build all generated files + run: npm run build - name: Deploy to GitHub Pages (gh-pages branch) uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml deleted file mode 100644 index 450d97b..0000000 --- a/.github/workflows/e2e-tests.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: E2E Tests - -on: - push: - branches: - - main - pull_request: - merge_group: - -permissions: - contents: read - -jobs: - changes: - runs-on: ubuntu-latest - outputs: - code: ${{ steps.filter.outputs.code }} - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Detect relevant file changes - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: filter - with: - filters: | - code: - - '**/*.js' - - '**/*.ts' - - '**/*.css' - - 'tests/**' - - 'package*.json' - - test-e2e: - needs: changes - if: needs.changes.outputs.code == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Install Playwright browsers - run: npx playwright install --with-deps chromium - - - name: Build generated files - run: npm run build - - - name: Run E2E tests - run: npm run test:e2e - - e2e-test-status: - needs: [changes, test-e2e] - if: always() - runs-on: ubuntu-latest - steps: - - name: Report status - run: | - if [[ "${{ needs.test-e2e.result }}" == "failure" ]]; then - echo "E2E tests failed" - exit 1 - fi - echo "E2E tests passed or were skipped (no relevant files changed)" diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 1596bfb..aee8607 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -12,17 +12,7 @@ jobs: preview: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci + - uses: ./.github/actions/setup - name: Build all generated files run: npm run build @@ -35,8 +25,8 @@ jobs: destination_dir: previews/pr-${{ github.event.number }} # Preserve existing previews and production files keep_files: true - # Exclude non-site files - exclude_assets: '.github,node_modules,tests,scripts,package-lock.json,package.json,milestones.yaml' + # Exclude non-site files (keep in sync with deploy.yml) + exclude_assets: '.github,node_modules,tests,scripts,package-lock.json,package.json,milestones.yaml,project-stats.yaml' - name: Post or update preview URL comment uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/weekly-stats-check.yml b/.github/workflows/weekly-stats-check.yml new file mode 100644 index 0000000..5accbc6 --- /dev/null +++ b/.github/workflows/weekly-stats-check.yml @@ -0,0 +1,94 @@ +name: Weekly Project Stats Check + +# Opens a reminder issue when the recorded pr_count in project-stats.yaml +# is lower than the actual number of merged PRs in the repository. + +on: + schedule: + - cron: '0 9 * * 1' # Every Monday at 09:00 UTC + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + check-stats: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Check project-stats.yaml against actual merged PR count + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const fs = require('fs'); + + // Read the recorded pr_count from project-stats.yaml + const yaml = fs.readFileSync('project-stats.yaml', 'utf8'); + const match = yaml.match(/^pr_count:\s*(\d+)/m); + const recordedCount = match ? parseInt(match[1], 10) : 0; + + // Count actual merged PRs via the GitHub API + let page = 1; + let totalMerged = 0; + while (true) { + const { data } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed', + per_page: 100, + page, + }); + if (data.length === 0) break; + totalMerged += data.filter(pr => pr.merged_at !== null).length; + if (data.length < 100) break; + page++; + } + + console.log(`Recorded pr_count: ${recordedCount}, actual merged PRs: ${totalMerged}`); + + if (recordedCount >= totalMerged) { + console.log('project-stats.yaml is up to date — no action needed.'); + return; + } + + const title = `chore: update project-stats.yaml (recorded ${recordedCount}, actual ${totalMerged} merged PRs)`; + const body = [ + '## 📊 Project Stats Are Out of Date', + '', + `**Recorded \`pr_count\`:** ${recordedCount}`, + `**Actual merged PRs:** ${totalMerged}`, + `**Difference:** ${totalMerged - recordedCount} untracked PR(s)`, + '', + 'After the next agent session, update `project-stats.yaml`:', + '', + '```yaml', + `pr_count: ${totalMerged}`, + '```', + '', + 'Then run `npm run build:project-stats` to regenerate `project-stats-data.js`, or let the deploy workflow handle it.', + ].join('\n'); + + // Avoid duplicate open issues + const { data: openIssues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + }); + const duplicate = openIssues.find(i => + i.title.startsWith('chore: update project-stats.yaml') + ); + if (duplicate) { + console.log(`Reminder issue already open: #${duplicate.number} — skipping.`); + return; + } + + const { data: issue } = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + }); + console.log(`Created reminder issue: #${issue.number}`); diff --git a/scripts/build-bundle.js b/scripts/build-bundle.js new file mode 100644 index 0000000..16509fc --- /dev/null +++ b/scripts/build-bundle.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node +/** + * build-bundle.js + * + * Shared helper used by build-js.js and build-css.js. + * Concatenates an ordered list of source files, optionally wraps them with a + * header and footer string, then minifies the result with esbuild. + * + * This module is not a standalone script — import it via require(). + * + * @example + * const { buildBundle } = require('./build-bundle'); + * buildBundle({ parts, srcDir, outPath, loader: 'css' }); + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const esbuild = require('esbuild'); + +/** + * Build a minified bundle from an ordered list of source files. + * + * @param {object} opts + * @param {string[]} opts.parts - Ordered source file names + * @param {string} opts.srcDir - Directory that contains the source files + * @param {string} opts.outPath - Absolute path for the output file + * @param {'js'|'css'} opts.loader - esbuild loader type + * @param {string} [opts.header] - Text prepended before minification + * @param {string} [opts.footer] - Text appended before minification + * @param {object} [opts.esbuildOptions]- Extra options merged into the esbuild call + */ +function buildBundle(opts) { + const chunks = opts.parts.map((file) => { + const fullPath = path.join(opts.srcDir, file); + if (!fs.existsSync(fullPath)) { + throw new Error(`Missing source file: ${file}`); + } + return fs.readFileSync(fullPath, 'utf8'); + }); + + // Concatenate — each source file preserves its own trailing blank lines so + // no additional separator is needed. + let unminified = chunks.join(''); + if (opts.header) unminified = opts.header + '\n' + unminified; + if (opts.footer) unminified = unminified + opts.footer + '\n'; + + const esbuildOpts = Object.assign( + { minify: true, loader: opts.loader }, + opts.esbuildOptions || {}, + ); + + const result = esbuild.transformSync(unminified, esbuildOpts); + fs.writeFileSync(opts.outPath, result.code); + + const outName = path.basename(opts.outPath); + const ratio = ((1 - result.code.length / unminified.length) * 100).toFixed(1); + console.log( + `${outName} rebuilt from ${opts.parts.length} source files ` + + `(${unminified.split('\n').length - 1} lines → ${result.code.length} bytes, −${ratio}% via esbuild minification)`, + ); +} + +module.exports = { buildBundle }; diff --git a/scripts/build-css.js b/scripts/build-css.js index c68867a..2e82384 100644 --- a/scripts/build-css.js +++ b/scripts/build-css.js @@ -14,9 +14,8 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); -const esbuild = require('esbuild'); +const path = require('path'); +const { buildBundle } = require('./build-bundle'); const ROOT = path.resolve(__dirname, '..'); @@ -36,30 +35,9 @@ const PARTS = [ 'scary-features.css', ]; -const chunks = PARTS.map((file) => { - const fullPath = path.join(ROOT, 'styles', file); - if (!fs.existsSync(fullPath)) { - throw new Error(`Missing source file: styles/${file}`); - } - return fs.readFileSync(fullPath, 'utf8'); -}); - -// Concatenate directly — each source file preserves its own trailing blank lines -// so no additional separator is needed. -const unminified = chunks.join(''); - -const outPath = path.join(ROOT, 'styles.css'); - -// Minify with esbuild (synchronous transform API — no temp files needed). -const result = esbuild.transformSync(unminified, { - minify: true, +buildBundle({ + parts: PARTS, + srcDir: path.join(ROOT, 'styles'), + outPath: path.join(ROOT, 'styles.css'), loader: 'css', }); - -fs.writeFileSync(outPath, result.code); - -const ratio = ((1 - result.code.length / unminified.length) * 100).toFixed(1); -console.log( - `styles.css rebuilt from ${PARTS.length} source files ` + - `(${unminified.split('\n').length - 1} lines → ${result.code.length} bytes, −${ratio}% via esbuild minification)`, -); diff --git a/scripts/build-js.js b/scripts/build-js.js index 71551db..dd8ae90 100644 --- a/scripts/build-js.js +++ b/scripts/build-js.js @@ -15,9 +15,8 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); -const esbuild = require('esbuild'); +const path = require('path'); +const { buildBundle } = require('./build-bundle'); const ROOT = path.resolve(__dirname, '..'); @@ -59,38 +58,17 @@ const HEADER = [ '(function () {', ].join('\n'); -const FOOTER = '})();'; - -const chunks = PARTS.map((file) => { - const fullPath = path.join(ROOT, 'src', 'js', file); - if (!fs.existsSync(fullPath)) { - throw new Error(`Missing source file: src/js/${file}`); - } - return fs.readFileSync(fullPath, 'utf8'); -}); - -// Concatenate inner body directly — each source file preserves its own -// trailing blank lines so no additional separator is needed. -const innerBody = chunks.join(''); - -const unminified = HEADER + '\n' + innerBody + FOOTER + '\n'; - -const outPath = path.join(ROOT, 'script.js'); - -// Minify with esbuild (synchronous transform API — no temp files needed). -const result = esbuild.transformSync(unminified, { - minify: true, - // Preserve the leading banner comment so tools can still identify the file. - banner: '/* AI DEATH CLOCK — browser/DOM layer (minified) */', - // Target all modern browsers; no transpilation needed. - target: ['es2018'], - loader: 'js', +buildBundle({ + parts: PARTS, + srcDir: path.join(ROOT, 'src', 'js'), + outPath: path.join(ROOT, 'script.js'), + loader: 'js', + header: HEADER, + footer: '})();', + esbuildOptions: { + // Preserve the leading banner comment so tools can still identify the file. + banner: '/* AI DEATH CLOCK — browser/DOM layer (minified) */', + // Target all modern browsers; no transpilation needed. + target: ['es2018'], + }, }); - -fs.writeFileSync(outPath, result.code); - -const ratio = ((1 - result.code.length / unminified.length) * 100).toFixed(1); -console.log( - `script.js rebuilt from ${PARTS.length} source files ` + - `(${unminified.split('\n').length - 1} lines → ${result.code.length} bytes, −${ratio}% via esbuild minification)`, -); From 851291c3bc44c4e19a43335269ab63b7c56f4108 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Apr 2026 20:34:01 +0000 Subject: [PATCH 2/4] fix: checkout repo before using local composite action Agent-Logs-Url: https://github.com/nitrocode/token-deathclock/sessions/b4a46e0d-829f-438a-a389-22e704bd74ad Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- .github/actions/setup/action.yml | 5 +---- .github/workflows/ci.yml | 2 ++ .github/workflows/deploy.yml | 1 + .github/workflows/preview.yml | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 346745e..6bd6bcb 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,12 +1,9 @@ name: 'Setup Node.js environment' -description: 'Checkout the repository, configure Node.js 22 with npm cache, and install dependencies' +description: 'Configure Node.js 22 with npm cache and install dependencies' runs: using: composite steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eeaf5a2..7f4d83a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,7 @@ jobs: if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/setup - name: Build generated files needed for tests @@ -65,6 +66,7 @@ jobs: if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/setup - name: Install Playwright browsers diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e30e0e0..fdde754 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,6 +20,7 @@ jobs: name: github-pages url: https://nitrocode.github.io/token-deathclock/ steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/setup - name: Build all generated files diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index aee8607..1522793 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -12,6 +12,7 @@ jobs: preview: runs-on: ubuntu-latest steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/setup - name: Build all generated files From 76a04fe5c3c485e5676c1b537ac256b1423de8f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:48:57 +0000 Subject: [PATCH 3/4] fix: validate pr_count parse, paginate duplicate-issue check, fix bundle separators and ratio NaN Agent-Logs-Url: https://github.com/nitrocode/token-deathclock/sessions/96190522-cc07-4b03-99c0-cf0948a23a2e Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- .github/workflows/weekly-stats-check.yml | 14 ++++++++++---- scripts/build-bundle.js | 12 +++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/weekly-stats-check.yml b/.github/workflows/weekly-stats-check.yml index 5accbc6..3220fce 100644 --- a/.github/workflows/weekly-stats-check.yml +++ b/.github/workflows/weekly-stats-check.yml @@ -28,7 +28,13 @@ jobs: // Read the recorded pr_count from project-stats.yaml const yaml = fs.readFileSync('project-stats.yaml', 'utf8'); const match = yaml.match(/^pr_count:\s*(\d+)/m); - const recordedCount = match ? parseInt(match[1], 10) : 0; + if (!match) { + throw new Error('Invalid or missing pr_count in project-stats.yaml'); + } + const recordedCount = parseInt(match[1], 10); + if (!Number.isInteger(recordedCount)) { + throw new Error('Invalid or missing pr_count in project-stats.yaml'); + } // Count actual merged PRs via the GitHub API let page = 1; @@ -71,14 +77,14 @@ jobs: 'Then run `npm run build:project-stats` to regenerate `project-stats-data.js`, or let the deploy workflow handle it.', ].join('\n'); - // Avoid duplicate open issues - const { data: openIssues } = await github.rest.issues.listForRepo({ + // Avoid duplicate open issues (paginate to cover all open items, exclude PRs) + const openIssues = await github.paginate(github.rest.issues.listForRepo, { owner: context.repo.owner, repo: context.repo.repo, state: 'open', }); const duplicate = openIssues.find(i => - i.title.startsWith('chore: update project-stats.yaml') + i.title.startsWith('chore: update project-stats.yaml') && !i.pull_request ); if (duplicate) { console.log(`Reminder issue already open: #${duplicate.number} — skipping.`); diff --git a/scripts/build-bundle.js b/scripts/build-bundle.js index 16509fc..bae2080 100644 --- a/scripts/build-bundle.js +++ b/scripts/build-bundle.js @@ -40,11 +40,11 @@ function buildBundle(opts) { return fs.readFileSync(fullPath, 'utf8'); }); - // Concatenate — each source file preserves its own trailing blank lines so - // no additional separator is needed. - let unminified = chunks.join(''); + // Concatenate with explicit newline separators so files without trailing + // newlines don't accidentally merge tokens across boundaries. + let unminified = chunks.join('\n'); if (opts.header) unminified = opts.header + '\n' + unminified; - if (opts.footer) unminified = unminified + opts.footer + '\n'; + if (opts.footer) unminified = unminified + '\n' + opts.footer; const esbuildOpts = Object.assign( { minify: true, loader: opts.loader }, @@ -55,7 +55,9 @@ function buildBundle(opts) { fs.writeFileSync(opts.outPath, result.code); const outName = path.basename(opts.outPath); - const ratio = ((1 - result.code.length / unminified.length) * 100).toFixed(1); + const ratio = unminified.length === 0 + ? '0.0' + : ((1 - result.code.length / unminified.length) * 100).toFixed(1); console.log( `${outName} rebuilt from ${opts.parts.length} source files ` + `(${unminified.split('\n').length - 1} lines → ${result.code.length} bytes, −${ratio}% via esbuild minification)`, From b80574f5686799899d15bd7464cf175cf4cae461 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:55:23 +0000 Subject: [PATCH 4/4] fix: correct line count in build-bundle.js log (remove off-by-one) Agent-Logs-Url: https://github.com/nitrocode/token-deathclock/sessions/24d22b66-7021-4e64-afe0-bd4be732fffb Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- scripts/build-bundle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-bundle.js b/scripts/build-bundle.js index bae2080..4b16c52 100644 --- a/scripts/build-bundle.js +++ b/scripts/build-bundle.js @@ -60,7 +60,7 @@ function buildBundle(opts) { : ((1 - result.code.length / unminified.length) * 100).toFixed(1); console.log( `${outName} rebuilt from ${opts.parts.length} source files ` + - `(${unminified.split('\n').length - 1} lines → ${result.code.length} bytes, −${ratio}% via esbuild minification)`, + `(${unminified ? unminified.split('\n').length : 0} lines → ${result.code.length} bytes, −${ratio}% via esbuild minification)`, ); }