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.
Files changed (54) hide show
  1. package/README.md +7 -0
  2. package/dist/codemods/v0-11/content-part-to-message-part.d.ts.map +1 -1
  3. package/dist/codemods/v0-11/content-part-to-message-part.js +126 -40
  4. package/dist/codemods/v0-11/content-part-to-message-part.js.map +1 -1
  5. package/dist/codemods/v0-12/event-names-to-camelcase.d.ts.map +1 -1
  6. package/dist/codemods/v0-12/event-names-to-camelcase.js +2 -2
  7. package/dist/codemods/v0-12/event-names-to-camelcase.js.map +1 -1
  8. package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
  9. package/dist/codemods/v0-12/primitive-if-to-aui-if.js +57 -41
  10. package/dist/codemods/v0-12/primitive-if-to-aui-if.js.map +1 -1
  11. package/dist/codemods/v0-8/ui-package-split.d.ts.map +1 -1
  12. package/dist/codemods/v0-8/ui-package-split.js +11 -35
  13. package/dist/codemods/v0-8/ui-package-split.js.map +1 -1
  14. package/dist/codemods/v0-9/edge-package-split.d.ts.map +1 -1
  15. package/dist/codemods/v0-9/edge-package-split.js +15 -69
  16. package/dist/codemods/v0-9/edge-package-split.js.map +1 -1
  17. package/dist/commands/cloud.d.ts.map +1 -1
  18. package/dist/commands/cloud.js +12 -3
  19. package/dist/commands/cloud.js.map +1 -1
  20. package/dist/commands/info.d.ts.map +1 -1
  21. package/dist/commands/info.js +8 -1
  22. package/dist/commands/info.js.map +1 -1
  23. package/dist/lib/cloud-auth.d.ts +1 -0
  24. package/dist/lib/cloud-auth.d.ts.map +1 -1
  25. package/dist/lib/cloud-auth.js +4 -3
  26. package/dist/lib/cloud-auth.js.map +1 -1
  27. package/dist/lib/cloud-remote.d.ts +22 -0
  28. package/dist/lib/cloud-remote.d.ts.map +1 -0
  29. package/dist/lib/cloud-remote.js +74 -0
  30. package/dist/lib/cloud-remote.js.map +1 -0
  31. package/dist/lib/utils/package-installer.d.ts.map +1 -1
  32. package/dist/lib/utils/package-installer.js +3 -2
  33. package/dist/lib/utils/package-installer.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/codemods/v0-11/__tests__/content-part-to-message-part.test.ts +268 -2
  36. package/src/codemods/v0-11/content-part-to-message-part.ts +211 -80
  37. package/src/codemods/v0-12/__tests__/event-names-to-camelcase.test.ts +23 -0
  38. package/src/codemods/v0-12/__tests__/primitive-if-to-aui-if.test.ts +150 -1
  39. package/src/codemods/v0-12/event-names-to-camelcase.ts +2 -8
  40. package/src/codemods/v0-12/primitive-if-to-aui-if.ts +92 -61
  41. package/src/codemods/v0-8/__tests__/ui-package-split.test.ts +83 -0
  42. package/src/codemods/v0-8/ui-package-split.ts +21 -59
  43. package/src/codemods/v0-9/edge-package-split.test.ts +100 -0
  44. package/src/codemods/v0-9/edge-package-split.ts +30 -139
  45. package/src/commands/cloud.test.ts +58 -0
  46. package/src/commands/cloud.ts +19 -2
  47. package/src/commands/info.ts +26 -1
  48. package/src/lib/cloud-auth.ts +14 -8
  49. package/src/lib/cloud-remote.test.ts +156 -0
  50. package/src/lib/cloud-remote.ts +150 -0
  51. package/src/lib/utils/package-installer.test.ts +39 -0
  52. package/src/lib/utils/package-installer.ts +3 -1
  53. package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +17 -8
  54. 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
  // Map of old ContentPart names to new MessagePart names
