assistant-ui 0.0.112 → 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
@@ -9,12 +9,17 @@ import {
9
9
  downloadProject,
10
10
  resolveLatestReleaseRef,
11
11
  resolvePackageManager,
12
- resolvePackageManagerForCwd,
13
12
  scaffoldProject,
14
13
  transformProject,
15
14
  type TransformResult,
16
15
  } from "../lib/create-project";
17
- import { runSpawn, SpawnExitError } from "../lib/run-spawn";
16
+ import {
17
+ hasActiveSpawn,
18
+ runSpawn,
19
+ SpawnExitError,
20
+ SpawnSignalError,
21
+ } from "../lib/run-spawn";
22
+ import { resolvePackageManagerForCwd } from "../lib/utils/package-manager";
18
23
  import {
19
24
  buildSkillsAddCommand,
20
25
  resolveSkillsInstall,
@@ -597,10 +602,31 @@ export const create = new Command()
597
602
  );
598
603
 
599
604
  // Clean up partial project directory on unexpected exit (e.g. Ctrl+C)
605
+ let cleanupArmed = true;
600
606
  const cleanupOnExit = () => {
607
+ if (!cleanupArmed) return;
608
+ cleanupArmed = false;
601
609
  fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
602
610
  };
611
+ const disarmCleanup = () => {
612
+ cleanupArmed = false;
613
+ process.removeListener("exit", cleanupOnExit);
614
+ process.removeListener("SIGINT", cleanupOnSignal);
615
+ process.removeListener("SIGTERM", cleanupOnSignal);
616
+ };
617
+ // Node emits no "exit" when a signal kills the process. An in-flight
618
+ // runSpawn forwards the signal itself, so the directory is removed on the
619
+ // error path once the child is reaped rather than while it is still writing.
620
+ const cleanupOnSignal = (signal: NodeJS.Signals) => {
621
+ if (hasActiveSpawn()) return;
622
+ cleanupOnExit();
623
+ disarmCleanup();
624
+ process.kill(process.pid, signal);
625
+ };
626
+
603
627
  process.once("exit", cleanupOnExit);
628
+ process.on("SIGINT", cleanupOnSignal);
629
+ process.on("SIGTERM", cleanupOnSignal);
604
630
 
605
631
  try {
606
632
  // 3. Resolve latest release ref (started before prompts)
@@ -655,7 +681,8 @@ export const create = new Command()
655
681
  });
656
682
  try {
657
683
  await runSpawn(skillsCmd, skillsArgs, absoluteProjectDir);
658
- } catch {
684
+ } catch (error) {
685
+ if (error instanceof SpawnSignalError) throw error;
659
686
  logger.warn(
660
687
  `Could not add assistant-ui agent skills. You can add them later with:\n ${skillsCmd} ${skillsArgs.join(" ")}`,
661
688
  );
@@ -663,12 +690,13 @@ export const create = new Command()
663
690
  }
664
691
  } catch (err) {
665
692
  // Clean up partially created project directory
666
- fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
693
+ cleanupOnExit();
694
+ disarmCleanup();
667
695
  throw err;
668
696
  }
669
697
 
670
698
  if (transformResult.registryInstallFailure) {
671
- process.removeListener("exit", cleanupOnExit);
699
+ disarmCleanup();
672
700
  logger.break();
673
701
  logger.error("Project created with missing components.");
674
702
  logger.info("Retry the component install with:");
@@ -696,14 +724,15 @@ export const create = new Command()
696
724
  ],
697
725
  absoluteProjectDir,
698
726
  );
699
- } catch {
727
+ } catch (error) {
728
+ if (error instanceof SpawnSignalError) throw error;
700
729
  logger.warn(
701
730
  `Preset application failed. You can retry manually with:\n ${dlxCmd} ${[...dlxArgs, "shadcn@latest", "add", presetUrl].join(" ")}`,
702
731
  );
703
732
  }
704
733
  }
705
734
 
706
- process.removeListener("exit", cleanupOnExit);
735
+ disarmCleanup();
707
736
 
708
737
  logger.break();
709
738
  logger.success("Project created successfully!");
@@ -736,6 +765,11 @@ export const create = new Command()
736
765
  logger.info(` # Set up your environment variables in ${envFile}`);
