assistant-ui 0.0.80 → 0.0.82
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 +1 -1
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +34 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/create.d.ts +13 -45
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +347 -102
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/create-project.d.ts +12 -0
- package/dist/lib/create-project.d.ts.map +1 -0
- package/dist/lib/create-project.js +273 -0
- package/dist/lib/create-project.js.map +1 -0
- package/package.json +7 -4
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/skills/assistant-ui/SKILL.md +167 -0
- package/src/commands/agent.ts +36 -0
- package/src/commands/create.ts +402 -129
- package/src/commands/init.ts +2 -2
- package/src/index.ts +2 -0
- package/src/lib/create-project.ts +367 -0
- package/dist/lib/create-from-example.d.ts +0 -9
- package/dist/lib/create-from-example.d.ts.map +0 -1
- package/dist/lib/create-from-example.js +0 -325
- package/dist/lib/create-from-example.js.map +0 -1
- package/src/lib/create-from-example.ts +0 -426
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { launch } from "@assistant-ui/agent-launcher";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
function getPluginPath(): string {
|
|
10
|
+
// In dist/, plugin is at ../../plugin relative to dist/commands/agent.js
|
|
11
|
+
// In dev (src/), plugin is at ../../plugin relative to src/commands/
|
|
12
|
+
const candidates = [
|
|
13
|
+
resolve(__dirname, "..", "..", "plugin"),
|
|
14
|
+
resolve(__dirname, "..", "plugin"),
|
|
15
|
+
];
|
|
16
|
+
for (const candidate of candidates) {
|
|
17
|
+
if (existsSync(candidate)) return candidate;
|
|
18
|
+
}
|
|
19
|
+
return candidates[0]!;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const agent = new Command()
|
|
23
|
+
.name("agent")
|
|
24
|
+
.description("launch Claude Code with assistant-ui skills")
|
|
25
|
+
.argument("<prompt...>", "prompt for the agent")
|
|
26
|
+
.option("--dry", "print the command instead of running it")
|
|
27
|
+
.action((promptParts: string[], opts) => {
|
|
28
|
+
const prompt = promptParts.join(" ");
|
|
29
|
+
|
|
30
|
+
launch({
|
|
31
|
+
pluginDir: getPluginPath(),
|
|
32
|
+
skillName: "assistant-ui",
|
|
33
|
+
prompt,
|
|
34
|
+
dry: opts.dry,
|
|
35
|
+
});
|
|
36
|
+
});
|
package/src/commands/create.ts
CHANGED
|
@@ -1,89 +1,288 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { spawn } from "cross-spawn";
|
|
4
|
+
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import * as p from "@clack/prompts";
|
|
6
7
|
import { logger } from "../lib/utils/logger";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
dlxCommand,
|
|
10
|
+
downloadProject,
|
|
11
|
+
resolveLatestReleaseRef,
|
|
12
|
+
resolvePackageManagerName,
|
|
13
|
+
transformProject,
|
|
14
|
+
type PackageManagerName,
|
|
15
|
+
} from "../lib/create-project";
|
|
16
|
+
|
|
17
|
+
export interface ProjectMetadata {
|
|
18
|
+
name: string;
|
|
19
|
+
label: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
category: "template" | "example";
|
|
22
|
+
path: string;
|
|
23
|
+
hasLocalComponents: boolean;
|
|
24
|
+
}
|
|
8
25
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
export const PROJECT_METADATA: ProjectMetadata[] = [
|
|
27
|
+
// Templates
|
|
28
|
+
{
|
|
29
|
+
name: "default",
|
|
13
30
|
label: "Default",
|
|
14
|
-
|
|
31
|
+
description: "Default template with Vercel AI SDK",
|
|
32
|
+
category: "template",
|
|
33
|
+
path: "templates/default",
|
|
34
|
+
hasLocalComponents: true,
|
|
15
35
|
},
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
{
|
|
37
|
+
name: "minimal",
|
|
18
38
|
label: "Minimal",
|
|
19
|
-
|
|
39
|
+
description: "Bare-bones starting point",
|
|
40
|
+
category: "template",
|
|
41
|
+
path: "templates/minimal",
|
|
42
|
+
hasLocalComponents: true,
|
|
20
43
|
},
|
|
21
|
-
|
|
22
|
-
|
|
44
|
+
{
|
|
45
|
+
name: "cloud",
|
|
23
46
|
label: "Cloud",
|
|
24
|
-
|
|
47
|
+
description: "Cloud-backed persistence starter",
|
|
48
|
+
category: "template",
|
|
49
|
+
path: "templates/cloud",
|
|
50
|
+
hasLocalComponents: true,
|
|
25
51
|
},
|
|
26
|
-
|
|
27
|
-
|
|
52
|
+
{
|
|
53
|
+
name: "cloud-clerk",
|
|
28
54
|
label: "Cloud + Clerk",
|
|
29
|
-
|
|
55
|
+
description: "Cloud-backed starter with Clerk auth",
|
|
56
|
+
category: "template",
|
|
57
|
+
path: "templates/cloud-clerk",
|
|
58
|
+
hasLocalComponents: true,
|
|
30
59
|
},
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
{
|
|
61
|
+
name: "langgraph",
|
|
33
62
|
label: "LangGraph",
|
|
34
|
-
|
|
63
|
+
description: "LangGraph starter template",
|
|
64
|
+
category: "template",
|
|
65
|
+
path: "templates/langgraph",
|
|
66
|
+
hasLocalComponents: true,
|
|
35
67
|
},
|
|
36
|
-
|
|
37
|
-
|
|
68
|
+
{
|
|
69
|
+
name: "mcp",
|
|
38
70
|
label: "MCP",
|
|
39
|
-
|
|
71
|
+
description: "MCP starter template",
|
|
72
|
+
category: "template",
|
|
73
|
+
path: "templates/mcp",
|
|
74
|
+
hasLocalComponents: true,
|
|
75
|
+
},
|
|
76
|
+
// Examples
|
|
77
|
+
{
|
|
78
|
+
name: "with-ag-ui",
|
|
79
|
+
label: "AG-UI",
|
|
80
|
+
description: "AG-UI protocol integration",
|
|
81
|
+
category: "example",
|
|
82
|
+
path: "examples/with-ag-ui",
|
|
83
|
+
hasLocalComponents: false,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "with-ai-sdk-v6",
|
|
87
|
+
label: "AI SDK v6",
|
|
88
|
+
description: "Vercel AI SDK v6",
|
|
89
|
+
category: "example",
|
|
90
|
+
path: "examples/with-ai-sdk-v6",
|
|
91
|
+
hasLocalComponents: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "with-artifacts",
|
|
95
|
+
label: "Artifacts",
|
|
96
|
+
description: "Artifact rendering",
|
|
97
|
+
category: "example",
|
|
98
|
+
path: "examples/with-artifacts",
|
|
99
|
+
hasLocalComponents: false,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "with-assistant-transport",
|
|
103
|
+
label: "Assistant Transport",
|
|
104
|
+
description: "Assistant transport protocol",
|
|
105
|
+
category: "example",
|
|
106
|
+
path: "examples/with-assistant-transport",
|
|
107
|
+
hasLocalComponents: false,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "with-chain-of-thought",
|
|
111
|
+
label: "Chain of Thought",
|
|
112
|
+
description: "Chain-of-thought rendering",
|
|
113
|
+
category: "example",
|
|
114
|
+
path: "examples/with-chain-of-thought",
|
|
115
|
+
hasLocalComponents: false,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "with-cloud",
|
|
119
|
+
label: "Cloud Example",
|
|
120
|
+
description: "Cloud integration example",
|
|
121
|
+
category: "example",
|
|
122
|
+
path: "examples/with-cloud",
|
|
123
|
+
hasLocalComponents: false,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "with-custom-thread-list",
|
|
127
|
+
label: "Custom Thread List",
|
|
128
|
+
description: "Custom thread list UI",
|
|
129
|
+
category: "example",
|
|
130
|
+
path: "examples/with-custom-thread-list",
|
|
131
|
+
hasLocalComponents: false,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "with-elevenlabs-scribe",
|
|
135
|
+
label: "ElevenLabs Scribe",
|
|
136
|
+
description: "Audio/speech integration",
|
|
137
|
+
category: "example",
|
|
138
|
+
path: "examples/with-elevenlabs-scribe",
|
|
139
|
+
hasLocalComponents: false,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "with-external-store",
|
|
143
|
+
label: "External Store",
|
|
144
|
+
description: "Custom message store",
|
|
145
|
+
category: "example",
|
|
146
|
+
path: "examples/with-external-store",
|
|
147
|
+
hasLocalComponents: false,
|
|
40
148
|
},
|
|
41
|
-
|
|
149
|
+
{
|
|
150
|
+
name: "with-ffmpeg",
|
|
151
|
+
label: "FFmpeg",
|
|
152
|
+
description: "File processing",
|
|
153
|
+
category: "example",
|
|
154
|
+
path: "examples/with-ffmpeg",
|
|
155
|
+
hasLocalComponents: false,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "with-langgraph",
|
|
159
|
+
label: "LangGraph Example",
|
|
160
|
+
description: "LangGraph integration",
|
|
161
|
+
category: "example",
|
|
162
|
+
path: "examples/with-langgraph",
|
|
163
|
+
hasLocalComponents: false,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "with-parent-id-grouping",
|
|
167
|
+
label: "Parent ID Grouping",
|
|
168
|
+
description: "Message grouping strategy",
|
|
169
|
+
category: "example",
|
|
170
|
+
path: "examples/with-parent-id-grouping",
|
|
171
|
+
hasLocalComponents: false,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "with-react-hook-form",
|
|
175
|
+
label: "React Hook Form",
|
|
176
|
+
description: "Form integration",
|
|
177
|
+
category: "example",
|
|
178
|
+
path: "examples/with-react-hook-form",
|
|
179
|
+
hasLocalComponents: false,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "with-react-router",
|
|
183
|
+
label: "React Router",
|
|
184
|
+
description: "React Router v7 + Vite",
|
|
185
|
+
category: "example",
|
|
186
|
+
path: "examples/with-react-router",
|
|
187
|
+
hasLocalComponents: false,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "with-tanstack",
|
|
191
|
+
label: "TanStack",
|
|
192
|
+
description: "TanStack/React Router + Vite",
|
|
193
|
+
category: "example",
|
|
194
|
+
path: "examples/with-tanstack",
|
|
195
|
+
hasLocalComponents: false,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
42
198
|
|
|
43
|
-
|
|
44
|
-
|
|
199
|
+
const templateNames = PROJECT_METADATA.filter(
|
|
200
|
+
(m) => m.category === "template",
|
|
201
|
+
).map((m) => m.name);
|
|
45
202
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
value: name,
|
|
52
|
-
label: templates[name].label,
|
|
53
|
-
hint: templates[name].hint,
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
export async function resolveCreateTemplateName(params: {
|
|
203
|
+
const exampleNames = PROJECT_METADATA.filter(
|
|
204
|
+
(m) => m.category === "example",
|
|
205
|
+
).map((m) => m.name);
|
|
206
|
+
|
|
207
|
+
export async function resolveProject(params: {
|
|
57
208
|
template?: string;
|
|
209
|
+
example?: string;
|
|
58
210
|
stdinIsTTY?: boolean;
|
|
59
211
|
select?: typeof p.select;
|
|
60
212
|
isCancel?: typeof p.isCancel;
|
|
61
|
-
}): Promise<
|
|
213
|
+
}): Promise<ProjectMetadata | null> {
|
|
62
214
|
const {
|
|
63
215
|
template,
|
|
216
|
+
example,
|
|
64
217
|
stdinIsTTY = process.stdin.isTTY,
|
|
65
218
|
select = p.select,
|
|
66
219
|
isCancel = p.isCancel,
|
|
67
220
|
} = params;
|
|
68
221
|
|
|
69
222
|
if (template) {
|
|
70
|
-
|
|
223
|
+
const meta = PROJECT_METADATA.find(
|
|
224
|
+
(m) => m.name === template && m.category === "template",
|
|
225
|
+
);
|
|
226
|
+
if (!meta) {
|
|
227
|
+
logger.error(`Unknown template: ${template}`);
|
|
228
|
+
logger.info(`Available templates: ${templateNames.join(", ")}`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
return meta;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (example) {
|
|
235
|
+
const meta = PROJECT_METADATA.find(
|
|
236
|
+
(m) => m.name === example && m.category === "example",
|
|
237
|
+
);
|
|
238
|
+
if (!meta) {
|
|
239
|
+
logger.error(`Unknown example: ${example}`);
|
|
240
|
+
logger.info(`Available examples: ${exampleNames.join(", ")}`);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
return meta;
|
|
71
244
|
}
|
|
72
245
|
|
|
73
246
|
if (!stdinIsTTY) {
|
|
74
|
-
return "default"
|
|
247
|
+
return PROJECT_METADATA.find((m) => m.name === "default")!;
|
|
75
248
|
}
|
|
76
249
|
|
|
77
250
|
const selected = await select({
|
|
78
|
-
message: "Select a
|
|
79
|
-
options:
|
|
251
|
+
message: "Select a project to scaffold:",
|
|
252
|
+
options: [
|
|
253
|
+
{
|
|
254
|
+
value: "_separator",
|
|
255
|
+
label: "────── Starter Templates ──────",
|
|
256
|
+
disabled: true,
|
|
257
|
+
},
|
|
258
|
+
...PROJECT_METADATA.filter((m) => m.category === "template").map((m) => ({
|
|
259
|
+
value: m.name,
|
|
260
|
+
label: m.label,
|
|
261
|
+
...(m.description ? { hint: m.description } : {}),
|
|
262
|
+
})),
|
|
263
|
+
{
|
|
264
|
+
value: "_separator",
|
|
265
|
+
label: "────── Feature Examples ──────",
|
|
266
|
+
disabled: true,
|
|
267
|
+
},
|
|
268
|
+
...PROJECT_METADATA.filter((m) => m.category === "example").map((m) => ({
|
|
269
|
+
value: m.name,
|
|
270
|
+
label: m.label,
|
|
271
|
+
...(m.description ? { hint: m.description } : {}),
|
|
272
|
+
})),
|
|
273
|
+
],
|
|
80
274
|
});
|
|
81
275
|
|
|
82
276
|
if (isCancel(selected)) {
|
|
83
277
|
return null;
|
|
84
278
|
}
|
|
85
279
|
|
|
86
|
-
|
|
280
|
+
const meta = PROJECT_METADATA.find((m) => m.name === selected);
|
|
281
|
+
if (!meta) {
|
|
282
|
+
logger.error(`Unknown selection: ${String(selected)}`);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
return meta;
|
|
87
286
|
}
|
|
88
287
|
|
|
89
288
|
class SpawnExitError extends Error {
|
|
@@ -117,37 +316,6 @@ async function runSpawn(
|
|
|
117
316
|
});
|
|
118
317
|
}
|
|
119
318
|
|
|
120
|
-
export function buildCreateNextAppArgs(params: {
|
|
121
|
-
projectDirectory?: string;
|
|
122
|
-
useNpm?: boolean;
|
|
123
|
-
usePnpm?: boolean;
|
|
124
|
-
useYarn?: boolean;
|
|
125
|
-
useBun?: boolean;
|
|
126
|
-
skipInstall?: boolean;
|
|
127
|
-
templateUrl: string;
|
|
128
|
-
}): string[] {
|
|
129
|
-
const {
|
|
130
|
-
projectDirectory,
|
|
131
|
-
useNpm,
|
|
132
|
-
usePnpm,
|
|
133
|
-
useYarn,
|
|
134
|
-
useBun,
|
|
135
|
-
skipInstall,
|
|
136
|
-
templateUrl,
|
|
137
|
-
} = params;
|
|
138
|
-
|
|
139
|
-
const args = ["create-next-app@latest"];
|
|
140
|
-
if (projectDirectory) args.push(projectDirectory);
|
|
141
|
-
if (useNpm) args.push("--use-npm");
|
|
142
|
-
if (usePnpm) args.push("--use-pnpm");
|
|
143
|
-
if (useYarn) args.push("--use-yarn");
|
|
144
|
-
if (useBun) args.push("--use-bun");
|
|
145
|
-
if (skipInstall) args.push("--skip-install");
|
|
146
|
-
|
|
147
|
-
args.push("-e", templateUrl);
|
|
148
|
-
return args;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
319
|
export function resolveCreateProjectDirectory(params: {
|
|
152
320
|
projectDirectory?: string;
|
|
153
321
|
stdinIsTTY?: boolean;
|
|
@@ -159,8 +327,27 @@ export function resolveCreateProjectDirectory(params: {
|
|
|
159
327
|
return undefined;
|
|
160
328
|
}
|
|
161
329
|
|
|
162
|
-
function
|
|
163
|
-
|
|
330
|
+
function resolvePackageManager(opts: {
|
|
331
|
+
useNpm?: boolean;
|
|
332
|
+
usePnpm?: boolean;
|
|
333
|
+
useYarn?: boolean;
|
|
334
|
+
useBun?: boolean;
|
|
335
|
+
}): PackageManagerName | undefined {
|
|
336
|
+
if (opts.useNpm) return "npm";
|
|
337
|
+
if (opts.usePnpm) return "pnpm";
|
|
338
|
+
if (opts.useYarn) return "yarn";
|
|
339
|
+
if (opts.useBun) return "bun";
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const PLAYGROUND_PRESET_BASE_URL =
|
|
344
|
+
"https://www.assistant-ui.com/playground/init";
|
|
345
|
+
|
|
346
|
+
export function resolvePresetUrl(preset: string): string {
|
|
347
|
+
if (preset.startsWith("http://") || preset.startsWith("https://")) {
|
|
348
|
+
return preset;
|
|
349
|
+
}
|
|
350
|
+
return `${PLAYGROUND_PRESET_BASE_URL}?preset=${encodeURIComponent(preset)}`;
|
|
164
351
|
}
|
|
165
352
|
|
|
166
353
|
export const create = new Command()
|
|
@@ -174,11 +361,11 @@ export const create = new Command()
|
|
|
174
361
|
)
|
|
175
362
|
.option(
|
|
176
363
|
"-e, --example <example>",
|
|
177
|
-
|
|
364
|
+
`create from an example (${exampleNames.join(", ")})`,
|
|
178
365
|
)
|
|
179
366
|
.option(
|
|
180
|
-
"-p, --preset <url>",
|
|
181
|
-
"preset
|
|
367
|
+
"-p, --preset <name-or-url>",
|
|
368
|
+
"preset name or URL (e.g., chatgpt or https://www.assistant-ui.com/playground/init?preset=chatgpt)",
|
|
182
369
|
)
|
|
183
370
|
.option("--use-npm", "explicitly use npm")
|
|
184
371
|
.option("--use-pnpm", "explicitly use pnpm")
|
|
@@ -186,88 +373,174 @@ export const create = new Command()
|
|
|
186
373
|
.option("--use-bun", "explicitly use bun")
|
|
187
374
|
.option("--skip-install", "skip installing packages")
|
|
188
375
|
.action(async (projectDirectory, opts) => {
|
|
189
|
-
const resolvedProjectDirectory = resolveCreateProjectDirectory({
|
|
190
|
-
projectDirectory,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
376
|
if (opts.example && opts.preset) {
|
|
194
377
|
logger.error("Cannot use --preset with --example.");
|
|
195
378
|
process.exit(1);
|
|
196
379
|
}
|
|
197
380
|
|
|
198
|
-
if (opts.
|
|
199
|
-
logger.error("
|
|
381
|
+
if (opts.template && opts.example) {
|
|
382
|
+
logger.error("Cannot use both --template and --example.");
|
|
200
383
|
process.exit(1);
|
|
201
384
|
}
|
|
202
385
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
386
|
+
// Start release ref resolution early (runs during user prompts)
|
|
387
|
+
const refPromise = resolveLatestReleaseRef();
|
|
388
|
+
|
|
389
|
+
// 1. Resolve project directory
|
|
390
|
+
let resolvedProjectDirectory = resolveCreateProjectDirectory({
|
|
391
|
+
projectDirectory,
|
|
392
|
+
});
|
|
209
393
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
394
|
+
if (!resolvedProjectDirectory) {
|
|
395
|
+
const result = await p.text({
|
|
396
|
+
message: "Project name:",
|
|
397
|
+
placeholder: "my-aui-app",
|
|
398
|
+
defaultValue: "my-aui-app",
|
|
399
|
+
validate: (value?: string) => {
|
|
400
|
+
const name = (value ?? "").trim();
|
|
401
|
+
if (!name) return "Project name cannot be empty";
|
|
402
|
+
if (name === "." || name === "..")
|
|
403
|
+
return "Project name cannot be . or ..";
|
|
404
|
+
if (name.includes("/") || name.includes("\\"))
|
|
405
|
+
return "Project name cannot contain path separators";
|
|
406
|
+
return undefined;
|
|
407
|
+
},
|
|
216
408
|
});
|
|
217
|
-
|
|
409
|
+
|
|
410
|
+
if (p.isCancel(result)) {
|
|
411
|
+
p.cancel("Project creation cancelled.");
|
|
412
|
+
process.exit(0);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
resolvedProjectDirectory = result;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Check directory
|
|
419
|
+
const absoluteProjectDir = path.resolve(resolvedProjectDirectory);
|
|
420
|
+
try {
|
|
421
|
+
const files = fs.readdirSync(absoluteProjectDir);
|
|
422
|
+
if (files.length > 0) {
|
|
423
|
+
logger.error(
|
|
424
|
+
`Directory ${resolvedProjectDirectory} already exists and is not empty`,
|
|
425
|
+
);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
} catch (err: any) {
|
|
429
|
+
if (err.code === "ENOENT") {
|
|
430
|
+
// Directory doesn't exist — good, proceed
|
|
431
|
+
} else if (err.code === "ENOTDIR") {
|
|
432
|
+
logger.error(
|
|
433
|
+
`${resolvedProjectDirectory} already exists and is not a directory`,
|
|
434
|
+
);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
} else {
|
|
437
|
+
logger.error(
|
|
438
|
+
`Cannot access ${resolvedProjectDirectory}: ${err.message}`,
|
|
439
|
+
);
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
218
442
|
}
|
|
219
443
|
|
|
220
|
-
//
|
|
221
|
-
const
|
|
444
|
+
// 2. Resolve scaffold target
|
|
445
|
+
const project = await resolveProject({
|
|
222
446
|
template: opts.template,
|
|
447
|
+
example: opts.example,
|
|
223
448
|
});
|
|
224
|
-
if (!
|
|
449
|
+
if (!project) {
|
|
225
450
|
p.cancel("Project creation cancelled.");
|
|
226
451
|
process.exit(0);
|
|
227
452
|
}
|
|
228
453
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (!templateUrl) {
|
|
232
|
-
logger.error(`Unknown template: ${opts.template}`);
|
|
233
|
-
logger.info(`Available templates: ${templateNames.join(", ")}`);
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
logger.info(`Creating project with template: ${templateName}`);
|
|
454
|
+
logger.info(`Creating project from ${project.category}: ${project.label}`);
|
|
238
455
|
logger.break();
|
|
239
456
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
457
|
+
const pm = await resolvePackageManagerName(
|
|
458
|
+
absoluteProjectDir,
|
|
459
|
+
resolvePackageManager(opts),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// Clean up partial project directory on unexpected exit (e.g. Ctrl+C)
|
|
463
|
+
const cleanupOnExit = () => {
|
|
464
|
+
fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
|
|
465
|
+
};
|
|
466
|
+
process.once("exit", cleanupOnExit);
|
|
251
467
|
|
|
252
468
|
try {
|
|
253
|
-
|
|
469
|
+
// 3. Resolve latest release ref (started before prompts)
|
|
470
|
+
logger.step("Resolving latest release...");
|
|
471
|
+
const ref = await refPromise;
|
|
472
|
+
if (!ref) {
|
|
473
|
+
logger.warn("Could not resolve latest release, downloading from HEAD");
|
|
474
|
+
}
|
|
254
475
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
476
|
+
// 4. Download project
|
|
477
|
+
logger.step("Downloading project...");
|
|
478
|
+
try {
|
|
479
|
+
await downloadProject(project.path, absoluteProjectDir, ref);
|
|
480
|
+
|
|
481
|
+
// If the template didn't exist at the release tag, retry from HEAD
|
|
482
|
+
if (
|
|
483
|
+
ref &&
|
|
484
|
+
!fs.existsSync(path.join(absoluteProjectDir, "package.json"))
|
|
485
|
+
) {
|
|
486
|
+
fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
|
|
487
|
+
logger.warn(
|
|
488
|
+
"Template not found at release tag, downloading from HEAD",
|
|
489
|
+
);
|
|
490
|
+
await downloadProject(project.path, absoluteProjectDir);
|
|
259
491
|
}
|
|
492
|
+
|
|
493
|
+
// 5. Run transform pipeline
|
|
494
|
+
await transformProject(absoluteProjectDir, {
|
|
495
|
+
hasLocalComponents: project.hasLocalComponents,
|
|
496
|
+
skipInstall: opts.skipInstall,
|
|
497
|
+
packageManager: pm,
|
|
498
|
+
});
|
|
499
|
+
} catch (err) {
|
|
500
|
+
// Clean up partially created project directory
|
|
501
|
+
fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
|
|
502
|
+
throw err;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// 6. Apply preset if provided
|
|
506
|
+
if (opts.preset) {
|
|
507
|
+
const presetUrl = resolvePresetUrl(opts.preset);
|
|
260
508
|
logger.info("Applying preset configuration...");
|
|
261
509
|
logger.break();
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
510
|
+
const [dlxCmd, dlxArgs] = dlxCommand(pm);
|
|
511
|
+
try {
|
|
512
|
+
await runSpawn(
|
|
513
|
+
dlxCmd,
|
|
514
|
+
[
|
|
515
|
+
...dlxArgs,
|
|
516
|
+
"shadcn@latest",
|
|
517
|
+
"add",
|
|
518
|
+
"--yes",
|
|
519
|
+
"--overwrite",
|
|
520
|
+
presetUrl,
|
|
521
|
+
],
|
|
522
|
+
absoluteProjectDir,
|
|
523
|
+
);
|
|
524
|
+
} catch {
|
|
525
|
+
logger.warn(
|
|
526
|
+
`Preset application failed. You can retry manually with:\n ${dlxCmd} ${[...dlxArgs, "shadcn@latest", "add", presetUrl].join(" ")}`,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
267
529
|
}
|
|
268
530
|
|
|
531
|
+
process.removeListener("exit", cleanupOnExit);
|
|
532
|
+
|
|
269
533
|
logger.break();
|
|
270
534
|
logger.success("Project created successfully!");
|
|
535
|
+
logger.break();
|
|
536
|
+
const runCmd = pm === "npm" ? "npm run" : pm;
|
|
537
|
+
logger.info("Next steps:");
|
|
538
|
+
logger.info(` cd ${resolvedProjectDirectory}`);
|
|
539
|
+
if (opts.skipInstall) {
|
|
540
|
+
logger.info(` ${pm} install`);
|
|
541
|
+
}
|
|
542
|
+
logger.info(" # Set up your environment variables in .env.local");
|
|
543
|
+
logger.info(` ${runCmd} dev`);
|
|
271
544
|
} catch (error) {
|
|
272
545
|
if (error instanceof SpawnExitError) {
|
|
273
546
|
logger.error(`Project creation failed with code ${error.code}`);
|
package/src/commands/init.ts
CHANGED
|
@@ -89,8 +89,8 @@ export const init = new Command()
|
|
|
89
89
|
)
|
|
90
90
|
.addOption(
|
|
91
91
|
new Option(
|
|
92
|
-
"-p, --preset <url>",
|
|
93
|
-
"preset
|
|
92
|
+
"-p, --preset <name-or-url>",
|
|
93
|
+
"preset name or URL (forwarded to 'assistant-ui create')",
|
|
94
94
|
).hideHelp(),
|
|
95
95
|
)
|
|
96
96
|
.option("--use-npm", "explicitly use npm")
|