assistant-ui 0.0.123 → 0.0.125
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 +7 -0
- package/dist/codemods/v0-11/content-part-to-message-part.d.ts.map +1 -1
- package/dist/codemods/v0-11/content-part-to-message-part.js +126 -40
- package/dist/codemods/v0-11/content-part-to-message-part.js.map +1 -1
- package/dist/codemods/v0-12/event-names-to-camelcase.d.ts.map +1 -1
- package/dist/codemods/v0-12/event-names-to-camelcase.js +2 -2
- package/dist/codemods/v0-12/event-names-to-camelcase.js.map +1 -1
- package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
- package/dist/codemods/v0-12/primitive-if-to-aui-if.js +57 -41
- package/dist/codemods/v0-12/primitive-if-to-aui-if.js.map +1 -1
- package/dist/codemods/v0-8/ui-package-split.d.ts.map +1 -1
- package/dist/codemods/v0-8/ui-package-split.js +11 -35
- package/dist/codemods/v0-8/ui-package-split.js.map +1 -1
- package/dist/codemods/v0-9/edge-package-split.d.ts.map +1 -1
- package/dist/codemods/v0-9/edge-package-split.js +15 -69
- package/dist/codemods/v0-9/edge-package-split.js.map +1 -1
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +12 -3
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/info.js +8 -1
- package/dist/commands/info.js.map +1 -1
- package/dist/lib/cloud-auth.d.ts +1 -0
- package/dist/lib/cloud-auth.d.ts.map +1 -1
- package/dist/lib/cloud-auth.js +4 -3
- package/dist/lib/cloud-auth.js.map +1 -1
- package/dist/lib/cloud-remote.d.ts +22 -0
- package/dist/lib/cloud-remote.d.ts.map +1 -0
- package/dist/lib/cloud-remote.js +74 -0
- package/dist/lib/cloud-remote.js.map +1 -0
- package/dist/lib/utils/package-installer.d.ts.map +1 -1
- package/dist/lib/utils/package-installer.js +3 -2
- package/dist/lib/utils/package-installer.js.map +1 -1
- package/package.json +1 -1
- package/src/codemods/v0-11/__tests__/content-part-to-message-part.test.ts +268 -2
- package/src/codemods/v0-11/content-part-to-message-part.ts +211 -80
- package/src/codemods/v0-12/__tests__/event-names-to-camelcase.test.ts +23 -0
- package/src/codemods/v0-12/__tests__/primitive-if-to-aui-if.test.ts +150 -1
- package/src/codemods/v0-12/event-names-to-camelcase.ts +2 -8
- package/src/codemods/v0-12/primitive-if-to-aui-if.ts +92 -61
- package/src/codemods/v0-8/__tests__/ui-package-split.test.ts +83 -0
- package/src/codemods/v0-8/ui-package-split.ts +21 -59
- package/src/codemods/v0-9/edge-package-split.test.ts +100 -0
- package/src/codemods/v0-9/edge-package-split.ts +30 -139
- package/src/commands/cloud.test.ts +58 -0
- package/src/commands/cloud.ts +19 -2
- package/src/commands/info.ts +26 -1
- package/src/lib/cloud-auth.ts +14 -8
- package/src/lib/cloud-remote.test.ts +156 -0
- package/src/lib/cloud-remote.ts +150 -0
- package/src/lib/utils/package-installer.test.ts +39 -0
- package/src/lib/utils/package-installer.ts +3 -1
- package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +17 -8
- package/templates/cloud-harness/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createTransformer } from "../utils/createTransformer";
|
|
2
|
+
import { resolveBinding } from "../utils/resolveBinding";
|
|
2
3
|
|
|
3
4
|
type ConditionFragment = {
|
|
4
5
|
expression: string;
|
|
@@ -41,8 +42,8 @@ const messagePropMap: Record<
|
|
|
41
42
|
expression: `s.message.role ${v === false ? "!==" : "==="} "system"`,
|
|
42
43
|
negated: false,
|
|
43
44
|
}),
|
|
44
|
-
hasBranches: () => ({
|
|
45
|
-
expression: "s.message.branchCount >= 2",
|
|
45
|
+
hasBranches: (v) => ({
|
|
46
|
+
expression: v === false ? "true" : "s.message.branchCount >= 2",
|
|
46
47
|
negated: false,
|
|
47
48
|
}),
|
|
48
49
|
copied: (v) => ({
|
|
@@ -53,8 +54,9 @@ const messagePropMap: Record<
|
|
|
53
54
|
expression: "s.message.isLast",
|
|
54
55
|
negated: v === false,
|
|
55
56
|
}),
|
|
56
|
-
lastOrHover: () => ({
|
|
57
|
-
expression:
|
|
57
|
+
lastOrHover: (v) => ({
|
|
58
|
+
expression:
|
|
59
|
+
v === false ? "true" : "s.message.isHovering || s.message.isLast",
|
|
58
60
|
negated: false,
|
|
59
61
|
}),
|
|
60
62
|
speaking: (v) => ({
|
|
@@ -185,37 +187,92 @@ const buildConditionString = (
|
|
|
185
187
|
|
|
186
188
|
const migratePrimitiveIfToAuiIf = createTransformer(
|
|
187
189
|
({ j, root, markAsChanged }) => {
|
|
188
|
-
|
|
190
|
+
const importedPrimitives = new Map<
|
|
191
|
+
string,
|
|
192
|
+
{ name: string; declaration: any; binding: any }
|
|
193
|
+
>();
|
|
194
|
+
const auiIfImports: { source: string; binding: any }[] = [];
|
|
195
|
+
const usedNames = new Set<string>();
|
|
196
|
+
root
|
|
197
|
+
.find(j.Identifier)
|
|
198
|
+
.forEach((path: any) => usedNames.add(path.value.name));
|
|
199
|
+
root
|
|
200
|
+
.find(j.JSXIdentifier)
|
|
201
|
+
.forEach((path: any) => usedNames.add(path.value.name));
|
|
189
202
|
|
|
190
|
-
// Track which primitive namespaces are imported
|
|
191
|
-
const importedPrimitives = new Set<string>();
|
|
192
203
|
root.find(j.ImportDeclaration).forEach((path: any) => {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
const declaration = path.value;
|
|
205
|
+
const source = declaration.source.value;
|
|
206
|
+
if (
|
|
207
|
+
typeof source !== "string" ||
|
|
208
|
+
!source.startsWith("@assistant-ui/") ||
|
|
209
|
+
declaration.importKind === "type"
|
|
210
|
+
)
|
|
211
|
+
return;
|
|
212
|
+
|
|
213
|
+
declaration.specifiers?.forEach((specifier: any) => {
|
|
214
|
+
if (
|
|
215
|
+
!j.ImportSpecifier.check(specifier) ||
|
|
216
|
+
!j.Identifier.check(specifier.imported) ||
|
|
217
|
+
!j.Identifier.check(specifier.local) ||
|
|
218
|
+
(specifier as { importKind?: string }).importKind === "type"
|
|
219
|
+
)
|
|
220
|
+
return;
|
|
221
|
+
const name = specifier.imported.name;
|
|
222
|
+
const binding = specifier.local;
|
|
223
|
+
if (primitiveMap[name] || fixedConditionMap[name]) {
|
|
224
|
+
importedPrimitives.set(binding.name, { name, declaration, binding });
|
|
225
|
+
}
|
|
226
|
+
if (name === "AuiIf") auiIfImports.push({ source, binding });
|
|
227
|
+
});
|
|
206
228
|
});
|
|
207
229
|
|
|
208
230
|
if (importedPrimitives.size === 0) return;
|
|
209
231
|
|
|
232
|
+
const getPrimitive = (path: any, localName: string) => {
|
|
233
|
+
const primitive = importedPrimitives.get(localName);
|
|
234
|
+
return primitive &&
|
|
235
|
+
resolveBinding(j, path, localName) === primitive.binding
|
|
236
|
+
? primitive
|
|
237
|
+
: undefined;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const getAuiIfName = (path: any, declaration: any) => {
|
|
241
|
+
const source = declaration.source.value;
|
|
242
|
+
const existing = auiIfImports.find(
|
|
243
|
+
(entry) =>
|
|
244
|
+
entry.source === source &&
|
|
245
|
+
!/^[a-z]/.test(entry.binding.name) &&
|
|
246
|
+
resolveBinding(j, path, entry.binding.name) === entry.binding,
|
|
247
|
+
);
|
|
248
|
+
if (existing) return existing.binding.name;
|
|
249
|
+
|
|
250
|
+
let name = "AuiIf";
|
|
251
|
+
for (let suffix = 1; usedNames.has(name); suffix++)
|
|
252
|
+
name = `AuiIf${suffix}`;
|
|
253
|
+
usedNames.add(name);
|
|
254
|
+
const binding = j.identifier(name);
|
|
255
|
+
declaration.specifiers.push(
|
|
256
|
+
j.importSpecifier(j.identifier("AuiIf"), binding),
|
|
257
|
+
);
|
|
258
|
+
auiIfImports.push({ source, binding });
|
|
259
|
+
return name;
|
|
260
|
+
};
|
|
261
|
+
|
|
210
262
|
// Opening and closing tags are rewritten together per element, so a
|
|
211
263
|
// skipped element can never be left with a mismatched closing tag.
|
|
212
|
-
const convertElementToAuiIf = (
|
|
264
|
+
const convertElementToAuiIf = (
|
|
265
|
+
elementPath: any,
|
|
266
|
+
conditionBody: string,
|
|
267
|
+
declaration: any,
|
|
268
|
+
) => {
|
|
269
|
+
const localName = getAuiIfName(elementPath, declaration);
|
|
213
270
|
const arrowFnAst = j(`(s) => ${conditionBody}`)
|
|
214
271
|
.find(j.ArrowFunctionExpression)
|
|
215
272
|
.paths()[0]!.value;
|
|
216
273
|
|
|
217
274
|
const opening = elementPath.value.openingElement;
|
|
218
|
-
opening.name = j.jsxIdentifier(
|
|
275
|
+
opening.name = j.jsxIdentifier(localName);
|
|
219
276
|
opening.attributes = [
|
|
220
277
|
j.jsxAttribute(
|
|
221
278
|
j.jsxIdentifier("condition"),
|
|
@@ -223,10 +280,9 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
223
280
|
),
|
|
224
281
|
];
|
|
225
282
|
if (elementPath.value.closingElement) {
|
|
226
|
-
elementPath.value.closingElement.name = j.jsxIdentifier(
|
|
283
|
+
elementPath.value.closingElement.name = j.jsxIdentifier(localName);
|
|
227
284
|
}
|
|
228
285
|
|
|
229
|
-
needsAuiIfImport = true;
|
|
230
286
|
markAsChanged();
|
|
231
287
|
};
|
|
232
288
|
|
|
@@ -237,17 +293,17 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
237
293
|
if (!j.JSXIdentifier.check(name.object)) return;
|
|
238
294
|
if (!j.JSXIdentifier.check(name.property)) return;
|
|
239
295
|
|
|
240
|
-
const
|
|
296
|
+
const primitive = getPrimitive(path, name.object.name);
|
|
297
|
+
if (!primitive) return;
|
|
241
298
|
const propertyName = name.property.name as string;
|
|
242
|
-
const conditionBody = fixedConditionMap[
|
|
299
|
+
const conditionBody = fixedConditionMap[primitive.name]?.[propertyName];
|
|
243
300
|
if (!conditionBody) return;
|
|
244
|
-
if (!importedPrimitives.has(primitiveName)) return;
|
|
245
301
|
|
|
246
302
|
// Only transform if there are no props (other than children, which are implicit)
|
|
247
303
|
const attrs: any[] = path.value.openingElement.attributes || [];
|
|
248
304
|
if (attrs.length > 0) return;
|
|
249
305
|
|
|
250
|
-
convertElementToAuiIf(path, conditionBody);
|
|
306
|
+
convertElementToAuiIf(path, conditionBody, primitive.declaration);
|
|
251
307
|
});
|
|
252
308
|
|
|
253
309
|
// Process JSX elements: <ThreadPrimitive.If ...> → <AuiIf condition={...}>
|
|
@@ -260,10 +316,10 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
260
316
|
if (!j.JSXIdentifier.check(name.property)) return;
|
|
261
317
|
if (name.property.name !== "If") return;
|
|
262
318
|
|
|
263
|
-
const
|
|
264
|
-
|
|
319
|
+
const primitive = getPrimitive(path, name.object.name);
|
|
320
|
+
if (!primitive) return;
|
|
321
|
+
const propMap = primitiveMap[primitive.name];
|
|
265
322
|
if (!propMap) return;
|
|
266
|
-
if (!importedPrimitives.has(primitiveName)) return;
|
|
267
323
|
|
|
268
324
|
// Extract props
|
|
269
325
|
const attrs: any[] = path.value.openingElement.attributes || [];
|
|
@@ -304,37 +360,12 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
304
360
|
// If we couldn't map all props, skip this element
|
|
305
361
|
if (hasUnknownProp || fragments.length === 0) return;
|
|
306
362
|
|
|
307
|
-
convertElementToAuiIf(
|
|
363
|
+
convertElementToAuiIf(
|
|
364
|
+
path,
|
|
365
|
+
buildConditionString(j, fragments),
|
|
366
|
+
primitive.declaration,
|
|
367
|
+
);
|
|
308
368
|
});
|
|
309
|
-
|
|
310
|
-
// Add AuiIf import if needed
|
|
311
|
-
if (needsAuiIfImport) {
|
|
312
|
-
let hasAuiIfImport = false;
|
|
313
|
-
let assistantUiImport: any = null;
|
|
314
|
-
|
|
315
|
-
root.find(j.ImportDeclaration).forEach((path: any) => {
|
|
316
|
-
const source = path.value.source.value;
|
|
317
|
-
if (typeof source === "string" && source.startsWith("@assistant-ui/")) {
|
|
318
|
-
assistantUiImport = path;
|
|
319
|
-
path.value.specifiers?.forEach((specifier: any) => {
|
|
320
|
-
if (
|
|
321
|
-
j.ImportSpecifier.check(specifier) &&
|
|
322
|
-
(specifier.imported.name === "AuiIf" ||
|
|
323
|
-
specifier.local?.name === "AuiIf")
|
|
324
|
-
) {
|
|
325
|
-
hasAuiIfImport = true;
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
if (!hasAuiIfImport && assistantUiImport) {
|
|
332
|
-
assistantUiImport.value.specifiers.push(
|
|
333
|
-
j.importSpecifier(j.identifier("AuiIf")),
|
|
334
|
-
);
|
|
335
|
-
markAsChanged();
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
369
|
},
|
|
339
370
|
);
|
|
340
371
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import jscodeshift, { type API } from "jscodeshift";
|
|
3
|
+
import transform from "../ui-package-split";
|
|
4
|
+
|
|
5
|
+
const j = jscodeshift.withParser("tsx");
|
|
6
|
+
const api: API = { jscodeshift: j, j, stats: () => {}, report: () => {} };
|
|
7
|
+
const apply = (source: string) =>
|
|
8
|
+
transform({ path: "app.tsx", source }, api, {});
|
|
9
|
+
|
|
10
|
+
describe("ui package split", () => {
|
|
11
|
+
it("preserves an aliased moved binding beside the same exported name", () => {
|
|
12
|
+
const output =
|
|
13
|
+
apply(`import { Thread as MigratedThread } from "@assistant-ui/react";
|
|
14
|
+
import { Thread as ExistingThread } from "@assistant-ui/react-ui";
|
|
15
|
+
const thread = MigratedThread;`)!;
|
|
16
|
+
const imports = j(output)
|
|
17
|
+
.find(j.ImportDeclaration, {
|
|
18
|
+
source: { value: "@assistant-ui/react-ui" },
|
|
19
|
+
})
|
|
20
|
+
.nodes();
|
|
21
|
+
|
|
22
|
+
expect(
|
|
23
|
+
imports.flatMap((declaration) =>
|
|
24
|
+
declaration.specifiers?.map((specifier) => specifier.local?.name),
|
|
25
|
+
),
|
|
26
|
+
).toEqual(expect.arrayContaining(["MigratedThread", "ExistingThread"]));
|
|
27
|
+
expect(output).toContain("const thread = MigratedThread;");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("preserves a moved binding beside a namespace import", () => {
|
|
31
|
+
const output = apply(`import { Thread } from "@assistant-ui/react";
|
|
32
|
+
import * as ui from "@assistant-ui/react-ui";
|
|
33
|
+
const thread = Thread;`)!;
|
|
34
|
+
const imports = j(output)
|
|
35
|
+
.find(j.ImportDeclaration, {
|
|
36
|
+
source: { value: "@assistant-ui/react-ui" },
|
|
37
|
+
})
|
|
38
|
+
.nodes();
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
imports.flatMap((declaration) =>
|
|
42
|
+
declaration.specifiers?.map((specifier) => specifier.local?.name),
|
|
43
|
+
),
|
|
44
|
+
).toEqual(expect.arrayContaining(["Thread", "ui"]));
|
|
45
|
+
expect(output).toContain("const thread = Thread;");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("keeps a moved value import out of an existing type declaration", () => {
|
|
49
|
+
const output = apply(`import { Composer } from "@assistant-ui/react";
|
|
50
|
+
import type { ThreadConfig } from "@assistant-ui/react-ui";
|
|
51
|
+
const composer = Composer;`)!;
|
|
52
|
+
const imports = j(output)
|
|
53
|
+
.find(j.ImportDeclaration, {
|
|
54
|
+
source: { value: "@assistant-ui/react-ui" },
|
|
55
|
+
})
|
|
56
|
+
.nodes();
|
|
57
|
+
const valueImport = imports.find((declaration) =>
|
|
58
|
+
declaration.specifiers?.some(
|
|
59
|
+
(specifier) => specifier.local?.name === "Composer",
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(valueImport).toBeDefined();
|
|
64
|
+
expect(valueImport?.importKind).not.toBe("type");
|
|
65
|
+
expect(output).toContain("const composer = Composer;");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("keeps the declaration-level type kind when splitting an import", () => {
|
|
69
|
+
const output = apply(
|
|
70
|
+
'import type { ThreadConfig, AssistantRuntime } from "@assistant-ui/react";',
|
|
71
|
+
)!;
|
|
72
|
+
const declarations = j(output).find(j.ImportDeclaration).nodes();
|
|
73
|
+
|
|
74
|
+
expect(declarations).toHaveLength(2);
|
|
75
|
+
expect(declarations.map((declaration) => declaration.importKind)).toEqual([
|
|
76
|
+
"type",
|
|
77
|
+
"type",
|
|
78
|
+
]);
|
|
79
|
+
expect(declarations.map((declaration) => declaration.source.value)).toEqual(
|
|
80
|
+
["@assistant-ui/react", "@assistant-ui/react-ui"],
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -33,72 +33,34 @@ const migrateAssistantUI = createTransformer(({ j, root, markAsChanged }) => {
|
|
|
33
33
|
"@assistant-ui/react",
|
|
34
34
|
"@assistant-ui/react-markdown",
|
|
35
35
|
];
|
|
36
|
-
const movedSpecifiers: any[] = [];
|
|
37
|
-
let lastMigratedImportPath: any = null;
|
|
38
36
|
|
|
39
37
|
root
|
|
40
38
|
.find(j.ImportDeclaration)
|
|
41
|
-
.filter((path
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
path.
|
|
46
|
-
|
|
39
|
+
.filter((path) =>
|
|
40
|
+
sourcesToMigrate.includes(path.node.source.value as string),
|
|
41
|
+
)
|
|
42
|
+
.forEach((path) => {
|
|
43
|
+
const specifiers = path.node.specifiers ?? [];
|
|
44
|
+
const moved = specifiers.filter(
|
|
45
|
+
(specifier) =>
|
|
47
46
|
j.ImportSpecifier.check(specifier) &&
|
|
48
|
-
reactUIExports.includes(specifier.imported.name as string)
|
|
49
|
-
) {
|
|
50
|
-
movedSpecifiers.push(specifier);
|
|
51
|
-
hadMigratedSpecifiers = true;
|
|
52
|
-
} else {
|
|
53
|
-
remainingSpecifiers.push(specifier);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
if (hadMigratedSpecifiers) {
|
|
57
|
-
lastMigratedImportPath = path;
|
|
58
|
-
}
|
|
59
|
-
if (remainingSpecifiers.length === 0) {
|
|
60
|
-
j(path).remove();
|
|
61
|
-
markAsChanged();
|
|
62
|
-
} else if (remainingSpecifiers.length !== path.value.specifiers.length) {
|
|
63
|
-
path.value.specifiers = remainingSpecifiers;
|
|
64
|
-
markAsChanged();
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (movedSpecifiers.length > 0) {
|
|
69
|
-
const existingReactUIImport = root.find(j.ImportDeclaration, {
|
|
70
|
-
source: { value: "@assistant-ui/react-ui" },
|
|
71
|
-
});
|
|
72
|
-
if (existingReactUIImport.size() > 0) {
|
|
73
|
-
existingReactUIImport.forEach((path: any) => {
|
|
74
|
-
movedSpecifiers.forEach((specifier: any) => {
|
|
75
|
-
if (
|
|
76
|
-
!path.value.specifiers.some(
|
|
77
|
-
(s: any) => s.imported.name === specifier.imported.name,
|
|
78
|
-
)
|
|
79
|
-
) {
|
|
80
|
-
path.value.specifiers.push(specifier);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
} else {
|
|
85
|
-
const newImport = j.importDeclaration(
|
|
86
|
-
movedSpecifiers,
|
|
87
|
-
j.literal("@assistant-ui/react-ui"),
|
|
47
|
+
reactUIExports.includes(specifier.imported.name as string),
|
|
88
48
|
);
|
|
89
|
-
if (
|
|
90
|
-
|
|
49
|
+
if (moved.length === 0) return;
|
|
50
|
+
|
|
51
|
+
const destination = j.literal("@assistant-ui/react-ui");
|
|
52
|
+
if (moved.length === specifiers.length) {
|
|
53
|
+
path.node.source = destination;
|
|
91
54
|
} else {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
55
|
+
path.node.specifiers = specifiers.filter(
|
|
56
|
+
(specifier) => !moved.includes(specifier),
|
|
57
|
+
);
|
|
58
|
+
const declaration = j.importDeclaration(moved, destination);
|
|
59
|
+
declaration.importKind = path.node.importKind ?? "value";
|
|
60
|
+
j(path).insertAfter(declaration);
|
|
98
61
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
62
|
+
markAsChanged();
|
|
63
|
+
});
|
|
102
64
|
|
|
103
65
|
const cssReplacements: Record<string, string> = {
|
|
104
66
|
"@assistant-ui/react/styles/index.css":
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import jscodeshift, { type API } from "jscodeshift";
|
|
3
|
+
import transform from "./edge-package-split";
|
|
4
|
+
|
|
5
|
+
const j = jscodeshift.withParser("tsx");
|
|
6
|
+
const api: API = { jscodeshift: j, j, stats: () => {}, report: () => {} };
|
|
7
|
+
const apply = (source: string) =>
|
|
8
|
+
transform({ path: "app.tsx", source }, api, {});
|
|
9
|
+
|
|
10
|
+
describe.each(["@assistant-ui/react", "@assistant-ui/react-edge"])(
|
|
11
|
+
"edge migration from %s",
|
|
12
|
+
(source) => {
|
|
13
|
+
it.each([
|
|
14
|
+
'import { useChatRuntime as existingRuntime } from "@assistant-ui/react-ai-sdk";',
|
|
15
|
+
'import * as sdk from "@assistant-ui/react-ai-sdk";',
|
|
16
|
+
'import { useChatRuntime as first } from "@assistant-ui/react-ai-sdk";\nimport { useChatRuntime as second } from "@assistant-ui/react-ai-sdk";',
|
|
17
|
+
'import type { CoreMessage } from "@assistant-ui/react-ai-sdk";',
|
|
18
|
+
])("preserves the moved local binding beside %s", (existing) => {
|
|
19
|
+
const output = apply(`import { useChatRuntime } from "${source}";
|
|
20
|
+
${existing}
|
|
21
|
+
const runtime = useChatRuntime;`)!;
|
|
22
|
+
|
|
23
|
+
expect(output).toContain(existing);
|
|
24
|
+
const imports = j(output)
|
|
25
|
+
.find(j.ImportDeclaration, {
|
|
26
|
+
source: { value: "@assistant-ui/react-ai-sdk" },
|
|
27
|
+
})
|
|
28
|
+
.nodes();
|
|
29
|
+
const binding = imports.filter((declaration) =>
|
|
30
|
+
declaration.specifiers?.some(
|
|
31
|
+
(specifier) => specifier.local?.name === "useChatRuntime",
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
expect(binding).toHaveLength(1);
|
|
35
|
+
expect(binding[0]!.importKind).not.toBe("type");
|
|
36
|
+
expect(output).toContain("const runtime = useChatRuntime;");
|
|
37
|
+
expect(apply(output)).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("keeps declaration-level type imports type-only", () => {
|
|
41
|
+
const output = apply(`import type { CoreMessage } from "${source}";
|
|
42
|
+
type Message = CoreMessage;`)!;
|
|
43
|
+
const declaration = j(output).find(j.ImportDeclaration).nodes()[0]!;
|
|
44
|
+
expect(declaration.source.value).toBe("@assistant-ui/react-ai-sdk");
|
|
45
|
+
expect(declaration.importKind).toBe("type");
|
|
46
|
+
expect(output).toContain("type Message = CoreMessage;");
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
it("keeps unrelated bindings and specifier-level type imports", () => {
|
|
52
|
+
const output =
|
|
53
|
+
apply(`import { AssistantRuntimeProvider, type CoreMessage, useChatRuntime as useRuntime } from "@assistant-ui/react";
|
|
54
|
+
export { AssistantRuntimeProvider, useRuntime };
|
|
55
|
+
export type { CoreMessage };`)!;
|
|
56
|
+
const root = j(output);
|
|
57
|
+
const declarations = root.find(j.ImportDeclaration).nodes();
|
|
58
|
+
const retained = declarations.find(
|
|
59
|
+
(declaration) => declaration.source.value === "@assistant-ui/react",
|
|
60
|
+
)!;
|
|
61
|
+
expect(
|
|
62
|
+
retained.specifiers?.map((specifier) => specifier.local?.name),
|
|
63
|
+
).toEqual(["AssistantRuntimeProvider"]);
|
|
64
|
+
const moved = root.find(j.ImportSpecifier).nodes();
|
|
65
|
+
expect(
|
|
66
|
+
moved.find((specifier) => specifier.local?.name === "CoreMessage"),
|
|
67
|
+
).toMatchObject({ importKind: "type" });
|
|
68
|
+
expect(
|
|
69
|
+
moved.find((specifier) => specifier.local?.name === "useRuntime")?.imported
|
|
70
|
+
.name,
|
|
71
|
+
).toBe("useChatRuntime");
|
|
72
|
+
expect(apply(output)).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("keeps both halves of a split type declaration type-only", () => {
|
|
76
|
+
const output = apply(
|
|
77
|
+
'import type { AssistantRuntime, CoreMessage } from "@assistant-ui/react";',
|
|
78
|
+
)!;
|
|
79
|
+
const declarations = j(output).find(j.ImportDeclaration).nodes();
|
|
80
|
+
expect(declarations).toHaveLength(2);
|
|
81
|
+
expect(declarations.map((declaration) => declaration.importKind)).toEqual([
|
|
82
|
+
"type",
|
|
83
|
+
"type",
|
|
84
|
+
]);
|
|
85
|
+
expect(declarations.map((declaration) => declaration.source.value)).toEqual([
|
|
86
|
+
"@assistant-ui/react",
|
|
87
|
+
"@assistant-ui/react-ai-sdk",
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it.each(["edge/converters", "dangerous-in-browser/converters"])(
|
|
92
|
+
"continues migrating the %s subpath",
|
|
93
|
+
(subpath) => {
|
|
94
|
+
expect(
|
|
95
|
+
apply(
|
|
96
|
+
`export { toCoreMessages } from "@assistant-ui/react/${subpath}";\nimport { fromCoreMessages } from "@assistant-ui/react/${subpath}";`,
|
|
97
|
+
),
|
|
98
|
+
).toContain(`from "@assistant-ui/react-ai-sdk/${subpath}"`);
|
|
99
|
+
},
|
|
100
|
+
);
|