@magicappdev/cli 0.0.7 → 0.0.9
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/dist/cli.d.ts +0 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +51 -2
- package/dist/commands/auth.js +28 -4
- package/dist/commands/chat.d.ts +1 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +165 -55
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +71 -7
- package/dist/lib/prompts.d.ts +11 -0
- package/dist/lib/prompts.d.ts.map +1 -1
- package/dist/lib/prompts.js +53 -7
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2CpC,6BAA6B;AAC7B,wBAAgB,aAAa,IAAI,OAAO,CA2BvC;AAED,kBAAkB;AAClB,wBAAsB,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxD"}
|
package/dist/cli.js
CHANGED
|
@@ -13,14 +13,53 @@ const require = createRequire(import.meta.url);
|
|
|
13
13
|
const pkg = require("../package.json");
|
|
14
14
|
/** Package version */
|
|
15
15
|
const VERSION = pkg.version;
|
|
16
|
-
/**
|
|
16
|
+
/** Compare semver versions: returns 1 if a > b, -1 if a < b, 0 if equal */
|
|
17
|
+
function compareVersions(a, b) {
|
|
18
|
+
const partsA = a.split(".").map(Number);
|
|
19
|
+
const partsB = b.split(".").map(Number);
|
|
20
|
+
for (let i = 0; i < 3; i++) {
|
|
21
|
+
if (partsA[i] > partsB[i])
|
|
22
|
+
return 1;
|
|
23
|
+
if (partsA[i] < partsB[i])
|
|
24
|
+
return -1;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
/** Check for updates by fetching npm registry (non-blocking) */
|
|
29
|
+
async function checkForUpdates() {
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(3000) });
|
|
32
|
+
if (response.ok) {
|
|
33
|
+
const data = (await response.json());
|
|
34
|
+
const latestVersion = data.version;
|
|
35
|
+
// Only show update message if npm version is newer than local
|
|
36
|
+
if (latestVersion && compareVersions(latestVersion, pkg.version) > 0) {
|
|
37
|
+
console.log(`\n\x1b[33mUpdate available:\x1b[0m ${pkg.version} → ${latestVersion}`);
|
|
38
|
+
console.log(`Run \x1b[36mnpm install -g ${pkg.name}\x1b[0m to update\n`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Silently ignore update check errors (network issues, timeouts, etc.)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
17
46
|
/** Create the CLI program */
|
|
18
47
|
export function createProgram() {
|
|
19
48
|
const program = new Command();
|
|
20
49
|
program
|
|
21
50
|
.name("magicappdev")
|
|
22
51
|
.description("CLI for creating and managing MagicAppDev apps")
|
|
23
|
-
.version(VERSION, "-
|
|
52
|
+
.version(VERSION, "-V, --version", "Display version number")
|
|
53
|
+
.option("-d, --debug", "Enable debug mode for verbose logging")
|
|
54
|
+
.hook("preAction", thisCommand => {
|
|
55
|
+
const opts = thisCommand.opts();
|
|
56
|
+
if (opts.debug) {
|
|
57
|
+
process.env.DEBUG = "true";
|
|
58
|
+
console.log(`[DEBUG] MagicAppDev CLI v${VERSION}`);
|
|
59
|
+
console.log(`[DEBUG] Node.js ${process.version}`);
|
|
60
|
+
console.log(`[DEBUG] Platform: ${process.platform} ${process.arch}`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
24
63
|
// Add commands
|
|
25
64
|
program.addCommand(initCommand);
|
|
26
65
|
program.addCommand(authCommand);
|
|
@@ -32,6 +71,16 @@ export function createProgram() {
|
|
|
32
71
|
}
|
|
33
72
|
/** Run the CLI */
|
|
34
73
|
export async function run(argv) {
|
|
74
|
+
// Handle Ctrl+C gracefully
|
|
75
|
+
process.on("SIGINT", () => {
|
|
76
|
+
console.log("\n\x1b[2mInterrupted.\x1b[0m");
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
|
+
process.on("SIGTERM", () => {
|
|
80
|
+
process.exit(0);
|
|
81
|
+
});
|
|
82
|
+
// Check for updates (non-blocking)
|
|
83
|
+
checkForUpdates();
|
|
35
84
|
const program = createProgram();
|
|
36
85
|
await program.parseAsync(argv || process.argv);
|
|
37
86
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -18,11 +18,34 @@ authCommand
|
|
|
18
18
|
// Setup local callback server
|
|
19
19
|
const server = http.createServer(async (req, res) => {
|
|
20
20
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
21
|
+
// Ignore favicon and other non-callback requests
|
|
22
|
+
if (url.pathname !== "/" || req.method !== "GET") {
|
|
23
|
+
res.writeHead(204);
|
|
24
|
+
res.end();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
21
27
|
const accessToken = url.searchParams.get("accessToken");
|
|
22
28
|
const refreshToken = url.searchParams.get("refreshToken");
|
|
23
|
-
|
|
29
|
+
// If no tokens yet, this is just the initial browser request - wait for callback
|
|
30
|
+
if (!accessToken && !refreshToken) {
|
|
31
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
32
|
+
res.end("<h1>Authenticating...</h1><p>Please complete the GitHub login in the popup.</p>");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Validate accessToken is JWT format (basic check)
|
|
36
|
+
const isValidJwt = (token) => typeof token === "string" &&
|
|
37
|
+
token.split(".").length === 3 &&
|
|
38
|
+
token.length > 20 &&
|
|
39
|
+
token.length < 2000;
|
|
40
|
+
// Validate refreshToken is UUID format
|
|
41
|
+
const isValidUuid = (token) => typeof token === "string" &&
|
|
42
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(token);
|
|
43
|
+
if (isValidJwt(accessToken) && isValidUuid(refreshToken)) {
|
|
24
44
|
// Store tokens
|
|
25
|
-
await saveConfig({
|
|
45
|
+
await saveConfig({
|
|
46
|
+
accessToken: accessToken,
|
|
47
|
+
refreshToken: refreshToken,
|
|
48
|
+
});
|
|
26
49
|
api.setToken(accessToken);
|
|
27
50
|
info(`Access Token received and saved`);
|
|
28
51
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
@@ -31,8 +54,9 @@ authCommand
|
|
|
31
54
|
process.exit(0);
|
|
32
55
|
}
|
|
33
56
|
else {
|
|
34
|
-
|
|
35
|
-
res.
|
|
57
|
+
error("Login failed: Invalid token format received");
|
|
58
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
59
|
+
res.end("<h1>Login Failed</h1><p>Invalid token format received.</p>");
|
|
36
60
|
}
|
|
37
61
|
});
|
|
38
62
|
server.listen(0, async () => {
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,eAAO,MAAM,WAAW,SAoEpB,CAAC"}
|
package/dist/commands/chat.js
CHANGED
|
@@ -1,95 +1,205 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Chat command - Interactive AI App Builder
|
|
3
|
+
* Uses native WebSocket with partykit-compatible URL
|
|
3
4
|
*/
|
|
4
5
|
import { header, logo, info } from "../lib/ui.js";
|
|
5
|
-
import { AgentClient } from "agents/client";
|
|
6
6
|
import { AGENT_HOST } from "../lib/api.js";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import prompts from "prompts";
|
|
9
|
+
import WebSocket from "ws";
|
|
9
10
|
import chalk from "chalk";
|
|
10
11
|
import ora from "ora";
|
|
12
|
+
// Handle Ctrl+C in prompts
|
|
13
|
+
const onCancel = () => {
|
|
14
|
+
console.log(chalk.dim("\nGoodbye!"));
|
|
15
|
+
process.exit(0);
|
|
16
|
+
};
|
|
11
17
|
export const chatCommand = new Command("chat")
|
|
12
18
|
.description("Chat with the Magic AI App Builder")
|
|
13
|
-
.
|
|
19
|
+
.option("-d, --debug", "Enable debug logging")
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const debug = options.debug || process.env.DEBUG === "true";
|
|
14
22
|
logo();
|
|
15
23
|
header("Magic AI Assistant");
|
|
16
24
|
info("Connecting to agent...");
|
|
17
25
|
const spinner = ora("Initializing connection").start();
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
// Build WebSocket URL with party headers embedded
|
|
27
|
+
// The agents SDK expects: /parties/{namespace}/{room} or /agents/{namespace}/{room}
|
|
28
|
+
const wsUrl = `wss://${AGENT_HOST}/agents/magic-agent/default`;
|
|
29
|
+
if (debug) {
|
|
30
|
+
console.log(chalk.dim(`\n[DEBUG] Connecting to: ${wsUrl}`));
|
|
31
|
+
}
|
|
32
|
+
const ws = new WebSocket(wsUrl, {
|
|
33
|
+
headers: {
|
|
34
|
+
"x-partykit-namespace": "magic-agent",
|
|
35
|
+
"x-partykit-room": "default",
|
|
36
|
+
},
|
|
23
37
|
});
|
|
24
|
-
|
|
38
|
+
// Connection timeout
|
|
39
|
+
const connectionTimeout = setTimeout(() => {
|
|
40
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
41
|
+
spinner.fail("Connection timeout - agent not responding");
|
|
42
|
+
ws.terminate();
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}, 10000);
|
|
46
|
+
ws.on("open", () => {
|
|
47
|
+
clearTimeout(connectionTimeout);
|
|
25
48
|
spinner.succeed("Connected to Magic AI Assistant");
|
|
26
|
-
|
|
49
|
+
if (debug) {
|
|
50
|
+
console.log(chalk.dim("[DEBUG] WebSocket connection established"));
|
|
51
|
+
}
|
|
52
|
+
startChatLoop(ws, debug);
|
|
27
53
|
});
|
|
28
|
-
|
|
29
|
-
|
|
54
|
+
ws.on("close", (code, reason) => {
|
|
55
|
+
clearTimeout(connectionTimeout);
|
|
56
|
+
const reasonStr = reason?.toString() || "Connection closed";
|
|
57
|
+
if (debug) {
|
|
58
|
+
console.log(chalk.dim(`\n[DEBUG] WebSocket closed: ${code} - ${reasonStr}`));
|
|
59
|
+
}
|
|
60
|
+
if (code !== 1000) {
|
|
61
|
+
// 1000 = normal closure
|
|
62
|
+
spinner.fail(`Disconnected from agent (${code})`);
|
|
63
|
+
}
|
|
30
64
|
process.exit(0);
|
|
31
65
|
});
|
|
32
|
-
|
|
33
|
-
|
|
66
|
+
ws.on("error", (err) => {
|
|
67
|
+
clearTimeout(connectionTimeout);
|
|
68
|
+
spinner.fail(`Connection error: ${err.message}`);
|
|
69
|
+
if (debug) {
|
|
70
|
+
console.error(chalk.red(`\n[DEBUG] WebSocket error:`), err);
|
|
71
|
+
}
|
|
34
72
|
process.exit(1);
|
|
35
73
|
});
|
|
36
74
|
});
|
|
37
|
-
async function startChatLoop(
|
|
75
|
+
async function startChatLoop(ws, debug) {
|
|
38
76
|
console.log(chalk.dim("\nType your message below (type 'exit' to quit)"));
|
|
39
|
-
|
|
77
|
+
// Track if we're waiting for a response
|
|
78
|
+
let waitingForResponse = false;
|
|
79
|
+
let currentResponse = "";
|
|
80
|
+
let responseSpinner = null;
|
|
81
|
+
let resolveResponse = null;
|
|
82
|
+
// Handle incoming messages
|
|
83
|
+
ws.on("message", (data) => {
|
|
84
|
+
try {
|
|
85
|
+
const raw = data.toString();
|
|
86
|
+
const message = JSON.parse(raw);
|
|
87
|
+
if (debug) {
|
|
88
|
+
console.log(chalk.dim(`\n[DEBUG] Received: ${message.type || "unknown"}`));
|
|
89
|
+
console.log(chalk.dim(`[DEBUG] Raw: ${raw.substring(0, 200)}...`));
|
|
90
|
+
}
|
|
91
|
+
// Handle different message types
|
|
92
|
+
switch (message.type) {
|
|
93
|
+
case "chat_start":
|
|
94
|
+
if (debug) {
|
|
95
|
+
console.log(chalk.dim(`[DEBUG] Using model: ${message.model}`));
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case "chat_chunk":
|
|
99
|
+
if (message.content) {
|
|
100
|
+
currentResponse += message.content;
|
|
101
|
+
if (responseSpinner) {
|
|
102
|
+
// Show last line of response in spinner
|
|
103
|
+
const lastLine = currentResponse.split("\n").pop() || "...";
|
|
104
|
+
responseSpinner.text = chalk.gray(lastLine.length > 60 ? lastLine.slice(-60) + "..." : lastLine);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case "chat_done":
|
|
109
|
+
if (responseSpinner) {
|
|
110
|
+
responseSpinner.stop();
|
|
111
|
+
}
|
|
112
|
+
if (currentResponse) {
|
|
113
|
+
console.log(chalk.green("\nMagic AI:"), currentResponse);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(chalk.yellow("\nMagic AI: (No response received)"));
|
|
117
|
+
}
|
|
118
|
+
if (message.suggestedTemplate) {
|
|
119
|
+
console.log(chalk.yellow("\nSuggested Template:"), chalk.bold(message.suggestedTemplate));
|
|
120
|
+
console.log(chalk.dim(`Run 'magicappdev init --template ${message.suggestedTemplate}' to use it.`));
|
|
121
|
+
}
|
|
122
|
+
console.log(""); // Spacing
|
|
123
|
+
// Reset state and resolve promise
|
|
124
|
+
currentResponse = "";
|
|
125
|
+
waitingForResponse = false;
|
|
126
|
+
if (resolveResponse) {
|
|
127
|
+
resolveResponse();
|
|
128
|
+
resolveResponse = null;
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
case "error":
|
|
132
|
+
if (responseSpinner) {
|
|
133
|
+
responseSpinner.fail(`Error: ${message.error || message.message || "Unknown error"}`);
|
|
134
|
+
}
|
|
135
|
+
currentResponse = "";
|
|
136
|
+
waitingForResponse = false;
|
|
137
|
+
if (resolveResponse) {
|
|
138
|
+
resolveResponse();
|
|
139
|
+
resolveResponse = null;
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
case "state:update":
|
|
143
|
+
case "cf_agent_state":
|
|
144
|
+
// Ignore state updates from agents SDK
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
if (debug) {
|
|
148
|
+
console.log(chalk.dim(`[DEBUG] Ignored message type: ${message.type}`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (debug) {
|
|
154
|
+
console.error(chalk.red("[DEBUG] Parse error:"), err);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
// Main chat loop
|
|
159
|
+
while (ws.readyState === WebSocket.OPEN) {
|
|
40
160
|
const response = await prompts({
|
|
41
161
|
type: "text",
|
|
42
162
|
name: "message",
|
|
43
163
|
message: chalk.cyan("You:"),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
|
|
164
|
+
}, { onCancel });
|
|
165
|
+
// Handle Ctrl+C or empty input
|
|
166
|
+
if (!response.message) {
|
|
167
|
+
console.log(chalk.dim("\nGoodbye!"));
|
|
168
|
+
ws.close(1000, "User exit");
|
|
47
169
|
break;
|
|
48
170
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
spinner.stop();
|
|
60
|
-
console.log(chalk.green("\nMagic AI:"), currentResponse);
|
|
61
|
-
if (data.suggestedTemplate) {
|
|
62
|
-
console.log(chalk.yellow("\n💡 Suggested Template:"), chalk.bold(data.suggestedTemplate));
|
|
63
|
-
console.log(chalk.dim(`Run 'magicappdev init --template ${data.suggestedTemplate}' to use it.`));
|
|
64
|
-
}
|
|
65
|
-
console.log(""); // Spacing
|
|
66
|
-
client.removeEventListener("message", messageHandler);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
// Ignore parse errors for chunks
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
client.addEventListener("message", messageHandler);
|
|
74
|
-
client.send(JSON.stringify({
|
|
171
|
+
if (response.message.toLowerCase() === "exit") {
|
|
172
|
+
console.log(chalk.dim("\nGoodbye!"));
|
|
173
|
+
ws.close(1000, "User exit");
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
// Send message
|
|
177
|
+
waitingForResponse = true;
|
|
178
|
+
currentResponse = "";
|
|
179
|
+
responseSpinner = ora("Magic AI is thinking...").start();
|
|
180
|
+
ws.send(JSON.stringify({
|
|
75
181
|
type: "chat",
|
|
76
182
|
content: response.message,
|
|
77
183
|
}));
|
|
78
|
-
// Wait for
|
|
184
|
+
// Wait for response to complete
|
|
79
185
|
await new Promise(resolve => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
186
|
+
resolveResponse = resolve;
|
|
187
|
+
// Timeout for response
|
|
188
|
+
const responseTimeout = setTimeout(() => {
|
|
189
|
+
if (waitingForResponse) {
|
|
190
|
+
if (responseSpinner) {
|
|
191
|
+
responseSpinner.fail("Response timeout");
|
|
86
192
|
}
|
|
193
|
+
waitingForResponse = false;
|
|
194
|
+
resolve();
|
|
87
195
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
196
|
+
}, 60000); // 60 second timeout for AI response
|
|
197
|
+
// Clean up timeout when resolved
|
|
198
|
+
const originalResolve = resolveResponse;
|
|
199
|
+
resolveResponse = () => {
|
|
200
|
+
clearTimeout(responseTimeout);
|
|
201
|
+
originalResolve();
|
|
91
202
|
};
|
|
92
|
-
client.addEventListener("message", doneHandler);
|
|
93
203
|
});
|
|
94
204
|
}
|
|
95
205
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8CpC,eAAO,MAAM,WAAW,SA2JpB,CAAC"}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,11 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Init command - Initialize a new MagicAppDev project
|
|
3
3
|
*/
|
|
4
|
-
import { promptProjectName, promptFramework, promptStyling, promptTypeScript, promptSelect, } from "../lib/prompts.js";
|
|
4
|
+
import { promptProjectName, promptFramework, promptStyling, promptTypeScript, promptSelect, promptPreferences, } from "../lib/prompts.js";
|
|
5
5
|
import { header, logo, success, error, info, keyValue, command, newline, divider, } from "../lib/ui.js";
|
|
6
|
-
import {
|
|
6
|
+
import { builtInTemplates } from "@magicappdev/templates";
|
|
7
|
+
import { generateApp } from "@magicappdev/templates";
|
|
7
8
|
import { withSpinner } from "../lib/spinner.js";
|
|
9
|
+
import { spawn } from "child_process";
|
|
8
10
|
import { Command } from "commander";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
/** Detect the package manager to use */
|
|
13
|
+
function detectPackageManager() {
|
|
14
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
15
|
+
if (userAgent.includes("pnpm"))
|
|
16
|
+
return "pnpm";
|
|
17
|
+
if (userAgent.includes("yarn"))
|
|
18
|
+
return "yarn";
|
|
19
|
+
if (userAgent.includes("bun"))
|
|
20
|
+
return "bun";
|
|
21
|
+
return "npm";
|
|
22
|
+
}
|
|
23
|
+
/** Run package manager install */
|
|
24
|
+
async function runInstall(projectDir, pm) {
|
|
25
|
+
return new Promise(resolve => {
|
|
26
|
+
const child = spawn(pm, ["install"], {
|
|
27
|
+
cwd: projectDir,
|
|
28
|
+
stdio: "inherit",
|
|
29
|
+
shell: true,
|
|
30
|
+
});
|
|
31
|
+
child.on("close", code => {
|
|
32
|
+
if (code === 0) {
|
|
33
|
+
resolve({ success: true });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
resolve({ success: false, error: `Exit code ${code}` });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
child.on("error", err => {
|
|
40
|
+
resolve({ success: false, error: err.message });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
9
44
|
export const initCommand = new Command("init")
|
|
10
45
|
.description("Initialize a new MagicAppDev project")
|
|
11
46
|
.argument("[name]", "Project name")
|
|
@@ -13,6 +48,7 @@ export const initCommand = new Command("init")
|
|
|
13
48
|
.option("-f, --framework <framework>", "Framework (expo, react-native, next)")
|
|
14
49
|
.option("--typescript", "Use TypeScript", true)
|
|
15
50
|
.option("-y, --yes", "Skip prompts and use defaults")
|
|
51
|
+
.option("--no-install", "Skip installing dependencies")
|
|
16
52
|
.action(async (name, options) => {
|
|
17
53
|
logo();
|
|
18
54
|
header("Create a new project");
|
|
@@ -34,7 +70,11 @@ export const initCommand = new Command("init")
|
|
|
34
70
|
value: "blank",
|
|
35
71
|
description: "Minimal starter template",
|
|
36
72
|
},
|
|
37
|
-
{
|
|
73
|
+
{
|
|
74
|
+
title: "Tabs",
|
|
75
|
+
value: "tabs",
|
|
76
|
+
description: "Tab-based navigation",
|
|
77
|
+
},
|
|
38
78
|
]);
|
|
39
79
|
}
|
|
40
80
|
if (!templateSlug) {
|
|
@@ -67,6 +107,11 @@ export const initCommand = new Command("init")
|
|
|
67
107
|
? "nativewind"
|
|
68
108
|
: "tailwind";
|
|
69
109
|
}
|
|
110
|
+
// Get additional preferences
|
|
111
|
+
let preferences = {};
|
|
112
|
+
if (!options.yes) {
|
|
113
|
+
preferences = (await promptPreferences()) || {};
|
|
114
|
+
}
|
|
70
115
|
newline();
|
|
71
116
|
divider();
|
|
72
117
|
info("Creating project with:");
|
|
@@ -75,27 +120,46 @@ export const initCommand = new Command("init")
|
|
|
75
120
|
keyValue("Framework", framework);
|
|
76
121
|
keyValue("TypeScript", typescript ? "Yes" : "No");
|
|
77
122
|
keyValue("Styling", styling);
|
|
123
|
+
keyValue("Preferences", Object.keys(preferences).join(", ") || "None");
|
|
78
124
|
divider();
|
|
79
125
|
newline();
|
|
80
126
|
// Find template
|
|
81
|
-
const template =
|
|
127
|
+
const template = builtInTemplates.find(t => t.id === templateSlug) ||
|
|
128
|
+
builtInTemplates[0];
|
|
82
129
|
// Generate project
|
|
83
130
|
const outputDir = process.cwd();
|
|
131
|
+
const projectDir = path.join(outputDir, projectName);
|
|
84
132
|
const result = await withSpinner(`Creating ${projectName}...`, async () => {
|
|
85
133
|
return generateApp(projectName, template, outputDir, {
|
|
86
134
|
typescript,
|
|
87
135
|
styling,
|
|
88
136
|
framework,
|
|
89
|
-
});
|
|
137
|
+
}, preferences);
|
|
90
138
|
}, { successText: `Created ${projectName}` });
|
|
91
139
|
newline();
|
|
92
140
|
success(`Project created successfully!`);
|
|
93
141
|
info(`Files created: ${result.files.length}`);
|
|
142
|
+
// Install dependencies
|
|
143
|
+
const shouldInstall = options.install !== false;
|
|
144
|
+
if (shouldInstall) {
|
|
145
|
+
const pm = detectPackageManager();
|
|
146
|
+
newline();
|
|
147
|
+
const installResult = await withSpinner(`Installing dependencies with ${pm}...`, async () => runInstall(projectDir, pm), { successText: "Dependencies installed" });
|
|
148
|
+
if (!installResult.success) {
|
|
149
|
+
error(`Failed to install dependencies: ${installResult.error || "Unknown error"}`);
|
|
150
|
+
info("You can install them manually:");
|
|
151
|
+
command(`cd ${projectName}`);
|
|
152
|
+
command(`${pm} install`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
94
155
|
newline();
|
|
95
156
|
info("Next steps:");
|
|
96
157
|
command(`cd ${projectName}`);
|
|
97
|
-
|
|
98
|
-
|
|
158
|
+
if (!shouldInstall || options.install === false) {
|
|
159
|
+
const pm = detectPackageManager();
|
|
160
|
+
command(`${pm} install`);
|
|
161
|
+
}
|
|
162
|
+
command(`${detectPackageManager()} start`);
|
|
99
163
|
newline();
|
|
100
164
|
}
|
|
101
165
|
catch (err) {
|
package/dist/lib/prompts.d.ts
CHANGED
|
@@ -14,6 +14,15 @@ export declare function promptSelect<T>(message: string, choices: Array<{
|
|
|
14
14
|
}>, options?: {
|
|
15
15
|
initial?: number;
|
|
16
16
|
}): Promise<T | undefined>;
|
|
17
|
+
/** Prompt for multi-selection from a list */
|
|
18
|
+
export declare function promptMultiSelect<T>(message: string, choices: Array<{
|
|
19
|
+
title: string;
|
|
20
|
+
value: T;
|
|
21
|
+
description?: string;
|
|
22
|
+
}>, options?: {
|
|
23
|
+
min?: number;
|
|
24
|
+
max?: number;
|
|
25
|
+
}): Promise<T[]>;
|
|
17
26
|
/** Prompt for confirmation (yes/no) */
|
|
18
27
|
export declare function promptConfirm(message: string, options?: {
|
|
19
28
|
initial?: boolean;
|
|
@@ -22,6 +31,8 @@ export declare function promptConfirm(message: string, options?: {
|
|
|
22
31
|
export declare function promptProjectName(initial?: string): Promise<string | undefined>;
|
|
23
32
|
/** Prompt for framework selection */
|
|
24
33
|
export declare function promptFramework(): Promise<string | undefined>;
|
|
34
|
+
/** Prompt for additional preferences */
|
|
35
|
+
export declare function promptPreferences(): Promise<Record<string, boolean>>;
|
|
25
36
|
/** Prompt for styling selection */
|
|
26
37
|
export declare function promptStyling(framework: string): Promise<string | undefined>;
|
|
27
38
|
/** Prompt for TypeScript */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/lib/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/lib/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,4BAA4B;AAC5B,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,CAAC;CAC3C,GACL,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED,uCAAuC;AACvC,wBAAsB,YAAY,CAAC,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACjE,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;CACb,GACL,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAaxB;AAED,6CAA6C;AAC7C,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACjE,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACT,GACL,OAAO,CAAC,CAAC,EAAE,CAAC,CAed;AAED,uCAAuC;AACvC,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,GACL,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,8CAA8C;AAC9C,wBAAsB,iBAAiB,CACrC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED,qCAAqC;AACrC,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAanE;AAED,wCAAwC;AACxC,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAuC1E;AAED,mCAAmC;AACnC,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0C7B;AAED,4BAA4B;AAC5B,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAEzD"}
|
package/dist/lib/prompts.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import prompts from "prompts";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
+
// Handle Ctrl+C in prompts - exit cleanly
|
|
7
|
+
const onCancel = () => {
|
|
8
|
+
console.log(chalk.dim("\nCancelled."));
|
|
9
|
+
process.exit(0);
|
|
10
|
+
};
|
|
6
11
|
/** Prompt for text input */
|
|
7
12
|
export async function promptText(message, options = {}) {
|
|
8
13
|
const response = await prompts({
|
|
@@ -11,7 +16,7 @@ export async function promptText(message, options = {}) {
|
|
|
11
16
|
message,
|
|
12
17
|
initial: options.initial,
|
|
13
18
|
validate: options.validate,
|
|
14
|
-
});
|
|
19
|
+
}, { onCancel });
|
|
15
20
|
return response.value;
|
|
16
21
|
}
|
|
17
22
|
/** Prompt for selection from a list */
|
|
@@ -22,9 +27,22 @@ export async function promptSelect(message, choices, options = {}) {
|
|
|
22
27
|
message,
|
|
23
28
|
choices,
|
|
24
29
|
initial: options.initial || 0,
|
|
25
|
-
});
|
|
30
|
+
}, { onCancel });
|
|
26
31
|
return response.value;
|
|
27
32
|
}
|
|
33
|
+
/** Prompt for multi-selection from a list */
|
|
34
|
+
export async function promptMultiSelect(message, choices, options = {}) {
|
|
35
|
+
const response = await prompts({
|
|
36
|
+
type: "multiselect",
|
|
37
|
+
name: "value",
|
|
38
|
+
message,
|
|
39
|
+
choices,
|
|
40
|
+
min: options.min,
|
|
41
|
+
max: options.max,
|
|
42
|
+
hint: "- Space to select. Return to submit",
|
|
43
|
+
}, { onCancel });
|
|
44
|
+
return response.value || [];
|
|
45
|
+
}
|
|
28
46
|
/** Prompt for confirmation (yes/no) */
|
|
29
47
|
export async function promptConfirm(message, options = {}) {
|
|
30
48
|
const response = await prompts({
|
|
@@ -32,7 +50,7 @@ export async function promptConfirm(message, options = {}) {
|
|
|
32
50
|
name: "value",
|
|
33
51
|
message,
|
|
34
52
|
initial: options.initial ?? true,
|
|
35
|
-
});
|
|
53
|
+
}, { onCancel });
|
|
36
54
|
return response.value;
|
|
37
55
|
}
|
|
38
56
|
/** Prompt for project name with validation */
|
|
@@ -63,13 +81,41 @@ export async function promptFramework() {
|
|
|
63
81
|
value: "react-native",
|
|
64
82
|
description: "React Native without Expo",
|
|
65
83
|
},
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
/** Prompt for additional preferences */
|
|
87
|
+
export async function promptPreferences() {
|
|
88
|
+
const preferences = await promptMultiSelect("Select additional preferences:", [
|
|
89
|
+
{
|
|
90
|
+
title: "ESLint (code linting)",
|
|
91
|
+
value: "eslint",
|
|
92
|
+
description: "Add ESLint for code quality",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
title: "Prettier (code formatting)",
|
|
96
|
+
value: "prettier",
|
|
97
|
+
description: "Add Prettier for code formatting",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
title: "Jest (testing framework)",
|
|
101
|
+
value: "jest",
|
|
102
|
+
description: "Add Jest for testing",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: "TypeScript (recommended)",
|
|
106
|
+
value: "typescript",
|
|
107
|
+
description: "Use TypeScript",
|
|
108
|
+
},
|
|
66
109
|
{
|
|
67
|
-
title: "
|
|
68
|
-
value: "
|
|
69
|
-
description: "
|
|
110
|
+
title: "Tailwind CSS (styling)",
|
|
111
|
+
value: "tailwind",
|
|
112
|
+
description: "Add Tailwind CSS for styling",
|
|
70
113
|
},
|
|
71
|
-
{ title: "Remix", value: "remix", description: "Full stack web framework" },
|
|
72
114
|
]);
|
|
115
|
+
return preferences.reduce((acc, pref) => {
|
|
116
|
+
acc[pref] = true;
|
|
117
|
+
return acc;
|
|
118
|
+
}, {});
|
|
73
119
|
}
|
|
74
120
|
/** Prompt for styling selection */
|
|
75
121
|
export async function promptStyling(framework) {
|