@spotpatch/vite 1.7.0 → 1.9.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
@@ -17,24 +17,26 @@ The supported Vite integration for SpotPatch: select React UI, trace it to JSX/T
17
17
 
18
18
  SpotPatch runs only with the Vite development server. Production builds contain no SpotPatch Runtime, source markers, or local API endpoints.
19
19
 
20
- ### Install
20
+ ### One-command setup (recommended)
21
21
 
22
22
  ```bash
23
- npm install --save-dev @spotpatch/vite
24
- # or
25
- pnpm add -D @spotpatch/vite
23
+ npx --yes @spotpatch/vite@latest setup
26
24
  ```
27
25
 
28
- ### Initialize
26
+ This npm-bootstrap command supports both npm and pnpm projects. It fetches the registry's actual latest CLI, detects the project package manager, installs that CLI's exact SpotPatch version, updates a supported `vite.config.*`, and verifies the result. The exact-version handoff avoids pnpm 11's default 24-hour `minimumReleaseAge` policy silently resolving `@latest` to an older mature release.
29
27
 
30
- Run the safe, idempotent initializer and its read-only diagnostic:
28
+ The initializer supports configuration objects and object-returning `defineConfig` callbacks, while ambiguous dynamic configurations fail without writing the config.
29
+
30
+ For npm, installation and initialization can also be kept separate:
31
31
 
32
32
  ```bash
33
- pnpm exec spotpatch-vite init
34
- pnpm exec spotpatch-vite check
33
+ npm install --save-dev @spotpatch/vite@latest
34
+ npx spotpatch-vite init
35
35
  ```
36
36
 
37
- The initializer places SpotPatch before the React plugin. When the project has a local TypeScript installation and root `tsconfig.json`, it also exposes the in-page Review / Trusted direct selector without duplicating provider configuration:
37
+ With pnpm 11, use the recommended setup command, install a trusted exact version, or wait until the release is 24 hours old. SpotPatch does not globally disable the project's supply-chain quarantine.
38
+
39
+ The initializer places SpotPatch before the React plugin so source markers are injected before React transforms the module:
38
40
 
39
41
  ```ts
40
42
  // vite.config.ts
@@ -43,10 +45,12 @@ import react from "@vitejs/plugin-react-swc";
43
45
  import { defineConfig } from "vite";
44
46
 
45
47
  export default defineConfig({
46
- plugins: [spotPatch({ trustedFastMode: true }), react()],
48
+ plugins: [spotPatch({ dataFlow: {}, trustedFastMode: true }), react()],
47
49
  });
48
50
  ```
49
51
 
52
+ When no safe local TypeScript check can be discovered, it generates `spotPatch({ dataFlow: {} })` and keeps Review mode. Unsupported dynamic configuration can still be integrated manually using the same plugin order.
53
+
50
54
  Start the application normally:
51
55
 
52
56
  ```bash
@@ -55,16 +59,22 @@ pnpm dev
55
59
 
56
60
  Select **Select element** in the bottom-right corner or press `Mod+Shift+S`. SpotPatch can collect multiple targets across same-project pages, preserve them through navigation and workbench close/reopen cycles, keep a separate instruction for each one, open the exact source location in Cursor or VS Code, and generate a structured prompt without requiring AI configuration.
57
61
 
62
+ ### Component data flow (Beta)
63
+
64
+ The current `setup/init` writes `spotPatch({ dataFlow: {} })` automatically; use the same option for manual integration. The Vite + React 18 development-only **Data flow** and **Page APIs** tabs show proven method/path, parameter keys, source-consumed response fields, data destinations, actually dispatched requests, and unassigned current-page traffic. Query values and response bodies are not collected. A relationship is reported only when stable component/source/callsite/invocation evidence agrees; ambiguous traffic remains unknown or unassigned. Data-flow AI, safe JSON response inspection, and Next.js are not included in this Beta. See the repository's [exact implementation status](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF/13-Beta%E5%AE%9E%E7%8E%B0%E7%8A%B6%E6%80%81%E4%B8%8E%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C.md).
65
+
66
+ Supported adapters include direct/component-service `fetch`, Axios, React Query/TanStack Query callbacks, and experimental tRPC logical procedures. tRPC batch HTTP transport remains separate evidence and is never assigned by timing or URL similarity.
67
+
58
68
  ### Compatibility
59
69
 
60
70
  | Dependency | Supported range |
61
71
  | -------------------- | ------------------------------ |
62
72
  | Node.js | `>=20.19.0` |
63
- | Vite | `^5.0.0 | | ^6.0.0 | | ^7.0.0` |
73
+ | Vite | `>=5.0.0 <8.0.0` |
64
74
  | React public support | `18.2–18.3` |
65
75
  | Default source files | `src/**/*.jsx`, `src/**/*.tsx` |
66
76
 
67
- React 19 is not part of the Vite v1 public support promise. Next.js projects must not use this package as a substitute for a Next adapter; see the repository's [Next.js status](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/Next%E9%80%82%E9%85%8D/00-%E7%B4%A2%E5%BC%95%E4%B8%8E%E6%9E%B6%E6%9E%84%E6%91%98%E8%A6%81.md).
77
+ React 19, including 19.2.x, is not part of the Vite v1 public support promise. You may try it experimentally, but verify picking, source resolution, HMR, and the AI workflow in your application before relying on it. Next.js projects must not use this package as a substitute for a Next adapter; see the repository's [Next.js status](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/Next%E9%80%82%E9%85%8D/00-%E7%B4%A2%E5%BC%95%E4%B8%8E%E6%9E%B6%E6%9E%84%E6%91%98%E8%A6%81.md).
68
78
 
69
79
  ### Options
70
80
 
@@ -79,6 +89,7 @@ spotPatch({
79
89
  locale: "auto",
80
90
  maxTargets: 8,
81
91
  ai: false,
92
+ dataFlow: false,
82
93
  });
83
94
  ```
