assistant-ui 0.0.121 → 0.0.123
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/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +1 -2
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +2 -2
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/info.js +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +95 -23
- package/dist/commands/mcp.js.map +1 -1
- package/dist/lib/agent-skill.d.ts +2 -2
- package/dist/lib/agent-skill.d.ts.map +1 -1
- package/dist/lib/agent-skill.js +10 -28
- package/dist/lib/agent-skill.js.map +1 -1
- package/dist/lib/create-project.d.ts +0 -9
- package/dist/lib/create-project.d.ts.map +1 -1
- package/dist/lib/create-project.js +15 -46
- package/dist/lib/create-project.js.map +1 -1
- package/dist/lib/transform.d.ts +0 -4
- package/dist/lib/transform.d.ts.map +1 -1
- package/dist/lib/transform.js +7 -16
- package/dist/lib/transform.js.map +1 -1
- package/dist/lib/utils/download.d.ts +5 -0
- package/dist/lib/utils/download.d.ts.map +1 -0
- package/dist/lib/utils/download.js +41 -0
- package/dist/lib/utils/download.js.map +1 -0
- package/dist/lib/utils/file-scanner.d.ts +0 -1
- package/dist/lib/utils/file-scanner.d.ts.map +1 -1
- package/dist/lib/utils/file-scanner.js +1 -16
- package/dist/lib/utils/file-scanner.js.map +1 -1
- package/dist/lib/utils/package-manager.d.ts +7 -0
- package/dist/lib/utils/package-manager.d.ts.map +1 -1
- package/dist/lib/utils/package-manager.js +15 -1
- package/dist/lib/utils/package-manager.js.map +1 -1
- package/package.json +3 -3
- package/src/commands/add.ts +2 -1
- package/src/commands/create.ts +5 -3
- package/src/commands/init.ts +5 -2
- package/src/commands/mcp.ts +223 -56
- package/src/lib/agent-skill.ts +18 -44
- package/src/lib/create-project.test.ts +0 -17
- package/src/lib/create-project.ts +33 -98
- package/src/lib/transform-files.test.ts +1 -3
- package/src/lib/transform.ts +6 -20
- package/src/lib/utils/download.ts +57 -0
- package/src/lib/utils/file-scanner.ts +0 -26
- package/src/lib/utils/package-manager.ts +26 -0
- package/src/lib/utils/registry.test.ts +0 -6
- package/templates/cloud-harness/app/api/chat/route.ts +1 -0
- package/templates/cloud-harness/components/assistant-ui/elements/collapsible-root.tsx +19 -0
- package/templates/cloud-harness/components/assistant-ui/elements/markdown-text.tsx +1 -1
- package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +198 -30
- package/templates/cloud-harness/package.json +12 -12
package/src/commands/mcp.ts
CHANGED
|
@@ -2,6 +2,14 @@ import { Command } from "commander";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
applyEdits,
|
|
7
|
+
parse as parseJsonc,
|
|
8
|
+
parseTree,
|
|
9
|
+
stripComments,
|
|
10
|
+
type Node,
|
|
11
|
+
type ParseError,
|
|
12
|
+
} from "jsonc-parser";
|
|
5
13
|
import { logger } from "../lib/utils/logger";
|
|
6
14
|
import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
|
|
7
15
|
import * as p from "@clack/prompts";
|
|
@@ -21,22 +29,17 @@ const MCP_CONFIGS: Record<
|
|
|
21
29
|
{
|
|
22
30
|
name: string;
|
|
23
31
|
getPath: () => string;
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
serverKey: string;
|
|
33
|
+
server: object;
|
|
34
|
+
jsonc?: boolean;
|
|
26
35
|
postInstall?: string;
|
|
27
36
|
}
|
|
28
37
|
> = {
|
|
29
38
|
cursor: {
|
|
30
39
|
name: "Cursor",
|
|
31
40
|
getPath: () => path.join(process.cwd(), ".cursor", "mcp.json"),
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"assistant-ui": {
|
|
35
|
-
url: HOSTED_MCP_URL,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
replaceServerKey: "mcpServers",
|
|
41
|
+
serverKey: "mcpServers",
|
|
42
|
+
server: { url: HOSTED_MCP_URL },
|
|
40
43
|
postInstall:
|
|
41
44
|
"Open Cursor Settings → MCP → find 'assistant-ui' and click enable.",
|
|
42
45
|
},
|
|
@@ -44,47 +47,33 @@ const MCP_CONFIGS: Record<
|
|
|
44
47
|
name: "Windsurf",
|
|
45
48
|
getPath: () =>
|
|
46
49
|
path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"assistant-ui": {
|
|
50
|
-
serverUrl: HOSTED_MCP_URL,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
replaceServerKey: "mcpServers",
|
|
50
|
+
serverKey: "mcpServers",
|
|
51
|
+
server: { serverUrl: HOSTED_MCP_URL },
|
|
55
52
|
postInstall: "Fully quit and re-open Windsurf to activate.",
|
|
56
53
|
},
|
|
57
54
|
vscode: {
|
|
58
55
|
name: "VSCode",
|
|
59
56
|
getPath: () => path.join(process.cwd(), ".vscode", "mcp.json"),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
type: "http",
|
|
64
|
-
url: HOSTED_MCP_URL,
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
replaceServerKey: "servers",
|
|
57
|
+
serverKey: "servers",
|
|
58
|
+
server: { type: "http", url: HOSTED_MCP_URL },
|
|
59
|
+
jsonc: true,
|
|
69
60
|
postInstall:
|
|
70
61
|
"Enable MCP in Settings → search 'MCP' → enable 'Chat > MCP'. Use Copilot Chat in Agent mode.",
|
|
71
62
|
},
|
|
72
63
|
zed: {
|
|
73
64
|
name: "Zed",
|
|
65
|
+
serverKey: "context_servers",
|
|
66
|
+
jsonc: true,
|
|
74
67
|
getPath: () => {
|
|
75
68
|
if (process.platform === "win32") {
|
|
76
69
|
return path.join(process.env.APPDATA || "", "Zed", "settings.json");
|
|
77
70
|
}
|
|
78
71
|
return path.join(os.homedir(), ".config", "zed", "settings.json");
|
|
79
72
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
path: "npx",
|
|
85
|
-
args: ["-y", "@assistant-ui/mcp-docs-server"],
|
|
86
|
-
},
|
|
87
|
-
},
|
|
73
|
+
server: {
|
|
74
|
+
command: {
|
|
75
|
+
path: "npx",
|
|
76
|
+
args: ["-y", "@assistant-ui/mcp-docs-server"],
|
|
88
77
|
},
|
|
89
78
|
},
|
|
90
79
|
postInstall: "The server starts automatically with the Assistant Panel.",
|
|
@@ -107,13 +96,10 @@ const MCP_CONFIGS: Record<
|
|
|
107
96
|
"claude_desktop_config.json",
|
|
108
97
|
);
|
|
109
98
|
},
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
args: ["-y", "@assistant-ui/mcp-docs-server"],
|
|
115
|
-
},
|
|
116
|
-
},
|
|
99
|
+
serverKey: "mcpServers",
|
|
100
|
+
server: {
|
|
101
|
+
command: "npx",
|
|
102
|
+
args: ["-y", "@assistant-ui/mcp-docs-server"],
|
|
117
103
|
},
|
|
118
104
|
postInstall: "Restart Claude Desktop to activate.",
|
|
119
105
|
},
|
|
@@ -147,6 +133,162 @@ class McpConfigParseError extends Error {
|
|
|
147
133
|
const getTargetFlag = (target: Exclude<MCPTarget, "claude-code">) =>
|
|
148
134
|
`--${target}`;
|
|
149
135
|
|
|
136
|
+
const lastPropertyValue = (node: Node, key: string) =>
|
|
137
|
+
node.children?.findLast((property) => property.children?.[0]?.value === key)
|
|
138
|
+
?.children?.[1];
|
|
139
|
+
|
|
140
|
+
const lineStartAt = (content: string, offset: number) =>
|
|
141
|
+
content.lastIndexOf("\n", offset - 1) + 1;
|
|
142
|
+
|
|
143
|
+
const lineIndentAt = (content: string, offset: number) =>
|
|
144
|
+
content.slice(lineStartAt(content, offset), offset).match(/^[\t ]*/)?.[0] ??
|
|
145
|
+
"";
|
|
146
|
+
|
|
147
|
+
const getIndentUnit = (content: string, object: Node) => {
|
|
148
|
+
const objectLine = lineStartAt(content, object.offset);
|
|
149
|
+
const objectIndent = lineIndentAt(content, object.offset);
|
|
150
|
+
const multilineChild = object.children?.find(
|
|
151
|
+
(child) => lineStartAt(content, child.offset) > objectLine,
|
|
152
|
+
);
|
|
153
|
+
if (multilineChild) {
|
|
154
|
+
const childIndent = lineIndentAt(content, multilineChild.offset);
|
|
155
|
+
if (
|
|
156
|
+
childIndent.startsWith(objectIndent) &&
|
|
157
|
+
childIndent.length > objectIndent.length
|
|
158
|
+
) {
|
|
159
|
+
return childIndent.slice(objectIndent.length);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const documentIndent = content.match(/(?:^|\r?\n)([\t ]+)(?=")/)?.[1];
|
|
164
|
+
if (documentIndent?.includes("\t")) return "\t";
|
|
165
|
+
return documentIndent || " ";
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const stringifyValue = (
|
|
169
|
+
value: object,
|
|
170
|
+
baseIndent: string,
|
|
171
|
+
indentUnit: string,
|
|
172
|
+
eol: string,
|
|
173
|
+
) =>
|
|
174
|
+
JSON.stringify(value, null, indentUnit).replaceAll(
|
|
175
|
+
"\n",
|
|
176
|
+
`${eol}${baseIndent}`,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const appendObjectProperty = (
|
|
180
|
+
content: string,
|
|
181
|
+
object: Node,
|
|
182
|
+
key: string,
|
|
183
|
+
value: object,
|
|
184
|
+
eol: string,
|
|
185
|
+
) => {
|
|
186
|
+
const objectIndent = lineIndentAt(content, object.offset);
|
|
187
|
+
const indentUnit = getIndentUnit(content, object);
|
|
188
|
+
const propertyIndent = objectIndent + indentUnit;
|
|
189
|
+
const property = `${propertyIndent}${JSON.stringify(key)}: ${stringifyValue(
|
|
190
|
+
value,
|
|
191
|
+
propertyIndent,
|
|
192
|
+
indentUnit,
|
|
193
|
+
eol,
|
|
194
|
+
)}`;
|
|
195
|
+
const closeOffset = object.offset + object.length - 1;
|
|
196
|
+
const closeLineStart = lineStartAt(content, closeOffset);
|
|
197
|
+
const closeOnOwnLine = /^[\t ]*$/.test(
|
|
198
|
+
content.slice(closeLineStart, closeOffset),
|
|
199
|
+
);
|
|
200
|
+
const edits = [
|
|
201
|
+
{
|
|
202
|
+
offset: closeOnOwnLine ? closeLineStart : closeOffset,
|
|
203
|
+
length: 0,
|
|
204
|
+
content: closeOnOwnLine
|
|
205
|
+
? `${property}${eol}`
|
|
206
|
+
: `${eol}${property}${eol}${objectIndent}`,
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
const lastProperty = object.children?.at(-1);
|
|
211
|
+
if (lastProperty) {
|
|
212
|
+
const propertyEnd = lastProperty.offset + lastProperty.length;
|
|
213
|
+
const trailingContent = stripComments(
|
|
214
|
+
content.slice(propertyEnd, closeOffset),
|
|
215
|
+
).trimStart();
|
|
216
|
+
if (!trailingContent.startsWith(",")) {
|
|
217
|
+
edits.unshift({ offset: propertyEnd, length: 0, content: "," });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return applyEdits(content, edits);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const replaceNodeValue = (
|
|
225
|
+
content: string,
|
|
226
|
+
node: Node,
|
|
227
|
+
container: Node,
|
|
228
|
+
value: object,
|
|
229
|
+
eol: string,
|
|
230
|
+
) =>
|
|
231
|
+
applyEdits(content, [
|
|
232
|
+
{
|
|
233
|
+
offset: node.offset,
|
|
234
|
+
length: node.length,
|
|
235
|
+
content: stringifyValue(
|
|
236
|
+
value,
|
|
237
|
+
lineIndentAt(content, node.offset),
|
|
238
|
+
getIndentUnit(content, container),
|
|
239
|
+
eol,
|
|
240
|
+
),
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
function updateJsoncConfig(
|
|
245
|
+
content: string,
|
|
246
|
+
serverKey: string,
|
|
247
|
+
server: object,
|
|
248
|
+
): string {
|
|
249
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
250
|
+
const root = parseTree(content, [], {
|
|
251
|
+
allowTrailingComma: true,
|
|
252
|
+
allowEmptyContent: true,
|
|
253
|
+
});
|
|
254
|
+
if (!root) {
|
|
255
|
+
const initialized = JSON.stringify(
|
|
256
|
+
{ [serverKey]: { "assistant-ui": server } },
|
|
257
|
+
null,
|
|
258
|
+
2,
|
|
259
|
+
).replaceAll("\n", eol);
|
|
260
|
+
if (!content.trim()) return `${initialized}${eol}`;
|
|
261
|
+
return `${content}${content.endsWith(eol) ? "" : eol}${initialized}${eol}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// JSONC resolves duplicate keys to the last value.
|
|
265
|
+
const servers = lastPropertyValue(root, serverKey);
|
|
266
|
+
if (!servers) {
|
|
267
|
+
return appendObjectProperty(
|
|
268
|
+
content,
|
|
269
|
+
root,
|
|
270
|
+
serverKey,
|
|
271
|
+
{ "assistant-ui": server },
|
|
272
|
+
eol,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (servers.type !== "object") {
|
|
277
|
+
return replaceNodeValue(
|
|
278
|
+
content,
|
|
279
|
+
servers,
|
|
280
|
+
root,
|
|
281
|
+
{ "assistant-ui": server },
|
|
282
|
+
eol,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const existing = lastPropertyValue(servers, "assistant-ui");
|
|
287
|
+
return existing
|
|
288
|
+
? replaceNodeValue(content, existing, servers, server, eol)
|
|
289
|
+
: appendObjectProperty(content, servers, "assistant-ui", server, eol);
|
|
290
|
+
}
|
|
291
|
+
|
|
150
292
|
async function installForTarget(target: MCPTarget): Promise<void> {
|
|
151
293
|
if (target === "claude-code") {
|
|
152
294
|
logger.info("Installing MCP server for Claude Code...");
|
|
@@ -209,10 +351,35 @@ async function installForTarget(target: MCPTarget): Promise<void> {
|
|
|
209
351
|
}
|
|
210
352
|
|
|
211
353
|
let existingConfig: any = {};
|
|
354
|
+
let content = "{}\n";
|
|
212
355
|
if (fs.existsSync(configPath)) {
|
|
213
|
-
|
|
356
|
+
content = fs.readFileSync(configPath, "utf-8");
|
|
214
357
|
try {
|
|
215
|
-
|
|
358
|
+
if (targetConfig.jsonc) {
|
|
359
|
+
const errors: ParseError[] = [];
|
|
360
|
+
existingConfig = parseJsonc(content, errors, {
|
|
361
|
+
allowTrailingComma: true,
|
|
362
|
+
allowEmptyContent: true,
|
|
363
|
+
});
|
|
364
|
+
if (existingConfig === undefined) existingConfig = {};
|
|
365
|
+
if (
|
|
366
|
+
errors.length > 0 ||
|
|
367
|
+
existingConfig === null ||
|
|
368
|
+
typeof existingConfig !== "object" ||
|
|
369
|
+
Array.isArray(existingConfig)
|
|
370
|
+
) {
|
|
371
|
+
throw new SyntaxError("Invalid MCP configuration");
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
existingConfig = JSON.parse(content);
|
|
375
|
+
if (
|
|
376
|
+
existingConfig === null ||
|
|
377
|
+
typeof existingConfig !== "object" ||
|
|
378
|
+
Array.isArray(existingConfig)
|
|
379
|
+
) {
|
|
380
|
+
throw new SyntaxError("Invalid MCP configuration");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
216
383
|
} catch {
|
|
217
384
|
const flag = getTargetFlag(target);
|
|
218
385
|
logger.error(`Could not parse ${targetConfig.name} MCP config.`);
|
|
@@ -225,23 +392,23 @@ async function installForTarget(target: MCPTarget): Promise<void> {
|
|
|
225
392
|
}
|
|
226
393
|
}
|
|
227
394
|
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
fs.writeFileSync(configPath,
|
|
395
|
+
const updatedContent = updateJsoncConfig(
|
|
396
|
+
content,
|
|
397
|
+
targetConfig.serverKey,
|
|
398
|
+
target === "zed" || target === "claude-desktop"
|
|
399
|
+
? deepMerge(
|
|
400
|
+
existingConfig[targetConfig.serverKey]?.["assistant-ui"] ?? {},
|
|
401
|
+
targetConfig.server,
|
|
402
|
+
)
|
|
403
|
+
: targetConfig.server,
|
|
404
|
+
);
|
|
405
|
+
fs.writeFileSync(configPath, updatedContent);
|
|
239
406
|
|
|
240
407
|
logger.break();
|
|
241
408
|
logger.success(`MCP server installed for ${targetConfig.name}!`);
|
|
242
409
|
logger.info(`Config written to: ${configPath}`);
|
|
243
410
|
|
|
244
|
-
if (
|
|
411
|
+
if (target === "cursor" || target === "windsurf" || target === "vscode") {
|
|
245
412
|
logger.break();
|
|
246
413
|
logger.info(
|
|
247
414
|
`Connects to the hosted assistant-ui MCP server at ${HOSTED_MCP_URL}.`,
|
package/src/lib/agent-skill.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
DOWNLOAD_TIMEOUT_MS,
|
|
7
|
-
dlxCommand,
|
|
8
|
-
resolveGitHubAuthToken,
|
|
9
|
-
} from "./create-project";
|
|
10
|
-
import { type PackageManagerName } from "./utils/package-manager";
|
|
4
|
+
import { withStagedDownload } from "./utils/download";
|
|
5
|
+
import { dlxCommand, type PackageManagerName } from "./utils/package-manager";
|
|
11
6
|
|
|
12
7
|
export const SKILLS_PACKAGE = "assistant-ui/skills";
|
|
13
|
-
export const SKILLS_COMMIT = "
|
|
8
|
+
export const SKILLS_COMMIT = "1ba8e9105c504fe4ac2591e432783872d6b3c8ba";
|
|
14
9
|
export const SKILLS_PLUGIN_SOURCE = `gh:${SKILLS_PACKAGE}/assistant-ui#${SKILLS_COMMIT}`;
|
|
15
10
|
|
|
16
11
|
export function resolveSkillsInstall(params: {
|
|
@@ -70,42 +65,21 @@ export async function ensureSkillsPlugin(): Promise<string> {
|
|
|
70
65
|
process.once("SIGINT", removeStagingOnSignal);
|
|
71
66
|
process.once("SIGTERM", removeStagingOnSignal);
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
downloadSettled = true;
|
|
89
|
-
});
|
|
90
|
-
const timeout = new Promise<never>((_, reject) => {
|
|
91
|
-
timer = setTimeout(
|
|
92
|
-
() => reject(new Error("Download timed out.")),
|
|
93
|
-
DOWNLOAD_TIMEOUT_MS,
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
await Promise.race([download, timeout]).catch((error: unknown) => {
|
|
97
|
-
throw new Error(
|
|
98
|
-
"Could not fetch the assistant-ui skills from GitHub. Check your network, or set GITHUB_TOKEN if you are rate limited.",
|
|
99
|
-
{ cause: error },
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
await publish(staging, dir, marker);
|
|
103
|
-
} finally {
|
|
104
|
-
clearTimeout(timer);
|
|
105
|
-
if (origDebug !== undefined) process.env.DEBUG = origDebug;
|
|
106
|
-
if (!download || downloadSettled) removeStaging();
|
|
107
|
-
else void download.then(removeStaging, removeStaging);
|
|
108
|
-
}
|
|
68
|
+
await withStagedDownload(
|
|
69
|
+
SKILLS_PLUGIN_SOURCE,
|
|
70
|
+
{ dir: staging, preferOffline: true, silent: true },
|
|
71
|
+
"Download timed out.",
|
|
72
|
+
removeStaging,
|
|
73
|
+
async (download) => {
|
|
74
|
+
await download.catch((error: unknown) => {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"Could not fetch the assistant-ui skills from GitHub. Check your network, or set GITHUB_TOKEN if you are rate limited.",
|
|
77
|
+
{ cause: error },
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
await publish(staging, dir, marker);
|
|
81
|
+
},
|
|
82
|
+
);
|
|
109
83
|
|
|
110
84
|
return dir;
|
|
111
85
|
}
|
|
@@ -24,23 +24,6 @@ describe("reconcileAssistantUIImportLayout", () => {
|
|
|
24
24
|
const read = (file: string) =>
|
|
25
25
|
fs.readFileSync(path.join(projectDir, file), "utf8");
|
|
26
26
|
|
|
27
|
-
it("rewrites a legacy import when only the elements layout exists", async () => {
|
|
28
|
-
write(
|
|
29
|
-
"app/page.tsx",
|
|
30
|
-
'import { Thread } from "@/components/assistant-ui/thread";\n' +
|
|
31
|
-
'import { ThreadList } from "@/components/assistant-ui/thread-list";\n',
|
|
32
|
-
);
|
|
33
|
-
write("components/assistant-ui/elements/thread.aui.tsx", "export {};");
|
|
34
|
-
write("components/assistant-ui/elements/thread-list.aui.tsx", "export {};");
|
|
35
|
-
|
|
36
|
-
await reconcileAssistantUIImportLayout(projectDir);
|
|
37
|
-
|
|
38
|
-
expect(read("app/page.tsx")).toBe(
|
|
39
|
-
'import { Thread } from "@/components/assistant-ui/elements/thread.aui";\n' +
|
|
40
|
-
'import { ThreadList } from "@/components/assistant-ui/elements/thread-list.aui";\n',
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
27
|
it("rewrites module declarations without changing import-like source text", async () => {
|
|
45
28
|
write(
|
|
46
29
|
"app/page.tsx",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { downloadTemplate } from "giget";
|
|
5
4
|
import {
|
|
6
5
|
parse as parseJsonc,
|
|
7
6
|
printParseErrorCode,
|
|
@@ -9,26 +8,14 @@ import {
|
|
|
9
8
|
} from "jsonc-parser";
|
|
10
9
|
import { logger } from "./utils/logger";
|
|
11
10
|
import { runSpawn, SpawnExitError, SpawnSignalError } from "./run-spawn";
|
|
12
|
-
import { type PackageManagerName } from "./utils/package-manager";
|
|
11
|
+
import { dlxCommand, type PackageManagerName } from "./utils/package-manager";
|
|
12
|
+
import { resolveGitHubAuthToken, withStagedDownload } from "./utils/download";
|
|
13
13
|
import { readProjectFiles } from "./utils/file-scanner";
|
|
14
14
|
import {
|
|
15
15
|
detectRegistryPlatform,
|
|
16
16
|
resolveRegistryItemUrl,
|
|
17
17
|
} from "./utils/registry";
|
|
18
18
|
|
|
19
|
-
export function dlxCommand(pm: PackageManagerName): [string, string[]] {
|
|
20
|
-
switch (pm) {
|
|
21
|
-
case "pnpm":
|
|
22
|
-
return ["pnpm", ["dlx"]];
|
|
23
|
-
case "yarn":
|
|
24
|
-
return ["yarn", ["dlx"]];
|
|
25
|
-
case "bun":
|
|
26
|
-
return ["bunx", []];
|
|
27
|
-
case "npm":
|
|
28
|
-
return ["npx", ["--yes"]];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
19
|
export interface TransformOptions {
|
|
33
20
|
hasLocalComponents: boolean;
|
|
34
21
|
skipInstall?: boolean;
|
|
@@ -56,26 +43,6 @@ const LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES = LOCAL_PROJECT_ARTIFACT_DIRS.map(
|
|
|
56
43
|
(dir) => `**/${dir}/**`,
|
|
57
44
|
);
|
|
58
45
|
|
|
59
|
-
export function resolvePackageManager(opts: {
|
|
60
|
-
useNpm?: boolean;
|
|
61
|
-
usePnpm?: boolean;
|
|
62
|
-
useYarn?: boolean;
|
|
63
|
-
useBun?: boolean;
|
|
64
|
-
}): PackageManagerName | undefined {
|
|
65
|
-
if (opts.useNpm) return "npm";
|
|
66
|
-
if (opts.usePnpm) return "pnpm";
|
|
67
|
-
if (opts.useYarn) return "yarn";
|
|
68
|
-
if (opts.useBun) return "bun";
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function resolveGitHubAuthToken(): string | undefined {
|
|
73
|
-
const token =
|
|
74
|
-
process.env.GIGET_AUTH ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
75
|
-
const trimmed = token?.trim();
|
|
76
|
-
return trimmed || undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
46
|
function toBearerAuthHeader(token: string): string {
|
|
80
47
|
return token.toLowerCase().startsWith("bearer ") ? token : `Bearer ${token}`;
|
|
81
48
|
}
|
|
@@ -97,7 +64,6 @@ export async function resolveLatestReleaseRef(): Promise<string | undefined> {
|
|
|
97
64
|
}
|
|
98
65
|
}
|
|
99
66
|
|
|
100
|
-
export const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
101
67
|
const pendingDownloadCleanups = new Set<() => void>();
|
|
102
68
|
|
|
103
69
|
export function cleanupPendingProjectDownloads(): void {
|
|
@@ -117,16 +83,9 @@ export async function downloadProject(
|
|
|
117
83
|
? `gh:assistant-ui/assistant-ui/${repoPath}#${ref}`
|
|
118
84
|
: `gh:assistant-ui/assistant-ui/${repoPath}`;
|
|
119
85
|
|
|
120
|
-
// Suppress giget's debug output. The `debug` package (used by the upgrade
|
|
121
|
-
// command) sets process.env.DEBUG at module-load time, and giget logs to
|
|
122
|
-
// console.debug whenever that env var is truthy — even for unrelated
|
|
123
|
-
// namespaces. Temporarily unsetting it targets the root cause.
|
|
124
|
-
const origDebug = process.env.DEBUG;
|
|
125
|
-
delete process.env.DEBUG;
|
|
126
86
|
let destinationCreated = false;
|
|
127
87
|
let stagingDir: string | undefined;
|
|
128
|
-
let
|
|
129
|
-
let downloadFinished = false;
|
|
88
|
+
let downloadStarted = false;
|
|
130
89
|
let downloadCommitted = false;
|
|
131
90
|
let cleanupRequested = false;
|
|
132
91
|
const removeCleanupListeners = () => {
|
|
@@ -183,62 +142,38 @@ export async function downloadProject(
|
|
|
183
142
|
process.once("exit", cleanupOnExit);
|
|
184
143
|
pendingDownloadCleanups.add(cleanupOnExit);
|
|
185
144
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
silent: true,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
destinationCreated = !fs.existsSync(destDir);
|
|
213
|
-
await fs.promises.mkdir(destDir, { recursive: true });
|
|
214
|
-
for (const entry of await fs.promises.readdir(stagingDir)) {
|
|
215
|
-
const target = path.join(destDir, entry);
|
|
216
|
-
const sourceEntry = path.join(stagingDir, entry);
|
|
217
|
-
await fs.promises.rm(target, { recursive: true, force: true });
|
|
218
|
-
try {
|
|
219
|
-
await fs.promises.rename(sourceEntry, target);
|
|
220
|
-
} catch (error) {
|
|
221
|
-
if ((error as NodeJS.ErrnoException).code !== "EXDEV") throw error;
|
|
222
|
-
await fs.promises.cp(sourceEntry, target, {
|
|
223
|
-
recursive: true,
|
|
224
|
-
force: true,
|
|
225
|
-
});
|
|
226
|
-
await fs.promises.rm(sourceEntry, { recursive: true, force: true });
|
|
145
|
+
const currentStagingDir = stagingDir;
|
|
146
|
+
downloadStarted = true;
|
|
147
|
+
await withStagedDownload(
|
|
148
|
+
source,
|
|
149
|
+
{ dir: currentStagingDir, force: true, silent: true },
|
|
150
|
+
"Download timed out. This may be due to GitHub rate limiting or a network issue. Try again in a few minutes.",
|
|
151
|
+
removeStagingDir,
|
|
152
|
+
async (download) => {
|
|
153
|
+
await download;
|
|
154
|
+
if (cleanupRequested) throw new Error("Download was interrupted.");
|
|
155
|
+
destinationCreated = !fs.existsSync(destDir);
|
|
156
|
+
await fs.promises.mkdir(destDir, { recursive: true });
|
|
157
|
+
for (const entry of await fs.promises.readdir(currentStagingDir)) {
|
|
158
|
+
const target = path.join(destDir, entry);
|
|
159
|
+
const sourceEntry = path.join(currentStagingDir, entry);
|
|
160
|
+
await fs.promises.rm(target, { recursive: true, force: true });
|
|
161
|
+
try {
|
|
162
|
+
await fs.promises.rename(sourceEntry, target);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if ((error as NodeJS.ErrnoException).code !== "EXDEV") throw error;
|
|
165
|
+
await fs.promises.cp(sourceEntry, target, {
|
|
166
|
+
recursive: true,
|
|
167
|
+
force: true,
|
|
168
|
+
});
|
|
169
|
+
await fs.promises.rm(sourceEntry, { recursive: true, force: true });
|
|
170
|
+
}
|
|
227
171
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
clearTimeout(timer!);
|
|
232
|
-
}
|
|
172
|
+
downloadCommitted = true;
|
|
173
|
+
},
|
|
174
|
+
);
|
|
233
175
|
} finally {
|
|
234
|
-
if (!
|
|
235
|
-
await removeStagingDir();
|
|
236
|
-
} else {
|
|
237
|
-
void downloadPromise.then(removeStagingDir, removeStagingDir);
|
|
238
|
-
}
|
|
239
|
-
if (origDebug !== undefined) {
|
|
240
|
-
process.env.DEBUG = origDebug;
|
|
241
|
-
}
|
|
176
|
+
if (!downloadStarted) await removeStagingDir();
|
|
242
177
|
}
|
|
243
178
|
}
|
|
244
179
|
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it, onTestFinished } from "vitest";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, join, relative } from "node:path";
|
|
5
|
-
import { getRelevantFiles
|
|
5
|
+
import { getRelevantFiles } from "./transform";
|
|
6
6
|
|
|
7
7
|
const fixture = () => {
|
|
8
8
|
const directory = mkdtempSync(join(tmpdir(), "aui-transform-files-"));
|
|
@@ -26,7 +26,6 @@ describe("codemod file discovery", () => {
|
|
|
26
26
|
const { write } = fixture();
|
|
27
27
|
const file = write(`my app.${extension}`);
|
|
28
28
|
expect(getRelevantFiles(file)).toEqual([file]);
|
|
29
|
-
expect(countFilesToProcess(file)).toBe(1);
|
|
30
29
|
expect(getRelevantFiles(relative(process.cwd(), file))).toEqual([file]);
|
|
31
30
|
},
|
|
32
31
|
);
|
|
@@ -54,7 +53,6 @@ describe("codemod file discovery", () => {
|
|
|
54
53
|
const { write } = fixture();
|
|
55
54
|
const file = write("app.tsx", source);
|
|
56
55
|
expect(getRelevantFiles(file)).toEqual([file]);
|
|
57
|
-
expect(countFilesToProcess(file)).toBe(1);
|
|
58
56
|
});
|
|
59
57
|
|
|
60
58
|
it("skips unsupported explicit files", () => {
|