From 8c1a01d856085d150c782b4ad3eb3fe9f614973a Mon Sep 17 00:00:00 2001 From: CRT Date: Tue, 20 Oct 2026 13:11:10 +0700 Subject: [PATCH] config loads without Node's MODULE_TYPELESS_PACKAGE_JSON warning in a CommonJS project; capture sign-in docs use captureAuth and check notes a local capture that signs in with reviewDemoAccount (the App Review account) --- CHANGELOG.md | 5 +++++ cli/check.ts | 7 +++++++ cli/lib/rt.ts | 5 ++++- skills/store-screenshots/SKILL.md | 11 +++++++---- tests/capture-review-account.test.ts | 15 +++++++++++++++ 5 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 tests/capture-review-account.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c3a9597..635bd3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +- **No more "Reparsing as ES module" warning** for a `.ts` config in a CommonJS project (Node's + MODULE_TYPELESS_PACKAGE_JSON); the app does not need `"type": "module"`. +- **Capture sign-in keeps App Review's account apart:** the docs use `captureAuth` for a local demo sign-in, and + `check` notes a capture against localhost that uses `reviewDemoAccount` (the account `apple review-details` sends). + ## 0.8.17 ยท 2026-10-20 - `shots capture --source web` can sign in first: `shots.capture.auth` is a sign-in request (run once per browser diff --git a/cli/check.ts b/cli/check.ts index aac7155..d85776e 100644 --- a/cli/check.ts +++ b/cli/check.ts @@ -152,6 +152,13 @@ export async function run(ctx: Ctx): Promise { try { for (const n of (await runScan(ctx.loaded.root, { config: ctx.loaded.path })).report.notes) if (n.startsWith('scan.ignore[')) note(n); } catch (e) { note(`scan.ignore not checked: ${(e as Error).message}`); } } if (config.apple && !config.app.copyright?.trim()) problems.push(COPYRIGHT_MISSING); + // the App Review demo account must work on the live backend: a capture sign-in against a local stack that uses it + // means local-only credentials are (or will be) stored where review-details sends them to App Review + { const cap = (config as { shots?: { capture?: { baseUrl?: string; auth?: { request?: { url?: string } } } } }).shots?.capture; + const authText = JSON.stringify(cap?.auth ?? {}); + const local = /\b(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]|[a-z0-9-]+\.(localhost|test|local))\b/i; + if (/cred:reviewDemoAccount/.test(authText) && (local.test(cap?.baseUrl ?? '') || local.test(cap?.auth?.request?.url ?? ''))) + note('shots.capture.auth signs in to a local stack with reviewDemoAccount, the account App Review gets from apple review-details: put the local demo sign-in in captureAuth in .storebound/credentials.json and use ${cred:}'); } const duo = iphoneDuoNote({ apple: !!config.apple, duo: !!config.apple?.iphoneDuo, play: !!config.play }); if (duo?.level === 'info') { hints.push(duo.msg); log('check', `hint: ${duo.msg}`); } else if (duo) note(duo.msg); const creative = creativeNote(config, ctx.loaded.resolve(config.renders ?? config.shots?.out ?? 'store/renders')); diff --git a/cli/lib/rt.ts b/cli/lib/rt.ts index da7bd45..9b43cc7 100644 --- a/cli/lib/rt.ts +++ b/cli/lib/rt.ts @@ -101,7 +101,10 @@ export async function importConfigModule(path: string): Promise { async function withoutTypeStripWarning(f: () => Promise): Promise { const emit = process.emitWarning; process.emitWarning = ((w: string | Error, ...rest: unknown[]) => { - if (/Type Stripping|type stripping/i.test(String(typeof w === 'string' ? w : w.message))) return; + // also Node's MODULE_TYPELESS_PACKAGE_JSON ("Reparsing as ES module") for a .ts config in a CommonJS package: + // the config is ours to load, the app need not add "type": "module" + const text = `${String(typeof w === 'string' ? w : w.message)} ${rest.map(r => (typeof r === 'object' && r ? JSON.stringify(r) : String(r))).join(' ')}`; + if (/Type Stripping|type stripping|Reparsing as ES module|MODULE_TYPELESS_PACKAGE_JSON/i.test(text)) return; return (emit as (...a: unknown[]) => void).call(process, w, ...rest); }) as typeof process.emitWarning; try { return await f(); } finally { process.emitWarning = emit; } diff --git a/skills/store-screenshots/SKILL.md b/skills/store-screenshots/SKILL.md index 0de95d9..7e99780 100644 --- a/skills/store-screenshots/SKILL.md +++ b/skills/store-screenshots/SKILL.md @@ -197,10 +197,13 @@ Key screens behind a login (`source: 'web'`): capture against a **local or demo demo account. Never real users' data in store screenshots. The same frames feed both stores (App Store and Google Play), so one sign-in setup covers both. Two ways to sign in, set in `shots.capture.auth`: -- **Sign-in request**: `{ request: { url: '/api/login', method: 'POST', body: { email: '${cred:reviewDemoAccount.username}', - password: '${cred:reviewDemoAccount.password}' } } }`. It runs once per browser context before its first shot, and - the context keeps the cookies. `${cred:NAME}` reads `.storebound/credentials.json` (`reviewDemoAccount.username` / - `.password`, also from REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD, or any key under `captureAuth`). `${env:NAME}` +- **Sign-in request**: `{ request: { url: '/api/login', method: 'POST', body: { email: '${cred:email}', + password: '${cred:password}' } } }` with `captureAuth: { email, password }` in `.storebound/credentials.json`. It + runs once per browser context before its first shot, and the context keeps the cookies. `${cred:NAME}` reads + `captureAuth.NAME` (or `reviewDemoAccount.username` / `.password`, also from REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD). + Keep the two apart: `reviewDemoAccount` is the account `apple review-details` sends to App Review and must work on + the live backend; a local demo stack's sign-in belongs in `captureAuth`. `check` warns when a capture against + localhost uses `reviewDemoAccount`. `${env:NAME}` reads the environment. A password-, token- or key-like field with a literal value is refused. A status outside 2xx/3xx stops the capture and shows the status, never the values. - **Saved session**: `{ storageState: '.storebound/auth.json' }`, made by `storebound shots login [--url ]`. It diff --git a/tests/capture-review-account.test.ts b/tests/capture-review-account.test.ts new file mode 100644 index 0000000..6b4e418 --- /dev/null +++ b/tests/capture-review-account.test.ts @@ -0,0 +1,15 @@ +// The App Review demo account (reviewDemoAccount, sent by apple review-details) must work on the live backend: a +// capture sign-in against a local stack that uses it gets a check note (a session nearly stored local-only creds there). +import { afterEach, expect, test } from 'bun:test'; +import { run as check } from '../cli/check'; +import { BASE, capture, jsonOut, project, restore } from './helpers/store-mock'; + +afterEach(restore); +const withAuth = (baseUrl: string, ref: string) => ({ ...BASE, play: undefined, shots: { capture: { baseUrl, auth: { request: { url: '/api/login', method: 'POST', body: { email: `\${cred:${ref}}`, password: `\${cred:${ref.replace('username', 'password').replace('email', 'password')}}` } } } } } }) as typeof BASE; +const notes = async (cfg: typeof BASE) => { const p = project(cfg, { '.gitignore': '.storebound/\n' }); const j = jsonOut((await capture(() => check(p.ctx(['--json'], ['json'])))).out); return JSON.stringify(j); }; + +test('local capture with reviewDemoAccount gets a note; captureAuth or a remote stack does not', async () => { + expect(await notes(withAuth('http://localhost:5173', 'reviewDemoAccount.username'))).toContain('captureAuth'); + expect(await notes(withAuth('http://localhost:5173', 'email'))).not.toContain('signs in to a local stack'); + expect(await notes(withAuth('https://staging.example.com', 'reviewDemoAccount.username'))).not.toContain('signs in to a local stack'); +});