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)

This commit is contained in:
CRTkafa 2026-10-20 13:11:10 +07:00
parent 2fdd96e68c
commit 8c1a01d856
5 changed files with 38 additions and 5 deletions

View file

@ -2,6 +2,11 @@
## Unreleased ## 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 ## 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 - `shots capture --source web` can sign in first: `shots.capture.auth` is a sign-in request (run once per browser

View file

@ -152,6 +152,13 @@ export async function run(ctx: Ctx): Promise<void> {
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}`); } 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); 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:<name>}'); }
const duo = iphoneDuoNote({ apple: !!config.apple, duo: !!config.apple?.iphoneDuo, play: !!config.play }); 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); 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')); const creative = creativeNote(config, ctx.loaded.resolve(config.renders ?? config.shots?.out ?? 'store/renders'));

View file

@ -101,7 +101,10 @@ export async function importConfigModule(path: string): Promise<any> {
async function withoutTypeStripWarning<T>(f: () => Promise<T>): Promise<T> { async function withoutTypeStripWarning<T>(f: () => Promise<T>): Promise<T> {
const emit = process.emitWarning; const emit = process.emitWarning;
process.emitWarning = ((w: string | Error, ...rest: unknown[]) => { 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); return (emit as (...a: unknown[]) => void).call(process, w, ...rest);
}) as typeof process.emitWarning; }) as typeof process.emitWarning;
try { return await f(); } finally { process.emitWarning = emit; } try { return await f(); } finally { process.emitWarning = emit; }

View file

@ -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 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`: 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}', - **Sign-in request**: `{ request: { url: '/api/login', method: 'POST', body: { email: '${cred:email}',
password: '${cred:reviewDemoAccount.password}' } } }`. It runs once per browser context before its first shot, and password: '${cred:password}' } } }` with `captureAuth: { email, password }` in `.storebound/credentials.json`. It
the context keeps the cookies. `${cred:NAME}` reads `.storebound/credentials.json` (`reviewDemoAccount.username` / runs once per browser context before its first shot, and the context keeps the cookies. `${cred:NAME}` reads
`.password`, also from REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD, or any key under `captureAuth`). `${env:NAME}` `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 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. 2xx/3xx stops the capture and shows the status, never the values.
- **Saved session**: `{ storageState: '.storebound/auth.json' }`, made by `storebound shots login [--url <url>]`. It - **Saved session**: `{ storageState: '.storebound/auth.json' }`, made by `storebound shots login [--url <url>]`. It

View file

@ -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');
});