@spotpatch/vite 1.5.0 → 1.6.1

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
@@ -25,9 +25,16 @@ npm install --save-dev @spotpatch/vite
25
25
  pnpm add -D @spotpatch/vite
26
26
  ```
27
27
 
28
- ### Configure
28
+ ### Initialize
29
29
 
30
- Place SpotPatch before the React plugin so its development source transform runs first.
30
+ Run the safe, idempotent initializer and its read-only diagnostic:
31
+
32
+ ```bash
33
+ pnpm exec spotpatch-vite init
34
+ pnpm exec spotpatch-vite check
35
+ ```
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 fast selector without duplicating provider configuration:
31
38
 
32
39
  ```ts
33
40
  // vite.config.ts
@@ -36,7 +43,7 @@ import react from "@vitejs/plugin-react-swc";
36
43
  import { defineConfig } from "vite";
37
44
 
38
45
  export default defineConfig({
39
- plugins: [spotPatch(), react()],
46
+ plugins: [spotPatch({ trustedFastMode: true }), react()],
40
47
  });
41
48
  ```
42
49
 
@@ -75,20 +82,21 @@ spotPatch({
75
82
  });
76
83
  ```
77
84
 
78
- | Option | Default | Description |
79
- | ------------ | ------------------------------------------------- | ----------------------------------------------------------------------- |
80
- | `enabled` | `true` | Enables the development plugin. |
81
- | `include` | JSX/TSX under `src` | Files eligible for source marker injection. |
82
- | `exclude` | dependencies, tests, stories and generated output | Files excluded from transformation. |
83
- | `editor` | `"auto"` | Auto-detect Cursor or VS Code; either can be fixed explicitly. |
84
- | `redact` | `true` | Sanitizes collected context; mandatory secret classes remain protected. |
85
- | `budget` | bounded defaults | Limits total, DOM, CSS and source context sizes. |
86
- | `shortcut` | `"Mod+Shift+S"` | Toggles element selection. |
87
- | `allowLan` | `false` | Keeps Host and Origin authorization loopback-only by default. |
88
- | `debug` | `false` | Enables development diagnostics without logging credentials. |
89
- | `locale` | `"auto"` | Resolves `en-US` or `zh-CN`. |
90
- | `maxTargets` | `8` | Targets allowed in one change request by default. |
91
- | `ai` | disabled or a detected complete environment | Optional provider and Agent settings. |
85
+ | Option | Default | Description |
86
+ | ----------------- | ------------------------------------------------- | ----------------------------------------------------------------------- |
87
+ | `enabled` | `true` | Enables the development plugin. |
88
+ | `include` | JSX/TSX under `src` | Files eligible for source marker injection. |
89
+ | `exclude` | dependencies, tests, stories and generated output | Files excluded from transformation. |
90
+ | `editor` | `"auto"` | Auto-detect Cursor or VS Code; either can be fixed explicitly. |
91
+ | `redact` | `true` | Sanitizes collected context; mandatory secret classes remain protected. |
92
+ | `budget` | bounded defaults | Limits total, DOM, CSS and source context sizes. |
93
+ | `shortcut` | `"Mod+Shift+S"` | Toggles element selection. |
94
+ | `allowLan` | `false` | Keeps Host and Origin authorization loopback-only by default. |
95
+ | `debug` | `false` | Enables development diagnostics without logging credentials. |
96
+ | `locale` | `"auto"` | Resolves `en-US` or `zh-CN`. |
97
+ | `maxTargets` | `8` | Targets allowed in one change request by default. |
98
+ | `ai` | disabled or a detected complete environment | Optional provider and Agent settings. |
99
+ | `trustedFastMode` | `false` | Exposes the page selector and auto-discovers a local TypeScript check. |
92
100
 
93
101
  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.
94
102
 