84
95
 
@@ -96,6 +107,7 @@ spotPatch({
96
107
  | `locale` | `"auto"` | Resolves `en-US` or `zh-CN`. |
97
108
  | `maxTargets` | `8` | Targets allowed in one change request by default. |
98
109
  | `ai` | disabled or a detected complete environment | Optional provider and Agent settings. |
110
+ | `dataFlow` | `false` | Opt-in dispatch-only component data-flow Beta. |
99
111
  | `trustedFastMode` | `false` | Exposes Review/Trusted direct and discovers TypeScript for Review. |
100
112
 
101
113
  The package exports the option types, AI provider types, Agent limits, and immutable defaults. See the [public API specification](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/03-%E5%85%AC%E5%85%B1API%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B.md) for the complete constraints.
@@ -129,7 +141,7 @@ spotPatch({
129
141
 
130
142
  The default Agent path is review-gated: **Check environment** provides an optional source-free capability diagnostic, while Run starts the real isolated tool session directly and proves tool continuation inline. The Agent supplies bounded nearby project conventions, exposes file tools rather than an arbitrary shell, reuses current host-run checks, and shows the complete Diff before Apply.
131
143
 
132
- `spotpatch-vite init` automatically enables the page selector when it can discover a local TypeScript check. The equivalent concise manual option is:
144
+ To expose the optional Trusted direct mode, configure it explicitly. It requires a local TypeScript project check; otherwise keep the default Review mode:
133
145
 
134
146
  ```ts
135
147
  spotPatch({ trustedFastMode: true });
@@ -149,6 +161,8 @@ The page defaults to Review. Choosing Trusted direct uses the exact SpotPatch so
149
161
 
150
162
  ### Troubleshooting
151
163
 
164
+ - **pnpm 11 installs an older version for `@latest`:** its default 24-hour `minimumReleaseAge` policy selects the newest mature release. Use the recommended `npx ... setup`, specify a trusted exact version, or wait 24 hours.
165
+ - **Initializer rejects the config:** use a configuration object or a callback with one unambiguous object return. For conditional returns or dynamic plugin arrays, configure `spotPatch({ dataFlow: {} })` manually before the React plugin.
152
166
  - **No selection button:** confirm `spotPatch()` appears before the React plugin and that the app is running through `vite`/`vite dev`, not `vite preview`.
153
167
  - **No exact source location:** confirm the component is authored in an included `.jsx` or `.tsx` file under `src`, or configure `include` explicitly.
154
168
  - **AI is unavailable:** provide all three required environment values or set `ai: false`; partial environment configuration fails closed.
@@ -169,24 +183,26 @@ The page defaults to Review. Choosing Trusted direct uses the exact SpotPatch so
169
183
 
170
184
  SpotPatch 只在 Vite 开发服务器中运行。生产构建不包含 SpotPatch Runtime、源码标记或本地 API 端点。
171
185
 
172
- ### 安装
186
+ ### 一条命令接入(推荐)
173
187
 
174
188
  ```bash
175
- npm install --save-dev @spotpatch/vite
176
- # 或
177
- pnpm add -D @spotpatch/vite
189
+ npx --yes @spotpatch/vite@latest setup
178
190
  ```
179
191
 
180
- ### 初始化
192
+ 这条由 npm 引导的命令同时支持 npm 和 pnpm 项目。它先取得 registry 真正的最新 CLI,再识别项目包管理器、安装该 CLI 对应的 SpotPatch 精确版本、安全更新受支持的 `vite.config.*` 并验证结果。精确版本交接可以避免 pnpm 11 默认 24 小时 `minimumReleaseAge` 把 `@latest` 解析成较旧的成熟版本。
181
193
 
182
- 运行安全、幂等的初始化命令,再执行只读诊断:
194
+ 初始化器支持配置对象和返回对象的 `defineConfig` 回调;有歧义的动态配置会在不写入配置文件的情况下失败。
195
+
196
+ npm 项目也可以分开安装和初始化:
183
197
 
184
198
  ```bash
185
- pnpm exec spotpatch-vite init
186
- pnpm exec spotpatch-vite check
199
+ npm install --save-dev @spotpatch/vite@latest
200
+ npx spotpatch-vite init
187
201
  ```
188
202
 
189
- 初始化器会把 SpotPatch 放在 React 插件之前;项目存在本地 TypeScript 与根 `tsconfig.json` 时,还会自动开放页面内的“审阅 / 可信极速”选择,不需要重复填写 Provider:
203
+ pnpm 11 项目请使用推荐的 setup 命令、安装已确认的精确版本,或等待发布满 24 小时。SpotPatch 不会全局关闭项目的供应链隔离策略。
204
+
205
+ 初始化器会将 SpotPatch 放在 React 插件之前,确保源码标记在 React 转换前注入:
190
206
 
191
207
  ```ts
192
208
  // vite.config.ts
@@ -195,10 +211,12 @@ import react from "@vitejs/plugin-react-swc";
195
211
  import { defineConfig } from "vite";
196
212
 
197
213
  export default defineConfig({
198
- plugins: [spotPatch({ trustedFastMode: true }), react()],
214
+ plugins: [spotPatch({ dataFlow: {}, trustedFastMode: true }), react()],
199
215
  });
200
216
  ```
201
217
 
218
+ 无法发现安全的本地 TypeScript 检查时,初始化器会生成 `spotPatch({ dataFlow: {} })` 并保持审阅模式。不受支持的动态配置仍可按相同插件顺序手动接入。
219
+
202
220
  照常启动应用:
203
221
 
204
222
  ```bash
@@ -207,16 +225,22 @@ pnpm dev
207
225
 
208
226
  点击右下角的 **选择元素** 或按下 `Mod+Shift+S`。SpotPatch 支持跨同一项目的多个页面采集目标,在页面跳转及工作台关闭/重开后保留目标与独立修改要求,在 Cursor 或 VS Code 中打开精确位置,并且在完全不配置 AI 的情况下生成结构化 Prompt。
209
227
 
228
+ ### 组件数据链路(Beta)
229
+
230
+ 新版 `setup/init` 会自动写入 `spotPatch({ dataFlow: {} })`,手工接入时使用同一选项。Vite + React 18 开发期的 **数据链路** 与 **页面接口** 页签会显示有证据的 method/path、参数键、源码消费字段、数据去向、实际 dispatch 请求和当前页面未归属流量;不采集 query 值或响应体。只有稳定组件、源码、callsite 与 invocation 证据一致时才建立关联,歧义流量保持 unknown/unassigned。当前不包含 data-flow AI、安全 JSON 响应读取或 Next.js 支持。准确范围见仓库中的 [实现状态文档](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E9%93%BE%E8%B7%AF/13-Beta%E5%AE%9E%E7%8E%B0%E7%8A%B6%E6%80%81%E4%B8%8E%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C.md)。
231
+
232
+ 已支持的适配器包括组件直接/Service `fetch`、Axios、React Query/TanStack Query 回调和实验性的 tRPC 逻辑 procedure。tRPC batch HTTP 传输保持为独立证据,绝不按时间或 URL 相似度强行归属。
233
+
210
234
  ### 兼容范围
211
235
 
212
236
  | 依赖 | 正式支持范围 |
213
237
  | -------------- | ------------------------------ |
214
238
  | Node.js | `>=20.19.0` |
215
- | Vite | `^5.0.0 | | ^6.0.0 | | ^7.0.0` |
239
+ | Vite | `>=5.0.0 <8.0.0` |
216
240
  | React 正式支持 | `18.2–18.3` |
217
241
  | 默认源码文件 | `src/**/*.jsx`、`src/**/*.tsx` |
218
242
 
219
- React 19 不在 Vite v1 正式承诺内。Next.js 项目不能把本包当作 Next 适配器使用;准确状态见仓库中的 [Next.js 适配说明](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/Next%E9%80%82%E9%85%8D/00-%E7%B4%A2%E5%BC%95%E4%B8%8E%E6%9E%B6%E6%9E%84%E6%91%98%E8%A6%81.md)。
243
+ React 19(包括 19.2.x)不在 Vite v1 正式承诺内。可以作为实验性组合尝试,但在日常使用前必须在自身项目验证元素选择、源码定位、HMR 和 AI 流程。Next.js 项目不能把本包当作 Next 适配器使用;准确状态见仓库中的 [Next.js 适配说明](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/Next%E9%80%82%E9%85%8D/00-%E7%B4%A2%E5%BC%95%E4%B8%8E%E6%9E%B6%E6%9E%84%E6%91%98%E8%A6%81.md)。
220
244
 
221
245
  ### 选项
222
246
 
@@ -231,6 +255,7 @@ spotPatch({
231
255
  locale: "auto",
232
256
  maxTargets: 8,
233
257
  ai: false,
258
+ dataFlow: false,
234
259
  });
235
260
  ```
236
261
 
@@ -248,6 +273,7 @@ spotPatch({
248
273
  | `locale` | `"auto"` | 自动解析 `en-US` 或 `zh-CN`。 |
249
274
  | `maxTargets` | `8` | 一次修改任务默认允许的目标数。 |
250
275
  | `ai` | 关闭或检测到完整环境配置 | 可选 Provider 和 Agent 配置。 |
276
+ | `dataFlow` | `false` | 可选 dispatch-only 组件数据链路 Beta。 |
251
277
  | `trustedFastMode` | `false` | 开放审阅/可信极速选择;TypeScript 检查供审阅模式使用。 |
252
278
 
253
279
  本包导出选项类型、AI Provider 类型、Agent 限制和不可变默认值。完整约束见[公共 API 规范](https://github.com/huanglvjing/spotpatch/blob/main/docs/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/03-%E5%85%AC%E5%85%B1API%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B.md)。
@@ -281,7 +307,7 @@ spotPatch({
281
307
 
282
308
  默认 Agent 路径必须经过审阅:“检查运行环境”提供不含源码的可选能力诊断;点击运行会直接进入真实隔离工具会话,并在会话内证明工具续接能力。Agent 会提供有界的就近项目规范,只暴露文件工具而不是任意 Shell,复用当前变更版本中由宿主实际执行的检查,并在 Apply 前展示完整 Diff。
283
309
 
284
- `spotpatch-vite init` 能发现本地 TypeScript 校验时会自动开放页面选择;等价的简洁手动配置只有一行:
310
+ 需要开放可选的“可信极速”模式时,请显式配置。它要求项目能提供本地 TypeScript 检查;否则请保持默认的审阅模式:
285
311
 
286
312
  ```ts
287
313
  spotPatch({ trustedFastMode: true });
@@ -301,6 +327,8 @@ spotPatch({ trustedFastMode: true });
301
327
 
302
328
  ### 常见问题
303
329
 
330
+ - **pnpm 11 对 `@latest` 安装了旧版本:**默认 24 小时 `minimumReleaseAge` 会选择最新的成熟版本。请使用推荐的 `npx ... setup`、安装已确认的精确版本,或等待 24 小时。
331
+ - **初始化器拒绝配置:**请使用配置对象,或只含一个明确对象返回的回调。存在条件返回或动态插件数组时,手动将 `spotPatch({ dataFlow: {} })` 放到 React 插件之前。
304
332
  - **没有选择元素按钮:**确认 `spotPatch()` 位于 React 插件之前,并且应用通过 `vite`/`vite dev` 而不是 `vite preview` 启动。
305
333
  - **没有精确源码位置:**确认组件来自 include 范围内的 `.jsx` 或 `.tsx` 文件,默认范围是 `src`。
306
334
  - **AI 不可用:**提供全部三个必需环境变量,或者显式设置 `ai: false`;不完整配置会安全失败。
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFile } from "fs/promises";
4
+ import { readFile as readFile2 } from "fs/promises";
5
5
  import { createRequire } from "module";
6
- import path2 from "path";
6
+ import path3 from "path";
7
7
 
8
8
  // src/initializer.ts
9
9
  import path from "path";
@@ -68,6 +68,16 @@ function insertStaticImport(magicString, program, statement) {
68
68
  magicString.appendRight(offset, `
69
69
  ${statement}`);
70
70
  }
71
+ function importQuote(source, program) {
72
+ const firstImport = importsOf(program)[0];
73
+ if (firstImport !== void 0) {
74
+ const quote = source[firstImport.source.start];
75
+ if (quote === '"' || quote === "'") {
76
+ return quote;
77
+ }
78
+ }
79
+ return '"';
80
+ }
71
81
  function collectIdentifierNames(program) {
72
82
  const names = /* @__PURE__ */ new Set();
73
83
  new Visitor({
@@ -153,6 +163,47 @@ function resolveConfigExpression(program) {
153
163
  }
154
164
  return unwrapExpression(resolved);
155
165
  }
166
+ function resolveFactoryConfigObject(expression) {
167
+ const factory = unwrapExpression(expression);
168
+ if (factory.type !== "ArrowFunctionExpression" && factory.type !== "FunctionExpression") {
169
+ return void 0;
170
+ }
171
+ if (factory.body === null) {
172
+ throw new Error(
173
+ "SpotPatch init cannot use a defineConfig callback without a body."
174
+ );
175
+ }
176
+ if (factory.body.type !== "BlockStatement") {
177
+ const returned2 = unwrapExpression(factory.body);
178
+ return returned2.type === "ObjectExpression" ? returned2 : void 0;
179
+ }
180
+ const returns = [];
181
+ new Visitor({
182
+ ReturnStatement(statement) {
183
+ returns.push(statement);
184
+ }
185
+ }).visit({
186
+ type: "Program",
187
+ body: factory.body.body,
188
+ sourceType: "module",
189
+ hashbang: null,
190
+ start: factory.body.start,
191
+ end: factory.body.end
192
+ });
193
+ const onlyReturn = returns.length === 1 ? returns[0] : void 0;
194
+ if (onlyReturn?.argument == null) {
195
+ throw new Error(
196
+ "SpotPatch init requires a defineConfig callback with exactly one top-level object return."
197
+ );
198
+ }
199
+ const returned = unwrapExpression(onlyReturn.argument);
200
+ if (returned.type !== "ObjectExpression") {
201
+ throw new Error(
202
+ "SpotPatch init requires the defineConfig callback to return a configuration object directly."
203
+ );
204
+ }
205
+ return returned;
206
+ }
156
207
  function resolveConfigObject(program) {
157
208
  const expression = resolveConfigExpression(program);
158
209
  if (expression.type === "ObjectExpression") {
@@ -166,10 +217,14 @@ function resolveConfigObject(program) {
166
217
  if (value.type === "ObjectExpression") {
167
218
  return value;
168
219
  }
220
+ const callbackObject = resolveFactoryConfigObject(value);
221
+ if (callbackObject !== void 0) {
222
+ return callbackObject;
223
+ }
169
224
  }
170
225
  }
171
226
  throw new Error(
172
- "SpotPatch init currently supports an object passed to defineConfig in vite.config."
227
+ "SpotPatch init supports a configuration object or an object-returning callback passed to defineConfig."
173
228
  );
174
229
  }
175
230
  function propertyName(property) {
@@ -204,8 +259,12 @@ function childIndent(source, object) {
204
259
  }
205
260
  return `${lineIndentAt(source, object.start)} `;
206
261
  }
207
- function trustedFastCall(pluginName, enabled) {
208
- return enabled ? `${pluginName}({ trustedFastMode: true })` : `${pluginName}()`;
262
+ function initializedPluginCall(pluginName, trustedFastMode) {
263
+ const options = [
264
+ "dataFlow: {}",
265
+ ...trustedFastMode ? ["trustedFastMode: true"] : []
266
+ ];
267
+ return `${pluginName}({ ${options.join(", ")} })`;
209
268
  }
210
269
  function directPluginCalls(plugins, pluginName) {
211
270
  return plugins.elements.filter((element) => {
@@ -216,12 +275,12 @@ function directPluginCalls(plugins, pluginName) {
216
275
  return callee.type === "Identifier" && callee.name === pluginName;
217
276
  });
218
277
  }
219
- function enableTrustedFastMode(magicString, source, call) {
278
+ function enableInitializedOptions(magicString, source, call, trustedFastMode) {
220
279
  if (call.arguments.length === 0) {
221
280
  magicString.overwrite(
222
281
  call.start,
223
282
  call.end,
224
- `${source.slice(call.start, call.end - 1)}{ trustedFastMode: true })`
283
+ initializedPluginCall(source.slice(call.start, call.callee.end), trustedFastMode)
225
284
  );
226
285
  return;
227
286
  }
@@ -233,33 +292,55 @@ function enableTrustedFastMode(magicString, source, call) {
233
292
  if (value.type !== "ObjectExpression") {
234
293
  throw new Error("SpotPatch init requires spotPatch options to be an object.");
235
294
  }
236
- const property = findProperty(value, "trustedFastMode");
237
- if (property !== void 0) {
238
- const propertyValue = unwrapExpression(property.value);
295
+ if (value.properties.some((property) => property.type === "SpreadElement")) {
296
+ throw new Error(
297
+ "SpotPatch init cannot prove dataFlow through spread spotPatch options."
298
+ );
299
+ }
300
+ const dataFlowProperty = findProperty(value, "dataFlow");
301
+ const trustedFastModeProperty = findProperty(value, "trustedFastMode");
302
+ const missingProperties = [];
303
+ if (dataFlowProperty === void 0) {
304
+ missingProperties.push("dataFlow: {}");
305
+ } else {
306
+ const dataFlowValue = unwrapExpression(dataFlowProperty.value);
307
+ if (dataFlowValue.type === "Literal" && dataFlowValue.value === false) {
308
+ magicString.overwrite(dataFlowValue.start, dataFlowValue.end, "{}");
309
+ } else if (dataFlowValue.type !== "ObjectExpression") {
310
+ throw new Error(
311
+ "SpotPatch init requires dataFlow to be false or an options object."
312
+ );
313
+ }
314
+ }
315
+ if (trustedFastMode && trustedFastModeProperty === void 0) {
316
+ missingProperties.push("trustedFastMode: true");
317
+ } else if (trustedFastModeProperty !== void 0) {
318
+ const propertyValue = unwrapExpression(trustedFastModeProperty.value);
239
319
  if (propertyValue.type !== "Literal" || typeof propertyValue.value !== "boolean") {
240
320
  throw new Error(
241
321
  "SpotPatch init requires trustedFastMode to be a boolean literal."
242
322
  );
243
323
  }
244
- if (!propertyValue.value) {
324
+ if (trustedFastMode && !propertyValue.value) {
245
325
  magicString.overwrite(propertyValue.start, propertyValue.end, "true");
246
326
  }
247
- return;
248
327
  }
328
+ if (missingProperties.length === 0) return;
249
329
  const indent = childIndent(source, value);
250
330
  if (value.properties.length === 0) {
251
- magicString.appendLeft(value.end - 1, " trustedFastMode: true ");
331
+ magicString.appendLeft(value.end - 1, ` ${missingProperties.join(", ")} `);
252
332
  } else {
253
333
  magicString.appendLeft(
254
334
  value.properties[0]?.start ?? value.end - 1,
255
- `trustedFastMode: true,
335
+ `${missingProperties.join(`,
336
+ ${indent}`)},
256
337
  ${indent}`
257
338
  );
258
339
  }
259
340
  }
260
341
  function addPluginCall(magicString, source, config, pluginName, trustedFastModeAvailable) {
261
342
  const pluginsProperty = findProperty(config, "plugins");
262
- const call = trustedFastCall(pluginName, trustedFastModeAvailable);
343
+ const call = initializedPluginCall(pluginName, trustedFastModeAvailable);
263
344
  if (pluginsProperty === void 0) {
264
345
  const indent = childIndent(source, config);
265
346
  if (config.properties.length === 0) {
@@ -284,14 +365,19 @@ ${indent}`
284
365
  throw new Error("SpotPatch init found duplicate spotPatch plugins.");
285
366
  }
286
367
  if (existing[0] !== void 0) {
287
- if (trustedFastModeAvailable) {
288
- enableTrustedFastMode(magicString, source, existing[0]);
289
- }
368
+ enableInitializedOptions(
369
+ magicString,
370
+ source,
371
+ existing[0],
372
+ trustedFastModeAvailable
373
+ );
290
374
  return;
291
375
  }
292
376
  const first = value.elements.find((element) => element !== null);
293
377
  if (first === void 0) {
294
378
  magicString.appendLeft(value.end - 1, call);
379
+ } else if (!source.slice(value.start, first.start).includes("\n")) {
380
+ magicString.appendLeft(first.start, `${call}, `);
295
381
  } else {
296
382
  magicString.appendLeft(
297
383
  first.start,
@@ -308,10 +394,11 @@ function transformViteConfig(absolutePath, source, trustedFastModeAvailable) {
308
394
  const magicString = new MagicString(source);
309
395
  if (existingPluginName === void 0) {
310
396
  const specifier = pluginName === "spotPatch" ? "spotPatch" : `spotPatch as ${pluginName}`;
397
+ const quote = importQuote(source, program);
311
398
  insertStaticImport(
312
399
  magicString,
313
400
  program,
314
- `import { ${specifier} } from ${JSON.stringify(ADAPTER_PACKAGE_NAME)};`
401
+ `import { ${specifier} } from ${quote}${ADAPTER_PACKAGE_NAME}${quote};`
315
402
  );
316
403
  }
317
404
  addPluginCall(magicString, source, config, pluginName, trustedFastModeAvailable);
@@ -384,15 +471,119 @@ async function checkViteIntegration(directory = process.cwd()) {
384
471
  }
385
472
  }
386
473
 
474
+ // src/setup.ts
475
+ import { spawn } from "child_process";
476
+ import { access, readFile } from "fs/promises";
477
+ import path2 from "path";
478
+ var VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
479
+ async function pathExists(absolutePath) {
480
+ try {
481
+ await access(absolutePath);
482
+ return true;
483
+ } catch (error) {
484
+ if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
485
+ return false;
486
+ }
487
+ throw error;
488
+ }
489
+ }
490
+ async function detectPackageManager(appRoot = process.cwd(), userAgent = process.env.npm_config_user_agent ?? "") {
491
+ const manifestPath = path2.join(appRoot, "package.json");
492
+ const manifest = JSON.parse(
493
+ await readFile(manifestPath, "utf8")
494
+ );
495
+ const declared = manifest.packageManager;
496
+ if (typeof declared === "string") {
497
+ const name = declared.split("@", 1)[0];
498
+ if (name === "pnpm" || name === "npm") {
499
+ return name;
500
+ }
501
+ throw new Error(
502
+ `SpotPatch setup supports npm and pnpm projects; package.json declares ${declared}.`
503
+ );
504
+ }
505
+ const [hasPnpmLock, hasNpmLock] = await Promise.all([
506
+ pathExists(path2.join(appRoot, "pnpm-lock.yaml")),
507
+ pathExists(path2.join(appRoot, "package-lock.json"))
508
+ ]);
509
+ if (hasPnpmLock !== hasNpmLock) {
510
+ return hasPnpmLock ? "pnpm" : "npm";
511
+ }
512
+ if (hasPnpmLock && hasNpmLock) {
513
+ throw new Error(
514
+ "SpotPatch setup found both pnpm-lock.yaml and package-lock.json; remove the stale lockfile or run installation and init separately."
515
+ );
516
+ }
517
+ if (userAgent.startsWith("pnpm/")) {
518
+ return "pnpm";
519
+ }
520
+ if (userAgent.startsWith("npm/")) {
521
+ return "npm";
522
+ }
523
+ throw new Error(
524
+ "SpotPatch setup could not determine npm or pnpm; run installation and init separately."
525
+ );
526
+ }
527
+ function createInstallCommand(packageManager, version, platform = process.platform) {
528
+ if (!VERSION_PATTERN.test(version)) {
529
+ throw new Error("SpotPatch setup could not determine its package version.");
530
+ }
531
+ const packageSpecifier = `@spotpatch/vite@${version}`;
532
+ return Object.freeze({
533
+ executable: platform === "win32" ? `${packageManager}.cmd` : packageManager,
534
+ arguments: Object.freeze(
535
+ packageManager === "pnpm" ? ["add", "-D", packageSpecifier] : ["install", "--save-dev", packageSpecifier]
536
+ )
537
+ });
538
+ }
539
+ async function readCurrentAdapterVersion() {
540
+ const manifest = JSON.parse(
541
+ await readFile(new URL("../package.json", import.meta.url), "utf8")
542
+ );
543
+ const version = manifest.version;
544
+ if (typeof version !== "string" || !VERSION_PATTERN.test(version)) {
545
+ throw new Error("SpotPatch setup could not determine its package version.");
546
+ }
547
+ return version;
548
+ }
549
+ async function installCurrentAdapter(packageManager, appRoot = process.cwd()) {
550
+ const version = await readCurrentAdapterVersion();
551
+ const command = createInstallCommand(packageManager, version);
552
+ process.stdout.write(
553
+ `[spotpatch:vite] installing @spotpatch/vite@${version} with ${packageManager}...
554
+ `
555
+ );
556
+ await new Promise((resolve, reject) => {
557
+ const child = spawn(command.executable, command.arguments, {
558
+ cwd: appRoot,
559
+ env: process.env,
560
+ shell: false,
561
+ stdio: "inherit"
562
+ });
563
+ child.once("error", reject);
564
+ child.once("exit", (code, signal) => {
565
+ if (code === 0) {
566
+ resolve();
567
+ return;
568
+ }
569
+ reject(
570
+ new Error(
571
+ `SpotPatch setup installation failed (${signal ?? `exit ${String(code)}`}).`
572
+ )
573
+ );
574
+ });
575
+ });
576
+ }
577
+
387
578
  // src/cli.ts
388
- var VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/u;
579
+ var VERSION_PATTERN2 = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/u;
389
580
  function writeUsage() {
390
581
  process.stderr.write(
391
- "Usage: spotpatch-vite <init|check>\n init Preview and apply safe Vite integration changes.\n check Verify the Vite integration without writing files.\n"
582
+ "Usage: spotpatch-vite <setup|init|check>\n setup Install this CLI's exact @spotpatch/vite version, then initialize it.\n init Preview and apply safe Vite integration changes.\n check Verify the Vite integration without writing files.\n"
392
583
  );
393
584
  }
394
585
  async function inspectViteProject(appRoot = process.cwd()) {
395
- const resolveFromApplication = createRequire(path2.join(appRoot, "package.json"));
586
+ const resolveFromApplication = createRequire(path3.join(appRoot, "package.json"));
396
587
  let adapterEntry;
397
588
  let viteManifestPath;
398
589
  try {
@@ -407,9 +598,9 @@ async function inspectViteProject(appRoot = process.cwd()) {
407
598
  if (adapterEntry.length === 0) {
408
599
  throw new Error("SpotPatch could not resolve the Vite adapter export.");
409
600
  }
410
- const manifest = JSON.parse(await readFile(viteManifestPath, "utf8"));
601
+ const manifest = JSON.parse(await readFile2(viteManifestPath, "utf8"));
411
602
  const version = typeof manifest === "object" && manifest !== null && "version" in manifest && typeof manifest.version === "string" ? manifest.version : void 0;
412
- const match = version === void 0 ? null : VERSION_PATTERN.exec(version);
603
+ const match = version === void 0 ? null : VERSION_PATTERN2.exec(version);
413
604
  const major = Number(match?.[1]);
414
605
  if (version === void 0 || match === null || !Number.isSafeInteger(major) || major < 5 || major >= 8) {
415
606
  throw new Error(
@@ -451,6 +642,14 @@ ${change.nextContent}`);
451
642
  writeTrustedModeStatus(plan.trustedFastModeAvailable);
452
643
  return 0;
453
644
  }
645
+ async function runSetup(arguments_) {
646
+ if (arguments_.length !== 0) {
647
+ throw new Error("SpotPatch setup does not accept positional arguments.");
648
+ }
649
+ const packageManager = await detectPackageManager();
650
+ await installCurrentAdapter(packageManager);
651
+ return runInit([]);
652
+ }
454
653
  async function runCheck(arguments_) {
455
654
  if (arguments_.length !== 0) {
456
655
  throw new Error("SpotPatch check does not accept positional arguments.");
@@ -473,6 +672,9 @@ async function runCheck(arguments_) {
473
672
  }
474
673
  async function main(arguments_) {
475
674
  const [command, ...rest] = arguments_;
675
+ if (command === "setup") {
676
+ return runSetup(rest);
677
+ }
476
678
  if (command === "init") {
477
679
  return runInit(rest);
478
680
  }