assistant-ui 0.0.122 → 0.0.124
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/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 +53 -37
- 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/lib/agent-skill.d.ts +2 -2
- package/dist/lib/agent-skill.js +1 -1
- package/dist/lib/agent-skill.js.map +1 -1
- 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 +115 -0
- package/src/codemods/v0-12/event-names-to-camelcase.ts +2 -8
- package/src/codemods/v0-12/primitive-if-to-aui-if.ts +87 -57
- 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/lib/agent-skill.ts +1 -1
- 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;
|
|
@@ -185,37 +186,92 @@ const buildConditionString = (
|
|
|
185
186
|
|
|
186
187
|
const migratePrimitiveIfToAuiIf = createTransformer(
|
|
187
188
|
({ j, root, markAsChanged }) => {
|
|
188
|
-
|
|
189
|
+
const importedPrimitives = new Map<
|
|
190
|
+
string,
|
|
191
|
+
{ name: string; declaration: any; binding: any }
|
|
192
|
+
>();
|
|
193
|
+
const auiIfImports: { source: string; binding: any }[] = [];
|
|
194
|
+
const usedNames = new Set<string>();
|
|
195
|
+
root
|
|
196
|
+
.find(j.Identifier)
|
|
197
|
+
.forEach((path: any) => usedNames.add(path.value.name));
|
|
198
|
+
root
|
|
199
|
+
.find(j.JSXIdentifier)
|
|
200
|
+
.forEach((path: any) => usedNames.add(path.value.name));
|
|
189
201
|
|
|
190
|
-
// Track which primitive namespaces are imported
|
|
191
|
-
const importedPrimitives = new Set<string>();
|
|
192
202
|
root.find(j.ImportDeclaration).forEach((path: any) => {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
const declaration = path.value;
|
|
204
|
+
const source = declaration.source.value;
|
|
205
|
+
if (
|
|
206
|
+
typeof source !== "string" ||
|
|
207
|
+
!source.startsWith("@assistant-ui/") ||
|
|
208
|
+
declaration.importKind === "type"
|
|
209
|
+
)
|
|
210
|
+
return;
|
|
211
|
+
|
|
212
|
+
declaration.specifiers?.forEach((specifier: any) => {
|
|
213
|
+
if (
|
|
214
|
+
!j.ImportSpecifier.check(specifier) ||
|
|
215
|
+
!j.Identifier.check(specifier.imported) ||
|
|
216
|
+
!j.Identifier.check(specifier.local) ||
|
|
217
|
+
(specifier as { importKind?: string }).importKind === "type"
|
|
218
|
+
)
|
|
219
|
+
return;
|
|
220
|
+
const name = specifier.imported.name;
|
|
221
|
+
const binding = specifier.local;
|
|
222
|
+
if (primitiveMap[name] || fixedConditionMap[name]) {
|
|
223
|
+
importedPrimitives.set(binding.name, { name, declaration, binding });
|
|
224
|
+
}
|
|
225
|
+
if (name === "AuiIf") auiIfImports.push({ source, binding });
|
|
226
|
+
});
|
|
206
227
|
});
|
|
207
228
|
|
|
208
229
|
if (importedPrimitives.size === 0) return;
|
|
209
230
|
|
|
231
|
+
const getPrimitive = (path: any, localName: string) => {
|
|
232
|
+
const primitive = importedPrimitives.get(localName);
|
|
233
|
+
return primitive &&
|
|
234
|
+
resolveBinding(j, path, localName) === primitive.binding
|
|
235
|
+
? primitive
|
|
236
|
+
: undefined;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const getAuiIfName = (path: any, declaration: any) => {
|
|
240
|
+
const source = declaration.source.value;
|
|
241
|
+
const existing = auiIfImports.find(
|
|
242
|
+
(entry) =>
|
|
243
|
+
entry.source === source &&
|
|
244
|
+
!/^[a-z]/.test(entry.binding.name) &&
|
|
245
|
+
resolveBinding(j, path, entry.binding.name) === entry.binding,
|
|
246
|
+
);
|
|
247
|
+
if (existing) return existing.binding.name;
|
|
248
|
+
|
|
249
|
+
let name = "AuiIf";
|
|
250
|
+
for (let suffix = 1; usedNames.has(name); suffix++)
|
|
251
|
+
name = `AuiIf${suffix}`;
|
|
252
|
+
usedNames.add(name);
|
|
253
|
+
const binding = j.identifier(name);
|
|
254
|
+
declaration.specifiers.push(
|
|
255
|
+
j.importSpecifier(j.identifier("AuiIf"), binding),
|
|
256
|
+
);
|
|
257
|
+
auiIfImports.push({ source, binding });
|
|
258
|
+
return name;
|
|
259
|
+
};
|
|
260
|
+
|
|
210
261
|
// Opening and closing tags are rewritten together per element, so a
|
|
211
262
|
// skipped element can never be left with a mismatched closing tag.
|
|
212
|
-
const convertElementToAuiIf = (
|
|
263
|
+
const convertElementToAuiIf = (
|
|
264
|
+
elementPath: any,
|
|
265
|
+
conditionBody: string,
|
|
266
|
+
declaration: any,
|
|
267
|
+
) => {
|
|
268
|
+
const localName = getAuiIfName(elementPath, declaration);
|
|
213
269
|
const arrowFnAst = j(`(s) => ${conditionBody}`)
|
|
214
270
|
.find(j.ArrowFunctionExpression)
|
|
215
271
|
.paths()[0]!.value;
|
|
216
272
|
|
|
217
273
|
const opening = elementPath.value.openingElement;
|
|
218
|
-
opening.name = j.jsxIdentifier(
|
|
274
|
+
opening.name = j.jsxIdentifier(localName);
|
|
219
275
|
opening.attributes = [
|
|
220
276
|
j.jsxAttribute(
|
|
221
277
|
j.jsxIdentifier("condition"),
|
|
@@ -223,10 +279,9 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
223
279
|
),
|
|
224
280
|
];
|
|
225
281
|
if (elementPath.value.closingElement) {
|
|
226
|
-
elementPath.value.closingElement.name = j.jsxIdentifier(
|
|
282
|
+
elementPath.value.closingElement.name = j.jsxIdentifier(localName);
|
|
227
283
|
}
|
|
228
284
|
|
|
229
|
-
needsAuiIfImport = true;
|
|
230
285
|
markAsChanged();
|
|
231
286
|
};
|
|
232
287
|
|
|
@@ -237,17 +292,17 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
237
292
|
if (!j.JSXIdentifier.check(name.object)) return;
|
|
238
293
|
if (!j.JSXIdentifier.check(name.property)) return;
|
|
239
294
|
|
|
240
|
-
const
|
|
295
|
+
const primitive = getPrimitive(path, name.object.name);
|
|
296
|
+
if (!primitive) return;
|
|
241
297
|
const propertyName = name.property.name as string;
|
|
242
|
-
const conditionBody = fixedConditionMap[
|
|
298
|
+
const conditionBody = fixedConditionMap[primitive.name]?.[propertyName];
|
|
243
299
|
if (!conditionBody) return;
|
|
244
|
-
if (!importedPrimitives.has(primitiveName)) return;
|
|
245
300
|
|
|
246
301
|
// Only transform if there are no props (other than children, which are implicit)
|
|
247
302
|
const attrs: any[] = path.value.openingElement.attributes || [];
|
|
248
303
|
if (attrs.length > 0) return;
|
|
249
304
|
|
|
250
|
-
convertElementToAuiIf(path, conditionBody);
|
|
305
|
+
convertElementToAuiIf(path, conditionBody, primitive.declaration);
|
|
251
306
|
});
|
|
252
307
|
|
|
253
308
|
// Process JSX elements: <ThreadPrimitive.If ...> → <AuiIf condition={...}>
|
|
@@ -260,10 +315,10 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
260
315
|
if (!j.JSXIdentifier.check(name.property)) return;
|
|
261
316
|
if (name.property.name !== "If") return;
|
|
262
317
|
|
|
263
|
-
const
|
|
264
|
-
|
|
318
|
+
const primitive = getPrimitive(path, name.object.name);
|
|
319
|
+
if (!primitive) return;
|
|
320
|
+
const propMap = primitiveMap[primitive.name];
|
|
265
321
|
if (!propMap) return;
|
|
266
|
-
if (!importedPrimitives.has(primitiveName)) return;
|
|
267
322
|
|
|
268
323
|
// Extract props
|
|
269
324
|
const attrs: any[] = path.value.openingElement.attributes || [];
|
|
@@ -304,37 +359,12 @@ const migratePrimitiveIfToAuiIf = createTransformer(
|
|
|
304
359
|
// If we couldn't map all props, skip this element
|
|
305
360
|
if (hasUnknownProp || fragments.length === 0) return;
|
|
306
361
|
|
|
307
|
-
convertElementToAuiIf(
|
|
362
|
+
convertElementToAuiIf(
|
|
363
|
+
path,
|
|
364
|
+
buildConditionString(j, fragments),
|
|
365
|
+
primitive.declaration,
|
|
366
|
+
);
|
|
308
367
|
});
|
|
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
368
|
},
|
|
339
369
|
);
|
|
340
370
|
|
|
@@ -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
|
+
);
|