737
766
  logger.info(` ${runCmd} ${devScript}`);
738
767
  } catch (error) {
768
+ if (error instanceof SpawnSignalError) {
769
+ cleanupOnExit();
770
+ disarmCleanup();
771
+ throw error;
772
+ }
739
773
  if (error instanceof SpawnExitError) {
740
774
  logger.error(`Project creation failed with code ${error.code}`);
741
775
  process.exit(error.code);
@@ -26,6 +26,7 @@ const ASSISTANT_UI_PACKAGES = [
26
26
  "@assistant-ui/cloud-ai-sdk",
27
27
  // Adapters
28
28
  "@assistant-ui/eve",
29
+ "@assistant-ui/ai-sdk",
29
30
  "@assistant-ui/react-ai-sdk",
30
31
  "@assistant-ui/react-langgraph",
31
32
  "@assistant-ui/react-ag-ui",
@@ -1,13 +1,10 @@
1
1
  import { Command, Option } from "commander";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import {
5
- dlxCommand,
6
- resolvePackageManager,
7
- resolvePackageManagerForCwd,
8
- } from "../lib/create-project";
9
- import { runSpawn, SpawnExitError } from "../lib/run-spawn";
4
+ import { dlxCommand, resolvePackageManager } from "../lib/create-project";
5
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
10
6
  import { logger } from "../lib/utils/logger";
7
+ import { resolvePackageManagerForCwd } from "../lib/utils/package-manager";
11
8
  import {
12
9
  getComponentsJsonStyle,
13
10
  resolveQuickStartRegistryUrl,
@@ -135,6 +132,7 @@ export const init = new Command()
135
132
  logger.success("Project initialized successfully!");
136
133
  logger.info("You can now add more components with 'assistant-ui add'");
137
134
  } catch (error) {
135
+ if (error instanceof SpawnSignalError) throw error;
138
136
  if (error instanceof SpawnExitError) {
139
137
  logger.error(`Initialization failed with code ${error.code}`);
140
138
  process.exit(error.code);
@@ -2,8 +2,8 @@ import { Command } from "commander";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
5
- import { spawn } from "cross-spawn";
6
5
  import { logger } from "../lib/utils/logger";
6
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
7
7
  import * as p from "@clack/prompts";
8
8
 
9
9
  type MCPTarget =
@@ -152,9 +152,8 @@ async function installForTarget(target: MCPTarget): Promise<void> {
152
152
  logger.info("Installing MCP server for Claude Code...");
153
153
  logger.break();
154
154
 
155
- const child = spawn(
156
- "claude",
157
- [
155
+ try {
156
+ await runSpawn("claude", [
158
157
  "mcp",
159
158
  "add",
160
159
  "assistant-ui",
@@ -162,35 +161,26 @@ async function installForTarget(target: MCPTarget): Promise<void> {
162
161
  "npx",
163
162
  "-y",
164
163
  "@assistant-ui/mcp-docs-server",
165
- ],
166
- {
167
- stdio: "inherit",
168
- },
169
- );
170
-
171
- return new Promise((resolve, reject) => {
172
- child.on("error", (error) => {
164
+ ]);
165
+ } catch (error) {
166
+ if (error instanceof SpawnSignalError) throw error;
167
+ if (error instanceof SpawnExitError) {
168
+ logger.error(`Installation failed with code ${error.code}`);
169
+ } else if (error instanceof Error) {
173
170
  logger.error(`Failed to install: ${error.message}`);
174
171
  logger.info(
175
172
  "Make sure Claude Code CLI is installed: https://docs.anthropic.com/en/docs/claude-code",
176
173
  );
177
- reject(error);
178
- });
174
+ }
175
+ throw error;
176
+ }
179
177
 
180
- child.on("close", (code) => {
181
- if (code !== 0) {
182
- logger.error(`Installation failed with code ${code}`);
183
- reject(new Error(`Exit code ${code}`));
184
- } else {
185
- logger.break();
186
- logger.success("MCP server installed for Claude Code!");
187
- logger.info(
188
- "The server starts automatically. Try asking about assistant-ui!",
189
- );
190
- resolve();
191
- }
192
- });
193
- });
178
+ logger.break();
179
+ logger.success("MCP server installed for Claude Code!");
180
+ logger.info(
181
+ "The server starts automatically. Try asking about assistant-ui!",
182
+ );
183
+ return;
194
184
  }
195
185
 
196
186
  if (target === "claude-desktop" && process.platform === "linux") {
@@ -288,7 +278,8 @@ export const mcp = new Command()
288
278
  for (const target of targets) {
289
279
  try {
290
280
  await installForTarget(target);
291
- } catch {
281
+ } catch (error) {
282
+ if (error instanceof SpawnSignalError) throw error;
292
283
  process.exit(1);
293
284
  }
294
285
  }
@@ -1,10 +1,10 @@
1
1
  import { Command } from "commander";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { sync as spawnSync } from "cross-spawn";
5
4
  import { logger } from "../lib/utils/logger";
6
5
  import { logPackageJsonParseError } from "../lib/utils/package-json";
7
6
  import { getInstallCommand } from "../lib/utils/package-manager";
7
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
8
8
 
9
9
  export const update = new Command()
10
10
  .name("update")
@@ -73,14 +73,12 @@ export const update = new Command()
73
73
  }
74
74
 
75
75
  logger.step("Updating packages...");
76
- const result = spawnSync(installCmd.command, installCmd.args, {
77
- stdio: "inherit",
78
- cwd: opts.cwd,
79
- });
80
-
81
- if (result.status !== 0) {
76
+ try {
77
+ await runSpawn(installCmd.command, installCmd.args, opts.cwd);
78
+ } catch (error) {
79
+ if (error instanceof SpawnSignalError) throw error;
82
80
  logger.error("Package manager update failed.");
83
- process.exit(result.status || 1);
81
+ process.exit(error instanceof SpawnExitError ? error.code : 1);
84
82
  }
85
83
 
86
84
  logger.break();
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { transform } from "../lib/transform";
3
3
  import { upgrade } from "../lib/upgrade";
4
+ import { SpawnSignalError } from "../lib/run-spawn";
4
5
  import debug from "debug";
5
6
 
6
7
  export interface TransformOptions {
@@ -30,10 +31,11 @@ export const codemodCommand = addTransformOptions(
30
31
  .description("CLI tool for running codemods")
31
32
  .argument("<codemod>", "Codemod to run (e.g., rewrite-framework-imports)")
32
33
  .argument("<source>", "Path to source files or directory to transform"),
33
- ).action((codemod, source, options: TransformOptions) => {
34
+ ).action(async (codemod, source, options: TransformOptions) => {
34
35
  try {
35
- transform(codemod, source, options);
36
+ await transform(codemod, source, options);
36
37
  } catch (err) {
38
+ if (err instanceof SpawnSignalError) throw err;
37
39
  const errorMessage = err instanceof Error ? err.message : String(err);
38
40
  const errorStack = err instanceof Error ? err.stack : undefined;
39
41
  error(`Error transforming: ${errorMessage}`);
@@ -52,6 +54,7 @@ export const upgradeCommand = addTransformOptions(
52
54
  try {
53
55
  await upgrade(options);
54
56
  } catch (err) {
57
+ if (err instanceof SpawnSignalError) throw err;
55
58
  const errorMessage = err instanceof Error ? err.message : String(err);
56
59
  const errorStack = err instanceof Error ? err.stack : undefined;
57
60
  error(`Error upgrading: ${errorMessage}`);
package/src/index.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { handleCliError } from "./lib/handle-cli-error";
3
4
  import { runCli } from "./run";
4
5
 
5
- process.on("SIGINT", () => process.exit(0));
6
- process.on("SIGTERM", () => process.exit(0));
7
-
8
- void runCli().catch((error: unknown) => {
9
- console.error(error);
10
- process.exitCode = 1;
11
- });
6
+ void runCli().catch(handleCliError);
@@ -1,4 +1,5 @@
1
- import { dlxCommand, type PackageManagerName } from "./create-project";
1
+ import { dlxCommand } from "./create-project";
2
+ import { type PackageManagerName } from "./utils/package-manager";
2
3
 
3
4
  export const SKILLS_PACKAGE = "assistant-ui/skills";
4
5
 
@@ -1,17 +1,15 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { downloadTemplate } from "giget";
4
- import { sync as globSync } from "glob";
5
- import { detect } from "detect-package-manager";
6
4
  import {
7
5
  parse as parseJsonc,
8
6
  printParseErrorCode,
9
7
  type ParseError,
10
8
  } from "jsonc-parser";
11
9
  import { logger } from "./utils/logger";
12
- import { runSpawn, SpawnExitError } from "./run-spawn";
13
-
14
- export type PackageManagerName = "npm" | "pnpm" | "yarn" | "bun";
10
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "./run-spawn";
11
+ import { type PackageManagerName } from "./utils/package-manager";
12
+ import { readProjectFiles } from "./utils/file-scanner";
15
13
 
16
14
  export function dlxCommand(pm: PackageManagerName): [string, string[]] {
17
15
  switch (pm) {
@@ -186,30 +184,6 @@ export async function scaffoldProject(
186
184
  }
187
185
  }
188
186
 
189
- function detectFromUserAgent(): PackageManagerName | undefined {
190
- const ua = process.env.npm_config_user_agent;
191
- if (!ua) return undefined;
192
- if (ua.startsWith("bun/")) return "bun";
193
- if (ua.startsWith("pnpm/")) return "pnpm";
194
- if (ua.startsWith("yarn/")) return "yarn";
195
- if (ua.startsWith("npm/")) return "npm";
196
- return undefined;
197
- }
198
-
199
- export async function resolvePackageManagerForCwd(
200
- cwd: string,
201
- packageManager?: PackageManagerName,
202
- ): Promise<PackageManagerName> {
203
- if (packageManager) return packageManager;
204
- const fromAgent = detectFromUserAgent();
205
- if (fromAgent) return fromAgent;
206
- try {
207
- return await detect({ cwd });
208
- } catch {
209
- return "npm";
210
- }
211
- }
212
-
213
187
  export interface TransformResult {
214
188
  registryInstallFailure?: { retryCommand: string };
215
189
  }
@@ -383,16 +357,11 @@ function transformTsConfig(projectDir: string): void {
383
357
  }
384
358
 
385
359
  function transformCssFiles(projectDir: string): void {
386
- const cssFiles = globSync("**/*.css", {
360
+ for (const { fullPath, content } of readProjectFiles("**/*.css", {
387
361
  cwd: projectDir,
388
362
  ignore: LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES,
389
- });
390
-
391
- for (const file of cssFiles) {
392
- const fullPath = path.join(projectDir, file);
363
+ })) {
393
364
  try {
394
- const content = fs.readFileSync(fullPath, "utf-8");
395
-
396
365
  const newContent = content.replace(
397
366
  /@source\s+["'][^"']*packages\/ui\/src[^"']*["'];\s*\n?/g,
398
367
  "",
@@ -402,7 +371,7 @@ function transformCssFiles(projectDir: string): void {
402
371
  fs.writeFileSync(fullPath, newContent);
403
372
  }
404
373
  } catch {
405
- // Ignore files that cannot be read/written
374
+ continue;
406
375
  }
407
376
  }
408
377
  }
@@ -417,31 +386,22 @@ function stripImportExtension(component: string): string {
417
386
  }
418
387
 
419
388
  function scanRequiredComponents(projectDir: string): RequiredComponents {
420
- const files = globSync("**/*.{ts,tsx}", {
421
- cwd: projectDir,
422
- ignore: LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES,
423
- });
424
-
425
389
  const assistantUIComponents = new Set<string>();
426
390
  const shadcnUIComponents = new Set<string>();
427
391
 
428
- for (const file of files) {
429
- const fullPath = path.join(projectDir, file);
430
- try {
431
- const content = fs.readFileSync(fullPath, "utf-8");
432
-
433
- const assistantUIRegex =
434
- /from\s+["']@\/components\/assistant-ui\/([^"']+)["']/g;
435
- for (const match of content.matchAll(assistantUIRegex)) {
436
- assistantUIComponents.add(stripImportExtension(match[1]!));
437
- }
392
+ for (const { content } of readProjectFiles("**/*.{ts,tsx}", {
393
+ cwd: projectDir,
394
+ ignore: LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES,
395
+ })) {
396
+ const assistantUIRegex =
397
+ /from\s+["']@\/components\/assistant-ui\/([^"']+)["']/g;
398
+ for (const match of content.matchAll(assistantUIRegex)) {
399
+ assistantUIComponents.add(stripImportExtension(match[1]!));
400
+ }
438
401
 
439
- const uiRegex = /from\s+["']@\/components\/ui\/([^"']+)["']/g;
440
- for (const match of content.matchAll(uiRegex)) {
441
- shadcnUIComponents.add(stripImportExtension(match[1]!));
442
- }
443
- } catch {
444
- // Ignore files that cannot be read
402
+ const uiRegex = /from\s+["']@\/components\/ui\/([^"']+)["']/g;
403
+ for (const match of content.matchAll(uiRegex)) {
404
+ shadcnUIComponents.add(stripImportExtension(match[1]!));
445
405
  }
446
406
  }
447
407
 
@@ -459,6 +419,9 @@ async function installDependencies(
459
419
  try {
460
420
  await runSpawn(pm, args, projectDir);
461
421
  } catch (error) {
422
+ if (error instanceof SpawnSignalError) {
423
+ throw error;
424
+ }
462
425
  if (error instanceof SpawnExitError) {
463
426
  throw new Error(`${pm} install exited with code ${error.code}`);
464
427
  }
@@ -483,6 +446,9 @@ async function installShadcnRegistry(
483
446
  await runSpawn(cmd, addArgs, projectDir);
484
447
  return undefined;
485
448
  } catch (error) {
449
+ if (error instanceof SpawnSignalError) {
450
+ throw error;
451
+ }
486
452
  if (error instanceof SpawnExitError) {
487
453
  logger.warn(`shadcn exited with code ${error.code}.`);
488
454
  return { retryCommand: `${cmd} ${retryArgs.join(" ")}` };
@@ -0,0 +1,67 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { handleCliError } from "./handle-cli-error";
4
+ import { SpawnExitError, SpawnSignalError } from "./run-spawn";
5
+
6
+ describe("handleCliError", () => {
7
+ afterEach(() => {
8
+ vi.restoreAllMocks();
9
+ process.exitCode = undefined;
10
+ });
11
+
12
+ it("exits by the forwarded signal's status and re-raises it", () => {
13
+ const kill = vi.spyOn(process, "kill").mockReturnValue(true);
14
+
15
+ handleCliError(new SpawnSignalError("SIGINT", true));
16
+
17
+ expect(process.exitCode).toBe(130);
18
+ expect(kill).toHaveBeenCalledWith(process.pid, "SIGINT");
19
+ });
20
+
21
+ it("maps SIGTERM to 143", () => {
22
+ const kill = vi.spyOn(process, "kill").mockReturnValue(true);
23
+
24
+ handleCliError(new SpawnSignalError("SIGTERM", true));
25
+
26
+ expect(process.exitCode).toBe(143);
27
+ expect(kill).toHaveBeenCalledWith(process.pid, "SIGTERM");
28
+ });
29
+
30
+ it("reports a signal the child raised on itself without re-raising it", () => {
31
+ const consoleError = vi
32
+ .spyOn(console, "error")
33
+ .mockImplementation(() => {});
34
+ const kill = vi.spyOn(process, "kill").mockReturnValue(true);
35
+
36
+ handleCliError(new SpawnSignalError("SIGKILL", false));
37
+
38
+ expect(process.exitCode).toBe(137);
39
+ expect(kill).not.toHaveBeenCalled();
40
+ expect(consoleError).toHaveBeenCalledWith("Process terminated by SIGKILL");
41
+ });
42
+
43
+ it("stays silent on a signal the user sent", () => {
44
+ const consoleError = vi
45
+ .spyOn(console, "error")
46
+ .mockImplementation(() => {});
47
+ vi.spyOn(process, "kill").mockReturnValue(true);
48
+
49
+ handleCliError(new SpawnSignalError("SIGINT", true));
50
+
51
+ expect(consoleError).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it("reports any other failure as exit 1", () => {
55
+ const consoleError = vi
56
+ .spyOn(console, "error")
57
+ .mockImplementation(() => {});
58
+ const kill = vi.spyOn(process, "kill").mockReturnValue(true);
59
+ const error = new SpawnExitError(7);
60
+
61
+ handleCliError(error);
62
+
63
+ expect(process.exitCode).toBe(1);
64
+ expect(kill).not.toHaveBeenCalled();
65
+ expect(consoleError).toHaveBeenCalledWith(error);
66
+ });
67
+ });
@@ -0,0 +1,17 @@
1
+ import { constants } from "node:os";
2
+ import { SpawnSignalError } from "./run-spawn";
3
+
4
+ export function handleCliError(error: unknown): void {
5
+ if (error instanceof SpawnSignalError) {
6
+ process.exitCode = 128 + (constants.signals[error.signal] ?? 0);
7
+ if (error.forwarded) {
8
+ process.kill(process.pid, error.signal);
9
+ } else {
10
+ console.error(error.message);
11
+ }
12
+ return;
13
+ }
14
+
15
+ console.error(error);
16
+ process.exitCode = 1;
17
+ }
@@ -1,6 +1,18 @@
1
1
  import { installPackageIfNeeded } from "./utils/package-installer";
2
2
 
3
3
  export default async function installAiSdkLib(): Promise<void> {
4
+ await installPackageIfNeeded({
5
+ packageName: "@assistant-ui/ai-sdk",
6
+ importPatterns: ["@assistant-ui/ai-sdk"],
7
+ promptMessage:
8
+ "AI SDK imports were added but @assistant-ui/ai-sdk is not installed. Do you want to install it? (Y/n) ",
9
+ skipMessage:
10
+ "@assistant-ui/ai-sdk is already installed. Skipping installation.",
11
+ notFoundMessage: "No AI SDK imports found; skipping installation.",
12
+ });
13
+
14
+ // The previous name is a separate package, so an import of it needs that
15
+ // package installed; the neutral one would not make it resolvable.
4
16
  await installPackageIfNeeded({
5
17
  packageName: "@assistant-ui/react-ai-sdk",
6
18
  importPatterns: ["@assistant-ui/react-ai-sdk"],
@@ -8,6 +20,6 @@ export default async function installAiSdkLib(): Promise<void> {
8
20
  "AI SDK imports were added but @assistant-ui/react-ai-sdk is not installed. Do you want to install it? (Y/n) ",
9
21
  skipMessage:
10
22
  "@assistant-ui/react-ai-sdk is already installed. Skipping installation.",
11
- notFoundMessage: "No AI SDK imports found; skipping installation.",
23
+ notFoundMessage: "No legacy AI SDK imports found; skipping installation.",
12
24
  });
13
25
  }
@@ -1,17 +1,16 @@
1
1
  import { installPackageIfNeeded } from "./utils/package-installer";
2
2
 
3
3
  export default async function installEdgeLib(): Promise<void> {
4
+ // Only the retired edge specifier is unambiguous here. `useChatRuntime` and
5
+ // the AI SDK package specifiers are shared by both AI SDK packages, so
6
+ // installAiSdkLib resolves those by the specifier the file actually imports.
4
7
  await installPackageIfNeeded({
5
- packageName: "@assistant-ui/react-ai-sdk",
6
- importPatterns: [
7
- "@assistant-ui/react-edge",
8
- "@assistant-ui/react-ai-sdk",
9
- "useChatRuntime",
10
- ],
8
+ packageName: "@assistant-ui/ai-sdk",
9
+ importPatterns: ["@assistant-ui/react-edge"],
11
10
  promptMessage:
12
- "Edge Runtime imports were detected but @assistant-ui/react-ai-sdk is not installed. Do you want to install it? (Y/n) ",
11
+ "Edge Runtime imports were detected but @assistant-ui/ai-sdk is not installed. Do you want to install it? (Y/n) ",
13
12
  skipMessage:
14
- "@assistant-ui/react-ai-sdk is already installed. Skipping installation.",
13
+ "@assistant-ui/ai-sdk is already installed. Skipping installation.",
15
14
  notFoundMessage: "No Edge Runtime imports found; skipping installation.",
16
15
  });
17
16
  }