@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 +54 -26
- package/dist/cli.js +226 -24
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +81 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +86 -8
- package/dist/index.js.map +1 -1
- package/dist/runtime-client.js +34 -34
- package/dist/runtime-data-flow-panel.js +26 -0
- package/dist/runtime-data-flow-prelude.js +1 -0
- package/dist/runtime-react-adapter.js +1 -1
- package/package.json +7 -7
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
|
-
###
|
|
20
|
+
### One-command setup (recommended)
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
24
|
-
# or
|
|
25
|
-
pnpm add -D @spotpatch/vite
|
|
23
|
+
npx --yes @spotpatch/vite@latest setup
|
|
26
24
|
```
|
|
27
25
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
33
|
+
npm install --save-dev @spotpatch/vite@latest
|
|
34
|
+
npx spotpatch-vite init
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
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 |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
199
|
+
npm install --save-dev @spotpatch/vite@latest
|
|
200
|
+
npx spotpatch-vite init
|
|
187
201
|
```
|
|
188
202
|
|
|
189
|
-
|
|
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 |
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
208
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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, "
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
288
|
-
|
|
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 ${
|
|
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
|
|
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(
|
|
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
|
|
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 :
|
|
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
|
}
|