4
5
  const typeRenamingMap: Record<string, string> = {
@@ -71,100 +72,230 @@ const typeRenamingMap: Record<string, string> = {
71
72
 
72
73
  const migrateContentPartToMessagePart = createTransformer(
73
74
  ({ j, root, markAsChanged }) => {
74
- // 1. Update imports
75
- root.find(j.ImportDeclaration).forEach((path: any) => {
76
- const source = path.value.source.value;
75
+ const renameExport = (path: any, newName: string) => {
76
+ const specifier = path.value;
77
+ const replacement: any = j.exportSpecifier.from({
78
+ local: j.identifier(newName),
79
+ exported: j.Identifier.check(specifier.exported)
80
+ ? j.identifier(specifier.exported.name)
81
+ : specifier.exported,
82
+ });
83
+ replacement.exportKind = specifier.exportKind;
84
+ replacement.comments = specifier.comments;
85
+ path.replace(replacement);
86
+ };
77
87
 
78
- // Only process imports from @assistant-ui packages
79
- if (typeof source === "string" && source.startsWith("@assistant-ui/")) {
80
- path.value.specifiers?.forEach((specifier: any) => {
81
- if (j.ImportSpecifier.check(specifier)) {
82
- const oldName = specifier.imported.name as string;
83
- if (typeRenamingMap[oldName]) {
84
- specifier.imported.name = typeRenamingMap[oldName];
85
- if (specifier.local && specifier.local.name === oldName) {
86
- specifier.local.name = typeRenamingMap[oldName];
87
- }
88
- markAsChanged();
89
- }
90
- }
91
- });
92
- }
88
+ const declaredNames = new Set<string>();
89
+ root.find(j.TSTypeParameter).forEach((path: any) => {
90
+ declaredNames.add(path.value.name.name ?? path.value.name);
93
91
  });
92
+ const identifiers = root.find(j.Identifier).paths();
93
+ for (const path of identifiers) {
94
+ if (path.parent.value.id === path.value)
95
+ declaredNames.add(path.value.name);
96
+ }
97
+ const usedNames = new Set(identifiers.map((path: any) => path.value.name));
98
+ const messageBindings = new Set<any>();
99
+ const namespaceBindings = new Set<any>();
100
+ const namespaceNames = new Set<string>();
94
101
 
95
- // 2. Update MessagePrimitive.Content to MessagePrimitive.Parts
96
- root.find(j.MemberExpression).forEach((path: any) => {
97
- if (
98
- path.value.object &&
99
- path.value.object.name === "MessagePrimitive" &&
100
- path.value.property &&
101
- path.value.property.name === "Content"
102
- ) {
103
- path.value.property.name = "Parts";
104
- markAsChanged();
105
- }
106
- });
107
-
108
- // 3. Update JSX member expressions (e.g., MessagePrimitive.Content in JSX)
109
- root.find(j.JSXMemberExpression).forEach((path: any) => {
110
- if (
111
- path.value.object &&
112
- path.value.object.name === "MessagePrimitive" &&
113
- path.value.property &&
114
- path.value.property.name === "Content"
115
- ) {
116
- path.value.property.name = "Parts";
117
- markAsChanged();
118
- }
119
- });
102
+ root.find(j.ImportDeclaration).forEach((path: any) => {
103
+ const source = path.value.source.value;
104
+ if (typeof source !== "string" || !source.startsWith("@assistant-ui/"))
105
+ return;
120
106
 
121
- // 4. Update all identifiers for types, hooks, and primitives
122
- Object.entries(typeRenamingMap).forEach(([oldName, newName]) => {
123
- // Update type references
124
- root.find(j.TSTypeReference).forEach((path: any) => {
125
- if (path.value.typeName && path.value.typeName.name === oldName) {
126
- path.value.typeName.name = newName;
127
- markAsChanged();
107
+ path.value.specifiers?.forEach((specifier: any, index: number) => {
108
+ if (j.ImportNamespaceSpecifier.check(specifier)) {
109
+ namespaceBindings.add(specifier.local);
110
+ if (j.Identifier.check(specifier.local))
111
+ namespaceNames.add(specifier.local.name);
112
+ return;
128
113
  }
129
- });
130
-
131
- // Update identifier references
132
- root.find(j.Identifier).forEach((path: any) => {
133
- if (path.value.name === oldName) {
134
- // Skip if this is part of an import declaration (already handled above)
135
- if (j.ImportSpecifier.check(path.parent.value)) {
136
- return;
137
- }
114
+ if (
115
+ !j.ImportSpecifier.check(specifier) ||
116
+ !j.Identifier.check(specifier.imported) ||
117
+ !j.Identifier.check(specifier.local)
118
+ )
119
+ return;
120
+ const oldName = specifier.imported.name;
121
+ const importKind = (specifier as { importKind?: string }).importKind;
122
+ const typeOnly =
123
+ path.value.importKind === "type" || importKind === "type";
124
+ if (oldName === "MessagePrimitive" && !typeOnly) {
125
+ messageBindings.add(specifier.local);
126
+ }
127
+ const newName = typeRenamingMap[oldName];
128
+ if (!newName) return;
138
129
 
139
- // Skip if this is a property key in an object
130
+ const references = identifiers.filter((reference: any) => {
131
+ const node = reference.value;
132
+ if (node.name !== oldName) return false;
133
+ const parent = reference.parent.value;
140
134
  if (
141
- j.Property.check(path.parent.value) &&
142
- path.parent.value.key === path.value
143
- ) {
144
- return;
145
- }
146
-
147
- // Skip if this is a property in a member expression
135
+ parent.type.startsWith("Import") ||
136
+ parent.id === node ||
137
+ parent.label === node
138
+ )
139
+ return false;
140
+ if (parent.key === node && !parent.computed && parent.value !== node)
141
+ return false;
142
+ if (parent.property === node && !parent.computed) return false;
143
+ if (j.TSQualifiedName.check(parent) && parent.right === node)
144
+ return false;
145
+ if (j.JSXAttribute.check(parent) || j.JSXNamespacedName.check(parent))
146
+ return false;
148
147
  if (
149
- j.MemberExpression.check(path.parent.value) &&
150
- path.parent.value.property === path.value
151
- ) {
152
- return;
148
+ (j.JSXOpeningElement.check(parent) ||
149
+ j.JSXClosingElement.check(parent)) &&
150
+ /^[a-z]/.test(oldName)
151
+ )
152
+ return false;
153
+ if (
154
+ j.ExportSpecifier.check(parent) &&
155
+ (reference.parent.parent.value.source || parent.local !== node)
156
+ )
157
+ return false;
158
+ return true;
159
+ });
160
+ // Retain the local alias when a declaration in either TypeScript namespace
161
+ // could capture a renamed reference. The resolver covers value bindings.
162
+ const canRename =
163
+ specifier.local.name === oldName &&
164
+ !usedNames.has(newName) &&
165
+ !declaredNames.has(newName) &&
166
+ !declaredNames.has(oldName) &&
167
+ references.every((reference: any) => {
168
+ const binding = resolveBinding(j, reference, oldName);
169
+ return binding === specifier.local || (typeOnly && !binding);
170
+ });
171
+ if (canRename) {
172
+ for (const reference of references) {
173
+ const parent = reference.parent.value;
174
+ if (
175
+ (j.Property.check(parent) || j.ObjectProperty.check(parent)) &&
176
+ parent.shorthand
177
+ ) {
178
+ parent.shorthand = false;
179
+ parent.key = j.identifier(oldName);
180
+ parent.value = j.identifier(newName);
181
+ } else if (j.ExportSpecifier.check(parent)) {
182
+ renameExport(reference.parent, newName);
183
+ } else {
184
+ reference.value.name = newName;
185
+ }
153
186
  }
154
-
155
- path.value.name = newName;
156
- markAsChanged();
157
187
  }
188
+ const replacement: any = j.importSpecifier(
189
+ j.identifier(newName),
190
+ j.identifier(canRename ? newName : specifier.local.name),
191
+ );
192
+ replacement.importKind = importKind;
193
+ replacement.comments = specifier.comments;
194
+ path.get("specifiers", index).replace(replacement);
195
+ markAsChanged();
158
196
  });
197
+ });
159
198
 
160
- // Update JSX element names
161
- root.find(j.JSXIdentifier).forEach((path: any) => {
162
- if (path.value.name === oldName) {
163
- path.value.name = newName;
164
- markAsChanged();
165
- }
199
+ root.find(j.ExportNamedDeclaration).forEach((path: any) => {
200
+ const source = path.value.source?.value;
201
+ if (typeof source !== "string" || !source.startsWith("@assistant-ui/"))
202
+ return;
203
+ path.value.specifiers?.forEach((specifier: any, index: number) => {
204
+ if (
205
+ !j.ExportSpecifier.check(specifier) ||
206
+ !j.Identifier.check(specifier.local)
207
+ )
208
+ return;
209
+ const newName = typeRenamingMap[specifier.local.name];
210
+ if (!newName) return;
211
+ renameExport(path.get("specifiers", index), newName);
212
+ markAsChanged();
166
213
  });
167
214
  });
215
+
216
+ const isTypeShadowed = (path: any, name: string) => {
217
+ if (!declaredNames.has(name)) return false;
218
+ for (let current = path.parent; current; current = current.parent) {
219
+ const node = current.value;
220
+ if (
221
+ node.type === "TSTypeParameterDeclaration" &&
222
+ node.params.some((param: any) => param.name === name)
223
+ )
224
+ return true;
225
+ if (node.type === "ClassExpression" && node.id?.name === name)
226
+ return true;
227
+ if (!["Program", "BlockStatement", "TSModuleBlock"].includes(node.type))
228
+ continue;
229
+ if (
230
+ node.body.some((statement: any) => {
231
+ const declaration = statement.declaration ?? statement;
232
+ return (
233
+ [
234
+ "TSInterfaceDeclaration",
235
+ "TSTypeAliasDeclaration",
236
+ "TSEnumDeclaration",
237
+ "ClassDeclaration",
238
+ "TSModuleDeclaration",
239
+ ].includes(declaration.type) && declaration.id?.name === name
240
+ );
241
+ })
242
+ )
243
+ return true;
244
+ }
245
+ return false;
246
+ };
247
+
248
+ const isInTypeQuery = (path: any) => {
249
+ for (let current = path.parent; current; current = current.parent) {
250
+ if (j.TSTypeQuery.check(current.value)) return true;
251
+ }
252
+ return false;
253
+ };
254
+
255
+ const renameNamespaceMember = (path: any) => {
256
+ const node = path.value;
257
+ const qualified = j.TSQualifiedName.check(node);
258
+ const object = qualified ? node.left : node.object;
259
+ const property = qualified ? node.right : node.property;
260
+ if (
261
+ node.computed ||
262
+ !j.Identifier.check(object) ||
263
+ !j.Identifier.check(property)
264
+ )
265
+ return;
266
+ const newName = typeRenamingMap[property.name];
267
+ if (!newName) return;
268
+ if (qualified && !isInTypeQuery(path)) {
269
+ if (
270
+ !namespaceNames.has(object.name) ||
271
+ isTypeShadowed(path, object.name)
272
+ )
273
+ return;
274
+ } else if (!namespaceBindings.has(resolveBinding(j, path, object.name))) {
275
+ return;
276
+ }
277
+ property.name = newName;
278
+ markAsChanged();
279
+ };
280
+ root.find(j.TSQualifiedName).forEach(renameNamespaceMember);
281
+ root.find(j.JSXMemberExpression).forEach(renameNamespaceMember);
282
+ root.find(j.MemberExpression).forEach(renameNamespaceMember);
283
+
284
+ const renameContent = (path: any) => {
285
+ const { object, property, computed } = path.value;
286
+ if (
287
+ computed ||
288
+ !j.Identifier.check(object) ||
289
+ !j.Identifier.check(property) ||
290
+ property.name !== "Content" ||
291
+ !messageBindings.has(resolveBinding(j, path, object.name))
292
+ )
293
+ return;
294
+ property.name = "Parts";
295
+ markAsChanged();
296
+ };
297
+ root.find(j.MemberExpression).forEach(renameContent);
298
+ root.find(j.JSXMemberExpression).forEach(renameContent);
168
299
  },
169
300
  );
170
301
 
@@ -125,3 +125,26 @@ function MyComponent() {
125
125
  );
126
126
  });
127
127
  });
128
+
129
+ describe("event-name migration preserves unrelated strings", () => {
130
+ const cases = [
131
+ "constructor",
132
+ "toString",
133
+ "__proto__",
134
+ "hasOwnProperty",
135
+ ].flatMap((name) => [JSON.stringify(name), `\`${name}\``]);
136
+
137
+ it.each(cases)("leaves %s unchanged without event renames", (literal) => {
138
+ expectNoChange(`const key = ${literal};`);
139
+ });
140
+
141
+ it.each(cases)("preserves %s beside an event rename", (literal) => {
142
+ const source = `import { useAuiEvent } from "@assistant-ui/react";
143
+ const key = ${literal};
144
+ useAuiEvent("thread.run-start", () => {});`;
145
+ const output = applyTransform(source);
146
+ expect(output).toBe(source.replace("thread.run-start", "thread.runStart"));
147
+ expect(() => j(output!)).not.toThrow();
148
+ expectNoChange(output!);
149
+ });
150
+ });
@@ -969,7 +969,7 @@ function MyComponent() {
969
969
  });
970
970
  });
971
971
 
972
- describe("condition precedence", () => {
972
+ describe("condition semantics", () => {
973
973
  const compileCondition = (jsx: string) => {
974
974
  const output = applyTransform(`
975
975
  import { MessagePrimitive, ComposerPrimitive } from "@assistant-ui/react";
@@ -982,6 +982,40 @@ const view = ${jsx};
982
982
  ) => boolean;
983
983
  };
984
984
 
985
+ it.each(["hasBranches", "lastOrHover"])(
986
+ "keeps %s={false} unconditional",
987
+ (filter) => {
988
+ const condition = compileCondition(
989
+ `<MessagePrimitive.If ${filter}={false} />`,
990
+ );
991
+
992
+ for (const branchCount of [1, 2]) {
993
+ for (const isLast of [false, true]) {
994
+ for (const isHovering of [false, true]) {
995
+ expect(
996
+ condition({ message: { branchCount, isLast, isHovering } }),
997
+ ).toBe(true);
998
+ }
999
+ }
1000
+ }
1001
+ },
1002
+ );
1003
+
1004
+ it.each(["hasBranches", "lastOrHover"])(
1005
+ "keeps the role filter when %s={false}",
1006
+ (filter) => {
1007
+ const condition = compileCondition(
1008
+ `<MessagePrimitive.If ${filter}={false} assistant />`,
1009
+ );
1010
+ const message = { branchCount: 1, isLast: false, isHovering: false };
1011
+
1012
+ expect(condition({ message: { ...message, role: "assistant" } })).toBe(
1013
+ true,
1014
+ );
1015
+ expect(condition({ message: { ...message, role: "user" } })).toBe(false);
1016
+ },
1017
+ );
1018
+
985
1019
  it.each([
986
1020
  {
987
1021
  jsx: "<MessagePrimitive.If speaking={false} />",
@@ -1015,3 +1049,118 @@ const view = ${jsx};
1015
1049
  expect(condition(visible)).toBe(true);
1016
1050
  });
1017
1051
  });
1052
+
1053
+ describe("conditional import bindings", () => {
1054
+ it("adds AuiIf to the primitive's module when another package is imported later", () => {
1055
+ const output =
1056
+ applyTransform(`import { ThreadPrimitive } from "@assistant-ui/react";
1057
+ import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown";
1058
+ export const Empty = () => <ThreadPrimitive.If empty><MarkdownTextPrimitive /></ThreadPrimitive.If>;`)!;
1059
+ expect(output).toContain(
1060
+ 'import { ThreadPrimitive, AuiIf } from "@assistant-ui/react"',
1061
+ );
1062
+ expect(output).toContain(
1063
+ 'import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown"',
1064
+ );
1065
+ });
1066
+
1067
+ it("uses an existing value import's local alias for both tags", () => {
1068
+ const output =
1069
+ applyTransform(`import { ThreadPrimitive, AuiIf as If } from "@assistant-ui/react";
1070
+ export const Empty = () => <ThreadPrimitive.If empty>Empty</ThreadPrimitive.If>;`)!;
1071
+ expect(output).toContain("<If condition=");
1072
+ expect(output).toContain("</If>");
1073
+ expect(
1074
+ j(output)
1075
+ .find(j.ImportSpecifier, { imported: { name: "AuiIf" } })
1076
+ .size(),
1077
+ ).toBe(1);
1078
+ });
1079
+
1080
+ it.each([
1081
+ 'import type { AuiIf } from "@assistant-ui/react";',
1082
+ 'import { type AuiIf } from "@assistant-ui/react";',
1083
+ 'import { AuiIf } from "./custom-if";',
1084
+ 'import { Other as AuiIf } from "@assistant-ui/react";',
1085
+ ])(
1086
+ "keeps a separate value binding when the name is occupied: %s",
1087
+ (declaration) => {
1088
+ const output =
1089
+ applyTransform(`import { ThreadPrimitive } from "@assistant-ui/react";
1090
+ ${declaration}
1091
+ export const Empty = () => <ThreadPrimitive.Empty>Empty</ThreadPrimitive.Empty>;`)!;
1092
+ expect(output).toContain("AuiIf as AuiIf1");
1093
+ expect(output).toContain("<AuiIf1 condition=");
1094
+ expect(output).toContain("</AuiIf1>");
1095
+ expect(output).toContain(declaration);
1096
+ },
1097
+ );
1098
+
1099
+ it("does not append a value component to a later type-only import", () => {
1100
+ const output =
1101
+ applyTransform(`import { ThreadPrimitive } from "@assistant-ui/react";
1102
+ import type { ThreadMessage } from "@assistant-ui/react";
1103
+ export const Empty = () => <ThreadPrimitive.If empty />;`)!;
1104
+ expect(output).toContain(
1105
+ 'import { ThreadPrimitive, AuiIf } from "@assistant-ui/react"',
1106
+ );
1107
+ expect(output).toContain(
1108
+ 'import type { ThreadMessage } from "@assistant-ui/react"',
1109
+ );
1110
+ });
1111
+
1112
+ it("avoids shadowed aliases and reuses the fresh binding for multiple elements", () => {
1113
+ const output =
1114
+ applyTransform(`import { ThreadPrimitive, AuiIf as If } from "@assistant-ui/react";
1115
+ export function Empty(If, AuiIf) {
1116
+ return <ThreadPrimitive.If empty><ThreadPrimitive.Empty /></ThreadPrimitive.If>;
1117
+ }`)!;
1118
+ expect(output).toContain("AuiIf as AuiIf1");
1119
+ expect(output.match(/<AuiIf1 condition=/g)).toHaveLength(2);
1120
+ expect(output).toContain("</AuiIf1>");
1121
+ expect(
1122
+ j(output)
1123
+ .find(j.ImportSpecifier, { imported: { name: "AuiIf" } })
1124
+ .size(),
1125
+ ).toBe(2);
1126
+ expect(applyTransform(output)).toBeNull();
1127
+ });
1128
+
1129
+ it("keeps namespace imports intact", () => {
1130
+ const output =
1131
+ applyTransform(`import { ThreadPrimitive } from "@assistant-ui/react";
1132
+ import * as primitives from "@assistant-ui/react";
1133
+ export const Empty = () => <ThreadPrimitive.If empty />;`)!;
1134
+ expect(output).toContain(
1135
+ 'import { ThreadPrimitive, AuiIf } from "@assistant-ui/react"',
1136
+ );
1137
+ expect(output).toContain(
1138
+ 'import * as primitives from "@assistant-ui/react"',
1139
+ );
1140
+ expect(() => j(output)).not.toThrow();
1141
+ });
1142
+
1143
+ it("uses a component name when an existing alias starts with lowercase", () => {
1144
+ const output =
1145
+ applyTransform(`import { ThreadPrimitive, AuiIf as conditional } from "@assistant-ui/react";
1146
+ export const Empty = () => <ThreadPrimitive.If empty />;`)!;
1147
+ expect(output).toContain("<AuiIf1 condition=");
1148
+ expect(output).toContain("AuiIf as conditional");
1149
+ expect(
1150
+ j(output)
1151
+ .find(j.ImportSpecifier, { local: { name: "AuiIf1" } })
1152
+ .size(),
1153
+ ).toBe(1);
1154
+ });
1155
+
1156
+ it("resolves aliased primitives without migrating their shadowed names", () => {
1157
+ const output =
1158
+ applyTransform(`import { ThreadPrimitive as Thread } from "@assistant-ui/react";
1159
+ export const Empty = () => <Thread.If empty />;
1160
+ export const Custom = (Thread) => <Thread.If empty />;`)!;
1161
+ expect(output).toContain("export const Empty = () => <AuiIf condition=");
1162
+ expect(output).toContain(
1163
+ "export const Custom = (Thread) => <Thread.If empty />;",
1164
+ );
1165
+ });
1166
+ });
@@ -1,6 +1,5 @@
1
1
  import { createTransformer } from "../utils/createTransformer";
2
2
 
3
- // Map of old kebab-case event names to new camelCase event names
4
3
  const eventNameMap: Record<string, string> = {
5
4
  "thread.run-start": "thread.runStart",
6
5
  "thread.run-end": "thread.runEnd",
@@ -12,16 +11,13 @@ const eventNameMap: Record<string, string> = {
12
11
 
13
12
  const migrateEventNamesToCamelCase = createTransformer(
14
13
  ({ j, root, markAsChanged }) => {
15
- // Find all string literals in the code
16
14
  root.find(j.Literal).forEach((path: any) => {
17
15
  const node = path.value;
18
16
 
19
- // Check if this is a string literal
20
17
  if (typeof node.value === "string") {
21
18
  const oldEventName = node.value;
22
19
 
23
- // Check if this event name needs to be migrated
24
- if (eventNameMap[oldEventName]) {
20
+ if (Object.hasOwn(eventNameMap, oldEventName)) {
25
21
  const newEventName = eventNameMap[oldEventName];
26
22
  node.value = newEventName;
27
23
  markAsChanged();
@@ -29,16 +25,14 @@ const migrateEventNamesToCamelCase = createTransformer(
29
25
  }
30
26
  });
31
27
 
32
- // Also handle template literals (in case event names are in template strings)
33
28
  root.find(j.TemplateLiteral).forEach((path: any) => {
34
29
  const node = path.value;
35
30
 
36
- // Check quasi (string parts) in template literals
37
31
  node.quasis.forEach((quasi: any) => {
38
32
  if (quasi.value && typeof quasi.value.raw === "string") {
39
33
  const oldEventName = quasi.value.raw;
40
34
 
41
- if (eventNameMap[oldEventName]) {
35
+ if (Object.hasOwn(eventNameMap, oldEventName)) {
42
36
  const newEventName = eventNameMap[oldEventName];
43
37
  quasi.value.raw = newEventName;
44
38
  quasi.value.cooked = newEventName;