@botim/mp-debug-sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -164,6 +164,14 @@ console.log(handle.sid); // server-issued session id
164
164
  - **Single in-flight long-poll**: at most one outbound request per device for the AI command channel.
165
165
  - **Self-event suppression**: the SDK captures `fetch` reference at import time and routes its own ingest/poll calls through that pre-interception fetch — no risk of the SDK observing itself and creating a feedback loop.
166
166
 
167
+ ## Debugging a live mini-program
168
+
169
+ Once your build is wired and shipped, see **[`docs/live-debugging.md`](./docs/live-debugging.md)** for the end-to-end runbook against the shared demo relay at `https://aistudiodemo.ext.algento.com/mp-debug-relay`. It covers:
170
+
171
+ - pointing the Vite plugin at the live relay URL,
172
+ - pulling errors and tailing sessions from the admin UI,
173
+ - a dedicated **For AI agents** section with `curl` recipes for `/v1/mp/{mpId}/events`, the SSE live-tail, and command POSTs.
174
+
167
175
  ## API reference
168
176
 
169
177
  | Symbol | Purpose |
@@ -66,24 +66,41 @@ function resolveBotimConfig(mode, root, opts = {}) {
66
66
  }
67
67
  const env = mapped;
68
68
  const projectRoot = (0, import_node_path.isAbsolute)(root) ? root : (0, import_node_path.resolve)(process.cwd(), root);
