CI / lint (push) Successful in 2s
CI / extension-version (push) Successful in 3s
extension / lint (push) Successful in 19s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 38s
CI / integration (push) Successful in 3m57s
extension / lint (pull_request) Successful in 34s
The XPI-content check added in 1c6452e did its job on its first run: the
archive carried empty `test/` and `scripts/` entries.
`test/**` matches the files inside a directory but not the directory entry
itself, and web-ext writes an entry for the directory separately -- so the
contents were correctly excluded while the empty directories shipped anyway.
Nothing harmful reached users (no dev code, just two empty entries), but our
single declaration claimed these do not ship and something was shipping.
Both forms are now listed per directory. The bare name alone would not do:
minimatch's `test` does not match `test/url.spec.js`, so dropping the glob
would ship the contents instead.
Verified locally: the derived version is unchanged at 1.0.19, confirming the
added entries are redundant for the git pathspec and only affect web-ext.
Refs #2400
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
113 lines
5.2 KiB
JavaScript
113 lines
5.2 KiB
JavaScript
import { describe, it, expect } from 'vitest'
|
|
import { readFileSync } from 'node:fs'
|
|
import { execFileSync } from 'node:child_process'
|
|
import { fileURLToPath } from 'node:url'
|
|
import path from 'node:path'
|
|
|
|
const EXT_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
const read = (name) => JSON.parse(readFileSync(path.join(EXT_DIR, name), 'utf8'))
|
|
const readText = (...seg) => readFileSync(path.join(EXT_DIR, ...seg), 'utf8')
|
|
|
|
// Only the git-free subcommands are exercised here: `version`/`patch` shell out
|
|
// to git, and the extension lane runs on node:24-bookworm-slim which may not
|
|
// ship it. Those two are covered where git is guaranteed — ci.yml and build.yml
|
|
// run on ci-python.
|
|
const packaging = (cmd) =>
|
|
execFileSync('sh', [path.join(EXT_DIR, 'scripts', 'packaging.sh'), cmd], {
|
|
cwd: EXT_DIR,
|
|
encoding: 'utf8'
|
|
})
|
|
.trim()
|
|
.split(/\s+/)
|
|
.filter(Boolean)
|
|
|
|
describe('packaging.sh — the single definition of what ships', () => {
|
|
it('emits an ignore list and a pathspec that agree on the tracked files', () => {
|
|
const ignore = packaging('ignore')
|
|
const pathspec = packaging('pathspec').map((e) => e.replace(':(exclude)extension/', ''))
|
|
|
|
// Every git-excluded path must also be hidden from web-ext. The reverse is
|
|
// not required: node_modules and web-ext-artifacts are build output git
|
|
// never tracks, so they appear only in the ignore list.
|
|
for (const entry of pathspec) {
|
|
expect(ignore, `pathspec has "${entry}" but --ignore-files does not`).toContain(entry)
|
|
}
|
|
expect(pathspec.length).toBeGreaterThan(0)
|
|
expect(ignore).toContain('node_modules')
|
|
})
|
|
|
|
it('emits glob patterns literally, never expanded against the working tree', () => {
|
|
// The script iterates its lists with deliberate word-splitting, so it must
|
|
// run with pathname expansion off. Without that, invoking it from a cwd
|
|
// where test/ exists (exactly how ci.yml and vitest call it) expands
|
|
// `test/**` into the individual spec files, and the pathspec silently stops
|
|
// covering anything added later.
|
|
const pathspec = packaging('pathspec')
|
|
expect(pathspec).toContain(':(exclude)extension/test/**')
|
|
expect(pathspec).toContain(':(exclude)extension/scripts/**')
|
|
expect(pathspec.some((e) => e.includes('.spec.js'))).toBe(false)
|
|
expect(pathspec.some((e) => e.includes('helpers'))).toBe(false)
|
|
})
|
|
|
|
it('keeps its own scripts and specs out of the XPI', () => {
|
|
// Both are repo infrastructure. web-ext packages everything not ignored, so
|
|
// omitting either would ship dev tooling to users -- and `test/**` in
|
|
// particular only survives because callers `set -f` before substituting it.
|
|
const ignore = packaging('ignore')
|
|
expect(ignore).toContain('vitest.config.js')
|
|
// Both forms per directory. The glob covers the contents; the bare name
|
|
// covers the directory ENTRY, which web-ext writes separately — with only
|
|
// the glob, the XPI carries an empty `test/` and `scripts/`.
|
|
for (const dir of ['test', 'scripts']) {
|
|
expect(ignore, `${dir} contents`).toContain(`${dir}/**`)
|
|
expect(ignore, `${dir} directory entry`).toContain(dir)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('consumers delegate rather than keeping their own copy', () => {
|
|
// These assertions are the actual anti-regression value: it is easy for a
|
|
// future edit to "simplify" by inlining a literal list again, which silently
|
|
// reintroduces the drift that issue #2397 was about.
|
|
it('package.json derives --ignore-files from the script', () => {
|
|
for (const [name, script] of Object.entries(read('package.json').scripts)) {
|
|
if (!script.includes('--ignore-files')) continue
|
|
expect(script, `${name} should call packaging.sh`).toContain('scripts/packaging.sh ignore')
|
|
expect(script, `${name} must set -f before the substitution`).toMatch(/set -f;/)
|
|
}
|
|
})
|
|
|
|
it('ci.yml derives its pathspec from the script and hardcodes none', () => {
|
|
const ci = readText('..', '.forgejo', 'workflows', 'ci.yml')
|
|
expect(ci).toContain('extension/scripts/packaging.sh pathspec')
|
|
// A literal :(exclude)extension/... in the workflow means someone bypassed
|
|
// the shared definition.
|
|
expect(ci).not.toMatch(/:\(exclude\)extension\//)
|
|
})
|
|
})
|
|
|
|
describe('extension version', () => {
|
|
it('keeps manifest.json and package.json in lockstep', () => {
|
|
expect(read('manifest.json').version).toBe(read('package.json').version)
|
|
})
|
|
|
|
it('uses a plain dotted numeric version AMO will accept', () => {
|
|
expect(read('package.json').version).toMatch(/^\d+(\.\d+)*$/)
|
|
})
|
|
|
|
it('declares manifest v3', () => {
|
|
expect(read('manifest.json').manifest_version).toBe(3)
|
|
})
|
|
|
|
it('lists every background script that exists, in dependency order', () => {
|
|
// url.js must load BEFORE api.js: api.js calls normalizeApiUrl at
|
|
// init()-time, and these are classic scripts sharing one scope, so a
|
|
// reordering here is a runtime ReferenceError with no build-time signal.
|
|
const scripts = read('manifest.json').background.scripts
|
|
for (const rel of scripts) {
|
|
expect(() => readFileSync(path.join(EXT_DIR, rel)), `missing ${rel}`).not.toThrow()
|
|
}
|
|
expect(scripts.indexOf('lib/url.js')).toBeLessThan(scripts.indexOf('lib/api.js'))
|
|
})
|
|
})
|