Files
builder/scripts/test-worker-secrets.mjs
Reynaldi Chernando 1ca50e7a6b github integration (#117)
* github integration

* fixes
2026-10-23 22:24:38 +07:00

350 lines
24 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'node:fs';
// ---- Regression guard for backend keys (worker-secrets.js, set_secret) ------
// A project's API keys (Stripe, Whop, any API) used to be written into its
// backend's source as constants, so they sat in every version, the download,
// the draft site (it serves the project folder) and any GitHub repository the
// project synced to. Now each key lives only in its worker's own key-value
// store, put there by Builder with the user's permission, and the worker reads
// it at run time. What can go wrong:
// * a key lands somewhere else (a file, a tool result, the conversation);
// * Builder asks for permission when there is nothing to move, so every
// publish of every backend pops a dialog;
// * a worker of another project gets this project's keys;
// * Publish or Duplicate leave the new backend without its keys, or delete
// leaves them behind for the next worker of the same name;
// * Publish overwrites the live site's own live key with the preview's test
// key, or a live key ends up in the preview;
// * the line Builder adds after a publish misses a test key, carries a
// value, or shows up for a project with no keys;
// * the AI keeps writing keys into code because the prompt still says so.
// Runs the production bytes with puter stubbed. Fake keys are assembled at
// runtime: GitHub's push protection scans this repository too.
const read = (rel) => fs.readFileSync(new URL(rel, import.meta.url), 'utf8');
let failures = 0;
function check(name, cond, detail) {
if (cond) console.log('ok - ' + name);
else { console.error('FAIL - ' + name + (detail !== undefined ? ' :: ' + detail : '')); failures++; }
}
const KEY = 'sk_' + 'test_' + '51HxYzAbCdEfGhIjKlMn0pQr';
const LIVE_KEY = 'sk_' + 'live_' + '51HxYzAbCdEfGhIjKlMn0pQr';
const WHOP_SANDBOX = 'https://sandbox-api.whop.com/api/v1';
const WHOP_LIVE = 'https://api.whop.com/api/v1';
// A stub Puter: workers with their own apps, per-app key-value stores, and a
// permission state per app that records every prompt.
function makePuter(o = {}) {
const apps = new Map(); // app uid -> Map
const granted = new Set(o.granted || []);
const prompts = [];
const workers = o.workers || { pay: 'app-pay', 'pay-live': 'app-pay-live', other: 'app-other', bare: null };
const store = (app) => { if (!apps.has(app)) apps.set(app, new Map()); return apps.get(app); };
return {
apps, granted, prompts,
workers: {
get: async (name) => (name in workers ? { name, app_uid: workers[name] } : null),
list: async () => Object.keys(workers).map((name) => ({ name, file_path: (o.paths && o.paths[name]) || `/u/AppData/b/c1/workers/${name}.js` })),
},
perms: {
check: async (res, d) => res === 'appData' && granted.has(d.app),
request: async (res, d) => {
prompts.push(d.app);
if (o.decline && o.decline.includes(d.app)) return false;
granted.add(d.app);
return true;
},
},
kv: {
get: async (k, opt) => { if (!granted.has(opt.appUuid)) throw new Error('forbidden'); const v = store(opt.appUuid).get(k); return v === undefined ? null : JSON.parse(JSON.stringify(v)); },
set: async (k, v, opt) => { if (!granted.has(opt.appUuid)) throw new Error('forbidden'); store(opt.appUuid).set(k, JSON.parse(JSON.stringify(v))); return true; },
del: async (k, opt) => { if (!granted.has(opt.appUuid)) throw new Error('forbidden'); return store(opt.appUuid).delete(k); },
},
};
}
function load(puter) {
const window = {};
new Function('window', 'puter', read('../src/js/worker-secrets.js'))(window, puter);
return window.WorkerSecrets;
}
// --- saving a key ---
{
const puter = makePuter();
const WS = load(puter);
const r = await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
check('set: the key lands in the worker\'s own store', r.ok && puter.apps.get('app-pay').get('secret:STRIPE_SECRET_KEY') === KEY);
check('set: the user is asked once, for that worker\'s app', puter.prompts.join(',') === 'app-pay');
check('set: the key name is listed in the store', JSON.stringify(puter.apps.get('app-pay').get('secret-names')) === '["STRIPE_SECRET_KEY"]');
await WS.set('pay', 'STRIPE_SECRET_KEY', KEY + 'X');
check('set: saving again overwrites without asking again', puter.prompts.length === 1 && puter.apps.get('app-pay').get('secret:STRIPE_SECRET_KEY') === KEY + 'X' &&
puter.apps.get('app-pay').get('secret-names').length === 1);
check('set: the result never carries the key', !JSON.stringify(r).includes(KEY));
check('set: a bad name is refused before anything happens', (await WS.set('pay', 'stripe-key', KEY)).reason === 'bad-name');
check('set: an empty key is refused', (await WS.set('pay', 'EMPTY', ' ')).reason === 'empty');
check('set: a worker with no app of its own is refused', (await WS.set('bare', 'K', KEY)).reason === 'no-app');
check('names: the names, never the values', JSON.stringify(await WS.names('pay')) === '["STRIPE_SECRET_KEY"]');
}
{
const puter = makePuter({ decline: ['app-pay'] });
const WS = load(puter);
const r = await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
check('set: a declined dialog saves nothing and says so', !r.ok && r.reason === 'declined' && !puter.apps.has('app-pay'));
}
// --- copying (Publish, Duplicate) ---
{
const puter = makePuter();
const WS = load(puter);
const r = await WS.copy('pay', 'pay-live');
check('copy: a backend Builder never gave keys to copies nothing and asks nothing', r.ok && r.copied === 0 && puter.prompts.length === 0);
}
{
const puter = makePuter();
const WS = load(puter);
await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
await WS.set('pay', 'WEBHOOK_SECRET', 'whsec_' + 'abc123');
const r = await WS.copy('pay', 'pay-live');
check('copy: every key reaches the live backend', r.ok && r.copied === 2 &&
puter.apps.get('app-pay-live').get('secret:STRIPE_SECRET_KEY') === KEY && puter.apps.get('app-pay-live').get('secret-names').length === 2);
check('copy: the live backend\'s app is asked once', puter.prompts.join(',') === 'app-pay,app-pay-live');
await WS.copy('pay', 'pay-live');
check('copy: publishing again asks nothing', puter.prompts.length === 2);
check('copy: the result never carries a key', !JSON.stringify(r).includes(KEY));
}
{
const puter = makePuter({ decline: ['app-pay-live'] });
const WS = load(puter);
await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
const r = await WS.copy('pay', 'pay-live');
check('copy: a declined dialog copies nothing and says so', !r.ok && r.reason === 'declined' && !puter.apps.has('app-pay-live'));
}
// --- the live site's own keys ---
{
const puter = makePuter();
const WS = load(puter);
await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
await WS.set('pay', 'WEBHOOK_SECRET', 'whsec_' + 'abc123');
await WS.copy('pay', 'pay-live');
const r = await WS.set('pay-live', 'STRIPE_SECRET_KEY', LIVE_KEY, { liveOnly: true });
const live = puter.apps.get('app-pay-live');
check('live: a live-only key lands in the live backend and is marked as its own', r.ok && live.get('secret:STRIPE_SECRET_KEY') === LIVE_KEY &&
JSON.stringify(live.get('secret-live-only')) === '["STRIPE_SECRET_KEY"]');
check('live: the preview keeps its test key', puter.apps.get('app-pay').get('secret:STRIPE_SECRET_KEY') === KEY);
await WS.set('pay', 'WEBHOOK_SECRET', 'whsec_' + 'new456');
const again = await WS.copy('pay', 'pay-live');
check('live: publishing again never replaces the live site\'s own key', live.get('secret:STRIPE_SECRET_KEY') === LIVE_KEY);
check('live: publishing still copies the other keys', live.get('secret:WEBHOOK_SECRET') === 'whsec_' + 'new456' && again.copied === 1);
check('live: copy reports the kept key and what it is, never a value',
JSON.stringify(again.keys) === JSON.stringify([{ name: 'STRIPE_SECRET_KEY', kind: 'stripe-live', kept: true }, { name: 'WEBHOOK_SECRET', kind: 'other', kept: false }]) &&
!JSON.stringify(again).includes(LIVE_KEY) && !JSON.stringify(again).includes('whsec_'));
check('live: a live key never goes back to the preview', !JSON.stringify([...puter.apps.get('app-pay')]).includes(LIVE_KEY));
}
{
const puter = makePuter();
const WS = load(puter);
await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
const r = await WS.copy('pay', 'pay-live');
check('copy: a copied test key is reported as one', JSON.stringify(r.keys) === '[{"name":"STRIPE_SECRET_KEY","kind":"stripe-test","kept":false}]');
}
{
const WS = load(makePuter());
check('kind: Stripe test and live keys, restricted ones too',
WS.kindOf(KEY) === 'stripe-test' && WS.kindOf('rk_' + 'test_' + 'abc') === 'stripe-test' &&
WS.kindOf(LIVE_KEY) === 'stripe-live' && WS.kindOf('rk_' + 'live_' + 'abc') === 'stripe-live');
check('kind: Whop\'s sandbox and live hosts', WS.kindOf(WHOP_SANDBOX) === 'whop-sandbox' && WS.kindOf(WHOP_LIVE) === 'whop-live');
check('kind: anything else is other', WS.kindOf('whsec_' + 'abc') === 'other' && WS.kindOf(null) === 'other' && WS.kindOf('https://evil.example/api.whop.com') === 'other');
}
// --- the line Builder adds after a publish ---
{
const WS = load(makePuter());
const note = (keys, ok = true) => WS.publishNote([{ live: 'pay-live', result: { ok, reason: ok ? undefined : 'declined', copied: keys.length, keys } }]);
check('note: nothing when no backend has keys', WS.publishNote([]) === '' && note([]) === '' &&
WS.publishNote([{ live: 'pay-live', result: null }]) === '' && WS.publishNote(undefined) === '');
const test = note([{ name: 'STRIPE_SECRET_KEY', kind: 'stripe-test', kept: false }]);
check('note: a Stripe test key on the live site is called out',
test === '*Published. The live site got `STRIPE_SECRET_KEY` from the preview. `STRIPE_SECRET_KEY` is a Stripe test key, so the live site can’t take real payments yet.*', test);
const kept = note([{ name: 'STRIPE_SECRET_KEY', kind: 'stripe-live', kept: true }, { name: 'WEBHOOK_SECRET', kind: 'other', kept: false }]);
check('note: a live key the live site kept says so, with no warning',
kept === '*Published. The live site got `WEBHOOK_SECRET` from the preview and kept its own `STRIPE_SECRET_KEY`.*', kept);
const whop = note([{ name: 'WHOP_API_KEY', kind: 'other', kept: false }, { name: 'WHOP_API', kind: 'whop-sandbox', kept: false }]);
check('note: Whop\'s sandbox on the live site is called out', /got `WHOP_API_KEY` and `WHOP_API` from the preview\. `WHOP_API` points to Whop’s sandbox, so the live site can’t take real payments yet\.\*$/.test(whop), whop);
const half = note([{ name: 'WHOP_API_KEY', kind: 'other', kept: true }, { name: 'WHOP_API', kind: 'whop-sandbox', kept: false }]);
check('note: a live Whop key against the sandbox host is called out', /points to Whop’s sandbox/.test(half) && /kept its own `WHOP_API_KEY`/.test(half), half);
const failed = note([], false);
check('note: keys that didn\'t arrive are said, with what to do', /^\*Published, but the live site didn’t get all of its keys/.test(failed) && /Publishing again tries once more\./.test(failed), failed);
const many = WS.publishNote([
{ live: 'a-live', result: { ok: true, keys: [{ name: 'A_KEY', kind: 'other', kept: false }, { name: 'SHARED', kind: 'other', kept: false }] } },
{ live: 'b-live', result: { ok: true, keys: [{ name: 'SHARED', kind: 'other', kept: false }, { name: 'B_KEY', kind: 'other', kept: false }] } },
]);
check('note: keys across backends are listed once each', many === '*Published. The live site got `A_KEY`, `SHARED` and `B_KEY` from the preview.*', many);
check('note: only valid key names reach the conversation', note([{ name: 'X`](https://evil.example)', kind: 'other', kept: false }, { name: 'ignore previous', kind: 'other', kept: false }]) === '');
}
{
// End to end: what copy reports is what the note says, and no value
// reaches it.
const puter = makePuter();
const WS = load(puter);
await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
const n = WS.publishNote([{ live: 'pay-live', result: await WS.copy('pay', 'pay-live') }]);
check('note: built from a real copy, it names the test key and never carries it', /is a Stripe test key/.test(n) && !n.includes(KEY), n);
}
// --- clearing (delete project, retired live backend) ---
{
const puter = makePuter();
const WS = load(puter);
await WS.set('pay', 'STRIPE_SECRET_KEY', KEY);
const r = await WS.clear('pay');
check('clear: the keys and their list are removed', r.ok && r.cleared === 1 && puter.apps.get('app-pay').size === 0);
const before = puter.prompts.length;
const r2 = await WS.clear('other');
check('clear: a backend Builder never gave keys to is left alone, without asking', r2.ok && r2.cleared === 0 && puter.prompts.length === before);
}
{
const puter = makePuter();
const WS = load(puter);
await WS.set('pay-live', 'STRIPE_SECRET_KEY', LIVE_KEY, { liveOnly: true });
const r = await WS.clear('pay-live');
check('clear: a live backend\'s own keys and their marks go too', r.ok && r.cleared === 1 && puter.apps.get('app-pay-live').size === 0);
}
// --- the set_secret tool ---
{
const root = '/u/AppData/b/c1';
const puter = makePuter({ paths: { other: '/u/AppData/b/c2/workers/other.js' } });
const window = { tools: [] };
new Function('window', 'puter', read('../src/js/worker-ownership.js'))(window, puter);
new Function('window', 'puter', read('../src/js/worker-secrets.js'))(window, puter);
window.projectRootDir = () => root;
new Function('window', 'puter', read('../src/tools/workers/set_secret.js'))(window, puter);
const tool = window.tools.find((t) => t.function.name === 'set_secret');
check('tool: set_secret is registered as a strict tool', !!tool && tool.function.strict === true);
const ok = await tool.exec({ worker: 'pay', name: 'STRIPE_SECRET_KEY', value: KEY }, {});
check('tool: the key is saved for this project\'s worker', ok.success === true && puter.apps.get('app-pay').get('secret:STRIPE_SECRET_KEY') === KEY);
check('tool: the result tells the AI how to read it and never repeats it',
ok.read_it_with === "await me.puter.kv.get('secret:STRIPE_SECRET_KEY')" && !JSON.stringify(ok).includes(KEY));
let foreign = null;
try { await tool.exec({ worker: 'other', name: 'K', value: KEY }, {}); } catch (e) { foreign = e; }
check('tool: another project\'s worker is refused', !!foreign && /does not belong to this project/.test(foreign.message) && !puter.apps.has('app-other'));
let missing = null;
try { await tool.exec({ worker: 'nope', name: 'K', value: KEY }, {}); } catch (e) { missing = e; }
check('tool: an unknown worker says to create it first', !!missing && /create_worker first/.test(missing.message));
let badName = null;
try { await tool.exec({ worker: 'pay', name: 'my key', value: KEY }, {}); } catch (e) { badName = e; }
check('tool: a bad key name is refused', !!badName && /UPPER_SNAKE_CASE/.test(badName.message) && !JSON.stringify(badName.message).includes(KEY));
}
{
const puter = makePuter({ decline: ['app-pay'] });
const window = { tools: [] };
new Function('window', 'puter', read('../src/js/worker-ownership.js'))(window, puter);
new Function('window', 'puter', read('../src/js/worker-secrets.js'))(window, puter);
window.projectRootDir = () => '/u/AppData/b/c1';
new Function('window', 'puter', read('../src/tools/workers/set_secret.js'))(window, puter);
const r = await window.tools[0].exec({ worker: 'pay', name: 'STRIPE_SECRET_KEY', value: KEY }, {});
check('tool: a declined dialog tells the AI what to say, without the key', r.success === false && /didn't allow Builder/.test(r.error) && !JSON.stringify(r).includes(KEY));
}
// The live site's own key, through the tool. The live backend is deployed
// from the project's published folder (deployPublishedWorkers in ui.js).
function liveToolSetup(o = {}) {
const puter = makePuter({
workers: o.workers,
paths: Object.assign({ 'pay-live': '/u/AppData/b/.published/c1/backend/pay-live.js', other: '/u/AppData/b/c2/workers/other.js' }, o.paths),
});
const window = { tools: [] };
new Function('window', 'puter', read('../src/js/worker-ownership.js'))(window, puter);
new Function('window', 'puter', read('../src/js/worker-secrets.js'))(window, puter);
window.projectRootDir = () => '/u/AppData/b/c1';
window.publishedDirForChat = (chatId) => '/u/AppData/b/.published/' + chatId;
window.liveWorkerName = (name) => name + '-live';
new Function('window', 'puter', read('../src/tools/workers/set_secret.js'))(window, puter);
return { puter, tool: window.tools.find((t) => t.function.name === 'set_secret'), WS: window.WorkerSecrets };
}
{
const { puter, tool } = liveToolSetup();
check('tool: live is a required yes-or-no', tool.function.parameters.properties.live.type === 'boolean' && tool.function.parameters.required.includes('live'));
await tool.exec({ worker: 'pay', name: 'STRIPE_SECRET_KEY', value: KEY, live: false }, { currentChatId: 'c1' });
const r = await tool.exec({ worker: 'pay', name: 'STRIPE_SECRET_KEY', value: LIVE_KEY, live: true }, { currentChatId: 'c1' });
check('tool: live saves the key only in the published copy of that backend', r.success === true && r.worker === 'pay-live' && r.live === true &&
puter.apps.get('app-pay-live').get('secret:STRIPE_SECRET_KEY') === LIVE_KEY && puter.apps.get('app-pay').get('secret:STRIPE_SECRET_KEY') === KEY);
check('tool: a live key is marked as the live site\'s own', JSON.stringify(puter.apps.get('app-pay-live').get('secret-live-only')) === '["STRIPE_SECRET_KEY"]');
check('tool: the live result never repeats the key', !JSON.stringify(r).includes(LIVE_KEY));
let direct = null;
try { await tool.exec({ worker: 'pay-live', name: 'STRIPE_SECRET_KEY', value: KEY, live: false }, { currentChatId: 'c1' }); } catch (e) { direct = e; }
check('tool: the live backend can\'t be named directly', !!direct && /does not belong to this project/.test(direct.message) &&
puter.apps.get('app-pay-live').get('secret:STRIPE_SECRET_KEY') === LIVE_KEY);
}
{
const { puter, tool } = liveToolSetup({ workers: { pay: 'app-pay' } });
const r = await tool.exec({ worker: 'pay', name: 'STRIPE_SECRET_KEY', value: LIVE_KEY, live: true }, { currentChatId: 'c1' });
check('tool: a live key before the first publish asks for a publish, and saves nothing', r.success === false && /isn’t published yet/.test(r.error) &&
!puter.apps.has('app-pay') && puter.prompts.length === 0);
}
{
// A "pay-live" that belongs to another project's published site.
const { puter, tool } = liveToolSetup({ paths: { 'pay-live': '/u/AppData/b/.published/c9/backend/pay-live.js' } });
const r = await tool.exec({ worker: 'pay', name: 'STRIPE_SECRET_KEY', value: LIVE_KEY, live: true }, { currentChatId: 'c1' });
check('tool: a live backend of another project never gets the key', r.success === false && !puter.apps.has('app-pay-live'));
}
// --- the prompt and the wiring ---
{
const prompt = read('../src/js/prompt.js');
check('prompt: keys never go into the worker\'s code any more', !/The key goes ONLY in the worker's code/.test(prompt) && !/constant next to the key/.test(prompt));
check('prompt: keys are saved with set_secret and read from the worker\'s own store',
/set_secret/.test(prompt) && prompt.includes("await me.puter.kv.get('secret:STRIPE_SECRET_KEY')") && /never at the top of the script/.test(prompt));
// A key cached in the worker kept the old value after set_secret saved a
// new one, until the worker was redeployed.
check('prompt: keys are read on every request, never cached', /Read it on every request and never keep it in a variable outside the handler or cache it/.test(prompt) &&
!/if \(!\(name in keys\)\)/.test(prompt));
check('prompt: Stripe and Whop keys have fixed names', /set_secret as STRIPE_SECRET_KEY/.test(prompt) && /set_secret as WHOP_API_KEY/.test(prompt));
// A key already in an older project's code is the user's call. The AI
// offers to move it and moves it only once they agree.
check('prompt: a key already in code is offered a move, never moved unasked',
/still has a key written in its code \(an older project\), don't move it on your own/.test(prompt) &&
/offer to move it into the worker's private store\. Only once they agree, save it with set_secret/.test(prompt) &&
!/move it: save it with set_secret/.test(prompt));
check('prompt: a live key is saved for the live site only', /Save a live key \(sk_live_ or rk_live_\) with live set to true/.test(prompt) &&
/Save it with set_secret as WHOP_API_KEY with live set to true/.test(prompt));
check('prompt: the AI knows what Builder\'s line after a publish means', /After each publish, Builder adds a line in italics/.test(prompt) &&
!/Remind them about the live key whenever they publish/.test(prompt));
// The preview and the live site run the same code, so anything that
// differs between them (Whop's host, its business id) can't be in it.
check('prompt: Whop\'s host and business id live in the store, not the code',
/save the API host next to it as WHOP_API/.test(prompt) && /Never put the host in the code/.test(prompt) &&
/set_secret as WHOP_COMPANY_ID/.test(prompt) && !/const WHOP_API =/.test(prompt) && !/keep it in memory\./.test(prompt) &&
!/store it as a constant in the worker/.test(prompt));
const src = read('../src/js/worker-secrets.js');
check('helper: values are never logged or tracked', !/console\.|window\.track|showToast/.test(src));
check('helper: key-value access only', /const SCOPES = \{ kv: \['get', 'set', 'del'\] \};/.test(src) && !/fs:/.test(src));
const ui = read('../src/js/ui.js');
const publish = ui.slice(ui.indexOf('async function deployPublishedWorkers('), ui.indexOf('async function stripBrandingFromRelease('));
check('publish: the live backend gets the draft backend\'s keys after it deploys',
publish.indexOf('WorkerSecrets?.copy(worker.name, liveName)') > publish.indexOf('puter.workers.create(liveName'));
check('publish: what each live backend got rides on its rename', /renames\.push\(\{[^}]*\bkeys,\s*\}\)/.test(publish));
const success = ui.slice(ui.indexOf('window.currentPublishedAt = now;'), ui.indexOf('ok = true;', ui.indexOf('window.currentPublishedAt = now;')));
check('publish: the line goes into the open chat before it is saved',
success.includes('WorkerSecrets?.publishNote?.(') && success.includes('versionNote: true') &&
success.indexOf('publishNote') < success.indexOf('saveCurrentChat('));
const retire = ui.slice(ui.indexOf('async function retirePublishedWorkers('), ui.indexOf('async function doPublish('));
check('publish: a retired live backend loses its keys before it is deleted',
retire.indexOf('WorkerSecrets?.clear(worker.name)') > -1 && retire.indexOf('WorkerSecrets?.clear(worker.name)') < retire.indexOf('puter.workers.delete(worker.name)'));
check('publish: the tool can find the live backend', /^window\.liveWorkerName = liveWorkerName;$/m.test(ui) && /^window\.publishedDirForChat = publishedDirForChat;$/m.test(ui));
const app = read('../src/js/app.js');
check('duplicate: the copy\'s backends get the original\'s keys', app.includes('WorkerSecrets?.copy(plan.oldName, plan.newName)'));
const del = app.slice(app.indexOf('async function deleteChat('), app.indexOf('// Remove the app directory', app.indexOf('async function deleteChat(')));
check('delete: keys are cleared before each worker is deleted',
del.indexOf('WorkerSecrets?.clear(worker.name)') > -1 && del.indexOf('WorkerSecrets?.clear(worker.name)') < del.indexOf('puter.workers.delete(worker.name)'));
const vite = read('../vite.config.js');
const scripts = vite.slice(vite.indexOf('const SCRIPTS = ['), vite.indexOf('];', vite.indexOf('const SCRIPTS = [')));
check('bundle: the helper and the tool ship', scripts.includes("'js/worker-secrets.js'") && scripts.includes("'tools/workers/set_secret.js'") &&
scripts.indexOf("'js/worker-secrets.js'") < scripts.indexOf("'tools/workers/set_secret.js'"));
}
console.log(failures ? `\n${failures} check(s) failed` : '\nAll worker-secrets checks passed');
process.exit(failures ? 1 : 0);