@@ -121,29 +129,13 @@ spotPatch({
121
129
 
122
130
  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.
123
131
 
124
- For a trusted local project, `trusted-auto` replaces the provider and dirty-worktree confirmations with one session-scoped consent and directly applies changes after required checks:
132
+ `spotpatch-vite init` automatically enables the page selector when it can discover a local TypeScript check. The equivalent concise manual option is:
125
133
 
126
134
  ```ts
127
- spotPatch({
128
- ai: {
129
- baseURL: "https://relay.example.com/v1",
130
- model: "provider-model-name",
131
- execution: {
132
- applyMode: "trusted-auto",
133
- checks: {
134
- typecheck: {
135
- label: "Typecheck",
136
- command: "pnpm",
137
- args: ["typecheck"],
138
- required: true,
139
- },
140
- },
141
- },
142
- },
143
- });
135
+ spotPatch({ trustedFastMode: true });
144
136
  ```
145
137
 
146
- This mode can directly apply validated file deletions and restart-sensitive configuration changes. It does not allow credentials, environment files, Git metadata, external paths, arbitrary shell, failed checks, or baseline conflicts. SpotPatch does not commit, push, publish, or deploy application code.
138
+ The page defaults to Review; choosing Trusted fast replaces the provider and dirty-worktree confirmations with one session-scoped consent and directly applies changes after required checks. Advanced projects can still configure explicit checks through `ai.execution`. This mode can directly apply validated file deletions and restart-sensitive configuration changes. It does not allow credentials, environment files, Git metadata, external paths, arbitrary shell, failed checks, or baseline conflicts. SpotPatch does not commit, push, publish, or deploy application code.
147
139
 
148
140
  ### Security and production behavior
149
141
 
@@ -185,9 +177,16 @@ npm install --save-dev @spotpatch/vite
185
177
  pnpm add -D @spotpatch/vite
186
178
  ```
187
179
 
188
- ### 配置
180
+ ### 初始化
189
181
 
190
- SpotPatch 必须放在 React 插件之前,让开发期源码转换先执行。
182
+ 运行安全、幂等的初始化命令,再执行只读诊断:
183
+
184
+ ```bash
185
+ pnpm exec spotpatch-vite init
186
+ pnpm exec spotpatch-vite check
187
+ ```
188
+
189
+ 初始化器会把 SpotPatch 放在 React 插件之前;项目存在本地 TypeScript 与根 `tsconfig.json` 时,还会自动开放页面内的“审阅 / 可信快速”选择,不需要重复填写 Provider:
191
190
 
192
191
  ```ts
193
192
  // vite.config.ts
@@ -196,7 +195,7 @@ import react from "@vitejs/plugin-react-swc";
196
195
  import { defineConfig } from "vite";
197
196
 
198
197
  export default defineConfig({
199
- plugins: [spotPatch(), react()],
198
+ plugins: [spotPatch({ trustedFastMode: true }), react()],
200
199
  });
201
200
  ```
202
201
 
@@ -235,20 +234,21 @@ spotPatch({
235
234
  });
236
235
  ```
237
236
 
238
- | 选项 | 默认值 | 说明 |
239
- | ------------ | ---------------------------- | ---------------------------------------------------- |
240
- | `enabled` | `true` | 启用开发期插件。 |
241
- | `include` | `src` 下 JSX/TSX | 允许注入源码标记的文件。 |
242
- | `exclude` | 依赖、测试、Story 与生成目录 | 不进行转换的文件。 |
243
- | `editor` | `"auto"` | 自动识别 Cursor 或 VS Code,也可显式固定。 |
244
- | `redact` | `true` | 清洗采集上下文;强制保护的秘密类型不会因关闭而暴露。 |
245
- | `budget` | 有界默认值 | 限制总量、DOM、CSS 和源码上下文大小。 |
246
- | `shortcut` | `"Mod+Shift+S"` | 切换元素选择器。 |
247
- | `allowLan` | `false` | 默认只允许 loopback Host 与 Origin。 |
248
- | `debug` | `false` | 输出不包含凭据的开发诊断。 |
249
- | `locale` | `"auto"` | 自动解析 `en-US` 或 `zh-CN`。 |
250
- | `maxTargets` | `8` | 一次修改任务默认允许的目标数。 |
251
- | `ai` | 关闭或检测到完整环境配置 | 可选 Provider 和 Agent 配置。 |
237
+ | 选项 | 默认值 | 说明 |
238
+ | ----------------- | ---------------------------- | ---------------------------------------------------- |
239
+ | `enabled` | `true` | 启用开发期插件。 |
240
+ | `include` | `src` 下 JSX/TSX | 允许注入源码标记的文件。 |
241
+ | `exclude` | 依赖、测试、Story 与生成目录 | 不进行转换的文件。 |
242
+ | `editor` | `"auto"` | 自动识别 Cursor 或 VS Code,也可显式固定。 |
243
+ | `redact` | `true` | 清洗采集上下文;强制保护的秘密类型不会因关闭而暴露。 |
244
+ | `budget` | 有界默认值 | 限制总量、DOM、CSS 和源码上下文大小。 |
245
+ | `shortcut` | `"Mod+Shift+S"` | 切换元素选择器。 |
246
+ | `allowLan` | `false` | 默认只允许 loopback Host 与 Origin。 |
247
+ | `debug` | `false` | 输出不包含凭据的开发诊断。 |
248
+ | `locale` | `"auto"` | 自动解析 `en-US` 或 `zh-CN`。 |
249
+ | `maxTargets` | `8` | 一次修改任务默认允许的目标数。 |
250
+ | `ai` | 关闭或检测到完整环境配置 | 可选 Provider 和 Agent 配置。 |
251
+ | `trustedFastMode` | `false` | 开放页面模式选择并自动发现本地 TypeScript 校验。 |
252
252
 
253
253
  本包导出选项类型、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)。
254
254
 
@@ -281,29 +281,13 @@ spotPatch({
281
281
 
282
282
  默认 Agent 路径必须经过审阅:“检查运行环境”提供不含源码的可选能力诊断;点击运行会直接进入真实隔离工具会话,并在会话内证明工具续接能力。Agent 会提供有界的就近项目规范,只暴露文件工具而不是任意 Shell,复用当前变更版本中由宿主实际执行的检查,并在 Apply 前展示完整 Diff。
283
283
 
284
- 可信的本地项目可以配置 `trusted-auto`:面板将 Provider 与脏工作区确认合并成一次会话级授权,必需检查通过后直接应用变更。
284
+ `spotpatch-vite init` 能发现本地 TypeScript 校验时会自动开放页面选择;等价的简洁手动配置只有一行:
285
285
 
286
286
  ```ts
287
- spotPatch({
288
- ai: {
289
- baseURL: "https://relay.example.com/v1",
290
- model: "provider-model-name",
291
- execution: {
292
- applyMode: "trusted-auto",
293
- checks: {
294
- typecheck: {
295
- label: "Typecheck",
296
- command: "pnpm",
297
- args: ["typecheck"],
298
- required: true,
299
- },
300
- },
301
- },
302
- },
303
- });
287
+ spotPatch({ trustedFastMode: true });
304
288
  ```
305
289
 
306
- 该模式可以直接应用验证通过的文件删除与需要重启开发服务的配置变更,但不会开放凭据、环境文件、Git 元数据、项目外路径、任意 Shell,也不会绕过失败检查或基线冲突。SpotPatch 不会替业务代码执行 commit、push、发包或部署。
290
+ 页面默认保持审阅模式;用户选择“可信快速”后,面板才会把 Provider 与脏工作区确认合并成一次会话级授权,并在必需检查通过后直接应用。高级项目仍可通过 `ai.execution` 配置自己的检查。该模式可以直接应用验证通过的文件删除与需要重启开发服务的配置变更,但不会开放凭据、环境文件、Git 元数据、项目外路径、任意 Shell,也不会绕过失败检查或基线冲突。SpotPatch 不会替业务代码执行 commit、push、发包或部署。
307
291
 
308
292
  ### 安全与生产行为
309
293
 
package/dist/cli.js ADDED
@@ -0,0 +1,494 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { readFile } from "fs/promises";
5
+ import { createRequire } from "module";
6
+ import path2 from "path";
7
+
8
+ // src/initializer.ts
9
+ import path from "path";
10
+ import {
11
+ applyIntegrationPlan,
12
+ createIntegrationFileChange,
13
+ discoverProjectValidationCheck,
14
+ integrationPathExists,
15
+ readIntegrationFile
16
+ } from "@spotpatch/dev-server";
17
+ import { DEFAULT_AGENT_LIMITS } from "@spotpatch/shared";
18
+ import { MagicString } from "magic-string";
19
+ import {
20
+ parseSync,
21
+ Visitor
22
+ } from "oxc-parser";
23
+ var ADAPTER_PACKAGE_NAME = "@spotpatch/vite";
24
+ var CONFIG_FILE_NAMES = Object.freeze([
25
+ "vite.config.ts",
26
+ "vite.config.mts",
27
+ "vite.config.js",
28
+ "vite.config.mjs"
29
+ ]);
30
+ function isParserErrorSeverity(value) {
31
+ return value === "Error";
32
+ }
33
+ function parseModule(absolutePath, source) {
34
+ const result = parseSync(absolutePath, source, {
35
+ sourceType: "module",
36
+ showSemanticErrors: true
37
+ });
38
+ const error = result.errors.find((entry) => isParserErrorSeverity(entry.severity));
39
+ if (error !== void 0) {
40
+ throw new SyntaxError(
41
+ `SpotPatch could not safely parse ${path.basename(absolutePath)} (${error.message}).`
42
+ );
43
+ }
44
+ return Object.freeze({ program: result.program, source });
45
+ }
46
+ function importsOf(program) {
47
+ return program.body.filter(
48
+ (statement) => statement.type === "ImportDeclaration"
49
+ );
50
+ }
51
+ function importInsertionOffset(program) {
52
+ const lastImport = importsOf(program).at(-1);
53
+ if (lastImport !== void 0) {
54
+ return lastImport.end;
55
+ }
56
+ const directives = program.body.filter(
57
+ (statement) => statement.type === "ExpressionStatement" && typeof statement.directive === "string"
58
+ );
59
+ return directives.at(-1)?.end ?? program.hashbang?.end ?? 0;
60
+ }
61
+ function insertStaticImport(magicString, program, statement) {
62
+ const offset = importInsertionOffset(program);
63
+ if (offset === 0) {
64
+ magicString.prepend(`${statement}
65
+ `);
66
+ return;
67
+ }
68
+ magicString.appendRight(offset, `
69
+ ${statement}`);
70
+ }
71
+ function collectIdentifierNames(program) {
72
+ const names = /* @__PURE__ */ new Set();
73
+ new Visitor({
74
+ Identifier(node) {
75
+ names.add(node.name);
76
+ }
77
+ }).visit(program);
78
+ return names;
79
+ }
80
+ function choosePluginName(program) {
81
+ const names = collectIdentifierNames(program);
82
+ let suffix = 0;
83
+ let candidate = "spotPatch";
84
+ while (names.has(candidate)) {
85
+ suffix += 1;
86
+ candidate = `spotPatch${String(suffix)}`;
87
+ }
88
+ return candidate;
89
+ }
90
+ function importedPluginName(program) {
91
+ const adapterImports = importsOf(program).filter(
92
+ (statement) => statement.source.value === ADAPTER_PACKAGE_NAME
93
+ );
94
+ if (adapterImports.length > 1) {
95
+ throw new Error("SpotPatch init found duplicate @spotpatch/vite imports.");
96
+ }
97
+ const adapterImport = adapterImports[0];
98
+ if (adapterImport === void 0) {
99
+ return void 0;
100
+ }
101
+ const plugin = adapterImport.specifiers.find(
102
+ (specifier) => specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "spotPatch" && specifier.importKind !== "type"
103
+ );
104
+ if (plugin === void 0) {
105
+ throw new Error(
106
+ "SpotPatch init cannot safely merge the existing @spotpatch/vite import."
107
+ );
108
+ }
109
+ return plugin.local.name;
110
+ }
111
+ function unwrapExpression(expression) {
112
+ let current = expression;
113
+ while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression") {
114
+ current = current.expression;
115
+ }
116
+ return current;
117
+ }
118
+ function findDefaultExport(program) {
119
+ const exports = program.body.filter(
120
+ (statement) => statement.type === "ExportDefaultDeclaration"
121
+ );
122
+ if (exports.length !== 1 || exports[0] === void 0) {
123
+ throw new Error(
124
+ "SpotPatch init requires exactly one ESM default export in vite.config."
125
+ );
126
+ }
127
+ return exports[0];
128
+ }
129
+ function findVariableInitializer(program, name) {
130
+ for (const statement of program.body) {
131
+ if (statement.type !== "VariableDeclaration") {
132
+ continue;
133
+ }
134
+ for (const declaration of statement.declarations) {
135
+ if (declaration.id.type === "Identifier" && declaration.id.name === name && declaration.init !== null) {
136
+ return declaration.init;
137
+ }
138
+ }
139
+ }
140
+ return void 0;
141
+ }
142
+ function resolveConfigExpression(program) {
143
+ const exported = findDefaultExport(program).declaration;
144
+ if (exported.type === "FunctionDeclaration" || exported.type === "ClassDeclaration" || exported.type === "TSInterfaceDeclaration") {
145
+ throw new Error(
146
+ "SpotPatch init requires vite.config to export a configuration expression."
147
+ );
148
+ }
149
+ const expression = unwrapExpression(exported);
150
+ const resolved = expression.type === "Identifier" ? findVariableInitializer(program, expression.name) : expression;
151
+ if (resolved === void 0) {
152
+ throw new Error("SpotPatch init could not resolve the vite.config export.");
153
+ }
154
+ return unwrapExpression(resolved);
155
+ }
156
+ function resolveConfigObject(program) {
157
+ const expression = resolveConfigExpression(program);
158
+ if (expression.type === "ObjectExpression") {
159
+ return expression;
160
+ }
161
+ if (expression.type === "CallExpression") {
162
+ const callee = unwrapExpression(expression.callee);
163
+ const argument = expression.arguments[0];
164
+ if (callee.type === "Identifier" && callee.name === "defineConfig" && expression.arguments.length === 1 && argument !== void 0 && argument.type !== "SpreadElement") {
165
+ const value = unwrapExpression(argument);
166
+ if (value.type === "ObjectExpression") {
167
+ return value;
168
+ }
169
+ }
170
+ }
171
+ throw new Error(
172
+ "SpotPatch init currently supports an object passed to defineConfig in vite.config."
173
+ );
174
+ }
175
+ function propertyName(property) {
176
+ if (property.computed) {
177
+ return void 0;
178
+ }
179
+ if (property.key.type === "Identifier") {
180
+ return property.key.name;
181
+ }
182
+ if (property.key.type === "Literal" && typeof property.key.value === "string") {
183
+ return property.key.value;
184
+ }
185
+ return void 0;
186
+ }
187
+ function findProperty(object, name) {
188
+ const matches = object.properties.filter(
189
+ (property) => property.type === "Property" && propertyName(property) === name
190
+ );
191
+ if (matches.length > 1) {
192
+ throw new Error(`SpotPatch init found duplicate ${name} properties.`);
193
+ }
194
+ return matches[0];
195
+ }
196
+ function lineIndentAt(source, offset) {
197
+ const lineStart = source.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
198
+ return /^[\t ]*/u.exec(source.slice(lineStart, offset))?.[0] ?? "";
199
+ }
200
+ function childIndent(source, object) {
201
+ const first = object.properties[0];
202
+ if (first !== void 0) {
203
+ return lineIndentAt(source, first.start);
204
+ }
205
+ return `${lineIndentAt(source, object.start)} `;
206
+ }
207
+ function trustedFastCall(pluginName, enabled) {
208
+ return enabled ? `${pluginName}({ trustedFastMode: true })` : `${pluginName}()`;
209
+ }
210
+ function directPluginCalls(plugins, pluginName) {
211
+ return plugins.elements.filter((element) => {
212
+ if (element?.type !== "CallExpression") {
213
+ return false;
214
+ }
215
+ const callee = unwrapExpression(element.callee);
216
+ return callee.type === "Identifier" && callee.name === pluginName;
217
+ });
218
+ }
219
+ function enableTrustedFastMode(magicString, source, call) {
220
+ if (call.arguments.length === 0) {
221
+ magicString.overwrite(
222
+ call.start,
223
+ call.end,
224
+ `${source.slice(call.start, call.end - 1)}{ trustedFastMode: true })`
225
+ );
226
+ return;
227
+ }
228
+ const argument = call.arguments[0];
229
+ if (call.arguments.length !== 1 || argument === void 0 || argument.type === "SpreadElement") {
230
+ throw new Error("SpotPatch init cannot safely update the spotPatch options.");
231
+ }
232
+ const value = unwrapExpression(argument);
233
+ if (value.type !== "ObjectExpression") {
234
+ throw new Error("SpotPatch init requires spotPatch options to be an object.");
235
+ }
236
+ const property = findProperty(value, "trustedFastMode");
237
+ if (property !== void 0) {
238
+ const propertyValue = unwrapExpression(property.value);
239
+ if (propertyValue.type !== "Literal" || typeof propertyValue.value !== "boolean") {
240
+ throw new Error(
241
+ "SpotPatch init requires trustedFastMode to be a boolean literal."
242
+ );
243
+ }
244
+ if (!propertyValue.value) {
245
+ magicString.overwrite(propertyValue.start, propertyValue.end, "true");
246
+ }
247
+ return;
248
+ }
249
+ const indent = childIndent(source, value);
250
+ if (value.properties.length === 0) {
251
+ magicString.appendLeft(value.end - 1, " trustedFastMode: true ");
252
+ } else {
253
+ magicString.appendLeft(
254
+ value.properties[0]?.start ?? value.end - 1,
255
+ `trustedFastMode: true,
256
+ ${indent}`
257
+ );
258
+ }
259
+ }
260
+ function addPluginCall(magicString, source, config, pluginName, trustedFastModeAvailable) {
261
+ const pluginsProperty = findProperty(config, "plugins");
262
+ const call = trustedFastCall(pluginName, trustedFastModeAvailable);
263
+ if (pluginsProperty === void 0) {
264
+ const indent = childIndent(source, config);
265
+ if (config.properties.length === 0) {
266
+ magicString.appendLeft(config.end - 1, `
267
+ ${indent}plugins: [${call}],
268
+ `);
269
+ } else {
270
+ magicString.appendLeft(
271
+ config.properties[0]?.start ?? config.end - 1,
272
+ `plugins: [${call}],
273
+ ${indent}`
274
+ );
275
+ }
276
+ return;
277
+ }
278
+ const value = unwrapExpression(pluginsProperty.value);
279
+ if (value.type !== "ArrayExpression") {
280
+ throw new Error("SpotPatch init requires vite.config plugins to be an array.");
281
+ }
282
+ const existing = directPluginCalls(value, pluginName);
283
+ if (existing.length > 1) {
284
+ throw new Error("SpotPatch init found duplicate spotPatch plugins.");
285
+ }
286
+ if (existing[0] !== void 0) {
287
+ if (trustedFastModeAvailable) {
288
+ enableTrustedFastMode(magicString, source, existing[0]);
289
+ }
290
+ return;
291
+ }
292
+ const first = value.elements.find((element) => element !== null);
293
+ if (first === void 0) {
294
+ magicString.appendLeft(value.end - 1, call);
295
+ } else {
296
+ magicString.appendLeft(
297
+ first.start,
298
+ `${call},
299
+ ${lineIndentAt(source, first.start)}`
300
+ );
301
+ }
302
+ }
303
+ function transformViteConfig(absolutePath, source, trustedFastModeAvailable) {
304
+ const { program } = parseModule(absolutePath, source);
305
+ const config = resolveConfigObject(program);
306
+ const existingPluginName = importedPluginName(program);
307
+ const pluginName = existingPluginName ?? choosePluginName(program);
308
+ const magicString = new MagicString(source);
309
+ if (existingPluginName === void 0) {
310
+ const specifier = pluginName === "spotPatch" ? "spotPatch" : `spotPatch as ${pluginName}`;
311
+ insertStaticImport(
312
+ magicString,
313
+ program,
314
+ `import { ${specifier} } from ${JSON.stringify(ADAPTER_PACKAGE_NAME)};`
315
+ );
316
+ }
317
+ addPluginCall(magicString, source, config, pluginName, trustedFastModeAvailable);
318
+ return magicString.toString();
319
+ }
320
+ async function findViteConfig(appRoot) {
321
+ const candidates = (await Promise.all(
322
+ CONFIG_FILE_NAMES.map(async (name) => {
323
+ const absolutePath = path.join(appRoot, name);
324
+ return await integrationPathExists(absolutePath) ? absolutePath : void 0;
325
+ })
326
+ )).filter((value) => value !== void 0);
327
+ if (candidates.length !== 1 || candidates[0] === void 0) {
328
+ throw new Error("SpotPatch init requires exactly one supported vite.config file.");
329
+ }
330
+ return candidates[0];
331
+ }
332
+ async function planViteIntegration(directory = process.cwd()) {
333
+ const appRoot = path.resolve(directory);
334
+ const configPath = await findViteConfig(appRoot);
335
+ const [configSource, discoveredCheck] = await Promise.all([
336
+ readIntegrationFile(configPath),
337
+ discoverProjectValidationCheck({
338
+ appRoot,
339
+ timeoutMs: DEFAULT_AGENT_LIMITS.checkTimeoutMs
340
+ })
341
+ ]);
342
+ const trustedFastModeAvailable = discoveredCheck !== void 0;
343
+ const nextContent = transformViteConfig(
344
+ configPath,
345
+ configSource,
346
+ trustedFastModeAvailable
347
+ );
348
+ const change = createIntegrationFileChange(
349
+ appRoot,
350
+ configPath,
351
+ nextContent,
352
+ configSource
353
+ );
354
+ return Object.freeze({
355
+ appRoot,
356
+ changes: Object.freeze(change === void 0 ? [] : [change]),
357
+ trustedFastModeAvailable
358
+ });
359
+ }
360
+ async function applyViteIntegrationPlan(plan) {
361
+ await applyIntegrationPlan(plan);
362
+ }
363
+ async function checkViteIntegration(directory = process.cwd()) {
364
+ try {
365
+ const plan = await planViteIntegration(directory);
366
+ const issues = plan.changes.map(
367
+ (change) => `INTEGRATION_REQUIRED:${change.relativePath}`
368
+ );
369
+ return Object.freeze({
370
+ appRoot: plan.appRoot,
371
+ issues: Object.freeze(issues),
372
+ ok: issues.length === 0,
373
+ trustedFastModeAvailable: plan.trustedFastModeAvailable
374
+ });
375
+ } catch (error) {
376
+ return Object.freeze({
377
+ appRoot: path.resolve(directory),
378
+ issues: Object.freeze([
379
+ error instanceof Error ? error.message : "SpotPatch integration check failed."
380
+ ]),
381
+ ok: false,
382
+ trustedFastModeAvailable: false
383
+ });
384
+ }
385
+ }
386
+
387
+ // src/cli.ts
388
+ var VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/u;
389
+ function writeUsage() {
390
+ 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"
392
+ );
393
+ }
394
+ async function inspectViteProject(appRoot = process.cwd()) {
395
+ const resolveFromApplication = createRequire(path2.join(appRoot, "package.json"));
396
+ let adapterEntry;
397
+ let viteManifestPath;
398
+ try {
399
+ adapterEntry = resolveFromApplication.resolve("@spotpatch/vite");
400
+ viteManifestPath = resolveFromApplication.resolve("vite/package.json");
401
+ } catch (error) {
402
+ throw new Error(
403
+ "SpotPatch could not resolve the local @spotpatch/vite and Vite packages.",
404
+ { cause: error }
405
+ );
406
+ }
407
+ if (adapterEntry.length === 0) {
408
+ throw new Error("SpotPatch could not resolve the Vite adapter export.");
409
+ }
410
+ const manifest = JSON.parse(await readFile(viteManifestPath, "utf8"));
411
+ 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);
413
+ const major = Number(match?.[1]);
414
+ if (version === void 0 || match === null || !Number.isSafeInteger(major) || major < 5 || major >= 8) {
415
+ throw new Error(
416
+ `SpotPatch Vite requires Vite >=5.0.0 <8.0.0; found ${version ?? "unknown"}.`
417
+ );
418
+ }
419
+ return version;
420
+ }
421
+ function writeTrustedModeStatus(available) {
422
+ process.stdout.write(
423
+ available ? "[spotpatch:vite] trusted fast mode is available in the page selector.\n" : "[spotpatch:vite] review mode is ready; trusted fast mode needs a local TypeScript project check.\n"
424
+ );
425
+ }
426
+ async function runInit(arguments_) {
427
+ if (arguments_.length !== 0) {
428
+ throw new Error("SpotPatch init does not accept positional arguments.");
429
+ }
430
+ await inspectViteProject();
431
+ const plan = await planViteIntegration();
432
+ if (plan.changes.length === 0) {
433
+ process.stdout.write("[spotpatch:vite] integration is already up to date.\n");
434
+ writeTrustedModeStatus(plan.trustedFastModeAvailable);
435
+ return 0;
436
+ }
437
+ process.stdout.write("[spotpatch:vite] integration preview (resulting files):\n");
438
+ for (const change of plan.changes) {
439
+ process.stdout.write(`
440
+ --- ${change.relativePath}
441
+ ${change.nextContent}`);
442
+ if (!change.nextContent.endsWith("\n")) {
443
+ process.stdout.write("\n");
444
+ }
445
+ }
446
+ await applyViteIntegrationPlan(plan);
447
+ process.stdout.write(
448
+ `[spotpatch:vite] updated ${String(plan.changes.length)} integration file(s).
449
+ `
450
+ );
451
+ writeTrustedModeStatus(plan.trustedFastModeAvailable);
452
+ return 0;
453
+ }
454
+ async function runCheck(arguments_) {
455
+ if (arguments_.length !== 0) {
456
+ throw new Error("SpotPatch check does not accept positional arguments.");
457
+ }
458
+ const version = await inspectViteProject();
459
+ const result = await checkViteIntegration();
460
+ if (!result.ok) {
461
+ for (const issue of result.issues) {
462
+ process.stderr.write(`[spotpatch:vite] ${issue}
463
+ `);
464
+ }
465
+ return 1;
466
+ }
467
+ const mode = result.trustedFastModeAvailable ? "trusted fast mode available" : "review mode";
468
+ process.stdout.write(
469
+ `[spotpatch:vite] integration verified for Vite ${version} \xB7 ${mode}.
470
+ `
471
+ );
472
+ return 0;
473
+ }
474
+ async function main(arguments_) {
475
+ const [command, ...rest] = arguments_;
476
+ if (command === "init") {
477
+ return runInit(rest);
478
+ }
479
+ if (command === "check") {
480
+ return runCheck(rest);
481
+ }
482
+ writeUsage();
483
+ return 1;
484
+ }
485
+ try {
486
+ process.exitCode = await main(process.argv.slice(2));
487
+ } catch (error) {
488
+ process.stderr.write(
489
+ `[spotpatch:vite] ${error instanceof Error ? error.message : "The command failed."}
490
+ `
491
+ );
492
+ process.exitCode = 1;
493
+ }
494
+ //# sourceMappingURL=cli.js.map