import { execFileSync } from 'node:child_process' import { readFileSync } from 'node:fs' import { join } from 'node:path' import { describe, expect, it } from 'vitest' const script = join(import.meta.dirname, 'deploy-ext.sh') const source = readFileSync(script, 'utf8') // Pull just the `link` helper (and the URL it prints) out of the release // script so the tests do not have to run a whole release to check it. function runLink({ tty }) { const helper = source.match(/^cws_console=.*$[\s\S]*?^link\(\) \{[\s\S]*?^\}$/m)?.[0] expect(helper, 'link() helper not found in deploy-ext.sh').toBeTruthy() const program = `${helper}\nlink "$cws_console"\n` if (!tty) return execFileSync('bash', ['-c', program], { encoding: 'utf8' }) // `script` gives stdout a pty, which is the branch that emits escapes. return execFileSync('script', ['-qec', 'bash -c \'' + program.replace(/'/g, "'\\''") + "'", '/dev/null'], { encoding: 'utf8', env: { ...process.env, TERM: 'xterm-256color' }, }) } const URL = 'https://chrome.google.com/webstore/devconsole/' // util-linux `script`; BSD/macOS spells its arguments differently. const hasPty = (() => { try { execFileSync('script', ['-qec', 'true', '/dev/null'], { stdio: 'ignore' }) return true } catch { return false } })() // The preflight cannot be run here (it wipes node_modules and needs a release // tag at HEAD), so its ordering is asserted against the source: the guarantee // is that the release is built from the lockfile, and that the cheap refusals // still come before anything destructive. describe('deploy-ext.sh preflight', () => { const at = (needle) => { const i = source.indexOf(needle) expect(i, `${needle} not found in deploy-ext.sh`).toBeGreaterThan(-1) return i } it('installs from the lockfile rather than trusting node_modules', () => { expect(source).toMatch(/^npm ci$/m) expect(source).not.toMatch(/^npm install/m) }) it('refuses a dirty tree before npm ci wipes node_modules', () => { expect(at('working tree is dirty')).toBeLessThan(at('\nnpm ci')) }) it('typechecks and tests after the install, so both see lockfile deps', () => { expect(at('\nnpm ci')).toBeLessThan(at('\nnpm run check')) expect(at('\nnpm run check')).toBeLessThan(at('\nnpm test')) }) // The store reviews minified code more slowly, and this extension's // all-hosts grant already earns an in-depth review; losing the maps // silently would give that back. it('requires the sourcemaps a reviewer reads, rather than banning them', () => { expect(source).not.toMatch(/contains sourcemaps/) for (const js of ['background.js', 'popup.js', 'content.js', 'offscreen.js', 'welcome.js']) { expect(source).toContain(js) } expect(source).toMatch(/grep -qx "\$js\.map"/) }) it('builds after the install', () => { expect(at('\nnpm ci')).toBeLessThan(at('\nnpm run build')) }) // Copy this repo has not had a human write yet is placeholder (see // CLAUDE.md), and every other check in this script passes with the token // still in — so this is the only thing standing between SNICKERSNEE and the // store listing. it('refuses a build whose copy is still the placeholder', () => { expect(source).toMatch(/grep -rl SNICKERSNEE dist/) expect(at('grep -rl SNICKERSNEE dist')).toBeLessThan(at('zipfile=')) }) }) describe('deploy-ext.sh link()', () => { it('prints the bare console URL when stdout is not a terminal', () => { const out = runLink({ tty: false }) expect(out.trim()).toBe(URL) // eslint-disable-next-line no-control-regex expect(out).not.toMatch(/\x1b/) }) it.runIf(hasPty)('wraps the URL in an OSC 8 hyperlink on a terminal', () => { const out = runLink({ tty: true }) expect(out).toContain(`\x1b]8;;${URL}\x1b\\${URL}\x1b]8;;\x1b\\`) }) })