From d3d31d6c1259365a110efdb9294cf69cc197511a Mon Sep 17 00:00:00 2001 From: CRT Date: Thu, 22 Oct 2026 23:18:30 +0800 Subject: [PATCH] Node: JSON configs are read as data, not imported (store-only apps failed under npx: needs an import attribute); test, pitfall 105 --- CHANGELOG.md | 2 ++ cli/lib/rt.ts | 3 +++ skills/store-pitfalls/SKILL.md | 7 +++++++ tests/config-json-node.test.ts | 12 ++++++++++++ 4 files changed, 24 insertions(+) create mode 100644 tests/config-json-node.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 844217e..630986b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Node: a `storebound.config.json` (and every store-only app) loads again; it needed an import attribute. Pitfall 105. + - **`storebound watch`**: store events for every app on the machine (rejected, approved, live, rollout halted, crash threshold, low or unanswered reviews), sent to ntfy (phone), Telegram, a webhook or an OS notification; runs as a loop (`--every 15m`) or `--once`; a PM2 recipe for a server is in docs/watch.md. `storebound events` and the MCP tool diff --git a/cli/lib/rt.ts b/cli/lib/rt.ts index 9b43cc7..d323d45 100644 --- a/cli/lib/rt.ts +++ b/cli/lib/rt.ts @@ -82,6 +82,9 @@ export const importPath = (path: string): Promise => import(pathToFileURL(p */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export async function importConfigModule(path: string): Promise { + // a JSON config is read, not imported: Node refuses a JSON import without `with { type: 'json' }` (every store-only + // app of `storebound watch` on a server failed; pitfall 105) + if (/\.json$/i.test(path)) return { default: JSON.parse(readFileSync(path, 'utf8')) }; if (isBun || !/\.[cm]?ts$/.test(path)) return importPath(path); try { return await withoutTypeStripWarning(() => importPath(path)); diff --git a/skills/store-pitfalls/SKILL.md b/skills/store-pitfalls/SKILL.md index c44253a..a88eb70 100644 --- a/skills/store-pitfalls/SKILL.md +++ b/skills/store-pitfalls/SKILL.md @@ -1265,3 +1265,10 @@ that fails without the fix (see CONTRIBUTING.md). - **Guard:** `iapLocalizationChanges` / `iapAttributeChanges` / `availabilityChange` (`cli/apple/iap.ts`), the subscription helpers in `cli/lib/subscriptions.ts`, `planAppInfo` / `reviewDetailDiff`, `campaignDiff`, `winBackDiff`, `eventAttrDiff`; tests `apple-iap-drift`, `subscriptions-drift`, `appinfo-drift`, `misc-drift`. + +## 105. `storebound watch` on a server read no app: "needs an import attribute of type: json" +- **Symptom:** on Node (npx, a server), every store-only app failed with `Module ".../storebound.config.json" needs an + import attribute of "type: json"`; on Bun it worked. +- **Cause:** a JSON config (store-only apps get one generated) was loaded with a bare dynamic import, which Node refuses. +- **Do:** update storebound. +- **Guard:** `importConfigModule` in `cli/lib/rt.ts` reads JSON configs as data (`tests/config-json-node.test.ts`). diff --git a/tests/config-json-node.test.ts b/tests/config-json-node.test.ts new file mode 100644 index 0000000..b2cd820 --- /dev/null +++ b/tests/config-json-node.test.ts @@ -0,0 +1,12 @@ +// A storebound.config.json loads without an import attribute (Node refuses a bare JSON import; pitfall 105). +import { expect, test } from 'bun:test'; +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { importConfigModule } from '../cli/lib/rt'; + +test('importConfigModule reads a JSON config as data', async () => { + const f = join(mkdtempSync(join(tmpdir(), 'sb-json-')), 'storebound.config.json'); + writeFileSync(f, JSON.stringify({ app: { name: 'Demo' }, play: { packageName: 'com.example.demo' } })); + expect((await importConfigModule(f)).default.play.packageName).toBe('com.example.demo'); +});