69
- const filePath = (0, import_node_path.resolve)(projectRoot, `botim.${env}.json`);
70
- if (!(0, import_node_fs.existsSync)(filePath)) {
69
+ const fileName = resolveFileName(opts.configFileName, env, mode);
70
+ const filePath = (0, import_node_path.isAbsolute)(fileName) ? fileName : (0, import_node_path.resolve)(projectRoot, fileName);
71
+ const inline = opts.inlineConfig;
72
+ const inlineHasIdentity = typeof inline?.mp_id === "string" && inline.mp_id.length > 0 ? true : typeof inline?.miniProgramId === "string" && inline.miniProgramId.length > 0;
73
+ let raw;
74
+ let parsedFromFile = {};
75
+ if ((0, import_node_fs.existsSync)(filePath)) {
76
+ raw = (0, import_node_fs.readFileSync)(filePath, "utf8");
77
+ try {
78
+ parsedFromFile = JSON.parse(raw);
79
+ } catch (err) {
80
+ const detail = err instanceof Error ? err.message : String(err);
81
+ throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {
82
+ code: "config-invalid-json",
83
+ path: filePath
84
+ });
85
+ }
86
+ } else if (!inlineHasIdentity) {
71
87
  throw new BotimConfigError(
72
88
  `expected config file not found: ${filePath}
73
- Create botim.${env}.json at the project root with at least { "mp_id": "..." }.`,
89
+ Either create ${fileName} at the project root with at least { "mp_id": "..." },
90
+ pass an explicit { configFileName } / { config: { mp_id: "..." } } to botimDebug(),
91
+ or supply inlineConfig with mp_id when calling resolveBotimConfig() directly.`,
74
92
  { code: "config-missing", path: filePath }
75
93
  );
76
94
  }
77
- const raw = (0, import_node_fs.readFileSync)(filePath, "utf8");
78
- let parsed;
79
- try {
80
- parsed = JSON.parse(raw);
81
- } catch (err) {
82
- const detail = err instanceof Error ? err.message : String(err);
83
- throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {
84
- code: "config-invalid-json",
85
- path: filePath
86
- });
95
+ const parsed = {
96
+ ...parsedFromFile,
97
+ ...inline
98
+ };
99
+ if (parsedFromFile.app || inline?.app) {
100
+ parsed.app = { ...parsedFromFile.app, ...inline?.app };
101
+ }
102
+ if (parsedFromFile.package_url || inline?.package_url) {
103
+ parsed.package_url = { ...parsedFromFile.package_url, ...inline?.package_url };
87
104
  }
88
105
  if (parsed.deleted === 1) {
89
106
  throw new BotimConfigError(
@@ -112,7 +129,8 @@ function resolveBotimConfig(mode, root, opts = {}) {
112
129
  } else if (versionForSig && md5) {
113
130
  buildSignature = `v${versionForSig}+md5:${md5.slice(0, 12)}`;
114
131
  } else {
115
- buildSignature = "sha256:" + (0, import_node_crypto.createHash)("sha256").update(raw).digest("hex").slice(0, 12);
132
+ const sigSource = raw ?? JSON.stringify({ mp_id: idValue, env, version: versionForSig, app: parsed.app });
133
+ buildSignature = "sha256:" + (0, import_node_crypto.createHash)("sha256").update(sigSource).digest("hex").slice(0, 12);
116
134
  }
117
135
  const cfg = {
118
136
  // Spread first so the canonical fields below override anything inherited.
@@ -125,6 +143,31 @@ function resolveBotimConfig(mode, root, opts = {}) {
125
143
  };
126
144
  return cfg;
127
145
  }
146
+ function resolveFileName(override, env, mode) {
147
+ if (override === void 0) return `botim.${env}.json`;
148
+ if (typeof override === "string") {
149
+ if (override.length === 0) {
150
+ throw new BotimConfigError("configFileName must be a non-empty string", {
151
+ code: "config-invalid-filename"
152
+ });
153
+ }
154
+ return override;
155
+ }
156
+ if (typeof override === "function") {
157
+ const result = override(env, mode);
158
+ if (typeof result !== "string" || result.length === 0) {
159
+ throw new BotimConfigError(
160
+ `configFileName(${env}, ${mode}) must return a non-empty string (got ${JSON.stringify(result)})`,
161
+ { code: "config-invalid-filename" }
162
+ );
163
+ }
164
+ return result;
165
+ }
166
+ throw new BotimConfigError(
167
+ `configFileName must be a string or (env, mode) => string (got ${typeof override})`,
168
+ { code: "config-invalid-filename" }
169
+ );
170
+ }
128
171
 
129
172
  // src/vite/plugin.ts
130
173
  var VIRTUAL_ID = "virtual:botim/config";
@@ -140,7 +183,11 @@ function botimDebug(options = {}) {
140
183
  const mode = options.mode ?? viteConfig.mode;
141
184
  const root = options.root ?? viteConfig.root;
142
185
  try {
143
- resolved = resolveBotimConfig(mode, root, { mapMode: options.mapMode });
186
+ resolved = resolveBotimConfig(mode, root, {
187
+ mapMode: options.mapMode,
188
+ configFileName: options.configFileName,
189
+ inlineConfig: options.config
190
+ });
144
191
  if (options.relayUrl) {
145
192
  resolved.relayUrl = options.relayUrl.replace(/\/+$/, "");
146
193
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vite/plugin.ts","../../src/vite/resolve-config.ts","../../src/errors.ts"],"sourcesContent":["/**\n * Vite plugin for `@botim/debug-sdk`.\n *\n * - Reads `botim.{mode}.json` from the consumer's project root at build time.\n * - Validates required fields; fails the build loudly if anything is wrong.\n * - Exposes the resolved config via the virtual module `virtual:botim/config`\n * as `export const botimConfig: Readonly<BotimConfig>`.\n *\n * The plugin's only Vite-specific surface is the `Plugin` type; we keep the\n * import as `type-only` so `vite` remains an *optional* peer dependency. A\n * consumer that never installs Vite will never load this file (it's only\n * reachable via the `./vite` subpath export).\n */\n\nimport type { Plugin } from 'vite';\nimport { resolveBotimConfig } from './resolve-config.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VIRTUAL_ID = 'virtual:botim/config';\nconst RESOLVED_VIRTUAL_ID = '\\0' + VIRTUAL_ID;\n\nexport interface BotimDebugPluginOptions {\n /**\n * Override Vite's `mode`. Useful when the build is driven outside Vite's\n * usual `--mode` flag, or in tests. If omitted, the plugin uses the mode\n * Vite resolves from the CLI / config.\n */\n mode?: string;\n /**\n * Map Vite's mode to a BotimEnv. Defaults: `development` → `dev`,\n * `production` → `prod`, others pass through verbatim.\n */\n mapMode?: (mode: string) => BotimEnv | string;\n /**\n * Project root override. Defaults to Vite's resolved `config.root`.\n */\n root?: string;\n /**\n * Relay endpoint to bake into `virtual:botim/config` as `relayUrl`. The\n * SDK reads this at runtime so the host app can call:\n *\n * enableRemoteDebug({ endpoint: botimConfig.relayUrl, … })\n *\n * Without this option, hosts must either set up a Vite proxy\n * (`server.proxy: { '/v1': 'http://localhost:8090' }`) and pass\n * `endpoint: location.origin`, OR pass an explicit URL at the call\n * site. Passing it through the plugin centralises the wiring and makes\n * mode-specific URLs trivial: read process.env.RELAY_URL in\n * vite.config and forward it here.\n *\n * The relay must allow the page's origin via CORS (CORS_ORIGINS env on\n * @botim/debug-relay defaults to \"*\"). Trailing slash is stripped at\n * resolve time so `/v1/attach` joins cleanly.\n */\n relayUrl?: string;\n}\n\nexport function botimDebug(options: BotimDebugPluginOptions = {}): Plugin {\n let resolved: BotimConfig | null = null;\n let resolvedFromMode: string | null = null;\n let resolvedError: Error | null = null;\n\n return {\n name: '@botim/debug-sdk:vite',\n enforce: 'pre',\n\n configResolved(viteConfig) {\n const mode = options.mode ?? viteConfig.mode;\n const root = options.root ?? viteConfig.root;\n try {\n resolved = resolveBotimConfig(mode, root, { mapMode: options.mapMode });\n // Merge the host-provided relay URL into the resolved config bag.\n // Strip trailing slash so callers don't get `/v1//attach` if they\n // pass `https://relay.example.com/`.\n if (options.relayUrl) {\n (resolved as BotimConfig & { relayUrl: string }).relayUrl =\n options.relayUrl.replace(/\\/+$/, '');\n }\n resolvedFromMode = mode;\n } catch (err) {\n // Stash the error; surface it the moment a build module imports the\n // virtual module. We don't throw from `configResolved` directly because\n // some consumers run Vite for non-build tasks (e.g. `vite-node` for\n // tooling) where the virtual module is never imported and a hard fail\n // would be a regression.\n resolved = null;\n resolvedError = err instanceof Error ? err : new Error(String(err));\n resolvedFromMode = mode;\n }\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_VIRTUAL_ID) return null;\n\n if (!resolved) {\n const message =\n resolvedError?.message ??\n `[@botim/debug-sdk] virtual:botim/config requested but configResolved did not run (mode=${resolvedFromMode ?? 'unknown'})`;\n // `this.error` aborts the build with a properly attributed Vite error.\n this.error(message);\n }\n\n // Frozen so a runtime mutation can't accidentally taint the constant.\n const body =\n `// AUTO-GENERATED by @botim/debug-sdk:vite — do not edit.\\n` +\n `export const botimConfig = Object.freeze(${JSON.stringify(resolved)});\\n` +\n `export default botimConfig;\\n`;\n return { code: body, map: null };\n },\n };\n}\n\nexport { resolveBotimConfig } from './resolve-config.js';\nexport { BotimConfigError } from '../errors.js';\nexport type { BotimConfig, BotimEnv } from '../types.js';\n","/**\n * Build-time resolver for `botim.{env}.json`.\n *\n * The real BOTIM config schema is large and platform-managed (logos, package\n * URLs, framework metadata, grayscale rollout, etc). The SDK only needs a few\n * canonical fields, so this resolver acts as a *normalizer*:\n *\n * external schema → SDK-internal `BotimConfig`\n * ─────────────── ──────────────────────────\n * mp_id → miniProgramId\n * app_id → appId (extra; preserved)\n * version → appVersion\n * app.md5 | package_url.md5 → buildSignature (deterministic per release)\n * <env from filename> → env\n *\n * Reads `botim.{env}.json` from the consumer's project root. Build-time only:\n * uses Node `fs`/`crypto`. Must NEVER be imported from device-targeted code.\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve, isAbsolute } from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport { BotimConfigError } from '../errors.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VALID_ENVS: readonly BotimEnv[] = ['dev', 'uat', 'beta', 'prod'];\n/**\n * Mini-program ids in the real config are short kebab/snake-case slugs like\n * `mbrx_p2p` — case-insensitive, allows `_` and `-`, must start with a letter.\n */\nconst ID_PATTERN = /^[a-z][a-z0-9_-]{1,63}$/i;\n\nexport interface ResolveOptions {\n /** Map a Vite mode (e.g. \"development\", \"staging\") to a BotimEnv. */\n mapMode?: (mode: string) => BotimEnv | string;\n}\n\n/** Built-in mapping; consumers can override via `mapMode`. */\nfunction defaultMapMode(mode: string): string {\n if (mode === 'development') return 'dev';\n if (mode === 'production') return 'prod';\n return mode;\n}\n\nfunction isBotimEnv(s: string): s is BotimEnv {\n return (VALID_ENVS as readonly string[]).includes(s);\n}\n\ninterface RawBotimFile {\n /** Canonical mini-program id used by the BOTIM platform, e.g. \"mbrx_p2p\". */\n mp_id?: string;\n /** Reverse-DNS app id, e.g. \"me.botim.rd.p2p\". Different concept from mp_id. */\n app_id?: string;\n /** Release version of the mini-program package, e.g. \"0.0.127\". */\n version?: string;\n /** Optional override; otherwise we derive from version+md5. */\n buildSignature?: string;\n /** Per-package metadata; when present we use `app.md5` for the signature. */\n app?: { version?: string; md5?: string; updateUrl?: string };\n /** Older/alternate location for the same md5. */\n package_url?: { md5?: string; version?: string };\n /** Operational flags maintained by the platform. */\n disabled?: number;\n deleted?: number;\n mp_status?: number;\n /** Forward-compat for everything else (logos, framework, grayscale, ...). */\n [extra: string]: unknown;\n /** Legacy alias accepted for forward/back compat. */\n miniProgramId?: string;\n}\n\n/**\n * Resolve `botim.{env}.json` for the given Vite `mode` from `root`.\n *\n * Throws `BotimConfigError` synchronously on:\n * - unknown env after `mapMode` is applied\n * - missing or unreadable file\n * - invalid JSON\n * - missing/invalid `mp_id` (or its legacy `miniProgramId` alias)\n * - the platform marking the mini-program as deleted (`deleted: 1`)\n */\nexport function resolveBotimConfig(\n mode: string,\n root: string,\n opts: ResolveOptions = {},\n): BotimConfig {\n if (!mode) {\n throw new BotimConfigError('mode is required', { code: 'no-mode' });\n }\n\n const mapped = (opts.mapMode ?? defaultMapMode)(mode);\n if (typeof mapped !== 'string' || !isBotimEnv(mapped)) {\n throw new BotimConfigError(\n `mode \"${mode}\" mapped to \"${mapped}\" which is not a valid BotimEnv (expected one of: ${VALID_ENVS.join(', ')})`,\n { code: 'invalid-env' },\n );\n }\n const env: BotimEnv = mapped;\n\n const projectRoot = isAbsolute(root) ? root : resolve(process.cwd(), root);\n const filePath = resolve(projectRoot, `botim.${env}.json`);\n\n if (!existsSync(filePath)) {\n throw new BotimConfigError(\n `expected config file not found: ${filePath}\\n` +\n ` Create botim.${env}.json at the project root with at least { \"mp_id\": \"...\" }.`,\n { code: 'config-missing', path: filePath },\n );\n }\n\n const raw = readFileSync(filePath, 'utf8');\n let parsed: RawBotimFile;\n try {\n parsed = JSON.parse(raw) as RawBotimFile;\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {\n code: 'config-invalid-json',\n path: filePath,\n });\n }\n\n // Hard-stop on a decommissioned mini-program — refusing the build is far\n // safer than shipping a debug SDK that targets a relay slot the platform\n // has retired.\n if (parsed.deleted === 1) {\n throw new BotimConfigError(\n `${filePath} reports the mini-program as deleted (deleted: 1). Cannot ship debug SDK against a retired mini-program.`,\n { code: 'config-deleted', path: filePath },\n );\n }\n\n // Read the canonical id; fall back to the SDK-internal alias for repos that\n // hand-write a config without the platform's `mp_id` field.\n const idValue = (parsed.mp_id ?? parsed.miniProgramId) as unknown;\n if (typeof idValue !== 'string' || idValue.length === 0) {\n throw new BotimConfigError(\n `${filePath} is missing required field \"mp_id\" (must be a non-empty string)`,\n { code: 'config-missing-field', path: filePath },\n );\n }\n if (!ID_PATTERN.test(idValue)) {\n throw new BotimConfigError(\n `${filePath} field \"mp_id\" must match ${ID_PATTERN.toString()} (got: ${JSON.stringify(idValue)})`,\n { code: 'config-invalid-id', path: filePath },\n );\n }\n\n // ── Derive a stable, release-correlated buildSignature ────────────────────\n // Preference order:\n // 1. explicit buildSignature in the file (consumers may pin one)\n // 2. version + package md5 (deterministic per release; survives unrelated\n // platform-side mutations like update_time / mp_status_time)\n // 3. sha256 of the file contents (last-resort fallback)\n const md5 =\n typeof parsed.app?.md5 === 'string'\n ? parsed.app.md5\n : typeof parsed.package_url?.md5 === 'string'\n ? parsed.package_url.md5\n : undefined;\n const versionForSig =\n (typeof parsed.version === 'string' && parsed.version) ||\n (typeof parsed.app?.version === 'string' && parsed.app.version) ||\n undefined;\n\n let buildSignature: string;\n if (typeof parsed.buildSignature === 'string' && parsed.buildSignature.length > 0) {\n buildSignature = parsed.buildSignature;\n } else if (versionForSig && md5) {\n buildSignature = `v${versionForSig}+md5:${md5.slice(0, 12)}`;\n } else {\n buildSignature = 'sha256:' + createHash('sha256').update(raw).digest('hex').slice(0, 12);\n }\n\n const cfg: BotimConfig = {\n // Spread first so the canonical fields below override anything inherited.\n ...parsed,\n miniProgramId: idValue,\n env,\n buildSignature,\n appName: typeof parsed.app_id === 'string' ? parsed.app_id : (parsed.appName as string | undefined),\n appVersion:\n versionForSig ?? (typeof parsed.appVersion === 'string' ? parsed.appVersion : undefined),\n };\n\n return cfg;\n}\n\nexport const __test__ = { defaultMapMode, ID_PATTERN, VALID_ENVS };\n","/**\n * Errors that escape the SDK into host code.\n *\n * The SDK's no-throw invariant has exactly two carve-outs:\n * - `BotimConfigError` — thrown synchronously by `enableRemoteDebug` when the\n * resolved `BotimConfig` is missing or invalid. Prevents any I/O.\n * - `BotimConsentError` — thrown synchronously by `enableRemoteDebug` when\n * consent has not been granted in a `prod` build. Prevents any I/O.\n *\n * Both are intentionally synchronous and pre-installation: at the moment they\n * fire, no globals have been wrapped, no listeners installed, no network sent.\n */\n\nconst BRAND = '@botim/debug-sdk' as const;\n\nexport class BotimConfigError extends Error {\n readonly name = 'BotimConfigError';\n readonly code: string;\n readonly path?: string;\n\n constructor(message: string, opts: { code: string; path?: string } = { code: 'invalid-config' }) {\n super(`[${BRAND}] ${message}`);\n this.code = opts.code;\n this.path = opts.path;\n }\n}\n\nexport class BotimConsentError extends Error {\n readonly name = 'BotimConsentError';\n constructor(message: string) {\n super(`[${BRAND}] ${message}`);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/vite/plugin.ts","../../src/vite/resolve-config.ts","../../src/errors.ts"],"sourcesContent":["/**\n * Vite plugin for `@botim/debug-sdk`.\n *\n * - Reads `botim.{mode}.json` from the consumer's project root at build time.\n * - Validates required fields; fails the build loudly if anything is wrong.\n * - Exposes the resolved config via the virtual module `virtual:botim/config`\n * as `export const botimConfig: Readonly<BotimConfig>`.\n *\n * The plugin's only Vite-specific surface is the `Plugin` type; we keep the\n * import as `type-only` so `vite` remains an *optional* peer dependency. A\n * consumer that never installs Vite will never load this file (it's only\n * reachable via the `./vite` subpath export).\n */\n\nimport type { Plugin } from 'vite';\nimport { resolveBotimConfig, type InlineBotimConfigInput } from './resolve-config.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VIRTUAL_ID = 'virtual:botim/config';\nconst RESOLVED_VIRTUAL_ID = '\\0' + VIRTUAL_ID;\n\nexport interface BotimDebugPluginOptions {\n /**\n * Override Vite's `mode`. Useful when the build is driven outside Vite's\n * usual `--mode` flag, or in tests. If omitted, the plugin uses the mode\n * Vite resolves from the CLI / config.\n */\n mode?: string;\n /**\n * Map Vite's mode to a BotimEnv. Defaults: `development` → `dev`,\n * `production` → `prod`, others pass through verbatim.\n */\n mapMode?: (mode: string) => BotimEnv | string;\n /**\n * Project root override. Defaults to Vite's resolved `config.root`.\n */\n root?: string;\n /**\n * Relay endpoint to bake into `virtual:botim/config` as `relayUrl`. The\n * SDK reads this at runtime so the host app can call:\n *\n * enableRemoteDebug({ endpoint: botimConfig.relayUrl, … })\n *\n * Without this option, hosts must either set up a Vite proxy\n * (`server.proxy: { '/v1': 'http://localhost:8090' }`) and pass\n * `endpoint: location.origin`, OR pass an explicit URL at the call\n * site. Passing it through the plugin centralises the wiring and makes\n * mode-specific URLs trivial: read process.env.RELAY_URL in\n * vite.config and forward it here.\n *\n * The relay must allow the page's origin via CORS (CORS_ORIGINS env on\n * @botim/debug-relay defaults to \"*\"). Trailing slash is stripped at\n * resolve time so `/v1/attach` joins cleanly.\n */\n relayUrl?: string;\n /**\n * Override the default `botim.{env}.json` filename pattern. Pass a string\n * for a fixed name (e.g. `\"botim_config.beta.json\"`) or a function for\n * per-env logic (e.g. `(env) => `botim_config.${env}.json``).\n *\n * Resolved relative to the project root unless absolute.\n */\n configFileName?: string | ((env: BotimEnv, mode: string) => string);\n /**\n * Inline config bag that **takes precedence** over the on-disk file.\n *\n * Useful when:\n * - mp_id / version come from env vars (CI builds, monorepo packages)\n * - the host wants to ship without any `botim.*.json` file at all\n * (set `mp_id` here and the resolver tolerates a missing file)\n * - a single field needs patching per build without editing JSON\n *\n * Merge semantics: the file is loaded first (if present), then these\n * keys are spread on top. Sub-objects (`app`, `package_url`) are\n * shallow-merged so patching `app.md5` keeps `app.version` from the file.\n */\n config?: InlineBotimConfigInput;\n}\n\nexport function botimDebug(options: BotimDebugPluginOptions = {}): Plugin {\n let resolved: BotimConfig | null = null;\n let resolvedFromMode: string | null = null;\n let resolvedError: Error | null = null;\n\n return {\n name: '@botim/debug-sdk:vite',\n enforce: 'pre',\n\n configResolved(viteConfig) {\n const mode = options.mode ?? viteConfig.mode;\n const root = options.root ?? viteConfig.root;\n try {\n resolved = resolveBotimConfig(mode, root, {\n mapMode: options.mapMode,\n configFileName: options.configFileName,\n inlineConfig: options.config,\n });\n // Merge the host-provided relay URL into the resolved config bag.\n // Strip trailing slash so callers don't get `/v1//attach` if they\n // pass `https://relay.example.com/`.\n if (options.relayUrl) {\n (resolved as BotimConfig & { relayUrl: string }).relayUrl =\n options.relayUrl.replace(/\\/+$/, '');\n }\n resolvedFromMode = mode;\n } catch (err) {\n // Stash the error; surface it the moment a build module imports the\n // virtual module. We don't throw from `configResolved` directly because\n // some consumers run Vite for non-build tasks (e.g. `vite-node` for\n // tooling) where the virtual module is never imported and a hard fail\n // would be a regression.\n resolved = null;\n resolvedError = err instanceof Error ? err : new Error(String(err));\n resolvedFromMode = mode;\n }\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_VIRTUAL_ID) return null;\n\n if (!resolved) {\n const message =\n resolvedError?.message ??\n `[@botim/debug-sdk] virtual:botim/config requested but configResolved did not run (mode=${resolvedFromMode ?? 'unknown'})`;\n // `this.error` aborts the build with a properly attributed Vite error.\n this.error(message);\n }\n\n // Frozen so a runtime mutation can't accidentally taint the constant.\n const body =\n `// AUTO-GENERATED by @botim/debug-sdk:vite — do not edit.\\n` +\n `export const botimConfig = Object.freeze(${JSON.stringify(resolved)});\\n` +\n `export default botimConfig;\\n`;\n return { code: body, map: null };\n },\n };\n}\n\nexport { resolveBotimConfig } from './resolve-config.js';\nexport type { InlineBotimConfigInput, ResolveOptions } from './resolve-config.js';\nexport { BotimConfigError } from '../errors.js';\nexport type { BotimConfig, BotimEnv } from '../types.js';\n","/**\n * Build-time resolver for `botim.{env}.json`.\n *\n * The real BOTIM config schema is large and platform-managed (logos, package\n * URLs, framework metadata, grayscale rollout, etc). The SDK only needs a few\n * canonical fields, so this resolver acts as a *normalizer*:\n *\n * external schema → SDK-internal `BotimConfig`\n * ─────────────── ──────────────────────────\n * mp_id → miniProgramId\n * app_id → appId (extra; preserved)\n * version → appVersion\n * app.md5 | package_url.md5 → buildSignature (deterministic per release)\n * <env from filename> → env\n *\n * Reads `botim.{env}.json` from the consumer's project root. Build-time only:\n * uses Node `fs`/`crypto`. Must NEVER be imported from device-targeted code.\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve, isAbsolute } from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport { BotimConfigError } from '../errors.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VALID_ENVS: readonly BotimEnv[] = ['dev', 'uat', 'beta', 'prod'];\n/**\n * Mini-program ids in the real config are short kebab/snake-case slugs like\n * `mbrx_p2p` — case-insensitive, allows `_` and `-`, must start with a letter.\n */\nconst ID_PATTERN = /^[a-z][a-z0-9_-]{1,63}$/i;\n\n/**\n * Inline config bag that consumers can pass to the Vite plugin (or the\n * resolver directly) instead of — or on top of — `botim.{env}.json`.\n *\n * Shape mirrors the on-disk file (`mp_id`, `app_id`, `version`, `app.md5`, …)\n * so the same normalizer pipeline runs whether the source is the filesystem\n * or a literal object. Unknown keys are forwarded as-is via the\n * `[extra: string]: unknown` index so the platform can keep adding fields\n * without breaking existing builds.\n */\nexport type InlineBotimConfigInput = {\n mp_id?: string;\n /** Legacy alias accepted for forward/back compat. */\n miniProgramId?: string;\n app_id?: string;\n version?: string;\n buildSignature?: string;\n app?: { version?: string; md5?: string; updateUrl?: string };\n package_url?: { md5?: string; version?: string };\n appName?: string;\n appVersion?: string;\n [extra: string]: unknown;\n};\n\nexport interface ResolveOptions {\n /** Map a Vite mode (e.g. \"development\", \"staging\") to a BotimEnv. */\n mapMode?: (mode: string) => BotimEnv | string;\n /**\n * Override the default `botim.{env}.json` filename pattern. Pass a string\n * for a fixed name (e.g. `\"botim_config.beta.json\"`) or a function that\n * receives the resolved env and original mode for per-env logic\n * (e.g. `(env) => `botim_config.${env}.json``).\n *\n * The result is resolved relative to the project root unless it is an\n * absolute path.\n */\n configFileName?: string | ((env: BotimEnv, mode: string) => string);\n /**\n * Inline config bag that **takes precedence** over the on-disk file.\n *\n * Merge semantics: file is loaded first (if present), then inline keys are\n * spread on top so callers can patch a single field without touching the\n * JSON (`{ mp_id: 'mbrx_p2p', version: process.env.VERSION }`).\n *\n * When no file exists at the resolved path, the resolver tolerates the\n * miss as long as `inlineConfig` carries a usable `mp_id` (or its legacy\n * `miniProgramId` alias). This is the file-less workflow — handy for\n * monorepos / native hosts that synthesize identity from env vars.\n */\n inlineConfig?: InlineBotimConfigInput;\n}\n\n/** Built-in mapping; consumers can override via `mapMode`. */\nfunction defaultMapMode(mode: string): string {\n if (mode === 'development') return 'dev';\n if (mode === 'production') return 'prod';\n return mode;\n}\n\nfunction isBotimEnv(s: string): s is BotimEnv {\n return (VALID_ENVS as readonly string[]).includes(s);\n}\n\ninterface RawBotimFile {\n /** Canonical mini-program id used by the BOTIM platform, e.g. \"mbrx_p2p\". */\n mp_id?: string;\n /** Reverse-DNS app id, e.g. \"me.botim.rd.p2p\". Different concept from mp_id. */\n app_id?: string;\n /** Release version of the mini-program package, e.g. \"0.0.127\". */\n version?: string;\n /** Optional override; otherwise we derive from version+md5. */\n buildSignature?: string;\n /** Per-package metadata; when present we use `app.md5` for the signature. */\n app?: { version?: string; md5?: string; updateUrl?: string };\n /** Older/alternate location for the same md5. */\n package_url?: { md5?: string; version?: string };\n /** Operational flags maintained by the platform. */\n disabled?: number;\n deleted?: number;\n mp_status?: number;\n /** Forward-compat for everything else (logos, framework, grayscale, ...). */\n [extra: string]: unknown;\n /** Legacy alias accepted for forward/back compat. */\n miniProgramId?: string;\n}\n\n/**\n * Resolve `botim.{env}.json` for the given Vite `mode` from `root`.\n *\n * Throws `BotimConfigError` synchronously on:\n * - unknown env after `mapMode` is applied\n * - missing or unreadable file\n * - invalid JSON\n * - missing/invalid `mp_id` (or its legacy `miniProgramId` alias)\n * - the platform marking the mini-program as deleted (`deleted: 1`)\n */\nexport function resolveBotimConfig(\n mode: string,\n root: string,\n opts: ResolveOptions = {},\n): BotimConfig {\n if (!mode) {\n throw new BotimConfigError('mode is required', { code: 'no-mode' });\n }\n\n const mapped = (opts.mapMode ?? defaultMapMode)(mode);\n if (typeof mapped !== 'string' || !isBotimEnv(mapped)) {\n throw new BotimConfigError(\n `mode \"${mode}\" mapped to \"${mapped}\" which is not a valid BotimEnv (expected one of: ${VALID_ENVS.join(', ')})`,\n { code: 'invalid-env' },\n );\n }\n const env: BotimEnv = mapped;\n\n const projectRoot = isAbsolute(root) ? root : resolve(process.cwd(), root);\n\n // Resolve the filename — either the legacy `botim.{env}.json` default, a\n // caller-provided literal (e.g. \"botim_config.beta.json\"), or a function\n // that derives one per env (e.g. monorepos that key on package name).\n const fileName = resolveFileName(opts.configFileName, env, mode);\n const filePath = isAbsolute(fileName) ? fileName : resolve(projectRoot, fileName);\n\n const inline = opts.inlineConfig;\n const inlineHasIdentity =\n typeof inline?.mp_id === 'string' && inline.mp_id.length > 0\n ? true\n : typeof inline?.miniProgramId === 'string' && inline.miniProgramId.length > 0;\n\n let raw: string | undefined;\n let parsedFromFile: RawBotimFile = {};\n\n if (existsSync(filePath)) {\n raw = readFileSync(filePath, 'utf8');\n try {\n parsedFromFile = JSON.parse(raw) as RawBotimFile;\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {\n code: 'config-invalid-json',\n path: filePath,\n });\n }\n } else if (!inlineHasIdentity) {\n // No file AND no inline mp_id → we have nothing to attach to. Mention\n // both escape hatches in the error so the next reader knows the options.\n throw new BotimConfigError(\n `expected config file not found: ${filePath}\\n` +\n ` Either create ${fileName} at the project root with at least { \"mp_id\": \"...\" },\\n` +\n ` pass an explicit { configFileName } / { config: { mp_id: \"...\" } } to botimDebug(),\\n` +\n ` or supply inlineConfig with mp_id when calling resolveBotimConfig() directly.`,\n { code: 'config-missing', path: filePath },\n );\n }\n\n // Inline overrides file — last writer wins. Per-section objects (`app`,\n // `package_url`) are shallow-merged so a caller can patch only `app.md5`\n // without losing `app.version` from the file. Only materialize the merged\n // sub-object when at least one side actually defines it, otherwise we'd\n // turn an absent field into an empty `{}` and confuse the buildSignature\n // fallback heuristics below.\n const parsed: RawBotimFile = {\n ...parsedFromFile,\n ...(inline as RawBotimFile | undefined),\n };\n if (parsedFromFile.app || inline?.app) {\n parsed.app = { ...parsedFromFile.app, ...inline?.app };\n }\n if (parsedFromFile.package_url || inline?.package_url) {\n parsed.package_url = { ...parsedFromFile.package_url, ...inline?.package_url };\n }\n\n // Hard-stop on a decommissioned mini-program — refusing the build is far\n // safer than shipping a debug SDK that targets a relay slot the platform\n // has retired.\n if (parsed.deleted === 1) {\n throw new BotimConfigError(\n `${filePath} reports the mini-program as deleted (deleted: 1). Cannot ship debug SDK against a retired mini-program.`,\n { code: 'config-deleted', path: filePath },\n );\n }\n\n // Read the canonical id; fall back to the SDK-internal alias for repos that\n // hand-write a config without the platform's `mp_id` field.\n const idValue = (parsed.mp_id ?? parsed.miniProgramId) as unknown;\n if (typeof idValue !== 'string' || idValue.length === 0) {\n throw new BotimConfigError(\n `${filePath} is missing required field \"mp_id\" (must be a non-empty string)`,\n { code: 'config-missing-field', path: filePath },\n );\n }\n if (!ID_PATTERN.test(idValue)) {\n throw new BotimConfigError(\n `${filePath} field \"mp_id\" must match ${ID_PATTERN.toString()} (got: ${JSON.stringify(idValue)})`,\n { code: 'config-invalid-id', path: filePath },\n );\n }\n\n // ── Derive a stable, release-correlated buildSignature ────────────────────\n // Preference order:\n // 1. explicit buildSignature in the file (consumers may pin one)\n // 2. version + package md5 (deterministic per release; survives unrelated\n // platform-side mutations like update_time / mp_status_time)\n // 3. sha256 of the file contents (last-resort fallback)\n const md5 =\n typeof parsed.app?.md5 === 'string'\n ? parsed.app.md5\n : typeof parsed.package_url?.md5 === 'string'\n ? parsed.package_url.md5\n : undefined;\n const versionForSig =\n (typeof parsed.version === 'string' && parsed.version) ||\n (typeof parsed.app?.version === 'string' && parsed.app.version) ||\n undefined;\n\n let buildSignature: string;\n if (typeof parsed.buildSignature === 'string' && parsed.buildSignature.length > 0) {\n buildSignature = parsed.buildSignature;\n } else if (versionForSig && md5) {\n buildSignature = `v${versionForSig}+md5:${md5.slice(0, 12)}`;\n } else {\n // No file content to hash (file-less inline workflow) → hash the merged\n // canonical fields instead. Two builds with identical mp_id+env+version\n // get the same signature, which is exactly what the relay correlates on.\n const sigSource =\n raw ??\n JSON.stringify({ mp_id: idValue, env, version: versionForSig, app: parsed.app });\n buildSignature = 'sha256:' + createHash('sha256').update(sigSource).digest('hex').slice(0, 12);\n }\n\n const cfg: BotimConfig = {\n // Spread first so the canonical fields below override anything inherited.\n ...parsed,\n miniProgramId: idValue,\n env,\n buildSignature,\n appName: typeof parsed.app_id === 'string' ? parsed.app_id : (parsed.appName as string | undefined),\n appVersion:\n versionForSig ?? (typeof parsed.appVersion === 'string' ? parsed.appVersion : undefined),\n };\n\n return cfg;\n}\n\n/**\n * Resolve the on-disk filename to read for this env. Three shapes accepted:\n * - undefined → legacy default `botim.{env}.json`\n * - string → use literally (e.g. \"botim_config.beta.json\")\n * - function → called with (env, mode) and must return a string\n *\n * Throws a `BotimConfigError` rather than letting a bad function return\n * propagate as a misleading `existsSync` miss.\n */\nfunction resolveFileName(\n override: ResolveOptions['configFileName'],\n env: BotimEnv,\n mode: string,\n): string {\n if (override === undefined) return `botim.${env}.json`;\n if (typeof override === 'string') {\n if (override.length === 0) {\n throw new BotimConfigError('configFileName must be a non-empty string', {\n code: 'config-invalid-filename',\n });\n }\n return override;\n }\n if (typeof override === 'function') {\n const result = override(env, mode);\n if (typeof result !== 'string' || result.length === 0) {\n throw new BotimConfigError(\n `configFileName(${env}, ${mode}) must return a non-empty string (got ${JSON.stringify(result)})`,\n { code: 'config-invalid-filename' },\n );\n }\n return result;\n }\n throw new BotimConfigError(\n `configFileName must be a string or (env, mode) => string (got ${typeof override})`,\n { code: 'config-invalid-filename' },\n );\n}\n\nexport const __test__ = { defaultMapMode, ID_PATTERN, VALID_ENVS, resolveFileName };\n","/**\n * Errors that escape the SDK into host code.\n *\n * The SDK's no-throw invariant has exactly two carve-outs:\n * - `BotimConfigError` — thrown synchronously by `enableRemoteDebug` when the\n * resolved `BotimConfig` is missing or invalid. Prevents any I/O.\n * - `BotimConsentError` — thrown synchronously by `enableRemoteDebug` when\n * consent has not been granted in a `prod` build. Prevents any I/O.\n *\n * Both are intentionally synchronous and pre-installation: at the moment they\n * fire, no globals have been wrapped, no listeners installed, no network sent.\n */\n\nconst BRAND = '@botim/debug-sdk' as const;\n\nexport class BotimConfigError extends Error {\n readonly name = 'BotimConfigError';\n readonly code: string;\n readonly path?: string;\n\n constructor(message: string, opts: { code: string; path?: string } = { code: 'invalid-config' }) {\n super(`[${BRAND}] ${message}`);\n this.code = opts.code;\n this.path = opts.path;\n }\n}\n\nexport class BotimConsentError extends Error {\n readonly name = 'BotimConsentError';\n constructor(message: string) {\n super(`[${BRAND}] ${message}`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBA,qBAAyC;AACzC,uBAAoC;AACpC,yBAA2B;;;ACR3B,IAAM,QAAQ;AAEP,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAK1C,YAAY,SAAiB,OAAwC,EAAE,MAAM,iBAAiB,GAAG;AAC/F,UAAM,IAAI,KAAK,KAAK,OAAO,EAAE;AAL/B,SAAS,OAAO;AAMd,SAAK,OAAO,KAAK;AACjB,SAAK,OAAO,KAAK;AAAA,EACnB;AACF;;;ADCA,IAAM,aAAkC,CAAC,OAAO,OAAO,QAAQ,MAAM;AAKrE,IAAM,aAAa;AAuDnB,SAAS,eAAe,MAAsB;AAC5C,MAAI,SAAS,cAAe,QAAO;AACnC,MAAI,SAAS,aAAc,QAAO;AAClC,SAAO;AACT;AAEA,SAAS,WAAW,GAA0B;AAC5C,SAAQ,WAAiC,SAAS,CAAC;AACrD;AAmCO,SAAS,mBACd,MACA,MACA,OAAuB,CAAC,GACX;AACb,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,iBAAiB,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAAA,EACpE;AAEA,QAAM,UAAU,KAAK,WAAW,gBAAgB,IAAI;AACpD,MAAI,OAAO,WAAW,YAAY,CAAC,WAAW,MAAM,GAAG;AACrD,UAAM,IAAI;AAAA,MACR,SAAS,IAAI,gBAAgB,MAAM,qDAAqD,WAAW,KAAK,IAAI,CAAC;AAAA,MAC7G,EAAE,MAAM,cAAc;AAAA,IACxB;AAAA,EACF;AACA,QAAM,MAAgB;AAEtB,QAAM,kBAAc,6BAAW,IAAI,IAAI,WAAO,0BAAQ,QAAQ,IAAI,GAAG,IAAI;AAKzE,QAAM,WAAW,gBAAgB,KAAK,gBAAgB,KAAK,IAAI;AAC/D,QAAM,eAAW,6BAAW,QAAQ,IAAI,eAAW,0BAAQ,aAAa,QAAQ;AAEhF,QAAM,SAAS,KAAK;AACpB,QAAM,oBACJ,OAAO,QAAQ,UAAU,YAAY,OAAO,MAAM,SAAS,IACvD,OACA,OAAO,QAAQ,kBAAkB,YAAY,OAAO,cAAc,SAAS;AAEjF,MAAI;AACJ,MAAI,iBAA+B,CAAC;AAEpC,UAAI,2BAAW,QAAQ,GAAG;AACxB,cAAM,6BAAa,UAAU,MAAM;AACnC,QAAI;AACF,uBAAiB,KAAK,MAAM,GAAG;AAAA,IACjC,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,IAAI,iBAAiB,2BAA2B,QAAQ,KAAK,MAAM,IAAI;AAAA,QAC3E,MAAM;AAAA,QACN,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF,WAAW,CAAC,mBAAmB;AAG7B,UAAM,IAAI;AAAA,MACR,mCAAmC,QAAQ;AAAA,uBACjB,QAAQ;AAAA;AAAA;AAAA,MAGlC,EAAE,MAAM,kBAAkB,MAAM,SAAS;AAAA,IAC3C;AAAA,EACF;AAQA,QAAM,SAAuB;AAAA,IAC3B,GAAG;AAAA,IACH,GAAI;AAAA,EACN;AACA,MAAI,eAAe,OAAO,QAAQ,KAAK;AACrC,WAAO,MAAM,EAAE,GAAG,eAAe,KAAK,GAAG,QAAQ,IAAI;AAAA,EACvD;AACA,MAAI,eAAe,eAAe,QAAQ,aAAa;AACrD,WAAO,cAAc,EAAE,GAAG,eAAe,aAAa,GAAG,QAAQ,YAAY;AAAA,EAC/E;AAKA,MAAI,OAAO,YAAY,GAAG;AACxB,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ;AAAA,MACX,EAAE,MAAM,kBAAkB,MAAM,SAAS;AAAA,IAC3C;AAAA,EACF;AAIA,QAAM,UAAW,OAAO,SAAS,OAAO;AACxC,MAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG;AACvD,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ;AAAA,MACX,EAAE,MAAM,wBAAwB,MAAM,SAAS;AAAA,IACjD;AAAA,EACF;AACA,MAAI,CAAC,WAAW,KAAK,OAAO,GAAG;AAC7B,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ,6BAA6B,WAAW,SAAS,CAAC,UAAU,KAAK,UAAU,OAAO,CAAC;AAAA,MAC9F,EAAE,MAAM,qBAAqB,MAAM,SAAS;AAAA,IAC9C;AAAA,EACF;AAQA,QAAM,MACJ,OAAO,OAAO,KAAK,QAAQ,WACvB,OAAO,IAAI,MACX,OAAO,OAAO,aAAa,QAAQ,WACjC,OAAO,YAAY,MACnB;AACR,QAAM,gBACH,OAAO,OAAO,YAAY,YAAY,OAAO,WAC7C,OAAO,OAAO,KAAK,YAAY,YAAY,OAAO,IAAI,WACvD;AAEF,MAAI;AACJ,MAAI,OAAO,OAAO,mBAAmB,YAAY,OAAO,eAAe,SAAS,GAAG;AACjF,qBAAiB,OAAO;AAAA,EAC1B,WAAW,iBAAiB,KAAK;AAC/B,qBAAiB,IAAI,aAAa,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC;AAAA,EAC5D,OAAO;AAIL,UAAM,YACJ,OACA,KAAK,UAAU,EAAE,OAAO,SAAS,KAAK,SAAS,eAAe,KAAK,OAAO,IAAI,CAAC;AACjF,qBAAiB,gBAAY,+BAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,EAC/F;AAEA,QAAM,MAAmB;AAAA;AAAA,IAEvB,GAAG;AAAA,IACH,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA,SAAS,OAAO,OAAO,WAAW,WAAW,OAAO,SAAU,OAAO;AAAA,IACrE,YACE,kBAAkB,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa;AAAA,EAClF;AAEA,SAAO;AACT;AAWA,SAAS,gBACP,UACA,KACA,MACQ;AACR,MAAI,aAAa,OAAW,QAAO,SAAS,GAAG;AAC/C,MAAI,OAAO,aAAa,UAAU;AAChC,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI,iBAAiB,6CAA6C;AAAA,QACtE,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,aAAa,YAAY;AAClC,UAAM,SAAS,SAAS,KAAK,IAAI;AACjC,QAAI,OAAO,WAAW,YAAY,OAAO,WAAW,GAAG;AACrD,YAAM,IAAI;AAAA,QACR,kBAAkB,GAAG,KAAK,IAAI,yCAAyC,KAAK,UAAU,MAAM,CAAC;AAAA,QAC7F,EAAE,MAAM,0BAA0B;AAAA,MACpC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,QAAM,IAAI;AAAA,IACR,iEAAiE,OAAO,QAAQ;AAAA,IAChF,EAAE,MAAM,0BAA0B;AAAA,EACpC;AACF;;;ADvSA,IAAM,aAAa;AACnB,IAAM,sBAAsB,OAAO;AA4D5B,SAAS,WAAW,UAAmC,CAAC,GAAW;AACxE,MAAI,WAA+B;AACnC,MAAI,mBAAkC;AACtC,MAAI,gBAA8B;AAElC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IAET,eAAe,YAAY;AACzB,YAAM,OAAO,QAAQ,QAAQ,WAAW;AACxC,YAAM,OAAO,QAAQ,QAAQ,WAAW;AACxC,UAAI;AACF,mBAAW,mBAAmB,MAAM,MAAM;AAAA,UACxC,SAAS,QAAQ;AAAA,UACjB,gBAAgB,QAAQ;AAAA,UACxB,cAAc,QAAQ;AAAA,QACxB,CAAC;AAID,YAAI,QAAQ,UAAU;AACpB,UAAC,SAAgD,WAC/C,QAAQ,SAAS,QAAQ,QAAQ,EAAE;AAAA,QACvC;AACA,2BAAmB;AAAA,MACrB,SAAS,KAAK;AAMZ,mBAAW;AACX,wBAAgB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAClE,2BAAmB;AAAA,MACrB;AAAA,IACF;AAAA,IAEA,UAAU,IAAI;AACZ,UAAI,OAAO,WAAY,QAAO;AAC9B,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,IAAI;AACP,UAAI,OAAO,oBAAqB,QAAO;AAEvC,UAAI,CAAC,UAAU;AACb,cAAM,UACJ,eAAe,WACf,0FAA0F,oBAAoB,SAAS;AAEzH,aAAK,MAAM,OAAO;AAAA,MACpB;AAGA,YAAM,OACJ;AAAA,2CAC4C,KAAK,UAAU,QAAQ,CAAC;AAAA;AAAA;AAEtE,aAAO,EAAE,MAAM,MAAM,KAAK,KAAK;AAAA,IACjC;AAAA,EACF;AACF;","names":[]}
@@ -35,9 +35,62 @@ interface BotimConfig {
35
35
  * uses Node `fs`/`crypto`. Must NEVER be imported from device-targeted code.
36
36
  */
37
37
 
38
+ /**
39
+ * Inline config bag that consumers can pass to the Vite plugin (or the
40
+ * resolver directly) instead of — or on top of — `botim.{env}.json`.
41
+ *
42
+ * Shape mirrors the on-disk file (`mp_id`, `app_id`, `version`, `app.md5`, …)
43
+ * so the same normalizer pipeline runs whether the source is the filesystem
44
+ * or a literal object. Unknown keys are forwarded as-is via the
45
+ * `[extra: string]: unknown` index so the platform can keep adding fields
46
+ * without breaking existing builds.
47
+ */
48
+ type InlineBotimConfigInput = {
49
+ mp_id?: string;
50
+ /** Legacy alias accepted for forward/back compat. */
51
+ miniProgramId?: string;
52
+ app_id?: string;
53
+ version?: string;
54
+ buildSignature?: string;
55
+ app?: {
56
+ version?: string;
57
+ md5?: string;
58
+ updateUrl?: string;
59
+ };
60
+ package_url?: {
61
+ md5?: string;
62
+ version?: string;
63
+ };
64
+ appName?: string;
65
+ appVersion?: string;
66
+ [extra: string]: unknown;
67
+ };
38
68
  interface ResolveOptions {
39
69
  /** Map a Vite mode (e.g. "development", "staging") to a BotimEnv. */
40
70
  mapMode?: (mode: string) => BotimEnv | string;
71
+ /**
72
+ * Override the default `botim.{env}.json` filename pattern. Pass a string
73
+ * for a fixed name (e.g. `"botim_config.beta.json"`) or a function that
74
+ * receives the resolved env and original mode for per-env logic
75
+ * (e.g. `(env) => `botim_config.${env}.json``).
76
+ *
77
+ * The result is resolved relative to the project root unless it is an
78
+ * absolute path.
79
+ */
80
+ configFileName?: string | ((env: BotimEnv, mode: string) => string);
81
+ /**
82
+ * Inline config bag that **takes precedence** over the on-disk file.
83
+ *
84
+ * Merge semantics: file is loaded first (if present), then inline keys are
85
+ * spread on top so callers can patch a single field without touching the
86
+ * JSON (`{ mp_id: 'mbrx_p2p', version: process.env.VERSION }`).
87
+ *
88
+ * When no file exists at the resolved path, the resolver tolerates the
89
+ * miss as long as `inlineConfig` carries a usable `mp_id` (or its legacy
90
+ * `miniProgramId` alias). This is the file-less workflow — handy for
91
+ * monorepos / native hosts that synthesize identity from env vars.
92
+ */
93
+ inlineConfig?: InlineBotimConfigInput;
41
94
  }
42
95
  /**
43
96
  * Resolve `botim.{env}.json` for the given Vite `mode` from `root`.
@@ -121,7 +174,29 @@ interface BotimDebugPluginOptions {
121
174
  * resolve time so `/v1/attach` joins cleanly.
122
175
  */
123
176
  relayUrl?: string;
177
+ /**
178
+ * Override the default `botim.{env}.json` filename pattern. Pass a string
179
+ * for a fixed name (e.g. `"botim_config.beta.json"`) or a function for
180
+ * per-env logic (e.g. `(env) => `botim_config.${env}.json``).
181
+ *
182
+ * Resolved relative to the project root unless absolute.
183
+ */
184
+ configFileName?: string | ((env: BotimEnv, mode: string) => string);
185
+ /**
186
+ * Inline config bag that **takes precedence** over the on-disk file.
187
+ *
188
+ * Useful when:
189
+ * - mp_id / version come from env vars (CI builds, monorepo packages)
190
+ * - the host wants to ship without any `botim.*.json` file at all
191
+ * (set `mp_id` here and the resolver tolerates a missing file)
192
+ * - a single field needs patching per build without editing JSON
193
+ *
194
+ * Merge semantics: the file is loaded first (if present), then these
195
+ * keys are spread on top. Sub-objects (`app`, `package_url`) are
196
+ * shallow-merged so patching `app.md5` keeps `app.version` from the file.
197
+ */
198
+ config?: InlineBotimConfigInput;
124
199
  }
125
200
  declare function botimDebug(options?: BotimDebugPluginOptions): Plugin;
126
201
 
127
- export { type BotimConfig, BotimConfigError, type BotimDebugPluginOptions, type BotimEnv, botimDebug, resolveBotimConfig };
202
+ export { type BotimConfig, BotimConfigError, type BotimDebugPluginOptions, type BotimEnv, type InlineBotimConfigInput, type ResolveOptions, botimDebug, resolveBotimConfig };
@@ -35,9 +35,62 @@ interface BotimConfig {
35
35
  * uses Node `fs`/`crypto`. Must NEVER be imported from device-targeted code.
36
36
  */
37
37
 
38
+ /**
39
+ * Inline config bag that consumers can pass to the Vite plugin (or the
40
+ * resolver directly) instead of — or on top of — `botim.{env}.json`.
41
+ *
42
+ * Shape mirrors the on-disk file (`mp_id`, `app_id`, `version`, `app.md5`, …)
43
+ * so the same normalizer pipeline runs whether the source is the filesystem
44
+ * or a literal object. Unknown keys are forwarded as-is via the
45
+ * `[extra: string]: unknown` index so the platform can keep adding fields
46
+ * without breaking existing builds.
47
+ */
48
+ type InlineBotimConfigInput = {
49
+ mp_id?: string;
50
+ /** Legacy alias accepted for forward/back compat. */
51
+ miniProgramId?: string;
52
+ app_id?: string;
53
+ version?: string;
54
+ buildSignature?: string;
55
+ app?: {
56
+ version?: string;
57
+ md5?: string;
58
+ updateUrl?: string;
59
+ };
60
+ package_url?: {
61
+ md5?: string;
62
+ version?: string;
63
+ };
64
+ appName?: string;
65
+ appVersion?: string;
66
+ [extra: string]: unknown;
67
+ };
38
68
  interface ResolveOptions {
39
69
  /** Map a Vite mode (e.g. "development", "staging") to a BotimEnv. */
40
70
  mapMode?: (mode: string) => BotimEnv | string;
71
+ /**
72
+ * Override the default `botim.{env}.json` filename pattern. Pass a string
73
+ * for a fixed name (e.g. `"botim_config.beta.json"`) or a function that
74
+ * receives the resolved env and original mode for per-env logic
75
+ * (e.g. `(env) => `botim_config.${env}.json``).
76
+ *
77
+ * The result is resolved relative to the project root unless it is an
78
+ * absolute path.
79
+ */
80
+ configFileName?: string | ((env: BotimEnv, mode: string) => string);
81
+ /**
82
+ * Inline config bag that **takes precedence** over the on-disk file.
83
+ *
84
+ * Merge semantics: file is loaded first (if present), then inline keys are
85
+ * spread on top so callers can patch a single field without touching the
86
+ * JSON (`{ mp_id: 'mbrx_p2p', version: process.env.VERSION }`).
87
+ *
88
+ * When no file exists at the resolved path, the resolver tolerates the
89
+ * miss as long as `inlineConfig` carries a usable `mp_id` (or its legacy
90
+ * `miniProgramId` alias). This is the file-less workflow — handy for
91
+ * monorepos / native hosts that synthesize identity from env vars.
92
+ */
93
+ inlineConfig?: InlineBotimConfigInput;
41
94
  }
42
95
  /**
43
96
  * Resolve `botim.{env}.json` for the given Vite `mode` from `root`.
@@ -121,7 +174,29 @@ interface BotimDebugPluginOptions {
121
174
  * resolve time so `/v1/attach` joins cleanly.
122
175
  */
123
176
  relayUrl?: string;
177
+ /**
178
+ * Override the default `botim.{env}.json` filename pattern. Pass a string
179
+ * for a fixed name (e.g. `"botim_config.beta.json"`) or a function for
180
+ * per-env logic (e.g. `(env) => `botim_config.${env}.json``).
181
+ *
182
+ * Resolved relative to the project root unless absolute.
183
+ */
184
+ configFileName?: string | ((env: BotimEnv, mode: string) => string);
185
+ /**
186
+ * Inline config bag that **takes precedence** over the on-disk file.
187
+ *
188
+ * Useful when:
189
+ * - mp_id / version come from env vars (CI builds, monorepo packages)
190
+ * - the host wants to ship without any `botim.*.json` file at all
191
+ * (set `mp_id` here and the resolver tolerates a missing file)
192
+ * - a single field needs patching per build without editing JSON
193
+ *
194
+ * Merge semantics: the file is loaded first (if present), then these
195
+ * keys are spread on top. Sub-objects (`app`, `package_url`) are
196
+ * shallow-merged so patching `app.md5` keeps `app.version` from the file.
197
+ */
198
+ config?: InlineBotimConfigInput;
124
199
  }
125
200
  declare function botimDebug(options?: BotimDebugPluginOptions): Plugin;
126
201
 
127
- export { type BotimConfig, BotimConfigError, type BotimDebugPluginOptions, type BotimEnv, botimDebug, resolveBotimConfig };
202
+ export { type BotimConfig, BotimConfigError, type BotimDebugPluginOptions, type BotimEnv, type InlineBotimConfigInput, type ResolveOptions, botimDebug, resolveBotimConfig };
@@ -38,24 +38,41 @@ function resolveBotimConfig(mode, root, opts = {}) {
38
38
  }
39
39
  const env = mapped;
40
40
  const projectRoot = isAbsolute(root) ? root : resolve(process.cwd(), root);
41
- const filePath = resolve(projectRoot, `botim.${env}.json`);
42
- if (!existsSync(filePath)) {
41
+ const fileName = resolveFileName(opts.configFileName, env, mode);
42
+ const filePath = isAbsolute(fileName) ? fileName : resolve(projectRoot, fileName);
43
+ const inline = opts.inlineConfig;
44
+ const inlineHasIdentity = typeof inline?.mp_id === "string" && inline.mp_id.length > 0 ? true : typeof inline?.miniProgramId === "string" && inline.miniProgramId.length > 0;
45
+ let raw;
46
+ let parsedFromFile = {};
47
+ if (existsSync(filePath)) {
48
+ raw = readFileSync(filePath, "utf8");
49
+ try {
50
+ parsedFromFile = JSON.parse(raw);
51
+ } catch (err) {
52
+ const detail = err instanceof Error ? err.message : String(err);
53
+ throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {
54
+ code: "config-invalid-json",
55
+ path: filePath
56
+ });
57
+ }
58
+ } else if (!inlineHasIdentity) {
43
59
  throw new BotimConfigError(
44
60
  `expected config file not found: ${filePath}
45
- Create botim.${env}.json at the project root with at least { "mp_id": "..." }.`,
61
+ Either create ${fileName} at the project root with at least { "mp_id": "..." },
62
+ pass an explicit { configFileName } / { config: { mp_id: "..." } } to botimDebug(),
63
+ or supply inlineConfig with mp_id when calling resolveBotimConfig() directly.`,
46
64
  { code: "config-missing", path: filePath }
47
65
  );
48
66
  }
49
- const raw = readFileSync(filePath, "utf8");
50
- let parsed;
51
- try {
52
- parsed = JSON.parse(raw);
53
- } catch (err) {
54
- const detail = err instanceof Error ? err.message : String(err);
55
- throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {
56
- code: "config-invalid-json",
57
- path: filePath
58
- });
67
+ const parsed = {
68
+ ...parsedFromFile,
69
+ ...inline
70
+ };
71
+ if (parsedFromFile.app || inline?.app) {
72
+ parsed.app = { ...parsedFromFile.app, ...inline?.app };
73
+ }
74
+ if (parsedFromFile.package_url || inline?.package_url) {
75
+ parsed.package_url = { ...parsedFromFile.package_url, ...inline?.package_url };
59
76
  }
60
77
  if (parsed.deleted === 1) {
61
78
  throw new BotimConfigError(
@@ -84,7 +101,8 @@ function resolveBotimConfig(mode, root, opts = {}) {
84
101
  } else if (versionForSig && md5) {
85
102
  buildSignature = `v${versionForSig}+md5:${md5.slice(0, 12)}`;
86
103
  } else {
87
- buildSignature = "sha256:" + createHash("sha256").update(raw).digest("hex").slice(0, 12);
104
+ const sigSource = raw ?? JSON.stringify({ mp_id: idValue, env, version: versionForSig, app: parsed.app });
105
+ buildSignature = "sha256:" + createHash("sha256").update(sigSource).digest("hex").slice(0, 12);
88
106
  }
89
107
  const cfg = {
90
108
  // Spread first so the canonical fields below override anything inherited.
@@ -97,6 +115,31 @@ function resolveBotimConfig(mode, root, opts = {}) {
97
115
  };
98
116
  return cfg;
99
117
  }
118
+ function resolveFileName(override, env, mode) {
119
+ if (override === void 0) return `botim.${env}.json`;
120
+ if (typeof override === "string") {
121
+ if (override.length === 0) {
122
+ throw new BotimConfigError("configFileName must be a non-empty string", {
123
+ code: "config-invalid-filename"
124
+ });
125
+ }
126
+ return override;
127
+ }
128
+ if (typeof override === "function") {
129
+ const result = override(env, mode);
130
+ if (typeof result !== "string" || result.length === 0) {
131
+ throw new BotimConfigError(
132
+ `configFileName(${env}, ${mode}) must return a non-empty string (got ${JSON.stringify(result)})`,
133
+ { code: "config-invalid-filename" }
134
+ );
135
+ }
136
+ return result;
137
+ }
138
+ throw new BotimConfigError(
139
+ `configFileName must be a string or (env, mode) => string (got ${typeof override})`,
140
+ { code: "config-invalid-filename" }
141
+ );
142
+ }
100
143
 
101
144
  // src/vite/plugin.ts
102
145
  var VIRTUAL_ID = "virtual:botim/config";
@@ -112,7 +155,11 @@ function botimDebug(options = {}) {
112
155
  const mode = options.mode ?? viteConfig.mode;
113
156
  const root = options.root ?? viteConfig.root;
114
157
  try {
115
- resolved = resolveBotimConfig(mode, root, { mapMode: options.mapMode });
158
+ resolved = resolveBotimConfig(mode, root, {
159
+ mapMode: options.mapMode,
160
+ configFileName: options.configFileName,
161
+ inlineConfig: options.config
162
+ });
116
163
  if (options.relayUrl) {
117
164
  resolved.relayUrl = options.relayUrl.replace(/\/+$/, "");
118
165
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vite/resolve-config.ts","../../src/errors.ts","../../src/vite/plugin.ts"],"sourcesContent":["/**\n * Build-time resolver for `botim.{env}.json`.\n *\n * The real BOTIM config schema is large and platform-managed (logos, package\n * URLs, framework metadata, grayscale rollout, etc). The SDK only needs a few\n * canonical fields, so this resolver acts as a *normalizer*:\n *\n * external schema → SDK-internal `BotimConfig`\n * ─────────────── ──────────────────────────\n * mp_id → miniProgramId\n * app_id → appId (extra; preserved)\n * version → appVersion\n * app.md5 | package_url.md5 → buildSignature (deterministic per release)\n * <env from filename> → env\n *\n * Reads `botim.{env}.json` from the consumer's project root. Build-time only:\n * uses Node `fs`/`crypto`. Must NEVER be imported from device-targeted code.\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve, isAbsolute } from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport { BotimConfigError } from '../errors.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VALID_ENVS: readonly BotimEnv[] = ['dev', 'uat', 'beta', 'prod'];\n/**\n * Mini-program ids in the real config are short kebab/snake-case slugs like\n * `mbrx_p2p` — case-insensitive, allows `_` and `-`, must start with a letter.\n */\nconst ID_PATTERN = /^[a-z][a-z0-9_-]{1,63}$/i;\n\nexport interface ResolveOptions {\n /** Map a Vite mode (e.g. \"development\", \"staging\") to a BotimEnv. */\n mapMode?: (mode: string) => BotimEnv | string;\n}\n\n/** Built-in mapping; consumers can override via `mapMode`. */\nfunction defaultMapMode(mode: string): string {\n if (mode === 'development') return 'dev';\n if (mode === 'production') return 'prod';\n return mode;\n}\n\nfunction isBotimEnv(s: string): s is BotimEnv {\n return (VALID_ENVS as readonly string[]).includes(s);\n}\n\ninterface RawBotimFile {\n /** Canonical mini-program id used by the BOTIM platform, e.g. \"mbrx_p2p\". */\n mp_id?: string;\n /** Reverse-DNS app id, e.g. \"me.botim.rd.p2p\". Different concept from mp_id. */\n app_id?: string;\n /** Release version of the mini-program package, e.g. \"0.0.127\". */\n version?: string;\n /** Optional override; otherwise we derive from version+md5. */\n buildSignature?: string;\n /** Per-package metadata; when present we use `app.md5` for the signature. */\n app?: { version?: string; md5?: string; updateUrl?: string };\n /** Older/alternate location for the same md5. */\n package_url?: { md5?: string; version?: string };\n /** Operational flags maintained by the platform. */\n disabled?: number;\n deleted?: number;\n mp_status?: number;\n /** Forward-compat for everything else (logos, framework, grayscale, ...). */\n [extra: string]: unknown;\n /** Legacy alias accepted for forward/back compat. */\n miniProgramId?: string;\n}\n\n/**\n * Resolve `botim.{env}.json` for the given Vite `mode` from `root`.\n *\n * Throws `BotimConfigError` synchronously on:\n * - unknown env after `mapMode` is applied\n * - missing or unreadable file\n * - invalid JSON\n * - missing/invalid `mp_id` (or its legacy `miniProgramId` alias)\n * - the platform marking the mini-program as deleted (`deleted: 1`)\n */\nexport function resolveBotimConfig(\n mode: string,\n root: string,\n opts: ResolveOptions = {},\n): BotimConfig {\n if (!mode) {\n throw new BotimConfigError('mode is required', { code: 'no-mode' });\n }\n\n const mapped = (opts.mapMode ?? defaultMapMode)(mode);\n if (typeof mapped !== 'string' || !isBotimEnv(mapped)) {\n throw new BotimConfigError(\n `mode \"${mode}\" mapped to \"${mapped}\" which is not a valid BotimEnv (expected one of: ${VALID_ENVS.join(', ')})`,\n { code: 'invalid-env' },\n );\n }\n const env: BotimEnv = mapped;\n\n const projectRoot = isAbsolute(root) ? root : resolve(process.cwd(), root);\n const filePath = resolve(projectRoot, `botim.${env}.json`);\n\n if (!existsSync(filePath)) {\n throw new BotimConfigError(\n `expected config file not found: ${filePath}\\n` +\n ` Create botim.${env}.json at the project root with at least { \"mp_id\": \"...\" }.`,\n { code: 'config-missing', path: filePath },\n );\n }\n\n const raw = readFileSync(filePath, 'utf8');\n let parsed: RawBotimFile;\n try {\n parsed = JSON.parse(raw) as RawBotimFile;\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {\n code: 'config-invalid-json',\n path: filePath,\n });\n }\n\n // Hard-stop on a decommissioned mini-program — refusing the build is far\n // safer than shipping a debug SDK that targets a relay slot the platform\n // has retired.\n if (parsed.deleted === 1) {\n throw new BotimConfigError(\n `${filePath} reports the mini-program as deleted (deleted: 1). Cannot ship debug SDK against a retired mini-program.`,\n { code: 'config-deleted', path: filePath },\n );\n }\n\n // Read the canonical id; fall back to the SDK-internal alias for repos that\n // hand-write a config without the platform's `mp_id` field.\n const idValue = (parsed.mp_id ?? parsed.miniProgramId) as unknown;\n if (typeof idValue !== 'string' || idValue.length === 0) {\n throw new BotimConfigError(\n `${filePath} is missing required field \"mp_id\" (must be a non-empty string)`,\n { code: 'config-missing-field', path: filePath },\n );\n }\n if (!ID_PATTERN.test(idValue)) {\n throw new BotimConfigError(\n `${filePath} field \"mp_id\" must match ${ID_PATTERN.toString()} (got: ${JSON.stringify(idValue)})`,\n { code: 'config-invalid-id', path: filePath },\n );\n }\n\n // ── Derive a stable, release-correlated buildSignature ────────────────────\n // Preference order:\n // 1. explicit buildSignature in the file (consumers may pin one)\n // 2. version + package md5 (deterministic per release; survives unrelated\n // platform-side mutations like update_time / mp_status_time)\n // 3. sha256 of the file contents (last-resort fallback)\n const md5 =\n typeof parsed.app?.md5 === 'string'\n ? parsed.app.md5\n : typeof parsed.package_url?.md5 === 'string'\n ? parsed.package_url.md5\n : undefined;\n const versionForSig =\n (typeof parsed.version === 'string' && parsed.version) ||\n (typeof parsed.app?.version === 'string' && parsed.app.version) ||\n undefined;\n\n let buildSignature: string;\n if (typeof parsed.buildSignature === 'string' && parsed.buildSignature.length > 0) {\n buildSignature = parsed.buildSignature;\n } else if (versionForSig && md5) {\n buildSignature = `v${versionForSig}+md5:${md5.slice(0, 12)}`;\n } else {\n buildSignature = 'sha256:' + createHash('sha256').update(raw).digest('hex').slice(0, 12);\n }\n\n const cfg: BotimConfig = {\n // Spread first so the canonical fields below override anything inherited.\n ...parsed,\n miniProgramId: idValue,\n env,\n buildSignature,\n appName: typeof parsed.app_id === 'string' ? parsed.app_id : (parsed.appName as string | undefined),\n appVersion:\n versionForSig ?? (typeof parsed.appVersion === 'string' ? parsed.appVersion : undefined),\n };\n\n return cfg;\n}\n\nexport const __test__ = { defaultMapMode, ID_PATTERN, VALID_ENVS };\n","/**\n * Errors that escape the SDK into host code.\n *\n * The SDK's no-throw invariant has exactly two carve-outs:\n * - `BotimConfigError` — thrown synchronously by `enableRemoteDebug` when the\n * resolved `BotimConfig` is missing or invalid. Prevents any I/O.\n * - `BotimConsentError` — thrown synchronously by `enableRemoteDebug` when\n * consent has not been granted in a `prod` build. Prevents any I/O.\n *\n * Both are intentionally synchronous and pre-installation: at the moment they\n * fire, no globals have been wrapped, no listeners installed, no network sent.\n */\n\nconst BRAND = '@botim/debug-sdk' as const;\n\nexport class BotimConfigError extends Error {\n readonly name = 'BotimConfigError';\n readonly code: string;\n readonly path?: string;\n\n constructor(message: string, opts: { code: string; path?: string } = { code: 'invalid-config' }) {\n super(`[${BRAND}] ${message}`);\n this.code = opts.code;\n this.path = opts.path;\n }\n}\n\nexport class BotimConsentError extends Error {\n readonly name = 'BotimConsentError';\n constructor(message: string) {\n super(`[${BRAND}] ${message}`);\n }\n}\n","/**\n * Vite plugin for `@botim/debug-sdk`.\n *\n * - Reads `botim.{mode}.json` from the consumer's project root at build time.\n * - Validates required fields; fails the build loudly if anything is wrong.\n * - Exposes the resolved config via the virtual module `virtual:botim/config`\n * as `export const botimConfig: Readonly<BotimConfig>`.\n *\n * The plugin's only Vite-specific surface is the `Plugin` type; we keep the\n * import as `type-only` so `vite` remains an *optional* peer dependency. A\n * consumer that never installs Vite will never load this file (it's only\n * reachable via the `./vite` subpath export).\n */\n\nimport type { Plugin } from 'vite';\nimport { resolveBotimConfig } from './resolve-config.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VIRTUAL_ID = 'virtual:botim/config';\nconst RESOLVED_VIRTUAL_ID = '\\0' + VIRTUAL_ID;\n\nexport interface BotimDebugPluginOptions {\n /**\n * Override Vite's `mode`. Useful when the build is driven outside Vite's\n * usual `--mode` flag, or in tests. If omitted, the plugin uses the mode\n * Vite resolves from the CLI / config.\n */\n mode?: string;\n /**\n * Map Vite's mode to a BotimEnv. Defaults: `development` → `dev`,\n * `production` → `prod`, others pass through verbatim.\n */\n mapMode?: (mode: string) => BotimEnv | string;\n /**\n * Project root override. Defaults to Vite's resolved `config.root`.\n */\n root?: string;\n /**\n * Relay endpoint to bake into `virtual:botim/config` as `relayUrl`. The\n * SDK reads this at runtime so the host app can call:\n *\n * enableRemoteDebug({ endpoint: botimConfig.relayUrl, … })\n *\n * Without this option, hosts must either set up a Vite proxy\n * (`server.proxy: { '/v1': 'http://localhost:8090' }`) and pass\n * `endpoint: location.origin`, OR pass an explicit URL at the call\n * site. Passing it through the plugin centralises the wiring and makes\n * mode-specific URLs trivial: read process.env.RELAY_URL in\n * vite.config and forward it here.\n *\n * The relay must allow the page's origin via CORS (CORS_ORIGINS env on\n * @botim/debug-relay defaults to \"*\"). Trailing slash is stripped at\n * resolve time so `/v1/attach` joins cleanly.\n */\n relayUrl?: string;\n}\n\nexport function botimDebug(options: BotimDebugPluginOptions = {}): Plugin {\n let resolved: BotimConfig | null = null;\n let resolvedFromMode: string | null = null;\n let resolvedError: Error | null = null;\n\n return {\n name: '@botim/debug-sdk:vite',\n enforce: 'pre',\n\n configResolved(viteConfig) {\n const mode = options.mode ?? viteConfig.mode;\n const root = options.root ?? viteConfig.root;\n try {\n resolved = resolveBotimConfig(mode, root, { mapMode: options.mapMode });\n // Merge the host-provided relay URL into the resolved config bag.\n // Strip trailing slash so callers don't get `/v1//attach` if they\n // pass `https://relay.example.com/`.\n if (options.relayUrl) {\n (resolved as BotimConfig & { relayUrl: string }).relayUrl =\n options.relayUrl.replace(/\\/+$/, '');\n }\n resolvedFromMode = mode;\n } catch (err) {\n // Stash the error; surface it the moment a build module imports the\n // virtual module. We don't throw from `configResolved` directly because\n // some consumers run Vite for non-build tasks (e.g. `vite-node` for\n // tooling) where the virtual module is never imported and a hard fail\n // would be a regression.\n resolved = null;\n resolvedError = err instanceof Error ? err : new Error(String(err));\n resolvedFromMode = mode;\n }\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_VIRTUAL_ID) return null;\n\n if (!resolved) {\n const message =\n resolvedError?.message ??\n `[@botim/debug-sdk] virtual:botim/config requested but configResolved did not run (mode=${resolvedFromMode ?? 'unknown'})`;\n // `this.error` aborts the build with a properly attributed Vite error.\n this.error(message);\n }\n\n // Frozen so a runtime mutation can't accidentally taint the constant.\n const body =\n `// AUTO-GENERATED by @botim/debug-sdk:vite — do not edit.\\n` +\n `export const botimConfig = Object.freeze(${JSON.stringify(resolved)});\\n` +\n `export default botimConfig;\\n`;\n return { code: body, map: null };\n },\n };\n}\n\nexport { resolveBotimConfig } from './resolve-config.js';\nexport { BotimConfigError } from '../errors.js';\nexport type { BotimConfig, BotimEnv } from '../types.js';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/vite/resolve-config.ts","../../src/errors.ts","../../src/vite/plugin.ts"],"sourcesContent":["/**\n * Build-time resolver for `botim.{env}.json`.\n *\n * The real BOTIM config schema is large and platform-managed (logos, package\n * URLs, framework metadata, grayscale rollout, etc). The SDK only needs a few\n * canonical fields, so this resolver acts as a *normalizer*:\n *\n * external schema → SDK-internal `BotimConfig`\n * ─────────────── ──────────────────────────\n * mp_id → miniProgramId\n * app_id → appId (extra; preserved)\n * version → appVersion\n * app.md5 | package_url.md5 → buildSignature (deterministic per release)\n * <env from filename> → env\n *\n * Reads `botim.{env}.json` from the consumer's project root. Build-time only:\n * uses Node `fs`/`crypto`. Must NEVER be imported from device-targeted code.\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve, isAbsolute } from 'node:path';\nimport { createHash } from 'node:crypto';\n\nimport { BotimConfigError } from '../errors.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VALID_ENVS: readonly BotimEnv[] = ['dev', 'uat', 'beta', 'prod'];\n/**\n * Mini-program ids in the real config are short kebab/snake-case slugs like\n * `mbrx_p2p` — case-insensitive, allows `_` and `-`, must start with a letter.\n */\nconst ID_PATTERN = /^[a-z][a-z0-9_-]{1,63}$/i;\n\n/**\n * Inline config bag that consumers can pass to the Vite plugin (or the\n * resolver directly) instead of — or on top of — `botim.{env}.json`.\n *\n * Shape mirrors the on-disk file (`mp_id`, `app_id`, `version`, `app.md5`, …)\n * so the same normalizer pipeline runs whether the source is the filesystem\n * or a literal object. Unknown keys are forwarded as-is via the\n * `[extra: string]: unknown` index so the platform can keep adding fields\n * without breaking existing builds.\n */\nexport type InlineBotimConfigInput = {\n mp_id?: string;\n /** Legacy alias accepted for forward/back compat. */\n miniProgramId?: string;\n app_id?: string;\n version?: string;\n buildSignature?: string;\n app?: { version?: string; md5?: string; updateUrl?: string };\n package_url?: { md5?: string; version?: string };\n appName?: string;\n appVersion?: string;\n [extra: string]: unknown;\n};\n\nexport interface ResolveOptions {\n /** Map a Vite mode (e.g. \"development\", \"staging\") to a BotimEnv. */\n mapMode?: (mode: string) => BotimEnv | string;\n /**\n * Override the default `botim.{env}.json` filename pattern. Pass a string\n * for a fixed name (e.g. `\"botim_config.beta.json\"`) or a function that\n * receives the resolved env and original mode for per-env logic\n * (e.g. `(env) => `botim_config.${env}.json``).\n *\n * The result is resolved relative to the project root unless it is an\n * absolute path.\n */\n configFileName?: string | ((env: BotimEnv, mode: string) => string);\n /**\n * Inline config bag that **takes precedence** over the on-disk file.\n *\n * Merge semantics: file is loaded first (if present), then inline keys are\n * spread on top so callers can patch a single field without touching the\n * JSON (`{ mp_id: 'mbrx_p2p', version: process.env.VERSION }`).\n *\n * When no file exists at the resolved path, the resolver tolerates the\n * miss as long as `inlineConfig` carries a usable `mp_id` (or its legacy\n * `miniProgramId` alias). This is the file-less workflow — handy for\n * monorepos / native hosts that synthesize identity from env vars.\n */\n inlineConfig?: InlineBotimConfigInput;\n}\n\n/** Built-in mapping; consumers can override via `mapMode`. */\nfunction defaultMapMode(mode: string): string {\n if (mode === 'development') return 'dev';\n if (mode === 'production') return 'prod';\n return mode;\n}\n\nfunction isBotimEnv(s: string): s is BotimEnv {\n return (VALID_ENVS as readonly string[]).includes(s);\n}\n\ninterface RawBotimFile {\n /** Canonical mini-program id used by the BOTIM platform, e.g. \"mbrx_p2p\". */\n mp_id?: string;\n /** Reverse-DNS app id, e.g. \"me.botim.rd.p2p\". Different concept from mp_id. */\n app_id?: string;\n /** Release version of the mini-program package, e.g. \"0.0.127\". */\n version?: string;\n /** Optional override; otherwise we derive from version+md5. */\n buildSignature?: string;\n /** Per-package metadata; when present we use `app.md5` for the signature. */\n app?: { version?: string; md5?: string; updateUrl?: string };\n /** Older/alternate location for the same md5. */\n package_url?: { md5?: string; version?: string };\n /** Operational flags maintained by the platform. */\n disabled?: number;\n deleted?: number;\n mp_status?: number;\n /** Forward-compat for everything else (logos, framework, grayscale, ...). */\n [extra: string]: unknown;\n /** Legacy alias accepted for forward/back compat. */\n miniProgramId?: string;\n}\n\n/**\n * Resolve `botim.{env}.json` for the given Vite `mode` from `root`.\n *\n * Throws `BotimConfigError` synchronously on:\n * - unknown env after `mapMode` is applied\n * - missing or unreadable file\n * - invalid JSON\n * - missing/invalid `mp_id` (or its legacy `miniProgramId` alias)\n * - the platform marking the mini-program as deleted (`deleted: 1`)\n */\nexport function resolveBotimConfig(\n mode: string,\n root: string,\n opts: ResolveOptions = {},\n): BotimConfig {\n if (!mode) {\n throw new BotimConfigError('mode is required', { code: 'no-mode' });\n }\n\n const mapped = (opts.mapMode ?? defaultMapMode)(mode);\n if (typeof mapped !== 'string' || !isBotimEnv(mapped)) {\n throw new BotimConfigError(\n `mode \"${mode}\" mapped to \"${mapped}\" which is not a valid BotimEnv (expected one of: ${VALID_ENVS.join(', ')})`,\n { code: 'invalid-env' },\n );\n }\n const env: BotimEnv = mapped;\n\n const projectRoot = isAbsolute(root) ? root : resolve(process.cwd(), root);\n\n // Resolve the filename — either the legacy `botim.{env}.json` default, a\n // caller-provided literal (e.g. \"botim_config.beta.json\"), or a function\n // that derives one per env (e.g. monorepos that key on package name).\n const fileName = resolveFileName(opts.configFileName, env, mode);\n const filePath = isAbsolute(fileName) ? fileName : resolve(projectRoot, fileName);\n\n const inline = opts.inlineConfig;\n const inlineHasIdentity =\n typeof inline?.mp_id === 'string' && inline.mp_id.length > 0\n ? true\n : typeof inline?.miniProgramId === 'string' && inline.miniProgramId.length > 0;\n\n let raw: string | undefined;\n let parsedFromFile: RawBotimFile = {};\n\n if (existsSync(filePath)) {\n raw = readFileSync(filePath, 'utf8');\n try {\n parsedFromFile = JSON.parse(raw) as RawBotimFile;\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new BotimConfigError(`failed to parse JSON in ${filePath}: ${detail}`, {\n code: 'config-invalid-json',\n path: filePath,\n });\n }\n } else if (!inlineHasIdentity) {\n // No file AND no inline mp_id → we have nothing to attach to. Mention\n // both escape hatches in the error so the next reader knows the options.\n throw new BotimConfigError(\n `expected config file not found: ${filePath}\\n` +\n ` Either create ${fileName} at the project root with at least { \"mp_id\": \"...\" },\\n` +\n ` pass an explicit { configFileName } / { config: { mp_id: \"...\" } } to botimDebug(),\\n` +\n ` or supply inlineConfig with mp_id when calling resolveBotimConfig() directly.`,\n { code: 'config-missing', path: filePath },\n );\n }\n\n // Inline overrides file — last writer wins. Per-section objects (`app`,\n // `package_url`) are shallow-merged so a caller can patch only `app.md5`\n // without losing `app.version` from the file. Only materialize the merged\n // sub-object when at least one side actually defines it, otherwise we'd\n // turn an absent field into an empty `{}` and confuse the buildSignature\n // fallback heuristics below.\n const parsed: RawBotimFile = {\n ...parsedFromFile,\n ...(inline as RawBotimFile | undefined),\n };\n if (parsedFromFile.app || inline?.app) {\n parsed.app = { ...parsedFromFile.app, ...inline?.app };\n }\n if (parsedFromFile.package_url || inline?.package_url) {\n parsed.package_url = { ...parsedFromFile.package_url, ...inline?.package_url };\n }\n\n // Hard-stop on a decommissioned mini-program — refusing the build is far\n // safer than shipping a debug SDK that targets a relay slot the platform\n // has retired.\n if (parsed.deleted === 1) {\n throw new BotimConfigError(\n `${filePath} reports the mini-program as deleted (deleted: 1). Cannot ship debug SDK against a retired mini-program.`,\n { code: 'config-deleted', path: filePath },\n );\n }\n\n // Read the canonical id; fall back to the SDK-internal alias for repos that\n // hand-write a config without the platform's `mp_id` field.\n const idValue = (parsed.mp_id ?? parsed.miniProgramId) as unknown;\n if (typeof idValue !== 'string' || idValue.length === 0) {\n throw new BotimConfigError(\n `${filePath} is missing required field \"mp_id\" (must be a non-empty string)`,\n { code: 'config-missing-field', path: filePath },\n );\n }\n if (!ID_PATTERN.test(idValue)) {\n throw new BotimConfigError(\n `${filePath} field \"mp_id\" must match ${ID_PATTERN.toString()} (got: ${JSON.stringify(idValue)})`,\n { code: 'config-invalid-id', path: filePath },\n );\n }\n\n // ── Derive a stable, release-correlated buildSignature ────────────────────\n // Preference order:\n // 1. explicit buildSignature in the file (consumers may pin one)\n // 2. version + package md5 (deterministic per release; survives unrelated\n // platform-side mutations like update_time / mp_status_time)\n // 3. sha256 of the file contents (last-resort fallback)\n const md5 =\n typeof parsed.app?.md5 === 'string'\n ? parsed.app.md5\n : typeof parsed.package_url?.md5 === 'string'\n ? parsed.package_url.md5\n : undefined;\n const versionForSig =\n (typeof parsed.version === 'string' && parsed.version) ||\n (typeof parsed.app?.version === 'string' && parsed.app.version) ||\n undefined;\n\n let buildSignature: string;\n if (typeof parsed.buildSignature === 'string' && parsed.buildSignature.length > 0) {\n buildSignature = parsed.buildSignature;\n } else if (versionForSig && md5) {\n buildSignature = `v${versionForSig}+md5:${md5.slice(0, 12)}`;\n } else {\n // No file content to hash (file-less inline workflow) → hash the merged\n // canonical fields instead. Two builds with identical mp_id+env+version\n // get the same signature, which is exactly what the relay correlates on.\n const sigSource =\n raw ??\n JSON.stringify({ mp_id: idValue, env, version: versionForSig, app: parsed.app });\n buildSignature = 'sha256:' + createHash('sha256').update(sigSource).digest('hex').slice(0, 12);\n }\n\n const cfg: BotimConfig = {\n // Spread first so the canonical fields below override anything inherited.\n ...parsed,\n miniProgramId: idValue,\n env,\n buildSignature,\n appName: typeof parsed.app_id === 'string' ? parsed.app_id : (parsed.appName as string | undefined),\n appVersion:\n versionForSig ?? (typeof parsed.appVersion === 'string' ? parsed.appVersion : undefined),\n };\n\n return cfg;\n}\n\n/**\n * Resolve the on-disk filename to read for this env. Three shapes accepted:\n * - undefined → legacy default `botim.{env}.json`\n * - string → use literally (e.g. \"botim_config.beta.json\")\n * - function → called with (env, mode) and must return a string\n *\n * Throws a `BotimConfigError` rather than letting a bad function return\n * propagate as a misleading `existsSync` miss.\n */\nfunction resolveFileName(\n override: ResolveOptions['configFileName'],\n env: BotimEnv,\n mode: string,\n): string {\n if (override === undefined) return `botim.${env}.json`;\n if (typeof override === 'string') {\n if (override.length === 0) {\n throw new BotimConfigError('configFileName must be a non-empty string', {\n code: 'config-invalid-filename',\n });\n }\n return override;\n }\n if (typeof override === 'function') {\n const result = override(env, mode);\n if (typeof result !== 'string' || result.length === 0) {\n throw new BotimConfigError(\n `configFileName(${env}, ${mode}) must return a non-empty string (got ${JSON.stringify(result)})`,\n { code: 'config-invalid-filename' },\n );\n }\n return result;\n }\n throw new BotimConfigError(\n `configFileName must be a string or (env, mode) => string (got ${typeof override})`,\n { code: 'config-invalid-filename' },\n );\n}\n\nexport const __test__ = { defaultMapMode, ID_PATTERN, VALID_ENVS, resolveFileName };\n","/**\n * Errors that escape the SDK into host code.\n *\n * The SDK's no-throw invariant has exactly two carve-outs:\n * - `BotimConfigError` — thrown synchronously by `enableRemoteDebug` when the\n * resolved `BotimConfig` is missing or invalid. Prevents any I/O.\n * - `BotimConsentError` — thrown synchronously by `enableRemoteDebug` when\n * consent has not been granted in a `prod` build. Prevents any I/O.\n *\n * Both are intentionally synchronous and pre-installation: at the moment they\n * fire, no globals have been wrapped, no listeners installed, no network sent.\n */\n\nconst BRAND = '@botim/debug-sdk' as const;\n\nexport class BotimConfigError extends Error {\n readonly name = 'BotimConfigError';\n readonly code: string;\n readonly path?: string;\n\n constructor(message: string, opts: { code: string; path?: string } = { code: 'invalid-config' }) {\n super(`[${BRAND}] ${message}`);\n this.code = opts.code;\n this.path = opts.path;\n }\n}\n\nexport class BotimConsentError extends Error {\n readonly name = 'BotimConsentError';\n constructor(message: string) {\n super(`[${BRAND}] ${message}`);\n }\n}\n","/**\n * Vite plugin for `@botim/debug-sdk`.\n *\n * - Reads `botim.{mode}.json` from the consumer's project root at build time.\n * - Validates required fields; fails the build loudly if anything is wrong.\n * - Exposes the resolved config via the virtual module `virtual:botim/config`\n * as `export const botimConfig: Readonly<BotimConfig>`.\n *\n * The plugin's only Vite-specific surface is the `Plugin` type; we keep the\n * import as `type-only` so `vite` remains an *optional* peer dependency. A\n * consumer that never installs Vite will never load this file (it's only\n * reachable via the `./vite` subpath export).\n */\n\nimport type { Plugin } from 'vite';\nimport { resolveBotimConfig, type InlineBotimConfigInput } from './resolve-config.js';\nimport type { BotimConfig, BotimEnv } from '../types.js';\n\nconst VIRTUAL_ID = 'virtual:botim/config';\nconst RESOLVED_VIRTUAL_ID = '\\0' + VIRTUAL_ID;\n\nexport interface BotimDebugPluginOptions {\n /**\n * Override Vite's `mode`. Useful when the build is driven outside Vite's\n * usual `--mode` flag, or in tests. If omitted, the plugin uses the mode\n * Vite resolves from the CLI / config.\n */\n mode?: string;\n /**\n * Map Vite's mode to a BotimEnv. Defaults: `development` → `dev`,\n * `production` → `prod`, others pass through verbatim.\n */\n mapMode?: (mode: string) => BotimEnv | string;\n /**\n * Project root override. Defaults to Vite's resolved `config.root`.\n */\n root?: string;\n /**\n * Relay endpoint to bake into `virtual:botim/config` as `relayUrl`. The\n * SDK reads this at runtime so the host app can call:\n *\n * enableRemoteDebug({ endpoint: botimConfig.relayUrl, … })\n *\n * Without this option, hosts must either set up a Vite proxy\n * (`server.proxy: { '/v1': 'http://localhost:8090' }`) and pass\n * `endpoint: location.origin`, OR pass an explicit URL at the call\n * site. Passing it through the plugin centralises the wiring and makes\n * mode-specific URLs trivial: read process.env.RELAY_URL in\n * vite.config and forward it here.\n *\n * The relay must allow the page's origin via CORS (CORS_ORIGINS env on\n * @botim/debug-relay defaults to \"*\"). Trailing slash is stripped at\n * resolve time so `/v1/attach` joins cleanly.\n */\n relayUrl?: string;\n /**\n * Override the default `botim.{env}.json` filename pattern. Pass a string\n * for a fixed name (e.g. `\"botim_config.beta.json\"`) or a function for\n * per-env logic (e.g. `(env) => `botim_config.${env}.json``).\n *\n * Resolved relative to the project root unless absolute.\n */\n configFileName?: string | ((env: BotimEnv, mode: string) => string);\n /**\n * Inline config bag that **takes precedence** over the on-disk file.\n *\n * Useful when:\n * - mp_id / version come from env vars (CI builds, monorepo packages)\n * - the host wants to ship without any `botim.*.json` file at all\n * (set `mp_id` here and the resolver tolerates a missing file)\n * - a single field needs patching per build without editing JSON\n *\n * Merge semantics: the file is loaded first (if present), then these\n * keys are spread on top. Sub-objects (`app`, `package_url`) are\n * shallow-merged so patching `app.md5` keeps `app.version` from the file.\n */\n config?: InlineBotimConfigInput;\n}\n\nexport function botimDebug(options: BotimDebugPluginOptions = {}): Plugin {\n let resolved: BotimConfig | null = null;\n let resolvedFromMode: string | null = null;\n let resolvedError: Error | null = null;\n\n return {\n name: '@botim/debug-sdk:vite',\n enforce: 'pre',\n\n configResolved(viteConfig) {\n const mode = options.mode ?? viteConfig.mode;\n const root = options.root ?? viteConfig.root;\n try {\n resolved = resolveBotimConfig(mode, root, {\n mapMode: options.mapMode,\n configFileName: options.configFileName,\n inlineConfig: options.config,\n });\n // Merge the host-provided relay URL into the resolved config bag.\n // Strip trailing slash so callers don't get `/v1//attach` if they\n // pass `https://relay.example.com/`.\n if (options.relayUrl) {\n (resolved as BotimConfig & { relayUrl: string }).relayUrl =\n options.relayUrl.replace(/\\/+$/, '');\n }\n resolvedFromMode = mode;\n } catch (err) {\n // Stash the error; surface it the moment a build module imports the\n // virtual module. We don't throw from `configResolved` directly because\n // some consumers run Vite for non-build tasks (e.g. `vite-node` for\n // tooling) where the virtual module is never imported and a hard fail\n // would be a regression.\n resolved = null;\n resolvedError = err instanceof Error ? err : new Error(String(err));\n resolvedFromMode = mode;\n }\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_VIRTUAL_ID) return null;\n\n if (!resolved) {\n const message =\n resolvedError?.message ??\n `[@botim/debug-sdk] virtual:botim/config requested but configResolved did not run (mode=${resolvedFromMode ?? 'unknown'})`;\n // `this.error` aborts the build with a properly attributed Vite error.\n this.error(message);\n }\n\n // Frozen so a runtime mutation can't accidentally taint the constant.\n const body =\n `// AUTO-GENERATED by @botim/debug-sdk:vite — do not edit.\\n` +\n `export const botimConfig = Object.freeze(${JSON.stringify(resolved)});\\n` +\n `export default botimConfig;\\n`;\n return { code: body, map: null };\n },\n };\n}\n\nexport { resolveBotimConfig } from './resolve-config.js';\nexport type { InlineBotimConfigInput, ResolveOptions } from './resolve-config.js';\nexport { BotimConfigError } from '../errors.js';\nexport type { BotimConfig, BotimEnv } from '../types.js';\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botim/mp-debug-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Remote-debug SDK for BOTIM mini-programs — streams console, network, and error events to a BOTIM debug-relay for live inspection, with an AI-observable command channel.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",