assistant-ui 0.0.91 → 0.0.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -108
- package/dist/commands/add.d.ts +13 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +48 -32
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/create.d.ts +13 -7
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +83 -71
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +4 -33
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/create-project.d.ts +15 -1
- package/dist/lib/create-project.d.ts.map +1 -1
- package/dist/lib/create-project.js +93 -61
- package/dist/lib/create-project.js.map +1 -1
- package/dist/lib/run-spawn.d.ts +6 -0
- package/dist/lib/run-spawn.d.ts.map +1 -0
- package/dist/lib/run-spawn.js +26 -0
- package/dist/lib/run-spawn.js.map +1 -0
- package/package.json +2 -2
- package/plugin/skills/assistant-ui/SKILL.md +2 -2
- package/src/commands/add.ts +67 -30
- package/src/commands/create.ts +116 -84
- package/src/commands/init.ts +9 -42
- package/src/lib/create-project.ts +123 -74
- package/src/lib/run-spawn.ts +32 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { downloadTemplate } from "giget";
|
|
4
|
-
import { spawn } from "cross-spawn";
|
|
5
4
|
import { sync as globSync } from "glob";
|
|
6
5
|
import { detect } from "detect-package-manager";
|
|
7
6
|
import { logger } from "./utils/logger";
|
|
7
|
+
import { runSpawn, SpawnExitError } from "./run-spawn";
|
|
8
8
|
|
|
9
9
|
export type PackageManagerName = "npm" | "pnpm" | "yarn" | "bun";
|
|
10
10
|
|
|
@@ -27,6 +27,40 @@ export interface TransformOptions {
|
|
|
27
27
|
packageManager: PackageManagerName;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export type ProjectSource =
|
|
31
|
+
| {
|
|
32
|
+
kind: "github";
|
|
33
|
+
ref: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
kind: "local";
|
|
37
|
+
rootDir: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const LOCAL_PROJECT_ARTIFACT_DIRS: readonly string[] = [
|
|
41
|
+
"node_modules",
|
|
42
|
+
".next",
|
|
43
|
+
"dist",
|
|
44
|
+
"build",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES = LOCAL_PROJECT_ARTIFACT_DIRS.map(
|
|
48
|
+
(dir) => `**/${dir}/**`,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export function resolvePackageManager(opts: {
|
|
52
|
+
useNpm?: boolean;
|
|
53
|
+
usePnpm?: boolean;
|
|
54
|
+
useYarn?: boolean;
|
|
55
|
+
useBun?: boolean;
|
|
56
|
+
}): PackageManagerName | undefined {
|
|
57
|
+
if (opts.useNpm) return "npm";
|
|
58
|
+
if (opts.usePnpm) return "pnpm";
|
|
59
|
+
if (opts.useYarn) return "yarn";
|
|
60
|
+
if (opts.useBun) return "bun";
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
30
64
|
export async function resolveLatestReleaseRef(): Promise<string | undefined> {
|
|
31
65
|
try {
|
|
32
66
|
const res = await fetch(
|
|
@@ -89,6 +123,47 @@ export async function downloadProject(
|
|
|
89
123
|
}
|
|
90
124
|
}
|
|
91
125
|
|
|
126
|
+
function shouldCopyLocalProjectPath(src: string, projectDir: string): boolean {
|
|
127
|
+
const relative = path.relative(projectDir, src);
|
|
128
|
+
if (!relative) return true;
|
|
129
|
+
|
|
130
|
+
const segments = relative.split(path.sep);
|
|
131
|
+
return !segments.some((segment) =>
|
|
132
|
+
LOCAL_PROJECT_ARTIFACT_DIRS.includes(segment),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function scaffoldProject(
|
|
137
|
+
repoPath: string,
|
|
138
|
+
destDir: string,
|
|
139
|
+
source: ProjectSource,
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
if (source.kind === "github") {
|
|
142
|
+
await downloadProject(repoPath, destDir, source.ref);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const localProjectDir = path.resolve(source.rootDir, repoPath);
|
|
147
|
+
try {
|
|
148
|
+
fs.cpSync(localProjectDir, destDir, {
|
|
149
|
+
recursive: true,
|
|
150
|
+
force: true,
|
|
151
|
+
filter: (src) => shouldCopyLocalProjectPath(src, localProjectDir),
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const code =
|
|
155
|
+
error instanceof Error
|
|
156
|
+
? (error as NodeJS.ErrnoException).code
|
|
157
|
+
: undefined;
|
|
158
|
+
if (code === "ENOENT") {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Local project source does not exist: ${localProjectDir}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
92
167
|
function detectFromUserAgent(): PackageManagerName | undefined {
|
|
93
168
|
const ua = process.env.npm_config_user_agent;
|
|
94
169
|
if (!ua) return undefined;
|
|
@@ -99,15 +174,15 @@ function detectFromUserAgent(): PackageManagerName | undefined {
|
|
|
99
174
|
return undefined;
|
|
100
175
|
}
|
|
101
176
|
|
|
102
|
-
export async function
|
|
103
|
-
|
|
177
|
+
export async function resolvePackageManagerForCwd(
|
|
178
|
+
cwd: string,
|
|
104
179
|
packageManager?: PackageManagerName,
|
|
105
180
|
): Promise<PackageManagerName> {
|
|
106
181
|
if (packageManager) return packageManager;
|
|
107
182
|
const fromAgent = detectFromUserAgent();
|
|
108
183
|
if (fromAgent) return fromAgent;
|
|
109
184
|
try {
|
|
110
|
-
return await detect({ cwd
|
|
185
|
+
return await detect({ cwd });
|
|
111
186
|
} catch {
|
|
112
187
|
return "npm";
|
|
113
188
|
}
|
|
@@ -117,9 +192,8 @@ export async function transformProject(
|
|
|
117
192
|
projectDir: string,
|
|
118
193
|
opts: TransformOptions,
|
|
119
194
|
): Promise<void> {
|
|
120
|
-
// 1. Transform package.json (always)
|
|
121
195
|
logger.step("Transforming package.json...");
|
|
122
|
-
|
|
196
|
+
transformPackageJson(projectDir);
|
|
123
197
|
|
|
124
198
|
let assistantUI: string[] | undefined;
|
|
125
199
|
let shadcnUI: string[] | undefined;
|
|
@@ -127,20 +201,14 @@ export async function transformProject(
|
|
|
127
201
|
if (!opts.hasLocalComponents) {
|
|
128
202
|
logger.step("Transforming project files...");
|
|
129
203
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
scanRequiredComponents(projectDir),
|
|
135
|
-
]);
|
|
204
|
+
transformTsConfig(projectDir);
|
|
205
|
+
transformCssFiles(projectDir);
|
|
206
|
+
|
|
207
|
+
const components = scanRequiredComponents(projectDir);
|
|
136
208
|
assistantUI = components.assistantUI;
|
|
137
209
|
shadcnUI = components.shadcnUI;
|
|
138
|
-
|
|
139
|
-
// 5. Remove workspace components (after scan completes — scan reads these files)
|
|
140
|
-
await removeWorkspaceComponents(projectDir);
|
|
141
210
|
}
|
|
142
211
|
|
|
143
|
-
// 6. Install dependencies
|
|
144
212
|
const pm = opts.packageManager;
|
|
145
213
|
if (!opts.skipInstall) {
|
|
146
214
|
logger.step("Installing dependencies...");
|
|
@@ -148,14 +216,12 @@ export async function transformProject(
|
|
|
148
216
|
}
|
|
149
217
|
|
|
150
218
|
if (!opts.hasLocalComponents && shadcnUI && assistantUI) {
|
|
151
|
-
// 7. Install shadcn UI components
|
|
152
219
|
const allShadcn = shadcnUI.includes("utils")
|
|
153
220
|
? shadcnUI
|
|
154
221
|
: [...shadcnUI, "utils"];
|
|
155
222
|
logger.step(`Installing shadcn UI components: ${allShadcn.join(", ")}...`);
|
|
156
223
|
await installShadcnRegistry(projectDir, allShadcn, "shadcn components", pm);
|
|
157
224
|
|
|
158
|
-
// 8. Install assistant-ui components
|
|
159
225
|
if (assistantUI.length > 0) {
|
|
160
226
|
const auiComponents = assistantUI.map((c) => `@assistant-ui/${c}`);
|
|
161
227
|
logger.step(
|
|
@@ -166,11 +232,11 @@ export async function transformProject(
|
|
|
166
232
|
}
|
|
167
233
|
}
|
|
168
234
|
|
|
169
|
-
|
|
235
|
+
function transformPackageJson(projectDir: string): void {
|
|
170
236
|
const pkgPath = path.join(projectDir, "package.json");
|
|
171
237
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
172
238
|
|
|
173
|
-
// Remove @assistant-ui/
|
|
239
|
+
// Remove @assistant-ui/ui dependency
|
|
174
240
|
if (pkg.dependencies?.["@assistant-ui/ui"]) {
|
|
175
241
|
delete pkg.dependencies["@assistant-ui/ui"];
|
|
176
242
|
}
|
|
@@ -199,7 +265,7 @@ async function transformPackageJson(projectDir: string): Promise<void> {
|
|
|
199
265
|
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
200
266
|
}
|
|
201
267
|
|
|
202
|
-
|
|
268
|
+
function transformTsConfig(projectDir: string): void {
|
|
203
269
|
const tsconfigPath = path.join(projectDir, "tsconfig.json");
|
|
204
270
|
|
|
205
271
|
if (!fs.existsSync(tsconfigPath)) {
|
|
@@ -212,7 +278,9 @@ async function transformTsConfig(projectDir: string): Promise<void> {
|
|
|
212
278
|
// Remove workspace paths
|
|
213
279
|
if (tsconfig.compilerOptions?.paths) {
|
|
214
280
|
delete tsconfig.compilerOptions.paths["@/components/assistant-ui/*"];
|
|
281
|
+
delete tsconfig.compilerOptions.paths["@/components/icons/*"];
|
|
215
282
|
delete tsconfig.compilerOptions.paths["@/components/ui/*"];
|
|
283
|
+
delete tsconfig.compilerOptions.paths["@/hooks/*"];
|
|
216
284
|
delete tsconfig.compilerOptions.paths["@/lib/utils"];
|
|
217
285
|
delete tsconfig.compilerOptions.paths["@assistant-ui/ui/*"];
|
|
218
286
|
|
|
@@ -254,10 +322,10 @@ async function transformTsConfig(projectDir: string): Promise<void> {
|
|
|
254
322
|
fs.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
|
|
255
323
|
}
|
|
256
324
|
|
|
257
|
-
|
|
325
|
+
function transformCssFiles(projectDir: string): void {
|
|
258
326
|
const cssFiles = globSync("**/*.css", {
|
|
259
327
|
cwd: projectDir,
|
|
260
|
-
ignore:
|
|
328
|
+
ignore: LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES,
|
|
261
329
|
});
|
|
262
330
|
|
|
263
331
|
for (const file of cssFiles) {
|
|
@@ -284,12 +352,14 @@ interface RequiredComponents {
|
|
|
284
352
|
shadcnUI: string[];
|
|
285
353
|
}
|
|
286
354
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
355
|
+
function stripImportExtension(component: string): string {
|
|
356
|
+
return component.replace(/\.[cm]?[tj]sx?$/, "");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function scanRequiredComponents(projectDir: string): RequiredComponents {
|
|
290
360
|
const files = globSync("**/*.{ts,tsx}", {
|
|
291
361
|
cwd: projectDir,
|
|
292
|
-
ignore:
|
|
362
|
+
ignore: LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES,
|
|
293
363
|
});
|
|
294
364
|
|
|
295
365
|
const assistantUIComponents = new Set<string>();
|
|
@@ -303,12 +373,12 @@ async function scanRequiredComponents(
|
|
|
303
373
|
const assistantUIRegex =
|
|
304
374
|
/from\s+["']@\/components\/assistant-ui\/([^"']+)["']/g;
|
|
305
375
|
for (const match of content.matchAll(assistantUIRegex)) {
|
|
306
|
-
assistantUIComponents.add(match[1]!);
|
|
376
|
+
assistantUIComponents.add(stripImportExtension(match[1]!));
|
|
307
377
|
}
|
|
308
378
|
|
|
309
379
|
const uiRegex = /from\s+["']@\/components\/ui\/([^"']+)["']/g;
|
|
310
380
|
for (const match of content.matchAll(uiRegex)) {
|
|
311
|
-
shadcnUIComponents.add(match[1]!);
|
|
381
|
+
shadcnUIComponents.add(stripImportExtension(match[1]!));
|
|
312
382
|
}
|
|
313
383
|
} catch {
|
|
314
384
|
// Ignore files that cannot be read
|
|
@@ -321,35 +391,20 @@ async function scanRequiredComponents(
|
|
|
321
391
|
};
|
|
322
392
|
}
|
|
323
393
|
|
|
324
|
-
async function removeWorkspaceComponents(projectDir: string): Promise<void> {
|
|
325
|
-
const componentsDir = path.join(projectDir, "components", "assistant-ui");
|
|
326
|
-
fs.rmSync(componentsDir, { recursive: true, force: true });
|
|
327
|
-
}
|
|
328
|
-
|
|
329
394
|
async function installDependencies(
|
|
330
395
|
projectDir: string,
|
|
331
396
|
pm: PackageManagerName,
|
|
332
397
|
): Promise<void> {
|
|
333
398
|
const args = pm === "yarn" ? [] : ["install"];
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
child.on("close", (code) => {
|
|
346
|
-
if (code !== 0) {
|
|
347
|
-
reject(new Error(`${pm} install exited with code ${code}`));
|
|
348
|
-
} else {
|
|
349
|
-
resolve();
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
});
|
|
399
|
+
try {
|
|
400
|
+
await runSpawn(pm, args, projectDir);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (error instanceof SpawnExitError) {
|
|
403
|
+
throw new Error(`${pm} install exited with code ${error.code}`);
|
|
404
|
+
}
|
|
405
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
406
|
+
throw new Error(`Failed to install dependencies: ${message}`);
|
|
407
|
+
}
|
|
353
408
|
}
|
|
354
409
|
|
|
355
410
|
async function installShadcnRegistry(
|
|
@@ -359,27 +414,21 @@ async function installShadcnRegistry(
|
|
|
359
414
|
pm: PackageManagerName,
|
|
360
415
|
): Promise<void> {
|
|
361
416
|
const [cmd, dlxArgs] = dlxCommand(pm);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
[...dlxArgs, "shadcn@latest", "add", ...components, "--yes"],
|
|
366
|
-
{
|
|
367
|
-
cwd: projectDir,
|
|
368
|
-
stdio: "inherit",
|
|
369
|
-
},
|
|
370
|
-
);
|
|
417
|
+
// For npm, dlxArgs may already include `--yes` for npx auto-install.
|
|
418
|
+
// The trailing `--yes` is for shadcn's own confirmation prompt.
|
|
419
|
+
const addArgs = [...dlxArgs, "shadcn@latest", "add", ...components, "--yes"];
|
|
371
420
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
421
|
+
try {
|
|
422
|
+
await runSpawn(cmd, addArgs, projectDir);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (error instanceof SpawnExitError) {
|
|
425
|
+
logger.warn(
|
|
426
|
+
`shadcn exited with code ${error.code}. Run the following to retry:\n ${cmd} ${addArgs.slice(0, -1).join(" ")}`,
|
|
427
|
+
);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
375
430
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
`shadcn exited with code ${code}. Run the following to retry:\n ${cmd} ${[...dlxArgs, "shadcn@latest", "add", ...components].join(" ")}`,
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
resolve();
|
|
383
|
-
});
|
|
384
|
-
});
|
|
431
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
432
|
+
throw new Error(`Failed to install ${label}: ${message}`);
|
|
433
|
+
}
|
|
385
434
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { spawn } from "cross-spawn";
|
|
2
|
+
|
|
3
|
+
export class SpawnExitError extends Error {
|
|
4
|
+
code: number;
|
|
5
|
+
|
|
6
|
+
constructor(code: number) {
|
|
7
|
+
super(`Process exited with code ${code}`);
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function runSpawn(
|
|
13
|
+
command: string,
|
|
14
|
+
args: string[],
|
|
15
|
+
cwd?: string,
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const child = spawn(command, args, {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
cwd,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child.on("error", (error) => reject(error));
|
|
24
|
+
child.on("close", (code) => {
|
|
25
|
+
if (code !== 0) {
|
|
26
|
+
reject(new SpawnExitError(code || 1));
|
|
27
|
+
} else {
|
|
28
|
+
resolve();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|