assistant-ui 0.0.111 → 0.0.113

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 (90) hide show
  1. package/dist/codemods/v0-12/assistant-api-to-aui.d.ts.map +1 -1
  2. package/dist/codemods/v0-12/assistant-api-to-aui.js +87 -62
  3. package/dist/codemods/v0-12/assistant-api-to-aui.js.map +1 -1
  4. package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
  5. package/dist/codemods/v0-12/primitive-if-to-aui-if.js +31 -45
  6. package/dist/codemods/v0-12/primitive-if-to-aui-if.js.map +1 -1
  7. package/dist/commands/add.d.ts +1 -1
  8. package/dist/commands/add.d.ts.map +1 -1
  9. package/dist/commands/add.js +4 -2
  10. package/dist/commands/add.js.map +1 -1
  11. package/dist/commands/create.d.ts.map +1 -1
  12. package/dist/commands/create.js +33 -10
  13. package/dist/commands/create.js.map +1 -1
  14. package/dist/commands/info.d.ts.map +1 -1
  15. package/dist/commands/info.js +2 -1
  16. package/dist/commands/info.js.map +1 -1
  17. package/dist/commands/init.d.ts.map +1 -1
  18. package/dist/commands/init.js +4 -2
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/mcp.d.ts.map +1 -1
  21. package/dist/commands/mcp.js +25 -28
  22. package/dist/commands/mcp.js.map +1 -1
  23. package/dist/commands/update.js +6 -7
  24. package/dist/commands/update.js.map +1 -1
  25. package/dist/commands/upgrade.d.ts.map +1 -1
  26. package/dist/commands/upgrade.js +5 -2
  27. package/dist/commands/upgrade.js.map +1 -1
  28. package/dist/index.js +2 -6
  29. package/dist/index.js.map +1 -1
  30. package/dist/lib/agent-skill.d.ts +1 -1
  31. package/dist/lib/agent-skill.d.ts.map +1 -1
  32. package/dist/lib/agent-skill.js.map +1 -1
  33. package/dist/lib/create-project.d.ts +2 -3
  34. package/dist/lib/create-project.d.ts.map +1 -1
  35. package/dist/lib/create-project.js +17 -42
  36. package/dist/lib/create-project.js.map +1 -1
  37. package/dist/lib/handle-cli-error.d.ts +5 -0
  38. package/dist/lib/handle-cli-error.d.ts.map +1 -0
  39. package/dist/lib/handle-cli-error.js +17 -0
  40. package/dist/lib/handle-cli-error.js.map +1 -0
  41. package/dist/lib/install-ai-sdk-lib.js +8 -1
  42. package/dist/lib/install-ai-sdk-lib.js.map +1 -1
  43. package/dist/lib/install-edge-lib.js +4 -8
  44. package/dist/lib/install-edge-lib.js.map +1 -1
  45. package/dist/lib/run-spawn.d.ts +16 -2
  46. package/dist/lib/run-spawn.d.ts.map +1 -1
  47. package/dist/lib/run-spawn.js +87 -9
  48. package/dist/lib/run-spawn.js.map +1 -1
  49. package/dist/lib/transform.d.ts +1 -1
  50. package/dist/lib/transform.d.ts.map +1 -1
  51. package/dist/lib/transform.js +12 -25
  52. package/dist/lib/transform.js.map +1 -1
  53. package/dist/lib/upgrade.js +20 -16
  54. package/dist/lib/upgrade.js.map +1 -1
  55. package/dist/lib/utils/file-scanner.d.ts +9 -1
  56. package/dist/lib/utils/file-scanner.d.ts.map +1 -1
  57. package/dist/lib/utils/file-scanner.js +22 -20
  58. package/dist/lib/utils/file-scanner.js.map +1 -1
  59. package/dist/lib/utils/package-installer.js +1 -1
  60. package/dist/lib/utils/package-manager.d.ts +3 -1
  61. package/dist/lib/utils/package-manager.d.ts.map +1 -1
  62. package/dist/lib/utils/package-manager.js +22 -10
  63. package/dist/lib/utils/package-manager.js.map +1 -1
  64. package/package.json +6 -6
  65. package/plugin/skills/assistant-ui/SKILL.md +3 -3
  66. package/src/codemods/v0-12/__tests__/assistant-api-to-aui.test.ts +370 -0
  67. package/src/codemods/v0-12/__tests__/primitive-if-to-aui-if.test.ts +100 -0
  68. package/src/codemods/v0-12/assistant-api-to-aui.ts +227 -134
  69. package/src/codemods/v0-12/primitive-if-to-aui-if.ts +44 -72
  70. package/src/commands/add.ts +5 -5
  71. package/src/commands/create.ts +41 -7
  72. package/src/commands/info.ts +1 -0
  73. package/src/commands/init.ts +4 -6
  74. package/src/commands/mcp.ts +20 -29
  75. package/src/commands/update.ts +6 -8
  76. package/src/commands/upgrade.ts +5 -2
  77. package/src/index.ts +2 -7
  78. package/src/lib/agent-skill.ts +2 -1
  79. package/src/lib/create-project.ts +24 -58
  80. package/src/lib/handle-cli-error.test.ts +67 -0
  81. package/src/lib/handle-cli-error.ts +17 -0
  82. package/src/lib/install-ai-sdk-lib.ts +13 -1
  83. package/src/lib/install-edge-lib.ts +7 -8
  84. package/src/lib/run-spawn.test.ts +148 -0
  85. package/src/lib/run-spawn.ts +121 -11
  86. package/src/lib/transform.test.ts +62 -0
  87. package/src/lib/transform.ts +21 -37
  88. package/src/lib/upgrade.ts +23 -19
  89. package/src/lib/utils/file-scanner.ts +26 -20
  90. package/src/lib/utils/package-manager.ts +29 -12
