/** * Prints the intake Lambda's six environment variables as the JSON that * `aws lambda update-function-configuration --environment` takes. * * ⚠️ THIS EXISTS SO THAT TWO PUBLISHED COMMITMENTS ARE NEVER RETYPED INTO A * SHELL COMMAND. `RESPONSE_TIME` and `NO_RETAINER_NOTICE` are read from * `src/data/site.ts` — the same constants `/contact/` renders — because a * hand-typed copy of the notice inside the handler had already dropped a clause * once (`docs/05`, and the handler's own comment on the constant). A deploy * procedure that asks an operator to paste a sentence is the same defect one * step further out, and the notice contains an EN DASH in "mediator–party", * which is exactly the character a retype loses. * * Resource names come from `AGENTS.md` §7 and are passed in, not defaulted from * a second copy here — except the two that are pure site facts. * * usage: node scripts/intake-env.mjs --table --notify --from * node scripts/intake-env.mjs ... --shell # export lines instead */ import { CONTACT, NO_RETAINER_NOTICE, SITE } from '../src/data/site.ts'; const args = process.argv.slice(2); const flag = (name) => { const i = args.indexOf(`--${name}`); return i === -1 ? undefined : args[i + 1]; }; const table = flag('table'); const notify = flag('notify'); const from = flag('from'); const missing = [ ['--table', table], ['--notify', notify], ['--from', from], ] .filter(([, v]) => !v) .map(([k]) => k); if (missing.length > 0) { console.error(`missing: ${missing.join(' ')}`); console.error( 'usage: node scripts/intake-env.mjs --table ' + '--notify
--from [--shell]', ); console.error('Resource names are in AGENTS.md §7.'); process.exit(2); } /* The site origin is not a deploy-time choice: the handler compares the request Origin against it and redirects to pages ON it, so it must be the canonical origin `astro.config.mjs` builds against. */ const origin = SITE.url.replace(/\/$/, ''); const vars = { INTAKE_TABLE: table, SITE_ORIGIN: origin, NOTIFY_TO: notify, MAIL_FROM: from, RESPONSE_TIME: CONTACT.responseTime, NO_RETAINER_NOTICE, }; /* Guards, not decoration. Each one is a failure this project has already had or has written down as the next one. */ for (const [k, v] of Object.entries(vars)) { if (typeof v !== 'string' || v.trim() === '') { throw new Error( `${k} resolved empty — the handler throws at cold start on that`, ); } } if (!/^https:\/\//.test(origin)) { throw new Error(`SITE_ORIGIN must be an https origin, got ${origin}`); } /* The clause a hand-copy dropped. `docs/01` §/contact/ requires it, so its absence is a published-disclosure defect rather than a typo. */ if (!NO_RETAINER_NOTICE.includes('create a conflict check')) { throw new Error( 'NO_RETAINER_NOTICE is missing its fourth clause about not itself creating ' + 'a conflict check — docs/01 §/contact/ requires it. Do not deploy this.', ); } if (!/–/.test(NO_RETAINER_NOTICE)) { throw new Error( 'NO_RETAINER_NOTICE no longer contains the en dash in "mediator–party". ' + 'Either the constant changed deliberately, or something re-typed it.', ); } if (!/\btwo business days\b/.test(CONTACT.responseTime)) { throw new Error( `RESPONSE_TIME is "${CONTACT.responseTime}" — AGENTS.md §4/Q27 is a ` + 'two-business-day commitment. If the commitment changed, /contact/, the ' + 'bio and this all move together.', ); } if (args.includes('--shell')) { for (const [k, v] of Object.entries(vars)) { console.log(`export ${k}=${JSON.stringify(v)}`); } } else { console.log(JSON.stringify({ Variables: vars })); }