@spotpatch/vite 1.4.4 → 1.6.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 +68 -36
- package/dist/cli.js +494 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +16 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/runtime-client.js +47 -35
- package/package.json +10 -5
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
|
-
###
|
|
28
|
+
### Initialize
|
|
29
29
|
|
|
30
|
-
|
|
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
|
|
79
|
-
|
|
|
80
|
-
| `enabled`
|
|
81
|
-
| `include`
|
|
82
|
-
| `exclude`
|
|
83
|
-
| `editor`
|
|
84
|
-
| `redact`
|
|
85
|
-
| `budget`
|
|
86
|
-
| `shortcut`
|
|
87
|
-
| `allowLan`
|
|
88
|
-
| `debug`
|
|
89
|
-
| `locale`
|
|
90
|
-
| `maxTargets`
|
|
91
|
-
| `ai`
|
|
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
|
|
|
@@ -119,7 +127,15 @@ spotPatch({
|
|
|
119
127
|
});
|
|
120
128
|
```
|
|
121
129
|
|
|
122
|
-
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.
|
|
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.
|
|
131
|
+
|
|
132
|
+
`spotpatch-vite init` automatically enables the page selector when it can discover a local TypeScript check. The equivalent concise manual option is:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
spotPatch({ trustedFastMode: true });
|
|
136
|
+
```
|
|
137
|
+
|
|
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.
|
|
123
139
|
|
|
124
140
|
### Security and production behavior
|
|
125
141
|
|
|
@@ -161,9 +177,16 @@ npm install --save-dev @spotpatch/vite
|
|
|
161
177
|
pnpm add -D @spotpatch/vite
|
|
162
178
|
```
|
|
163
179
|
|
|
164
|
-
###
|
|
180
|
+
### 初始化
|
|
165
181
|
|
|
166
|
-
|
|
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:
|
|
167
190
|
|
|
168
191
|
```ts
|
|
169
192
|
// vite.config.ts
|
|
@@ -172,7 +195,7 @@ import react from "@vitejs/plugin-react-swc";
|
|
|
172
195
|
import { defineConfig } from "vite";
|
|
173
196
|
|
|
174
197
|
export default defineConfig({
|
|
175
|
-
plugins: [spotPatch(), react()],
|
|
198
|
+
plugins: [spotPatch({ trustedFastMode: true }), react()],
|
|
176
199
|
});
|
|
177
200
|
```
|
|
178
201
|
|
|
@@ -211,20 +234,21 @@ spotPatch({
|
|
|
211
234
|
});
|
|
212
235
|
```
|
|
213
236
|
|
|
214
|
-
| 选项
|
|
215
|
-
|
|
|
216
|
-
| `enabled`
|
|
217
|
-
| `include`
|
|
218
|
-
| `exclude`
|
|
219
|
-
| `editor`
|
|
220
|
-
| `redact`
|
|
221
|
-
| `budget`
|
|
222
|
-
| `shortcut`
|
|
223
|
-
| `allowLan`
|
|
224
|
-
| `debug`
|
|
225
|
-
| `locale`
|
|
226
|
-
| `maxTargets`
|
|
227
|
-
| `ai`
|
|
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 校验。 |
|
|
228
252
|
|
|
229
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)。
|
|
230
254
|
|
|
@@ -255,7 +279,15 @@ spotPatch({
|
|
|
255
279
|
});
|
|
256
280
|
```
|
|
257
281
|
|
|
258
|
-
默认 Agent 路径必须经过审阅:“检查运行环境”提供不含源码的可选能力诊断;点击运行会直接进入真实隔离工具会话,并在会话内证明工具续接能力。Agent 会提供有界的就近项目规范,只暴露文件工具而不是任意 Shell,复用当前变更版本中由宿主实际执行的检查,并在 Apply 前展示完整 Diff。
|
|
282
|
+
默认 Agent 路径必须经过审阅:“检查运行环境”提供不含源码的可选能力诊断;点击运行会直接进入真实隔离工具会话,并在会话内证明工具续接能力。Agent 会提供有界的就近项目规范,只暴露文件工具而不是任意 Shell,复用当前变更版本中由宿主实际执行的检查,并在 Apply 前展示完整 Diff。
|
|
283
|
+
|
|
284
|
+
`spotpatch-vite init` 能发现本地 TypeScript 校验时会自动开放页面选择;等价的简洁手动配置只有一行:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
spotPatch({ trustedFastMode: true });
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
页面默认保持审阅模式;用户选择“可信快速”后,面板才会把 Provider 与脏工作区确认合并成一次会话级授权,并在必需检查通过后直接应用。高级项目仍可通过 `ai.execution` 配置自己的检查。该模式可以直接应用验证通过的文件删除与需要重启开发服务的配置变更,但不会开放凭据、环境文件、Git 元数据、项目外路径、任意 Shell,也不会绕过失败检查或基线冲突。SpotPatch 不会替业务代码执行 commit、push、发包或部署。
|
|
259
291
|
|
|
260
292
|
### 安全与生产行为
|
|
261
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
|