@@ -22,72 +22,8 @@ const isUseAuiCall = (j: any, node: any): boolean => {
22
22
  );
23
23
  };
24
24
 
25
- // Check if a scope node directly contains an 'api' variable declaration
26
- const scopeHasDirectApiDeclaration = (j: any, scopeNode: any): boolean => {
27
- // Get the body of the scope
28
- let body = null;
29
- if (
30
- j.FunctionDeclaration.check(scopeNode) ||
31
- j.FunctionExpression.check(scopeNode)
32
- ) {
33
- body = scopeNode.body?.body;
34
- } else if (j.ArrowFunctionExpression.check(scopeNode)) {
35
- // Arrow functions might have block or expression body
36
- if (j.BlockStatement.check(scopeNode.body)) {
37
- body = scopeNode.body.body;
38
- }
39
- } else if (j.BlockStatement.check(scopeNode)) {
40
- body = scopeNode.body;
41
- }
42
-
43
- if (!Array.isArray(body)) return false;
44
-
45
- // Check only direct statements in this scope's body
46
- for (const statement of body) {
47
- if (j.VariableDeclaration.check(statement)) {
48
- for (const declarator of statement.declarations) {
49
- if (j.Identifier.check(declarator.id) && declarator.id.name === "api") {
50
- // Don't count it as shadowing if it's from useAui
51
- if (!isUseAuiCall(j, declarator.init)) {
52
- return true;
53
- }
54
- }
55
- }
56
- }
57
- }
58
-
59
- return false;
60
- };
61
-
62
- // Check if a path is inside a scope that shadows the api variable
63
- const isInsideShadowingScope = (j: any, identifierPath: any): boolean => {
64
- let currentPath = identifierPath.parent;
65
-
66
- while (currentPath) {
67
- const node = currentPath.value;
68
-
69
- // Check if this is a scope-creating node (function, arrow function, block)
70
- if (
71
- j.FunctionDeclaration.check(node) ||
72
- j.FunctionExpression.check(node) ||
73
- j.ArrowFunctionExpression.check(node) ||
74
- j.BlockStatement.check(node)
75
- ) {
76
- if (scopeHasDirectApiDeclaration(j, node)) {
77
- return true;
78
- }
79
- }
80
-
81
- currentPath = currentPath.parent;
82
- }
83
-
84
- return false;
85
- };
86
-
87
25
  const migrateAssistantApiToAui = createTransformer(
88
26
  ({ j, root, markAsChanged }) => {
89
- let hasApiFromUseAui = false;
90
-
91
27
  // 1. Update imports
92
28
  root.find(j.ImportDeclaration).forEach((path: any) => {
93
29
  const source = path.value.source.value;
@@ -122,91 +58,248 @@ const migrateAssistantApiToAui = createTransformer(
122
58
  }
123
59
  });
124
60
 
125
- // 2. Find and rename variable declarations from useAui (or useAssistantApi)
61
+ // 2. Collect `api` declarators initialized from useAui / useAssistantApi.
62
+ // References are renamed by binding resolution, so an `api` bound
63
+ // elsewhere (function params, `const { api } = other()`) is never touched.
64
+ const renamedDeclaratorIds = new Set<any>();
126
65
  root.find(j.VariableDeclarator).forEach((path: any) => {
127
- const init = path.value.init;
128
-
129
- // Check if this is a call to useAui or useAssistantApi
130
- if (isUseAuiCall(j, init)) {
131
- if (j.Identifier.check(path.value.id)) {
132
- const oldVarName = path.value.id.name;
133
-
134
- // Only rename if it's called 'api'
135
- if (oldVarName === "api") {
136
- path.value.id.name = "aui";
137
- hasApiFromUseAui = true;
138
- markAsChanged();
139
- }
140
- }
66
+ if (
67
+ isUseAuiCall(j, path.value.init) &&
68
+ j.Identifier.check(path.value.id) &&
69
+ path.value.id.name === "api"
70
+ ) {
71
+ renamedDeclaratorIds.add(path.value.id);
141
72
  }
142
73
  });
143
74
 
144
- // 3. Rename all references to 'api' if we found it from useAui
145
- if (hasApiFromUseAui) {
146
- root.find(j.Identifier).forEach((path: any) => {
147
- if (path.value.name === "api") {
148
- // Skip if this is part of an import
149
- if (j.ImportSpecifier.check(path.parent.value)) {
150
- return;
151
- }
152
-
153
- // Skip if this is a variable declarator id
154
- if (j.VariableDeclarator.check(path.parent.value)) {
155
- const declarator = path.parent.value;
156
- if (declarator.id === path.value) {
157
- return;
158
- }
159
- }
160
-
161
- // Skip if this is a property key in an object (e.g., { api: true })
162
- if (j.Property.check(path.parent.value)) {
163
- const prop = path.parent.value;
164
- if (prop.key === path.value && !prop.shorthand && !prop.computed) {
165
- return;
166
- }
167
- }
168
-
169
- if (j.ObjectProperty.check(path.parent.value)) {
170
- const prop = path.parent.value;
171
- if (prop.key === path.value && !prop.shorthand && !prop.computed) {
172
- return;
173
- }
75
+ // 3. Rename references governed by one of those declarators. Resolution
76
+ // is lexical (nearest enclosing declaration wins) rather than via
77
+ // ast-types scopes, which have no block granularity: a block-scoped
78
+ // `const api = other()` inside the same function must shadow.
79
+ if (renamedDeclaratorIds.size > 0) {
80
+ const patternBindsApi = (id: any): boolean => {
81
+ if (
82
+ id &&
83
+ (id.type === "TSParameterProperty" ||
84
+ j.TSParameterProperty?.check?.(id))
85
+ ) {
86
+ return patternBindsApi(id.parameter);
87
+ }
88
+ if (j.Identifier.check(id)) return id.name === "api";
89
+ if (j.ObjectPattern.check(id)) {
90
+ return id.properties.some((prop: any) =>
91
+ patternBindsApi(prop.value ?? prop.argument ?? prop),
92
+ );
93
+ }
94
+ if (j.ArrayPattern.check(id)) {
95
+ return id.elements.some((el: any) => el && patternBindsApi(el));
96
+ }
97
+ if (j.AssignmentPattern.check(id)) return patternBindsApi(id.left);
98
+ if (j.RestElement.check(id)) return patternBindsApi(id.argument);
99
+ return false;
100
+ };
101
+
102
+ // What a statement-level node declares for `api`: the declarator id
103
+ // node when it is a plain `const/let/var api = ...`, "foreign" for any
104
+ // other binding of the name (patterns, functions, classes, enums), or
105
+ // undefined when it does not bind `api` at all.
106
+ const declaredApi = (statement: any): any => {
107
+ if (!statement) return undefined;
108
+ if (
109
+ j.ExportNamedDeclaration.check(statement) ||
110
+ j.ExportDefaultDeclaration.check(statement)
111
+ ) {
112
+ return declaredApi(statement.declaration);
113
+ }
114
+ if (j.VariableDeclaration.check(statement)) {
115
+ for (const declarator of statement.declarations) {
116
+ if (
117
+ j.Identifier.check(declarator.id) &&
118
+ declarator.id.name === "api"
119
+ )
120
+ return declarator.id;
121
+ if (patternBindsApi(declarator.id)) return "foreign";
174
122
  }
175
-
176
- // Skip if this is a property in a member expression (e.g., foo.api)
123
+ return undefined;
124
+ }
125
+ // Type-only declarations do not shadow the value binding.
126
+ if (
127
+ statement.type === "TSTypeAliasDeclaration" ||
128
+ statement.type === "TSInterfaceDeclaration" ||
129
+ statement.type === "TSDeclareFunction"
130
+ )
131
+ return undefined;
132
+ // FunctionDeclaration, ClassDeclaration, TS enums/namespaces, …
133
+ if (
134
+ statement.id &&
135
+ j.Identifier.check(statement.id) &&
136
+ statement.id.name === "api"
137
+ )
138
+ return "foreign";
139
+ return undefined;
140
+ };
141
+
142
+ const scanStatements = (statements: any[]): any => {
143
+ for (const statement of statements) {
144
+ const found = declaredApi(statement);
145
+ if (found !== undefined) return found;
146
+ }
147
+ return undefined;
148
+ };
149
+
150
+ // Returns the declarator id node governing `api` here, or "foreign"
151
+ // when any other binding of the name shadows it first.
152
+ const governingApiBinding = (path: any): any => {
153
+ let current = path.parent;
154
+ while (current) {
155
+ const node = current.value;
156
+
157
+ // Anything function-like (declarations, expressions, arrows,
158
+ // object/class methods) binds its params.
159
+ if (Array.isArray(node.params) && node.params.some(patternBindsApi))
160
+ return "foreign";
161
+ // A named function/class expression binds its own name in its body.
177
162
  if (
178
- j.MemberExpression.check(path.parent.value) &&
179
- path.parent.value.property === path.value &&
180
- !path.parent.value.computed
163
+ (j.FunctionExpression.check(node) ||
164
+ j.ClassExpression.check(node)) &&
165
+ node.id?.name === "api"
166
+ )
167
+ return "foreign";
168
+ if (
169
+ j.CatchClause.check(node) &&
170
+ node.param &&
171
+ patternBindsApi(node.param)
172
+ )
173
+ return "foreign";
174
+
175
+ let found: any;
176
+ if (j.BlockStatement.check(node) || j.Program.check(node)) {
177
+ found = scanStatements(node.body);
178
+ } else if (j.ForStatement.check(node)) {
179
+ found = declaredApi(node.init);
180
+ } else if (
181
+ j.ForOfStatement.check(node) ||
182
+ j.ForInStatement.check(node)
181
183
  ) {
182
- return;
184
+ found = declaredApi(node.left);
185
+ } else if (j.SwitchStatement.check(node)) {
186
+ found = scanStatements(
187
+ node.cases.flatMap((c: any) => c.consequent),
188
+ );
189
+ } else if (j.StaticBlock?.check?.(node)) {
190
+ found = scanStatements(node.body);
183
191
  }
192
+ if (found !== undefined) return found;
184
193
 
185
- // Skip if this is a JSX attribute name
186
- if (j.JSXAttribute.check(path.parent.value)) {
194
+ current = current.parent;
195
+ }
196
+ return undefined;
197
+ };
198
+
199
+ const bindsToRenamedApi = (path: any): boolean => {
200
+ const governing = governingApiBinding(path);
201
+ return governing !== "foreign" && renamedDeclaratorIds.has(governing);
202
+ };
203
+
204
+ const referencePaths: any[] = [];
205
+ root.find(j.Identifier, { name: "api" }).forEach((path: any) => {
206
+ const parent = path.parent.value;
207
+ if (j.ImportSpecifier.check(parent)) return;
208
+ // Declaration names (variable, function, class, type alias,
209
+ // interface) and TS type positions are not value references.
210
+ if (parent.id === path.value) return;
211
+ if (j.TSTypeReference?.check?.(parent)) return;
212
+ if (j.TSQualifiedName?.check?.(parent)) return;
213
+ // Any non-computed key position is a name, not a reference: object
214
+ // properties, object/class methods, class properties, TS signatures.
215
+ // Esprima-style shorthand reuses one node as key and value, so the
216
+ // value position must survive the guard.
217
+ if (
218
+ parent.key === path.value &&
219
+ !parent.computed &&
220
+ parent.value !== path.value
221
+ )
222
+ return;
223
+ if (
224
+ j.MemberExpression.check(parent) &&
225
+ parent.property === path.value &&
226
+ !parent.computed
227
+ )
228
+ return;
229
+ // JSXIdentifier extends Identifier, so JSX positions land here too:
230
+ // member properties (<config.api/>), namespace names, and lowercase
231
+ // element names (<api/> is an intrinsic tag) are not references.
232
+ if (
233
+ j.JSXMemberExpression?.check?.(parent) &&
234
+ parent.property === path.value
235
+ )
236
+ return;
237
+ if (j.JSXNamespacedName?.check?.(parent)) return;
238
+ if (
239
+ (j.JSXOpeningElement?.check?.(parent) ||
240
+ j.JSXClosingElement?.check?.(parent)) &&
241
+ parent.name === path.value
242
+ )
243
+ return;
244
+ if (j.JSXAttribute.check(parent)) return;
245
+ // The exported name of `export { api }` is the public alias, not a
246
+ // reference; only the local side is renamed (to `aui as api`). A
247
+ // source-bearing re-export binds in the other module, never here.
248
+ if (j.ExportSpecifier.check(parent)) {
249
+ const grandparent = path.parent.parent?.value;
250
+ if (
251
+ j.ExportNamedDeclaration.check(grandparent) &&
252
+ grandparent.source != null
253
+ )
187
254
  return;
188
- }
189
-
190
- // Skip if this identifier is inside a scope that shadows the api variable
191
- if (isInsideShadowingScope(j, path)) {
255
+ if (
256
+ grandparent?.exportKind === "type" ||
257
+ parent.exportKind === "type"
258
+ )
259
+ return;
260
+ if (parent.exported === path.value && parent.local !== path.value)
192
261
  return;
193
- }
194
-
195
- // Update the reference
196
- path.value.name = "aui";
197
- markAsChanged();
198
262
  }
263
+ if (!bindsToRenamedApi(path)) return;
264
+ referencePaths.push(path);
199
265
  });
200
266
 
201
- // Also handle JSX identifiers
202
- root.find(j.JSXIdentifier).forEach((path: any) => {
203
- if (path.value.name === "api") {
204
- if (!isInsideShadowingScope(j, path)) {
205
- path.value.name = "aui";
206
- markAsChanged();
207
- }
267
+ for (const path of referencePaths) {
268
+ const parent = path.parent.value;
269
+ if (
270
+ (j.Property.check(parent) || j.ObjectProperty.check(parent)) &&
271
+ parent.shorthand &&
272
+ parent.value === path.value
273
+ ) {
274
+ // `{ api }` in an object literal: keep the key, rename the value
275
+ parent.shorthand = false;
276
+ parent.key = j.identifier("api");
277
+ parent.value = j.identifier("aui");
278
+ } else if (
279
+ j.ExportSpecifier.check(parent) &&
280
+ parent.local === path.value
281
+ ) {
282
+ // `export { api }` / `export { api as name }`: rename the local
283
+ // binding, keep the public name. Replaced wholesale — recast keeps
284
+ // the shorthand form (dropping the alias) when only the fields of
285
+ // the original node change.
286
+ path.parent.replace(
287
+ j.exportSpecifier.from({
288
+ local: j.identifier("aui"),
289
+ exported: j.Identifier.check(parent.exported)
290
+ ? j.identifier(parent.exported.name)
291
+ : parent.exported,
292
+ }),
293
+ );
294
+ } else {
295
+ path.value.name = "aui";
208
296
  }
209
- });
297
+ markAsChanged();
298
+ }
299
+ for (const idNode of renamedDeclaratorIds) {
300
+ idNode.name = "aui";
301
+ markAsChanged();
302
+ }
210
303
  }
211
304
 
212
305
  // 4. Update hook call references (in case they're used as values)
@@ -120,12 +120,18 @@ const fixedConditionMap: Record<string, Record<string, string>> = {
120
120
  },
121
121
  };
122
122
 
123
+ // A prop value the maps cannot faithfully express as a static condition
124
+ // (dynamic expressions, `{undefined}`); elements carrying one are skipped
125
+ // so runtime behavior is never silently changed.
126
+ const UNSUPPORTED_VALUE: unique symbol = Symbol("unsupported");
127
+
123
128
  /**
124
129
  * Extract the value of a JSX attribute.
125
130
  * - Boolean prop (no value): `<X.If user>` → `true`
126
131
  * - `{true}` / `{false}`: → `true` / `false`
127
132
  * - `{"positive"}`: → `"positive"`
128
133
  * - `{null}`: → `null`
134
+ * - anything else (dynamic expressions): → UNSUPPORTED_VALUE
129
135
  */
130
136
  const getAttrValue = (j: any, attr: any): unknown => {
131
137
  // Boolean attribute (no value), e.g. `<X.If user>`
@@ -137,12 +143,14 @@ const getAttrValue = (j: any, attr: any): unknown => {
137
143
  if (j.JSXExpressionContainer.check(attr.value)) {
138
144
  const expr = attr.value.expression;
139
145
  if (j.BooleanLiteral.check(expr)) return expr.value;
146
+ // NullLiteral bases Literal in ast-types but carries no `value` field,
147
+ // so it must be recognized before the generic Literal branch.
148
+ if (j.NullLiteral.check(expr)) return null;
140
149
  if (j.Literal.check(expr)) {
141
150
  if (expr.value === null) return null;
142
151
  return expr.value;
143
152
  }
144
- if (j.NullLiteral.check(expr)) return null;
145
- if (j.Identifier.check(expr) && expr.name === "undefined") return undefined;
153
+ return UNSUPPORTED_VALUE;
146
154
  }
147
155
 
148
156
  // String literal
@@ -150,7 +158,7 @@ const getAttrValue = (j: any, attr: any): unknown => {
150
158
  return attr.value.value;
151
159
  }
152
160
 
153
- return undefined;
161
+ return UNSUPPORTED_VALUE;
154
162
  };
155
163
 
156
164
  const buildConditionString = (fragments: ConditionFragment[]): string => {
@@ -185,61 +193,52 @@ const migratePrimitiveIfToAuiIf = createTransformer(
185
193
 
186
194
  if (importedPrimitives.size === 0) return;
187
195
 
188
- // Process fixed-condition components: <ThreadPrimitive.Empty> → <AuiIf condition={...}>
189
- root.find(j.JSXOpeningElement).forEach((path: any) => {
190
- const name = path.value.name;
191
- if (!j.JSXMemberExpression.check(name)) return;
192
- if (!j.JSXIdentifier.check(name.object)) return;
193
- if (!j.JSXIdentifier.check(name.property)) return;
194
-
195
- const primitiveName = name.object.name as string;
196
- const propertyName = name.property.name as string;
197
- const fixedMap = fixedConditionMap[primitiveName];
198
- if (!fixedMap) return;
199
- const conditionBody = fixedMap[propertyName];
200
- if (!conditionBody) return;
201
- if (!importedPrimitives.has(primitiveName)) return;
202
-
203
- // Only transform if there are no props (other than children, which are implicit)
204
- const attrs: any[] = path.value.attributes || [];
205
- if (attrs.length > 0) return;
206
-
196
+ // Opening and closing tags are rewritten together per element, so a
197
+ // skipped element can never be left with a mismatched closing tag.
198
+ const convertElementToAuiIf = (elementPath: any, conditionBody: string) => {
207
199
  const arrowFnAst = j(`(s) => ${conditionBody}`)
208
200
  .find(j.ArrowFunctionExpression)
209
201
  .paths()[0]!.value;
210
202
 
211
- path.value.name = j.jsxIdentifier("AuiIf");
212
- path.value.attributes = [
203
+ const opening = elementPath.value.openingElement;
204
+ opening.name = j.jsxIdentifier("AuiIf");
205
+ opening.attributes = [
213
206
  j.jsxAttribute(
214
207
  j.jsxIdentifier("condition"),
215
208
  j.jsxExpressionContainer(arrowFnAst),
216
209
  ),
217
210
  ];
211
+ if (elementPath.value.closingElement) {
212
+ elementPath.value.closingElement.name = j.jsxIdentifier("AuiIf");
213
+ }
218
214
 
219
215
  needsAuiIfImport = true;
220
216
  markAsChanged();
221
- });
217
+ };
222
218
 
223
- // Update closing elements for fixed-condition components
224
- root.find(j.JSXClosingElement).forEach((path: any) => {
225
- const name = path.value.name;
219
+ // Process fixed-condition components: <ThreadPrimitive.Empty> → <AuiIf condition={...}>
220
+ root.find(j.JSXElement).forEach((path: any) => {
221
+ const name = path.value.openingElement.name;
226
222
  if (!j.JSXMemberExpression.check(name)) return;
227
223
  if (!j.JSXIdentifier.check(name.object)) return;
228
224
  if (!j.JSXIdentifier.check(name.property)) return;
229
225
 
230
226
  const primitiveName = name.object.name as string;
231
227
  const propertyName = name.property.name as string;
232
- const fixedMap = fixedConditionMap[primitiveName];
233
- if (!fixedMap?.[propertyName]) return;
228
+ const conditionBody = fixedConditionMap[primitiveName]?.[propertyName];
229
+ if (!conditionBody) return;
234
230
  if (!importedPrimitives.has(primitiveName)) return;
235
231
 
236
- path.value.name = j.jsxIdentifier("AuiIf");
237
- markAsChanged();
232
+ // Only transform if there are no props (other than children, which are implicit)
233
+ const attrs: any[] = path.value.openingElement.attributes || [];
234
+ if (attrs.length > 0) return;
235
+
236
+ convertElementToAuiIf(path, conditionBody);
238
237
  });
239
238
 
240
239
  // Process JSX elements: <ThreadPrimitive.If ...> → <AuiIf condition={...}>
241
- root.find(j.JSXOpeningElement).forEach((path: any) => {
242
- const name = path.value.name;
240
+ root.find(j.JSXElement).forEach((path: any) => {
241
+ const name = path.value.openingElement.name;
243
242
 
244
243
  // Check for `<XPrimitive.If ...>`
245
244
  if (!j.JSXMemberExpression.check(name)) return;
@@ -253,7 +252,7 @@ const migratePrimitiveIfToAuiIf = createTransformer(
253
252
  if (!importedPrimitives.has(primitiveName)) return;
254
253
 
255
254
  // Extract props
256
- const attrs: any[] = path.value.attributes || [];
255
+ const attrs: any[] = path.value.openingElement.attributes || [];
257
256
  const fragments: ConditionFragment[] = [];
258
257
  let hasUnknownProp = false;
259
258
 
@@ -265,7 +264,11 @@ const migratePrimitiveIfToAuiIf = createTransformer(
265
264
  }
266
265
  const propName =
267
266
  typeof attr.name.name === "string" ? attr.name.name : null;
268
- if (!propName) continue;
267
+ if (!propName) {
268
+ // e.g. JSXNamespacedName — not expressible as a condition
269
+ hasUnknownProp = true;
270
+ continue;
271
+ }
269
272
 
270
273
  const mapper = propMap[propName];
271
274
  if (!mapper) {
@@ -274,6 +277,10 @@ const migratePrimitiveIfToAuiIf = createTransformer(
274
277
  }
275
278
 
276
279
  const value = getAttrValue(j, attr);
280
+ if (value === UNSUPPORTED_VALUE) {
281
+ hasUnknownProp = true;
282
+ continue;
283
+ }
277
284
  const fragment = mapper(value);
278
285
  if (fragment) {
279
286
  fragments.push(fragment);
@@ -283,42 +290,7 @@ const migratePrimitiveIfToAuiIf = createTransformer(
283
290
  // If we couldn't map all props, skip this element
284
291
  if (hasUnknownProp || fragments.length === 0) return;
285
292
 
286
- const conditionBody = buildConditionString(fragments);
287
-
288
- // Parse the arrow function as an expression to get a proper AST node
289
- const arrowFnAst = j(`(s) => ${conditionBody}`)
290
- .find(j.ArrowFunctionExpression)
291
- .paths()[0]!.value;
292
-
293
- // Replace <XPrimitive.If ...> with <AuiIf condition={...}>
294
- path.value.name = j.jsxIdentifier("AuiIf");
295
-
296
- // Replace all attributes with a single condition prop
297
- path.value.attributes = [
298
- j.jsxAttribute(
299
- j.jsxIdentifier("condition"),
300
- j.jsxExpressionContainer(arrowFnAst),
301
- ),
302
- ];
303
-
304
- needsAuiIfImport = true;
305
- markAsChanged();
306
- });
307
-
308
- // Update closing elements to match
309
- root.find(j.JSXClosingElement).forEach((path: any) => {
310
- const name = path.value.name;
311
- if (!j.JSXMemberExpression.check(name)) return;
312
- if (!j.JSXIdentifier.check(name.object)) return;
313
- if (!j.JSXIdentifier.check(name.property)) return;
314
- if (name.property.name !== "If") return;
315
-
316
- const primitiveName = name.object.name;
317
- if (!primitiveMap[primitiveName]) return;
318
- if (!importedPrimitives.has(primitiveName)) return;
319
-
320
- path.value.name = j.jsxIdentifier("AuiIf");
321
- markAsChanged();
293
+ convertElementToAuiIf(path, buildConditionString(fragments));
322
294
  });
323
295
 
324
296
  // Add AuiIf import if needed
@@ -5,13 +5,12 @@ import {
5
5
  getComponentsJsonStyle,
6
6
  resolveRegistryItemUrl,
7
7
  } from "../lib/utils/registry";
8
+ import { dlxCommand, resolvePackageManager } from "../lib/create-project";
9
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
8
10
  import {
9
- dlxCommand,
10
- resolvePackageManager,
11
- resolvePackageManagerForCwd,
12
11
  type PackageManagerName,
13
- } from "../lib/create-project";
14
- import { runSpawn, SpawnExitError } from "../lib/run-spawn";
12
+ resolvePackageManagerForCwd,
13
+ } from "../lib/utils/package-manager";
15
14
 
16
15
  export interface AddComponentsPlan {
17
16
  command: string;
@@ -92,6 +91,7 @@ export const add = new Command()
92
91
  try {
93
92
  await runSpawn(command, args, opts.cwd);
94
93
  } catch (error) {
94
+ if (error instanceof SpawnSignalError) throw error;
95
95
  if (error instanceof SpawnExitError) {
96
96
  logger.error(`Process exited with code ${error.code}`);
97
97
  process.exit(error.code);