@magicappdev/cli 0.0.10 → 0.0.14
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.map +1 -1
- package/dist/cli.js +17 -2
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +39 -14
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +26 -13
- package/dist/commands/clone.d.ts +6 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/clone.js +202 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +5 -0
- package/dist/commands/generate/index.d.ts.map +1 -1
- package/dist/commands/generate/index.js +5 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -0
- package/package.json +2 -5
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;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2CpC,6BAA6B;AAC7B,wBAAgB,aAAa,IAAI,OAAO,CAwCvC;AAED,kBAAkB;AAClB,wBAAsB,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBxD"}
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { doctorCommand } from "./commands/doctor.js";
|
|
|
7
7
|
import { initCommand } from "./commands/init.js";
|
|
8
8
|
import { chatCommand } from "./commands/chat.js";
|
|
9
9
|
import { authCommand } from "./commands/auth.js";
|
|
10
|
+
import { cloneCommand } from "./commands/clone.js";
|
|
10
11
|
import { createRequire } from "module";
|
|
11
12
|
import { Command } from "commander";
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
@@ -51,6 +52,15 @@ export function createProgram() {
|
|
|
51
52
|
.description("CLI for creating and managing MagicAppDev apps")
|
|
52
53
|
.version(VERSION, "-V, --version", "Display version number")
|
|
53
54
|
.option("-d, --debug", "Enable debug mode for verbose logging")
|
|
55
|
+
.option("--no-update-check", "Skip checking for updates")
|
|
56
|
+
.addHelpText("after", `
|
|
57
|
+
Examples:
|
|
58
|
+
$ magicappdev init my-app
|
|
59
|
+
$ magicappdev chat
|
|
60
|
+
$ magicappdev auth login
|
|
61
|
+
$ magicappdev clone --list
|
|
62
|
+
$ magicappdev doctor
|
|
63
|
+
`)
|
|
54
64
|
.hook("preAction", thisCommand => {
|
|
55
65
|
const opts = thisCommand.opts();
|
|
56
66
|
if (opts.debug) {
|
|
@@ -64,6 +74,7 @@ export function createProgram() {
|
|
|
64
74
|
program.addCommand(initCommand);
|
|
65
75
|
program.addCommand(authCommand);
|
|
66
76
|
program.addCommand(chatCommand);
|
|
77
|
+
program.addCommand(cloneCommand);
|
|
67
78
|
program.addCommand(generateCommand);
|
|
68
79
|
program.addCommand(doctorCommand);
|
|
69
80
|
program.addCommand(completionsCommand);
|
|
@@ -79,10 +90,14 @@ export async function run(argv) {
|
|
|
79
90
|
process.on("SIGTERM", () => {
|
|
80
91
|
process.exit(0);
|
|
81
92
|
});
|
|
93
|
+
const args = argv || process.argv;
|
|
94
|
+
const noUpdateCheck = args.includes("--no-update-check");
|
|
82
95
|
// Check for updates (non-blocking)
|
|
83
|
-
|
|
96
|
+
if (!noUpdateCheck) {
|
|
97
|
+
checkForUpdates();
|
|
98
|
+
}
|
|
84
99
|
const program = createProgram();
|
|
85
|
-
await program.parseAsync(
|
|
100
|
+
await program.parseAsync(args);
|
|
86
101
|
}
|
|
87
102
|
// Run if executed directly
|
|
88
103
|
if (import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"))) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,WAAW,SASrB,CAAC"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -7,7 +7,13 @@ import { api } from "../lib/api.js";
|
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import open from "open";
|
|
9
9
|
import http from "http";
|
|
10
|
-
export const authCommand = new Command("auth")
|
|
10
|
+
export const authCommand = new Command("auth")
|
|
11
|
+
.description("Authentication commands")
|
|
12
|
+
.addHelpText("after", `
|
|
13
|
+
Examples:
|
|
14
|
+
$ magicappdev auth login
|
|
15
|
+
$ magicappdev auth whoami
|
|
16
|
+
`);
|
|
11
17
|
authCommand
|
|
12
18
|
.command("login")
|
|
13
19
|
.description("Login to MagicAppDev using GitHub")
|
|
@@ -16,6 +22,7 @@ authCommand
|
|
|
16
22
|
header("Authentication");
|
|
17
23
|
info("Opening GitHub login in your browser...");
|
|
18
24
|
// Setup local callback server
|
|
25
|
+
const authState = crypto.randomUUID();
|
|
19
26
|
const server = http.createServer(async (req, res) => {
|
|
20
27
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
21
28
|
// Ignore favicon and other non-callback requests
|
|
@@ -26,12 +33,20 @@ authCommand
|
|
|
26
33
|
}
|
|
27
34
|
const accessToken = url.searchParams.get("accessToken");
|
|
28
35
|
const refreshToken = url.searchParams.get("refreshToken");
|
|
36
|
+
const returnedState = url.searchParams.get("state");
|
|
29
37
|
// If no tokens yet, this is just the initial browser request - wait for callback
|
|
30
|
-
if (!accessToken
|
|
38
|
+
if (!accessToken || !refreshToken) {
|
|
31
39
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
32
40
|
res.end("<h1>Authenticating...</h1><p>Please complete the GitHub login in the popup.</p>");
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
43
|
+
// Verify state to prevent CSRF/bypass
|
|
44
|
+
if (returnedState !== authState) {
|
|
45
|
+
error("Login failed: Security state mismatch");
|
|
46
|
+
res.writeHead(403, { "Content-Type": "text/html" });
|
|
47
|
+
res.end("<h1>Login Failed</h1><p>Security state mismatch. Please try again.</p>");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
35
50
|
// Validate accessToken is JWT format (basic check)
|
|
36
51
|
const isValidJwt = (token) => typeof token === "string" &&
|
|
37
52
|
token.split(".").length === 3 &&
|
|
@@ -41,17 +56,26 @@ authCommand
|
|
|
41
56
|
const isValidUuid = (token) => typeof token === "string" &&
|
|
42
57
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(token);
|
|
43
58
|
if (isValidJwt(accessToken) && isValidUuid(refreshToken)) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
accessToken
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
try {
|
|
60
|
+
// Verify token with API before saving (fixes user-controlled bypass)
|
|
61
|
+
api.setToken(accessToken);
|
|
62
|
+
await api.getCurrentUser();
|
|
63
|
+
// Store tokens
|
|
64
|
+
await saveConfig({
|
|
65
|
+
accessToken,
|
|
66
|
+
refreshToken,
|
|
67
|
+
});
|
|
68
|
+
info(`Access Token verified and saved`);
|
|
69
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
70
|
+
res.end("<h1>Login Successful!</h1><p>You can close this window now.</p>");
|
|
71
|
+
success("Successfully logged in!");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
error(`Verification failed: ${err instanceof Error ? err.message : "Invalid token"}`);
|
|
76
|
+
res.writeHead(401, { "Content-Type": "text/html" });
|
|
77
|
+
res.end("<h1>Login Failed</h1><p>Token verification failed. Please try again.</p>");
|
|
78
|
+
}
|
|
55
79
|
}
|
|
56
80
|
else {
|
|
57
81
|
error("Login failed: Invalid token format received");
|
|
@@ -64,7 +88,8 @@ authCommand
|
|
|
64
88
|
const port = address.port;
|
|
65
89
|
const redirectUri = `http://localhost:${port}`;
|
|
66
90
|
const loginUrl = api.getGitHubLoginUrl("mobile") +
|
|
67
|
-
`&redirect_uri=${encodeURIComponent(redirectUri)}
|
|
91
|
+
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
|
|
92
|
+
`&state=${authState}`;
|
|
68
93
|
await open(loginUrl);
|
|
69
94
|
info(`If the browser didn't open, visit: ${loginUrl}`);
|
|
70
95
|
});
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AA0BpC,eAAO,MAAM,WAAW,SA4EpB,CAAC"}
|
package/dist/commands/chat.js
CHANGED
|
@@ -9,6 +9,10 @@ import prompts from "prompts";
|
|
|
9
9
|
import WebSocket from "ws";
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import ora from "ora";
|
|
12
|
+
/** Sanitize user-controlled strings for logging to prevent log injection */
|
|
13
|
+
function sanitize(str) {
|
|
14
|
+
return str.replace(/[\r\n]/g, " ");
|
|
15
|
+
}
|
|
12
16
|
// Handle Ctrl+C in prompts
|
|
13
17
|
const onCancel = () => {
|
|
14
18
|
console.log(chalk.dim("\nGoodbye!"));
|
|
@@ -17,6 +21,11 @@ const onCancel = () => {
|
|
|
17
21
|
export const chatCommand = new Command("chat")
|
|
18
22
|
.description("Chat with the Magic AI App Builder")
|
|
19
23
|
.option("-d, --debug", "Enable debug logging")
|
|
24
|
+
.addHelpText("after", `
|
|
25
|
+
Examples:
|
|
26
|
+
$ magicappdev chat
|
|
27
|
+
$ magicappdev chat --debug
|
|
28
|
+
`)
|
|
20
29
|
.action(async (options) => {
|
|
21
30
|
const debug = options.debug || process.env.DEBUG === "true";
|
|
22
31
|
logo();
|
|
@@ -53,7 +62,7 @@ export const chatCommand = new Command("chat")
|
|
|
53
62
|
});
|
|
54
63
|
ws.on("close", (code, reason) => {
|
|
55
64
|
clearTimeout(connectionTimeout);
|
|
56
|
-
const reasonStr = reason?.toString() || "Connection closed";
|
|
65
|
+
const reasonStr = sanitize(reason?.toString() || "Connection closed");
|
|
57
66
|
if (debug) {
|
|
58
67
|
console.log(chalk.dim(`\n[DEBUG] WebSocket closed: ${code} - ${reasonStr}`));
|
|
59
68
|
}
|
|
@@ -65,9 +74,9 @@ export const chatCommand = new Command("chat")
|
|
|
65
74
|
});
|
|
66
75
|
ws.on("error", (err) => {
|
|
67
76
|
clearTimeout(connectionTimeout);
|
|
68
|
-
spinner.fail(`Connection error: ${err.message}`);
|
|
77
|
+
spinner.fail(`Connection error: ${sanitize(err.message)}`);
|
|
69
78
|
if (debug) {
|
|
70
|
-
console.error(chalk.red(`\n[DEBUG] WebSocket error:`), err);
|
|
79
|
+
console.error(chalk.red(`\n[DEBUG] WebSocket error:`), sanitize(err.toString()));
|
|
71
80
|
}
|
|
72
81
|
process.exit(1);
|
|
73
82
|
});
|
|
@@ -85,14 +94,14 @@ async function startChatLoop(ws, debug) {
|
|
|
85
94
|
const raw = data.toString();
|
|
86
95
|
const message = JSON.parse(raw);
|
|
87
96
|
if (debug) {
|
|
88
|
-
console.log(chalk.dim(`\n[DEBUG] Received: ${message.type || "unknown"}`));
|
|
89
|
-
console.log(chalk.dim(`[DEBUG] Raw: ${raw.substring(0, 200)}...`));
|
|
97
|
+
console.log(chalk.dim(`\n[DEBUG] Received: ${sanitize(message.type || "unknown")}`));
|
|
98
|
+
console.log(chalk.dim(`[DEBUG] Raw: ${sanitize(raw.substring(0, 200))}...`));
|
|
90
99
|
}
|
|
91
100
|
// Handle different message types
|
|
92
101
|
switch (message.type) {
|
|
93
102
|
case "chat_start":
|
|
94
103
|
if (debug) {
|
|
95
|
-
console.log(chalk.dim(`[DEBUG] Using model: ${message.model}`));
|
|
104
|
+
console.log(chalk.dim(`[DEBUG] Using model: ${sanitize(message.model || "unknown")}`));
|
|
96
105
|
}
|
|
97
106
|
break;
|
|
98
107
|
case "chat_chunk":
|
|
@@ -101,7 +110,10 @@ async function startChatLoop(ws, debug) {
|
|
|
101
110
|
if (responseSpinner) {
|
|
102
111
|
// Show last line of response in spinner
|
|
103
112
|
const lastLine = currentResponse.split("\n").pop() || "...";
|
|
104
|
-
|
|
113
|
+
const sanitizedLine = sanitize(lastLine);
|
|
114
|
+
responseSpinner.text = chalk.gray(sanitizedLine.length > 60
|
|
115
|
+
? sanitizedLine.slice(-60) + "..."
|
|
116
|
+
: sanitizedLine);
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
break;
|
|
@@ -110,14 +122,14 @@ async function startChatLoop(ws, debug) {
|
|
|
110
122
|
responseSpinner.stop();
|
|
111
123
|
}
|
|
112
124
|
if (currentResponse) {
|
|
113
|
-
console.log(chalk.green("\nMagic AI:"), currentResponse);
|
|
125
|
+
console.log(chalk.green("\nMagic AI:"), sanitize(currentResponse));
|
|
114
126
|
}
|
|
115
127
|
else {
|
|
116
128
|
console.log(chalk.yellow("\nMagic AI: (No response received)"));
|
|
117
129
|
}
|
|
118
130
|
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.`));
|
|
131
|
+
console.log(chalk.yellow("\nSuggested Template:"), chalk.bold(sanitize(message.suggestedTemplate)));
|
|
132
|
+
console.log(chalk.dim(`Run 'magicappdev init --template ${sanitize(message.suggestedTemplate)}' to use it.`));
|
|
121
133
|
}
|
|
122
134
|
console.log(""); // Spacing
|
|
123
135
|
// Reset state and resolve promise
|
|
@@ -130,7 +142,8 @@ async function startChatLoop(ws, debug) {
|
|
|
130
142
|
break;
|
|
131
143
|
case "error":
|
|
132
144
|
if (responseSpinner) {
|
|
133
|
-
|
|
145
|
+
const errorMsg = sanitize(message.error || message.message || "Unknown error");
|
|
146
|
+
responseSpinner.fail(`Error: ${errorMsg}`);
|
|
134
147
|
}
|
|
135
148
|
currentResponse = "";
|
|
136
149
|
waitingForResponse = false;
|
|
@@ -145,13 +158,13 @@ async function startChatLoop(ws, debug) {
|
|
|
145
158
|
break;
|
|
146
159
|
default:
|
|
147
160
|
if (debug) {
|
|
148
|
-
console.log(chalk.dim(`[DEBUG] Ignored message type: ${message.type}`));
|
|
161
|
+
console.log(chalk.dim(`[DEBUG] Ignored message type: ${sanitize(message.type)}`));
|
|
149
162
|
}
|
|
150
163
|
}
|
|
151
164
|
}
|
|
152
165
|
catch (err) {
|
|
153
166
|
if (debug) {
|
|
154
|
-
console.error(chalk.red("[DEBUG] Parse error:"), err);
|
|
167
|
+
console.error(chalk.red("[DEBUG] Parse error:"), sanitize(err?.toString() || "Unknown"));
|
|
155
168
|
}
|
|
156
169
|
}
|
|
157
170
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../../src/commands/clone.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkBH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsDpC,eAAO,MAAM,YAAY,SAgMrB,CAAC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone command - Clone a project from the database to local filesystem
|
|
3
|
+
*/
|
|
4
|
+
import { header, logo, success, error, info, keyValue, command, newline, divider, } from "../lib/ui.js";
|
|
5
|
+
import { api } from "../lib/api.js";
|
|
6
|
+
import { withSpinner } from "../lib/spinner.js";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
/** Ensure directory exists, create if not */
|
|
12
|
+
function ensureDir(dir) {
|
|
13
|
+
if (!fs.existsSync(dir)) {
|
|
14
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Write file to filesystem */
|
|
18
|
+
function writeFile(filePath, content) {
|
|
19
|
+
ensureDir(path.dirname(filePath));
|
|
20
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
21
|
+
}
|
|
22
|
+
/** Install dependencies for the project */
|
|
23
|
+
async function installDependencies(projectDir) {
|
|
24
|
+
return new Promise(resolve => {
|
|
25
|
+
// Detect package manager
|
|
26
|
+
let pm = "npm";
|
|
27
|
+
if (fs.existsSync(path.join(projectDir, "pnpm-lock.yaml"))) {
|
|
28
|
+
pm = "pnpm";
|
|
29
|
+
}
|
|
30
|
+
else if (fs.existsSync(path.join(projectDir, "yarn.lock"))) {
|
|
31
|
+
pm = "yarn";
|
|
32
|
+
}
|
|
33
|
+
else if (fs.existsSync(path.join(projectDir, "bun.lockb"))) {
|
|
34
|
+
pm = "bun";
|
|
35
|
+
}
|
|
36
|
+
const child = spawn(pm, ["install"], {
|
|
37
|
+
cwd: projectDir,
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
shell: true,
|
|
40
|
+
});
|
|
41
|
+
child.on("close", code => {
|
|
42
|
+
if (code === 0) {
|
|
43
|
+
resolve({ success: true });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
resolve({ success: false, error: `Exit code ${code}` });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
child.on("error", err => {
|
|
50
|
+
resolve({ success: false, error: err.message });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export const cloneCommand = new Command("clone")
|
|
55
|
+
.description("Clone a project from the database to local filesystem")
|
|
56
|
+
.argument("[project-id]", "Project ID to clone")
|
|
57
|
+
.option("-o, --output <dir>", "Output directory")
|
|
58
|
+
.option("--list", "List available projects")
|
|
59
|
+
.option("--no-install", "Skip installing dependencies")
|
|
60
|
+
.addHelpText("after", `
|
|
61
|
+
Examples:
|
|
62
|
+
$ magicappdev clone --list
|
|
63
|
+
$ magicappdev clone abc-123-def
|
|
64
|
+
$ magicappdev clone abc-123-def -o my-app
|
|
65
|
+
`)
|
|
66
|
+
.action(async (projectId, options) => {
|
|
67
|
+
logo();
|
|
68
|
+
header("Clone Project");
|
|
69
|
+
try {
|
|
70
|
+
// List projects mode
|
|
71
|
+
if (options.list) {
|
|
72
|
+
divider();
|
|
73
|
+
info("Fetching available projects...");
|
|
74
|
+
const projects = await withSpinner("Loading projects...", async () => {
|
|
75
|
+
return await api.listExportableProjects();
|
|
76
|
+
}, { successText: "Projects loaded" });
|
|
77
|
+
newline();
|
|
78
|
+
if (projects.length === 0) {
|
|
79
|
+
info("No projects available to clone.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
info(`Found ${projects.length} project(s):`);
|
|
83
|
+
newline();
|
|
84
|
+
for (const p of projects) {
|
|
85
|
+
divider();
|
|
86
|
+
keyValue("Name", p.name);
|
|
87
|
+
keyValue("ID", p.id);
|
|
88
|
+
keyValue("Framework", p.framework);
|
|
89
|
+
keyValue("Files", String(p.fileCount));
|
|
90
|
+
keyValue("Status", p.status);
|
|
91
|
+
keyValue("Updated", new Date(p.updatedAt).toLocaleDateString());
|
|
92
|
+
}
|
|
93
|
+
newline();
|
|
94
|
+
info("To clone a project, run:");
|
|
95
|
+
command(`magicappdev clone <project-id>`);
|
|
96
|
+
newline();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Validate project ID
|
|
100
|
+
if (!projectId) {
|
|
101
|
+
error("Project ID is required. Use --list to see available projects.");
|
|
102
|
+
newline();
|
|
103
|
+
info("Usage:");
|
|
104
|
+
command("magicappdev clone <project-id>");
|
|
105
|
+
command("magicappdev clone --list");
|
|
106
|
+
newline();
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
divider();
|
|
110
|
+
info(`Cloning project: ${projectId}`);
|
|
111
|
+
newline();
|
|
112
|
+
// Export project from database
|
|
113
|
+
const exportData = await withSpinner("Exporting project from database...", async () => {
|
|
114
|
+
return await api.exportProject(projectId);
|
|
115
|
+
}, { successText: "Project exported" });
|
|
116
|
+
newline();
|
|
117
|
+
info("Project details:");
|
|
118
|
+
keyValue("Name", exportData.project.name);
|
|
119
|
+
keyValue("Framework", exportData.project.framework);
|
|
120
|
+
keyValue("Files", String(exportData.metadata.fileCount));
|
|
121
|
+
keyValue("Size", `${(exportData.metadata.totalSize / 1024).toFixed(1)} KB`);
|
|
122
|
+
divider();
|
|
123
|
+
newline();
|
|
124
|
+
// Determine output directory
|
|
125
|
+
const outputDir = options.output || exportData.project.name;
|
|
126
|
+
const projectPath = path.resolve(process.cwd(), outputDir);
|
|
127
|
+
// Check if directory exists
|
|
128
|
+
if (fs.existsSync(projectPath)) {
|
|
129
|
+
error(`Directory already exists: ${outputDir}`);
|
|
130
|
+
newline();
|
|
131
|
+
info("Please choose a different output directory or remove the existing one.");
|
|
132
|
+
command(`rm -rf "${outputDir}"`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
// Create project directory
|
|
136
|
+
await withSpinner("Creating project directory...", async () => {
|
|
137
|
+
ensureDir(projectPath);
|
|
138
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // Small delay
|
|
139
|
+
}, { successText: "Directory created" });
|
|
140
|
+
newline();
|
|
141
|
+
// Write files
|
|
142
|
+
const writeFiles = async () => {
|
|
143
|
+
for (const file of exportData.files) {
|
|
144
|
+
const filePath = path.join(projectPath, file.path);
|
|
145
|
+
writeFile(filePath, file.content);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
await withSpinner(`Writing ${exportData.metadata.fileCount} files...`, writeFiles, { successText: `${exportData.metadata.fileCount} files written` });
|
|
149
|
+
newline();
|
|
150
|
+
success(`Project cloned successfully to: ${outputDir}`);
|
|
151
|
+
newline();
|
|
152
|
+
// Write export metadata
|
|
153
|
+
const metadataPath = path.join(projectPath, ".magicappdev.json");
|
|
154
|
+
writeFile(metadataPath, JSON.stringify({
|
|
155
|
+
version: exportData.version,
|
|
156
|
+
exportedAt: exportData.exportedAt,
|
|
157
|
+
project: exportData.project,
|
|
158
|
+
metadata: exportData.metadata,
|
|
159
|
+
}, null, 2));
|
|
160
|
+
// Install dependencies if requested
|
|
161
|
+
const shouldInstall = options.install !== false;
|
|
162
|
+
if (shouldInstall) {
|
|
163
|
+
// Check if package.json exists
|
|
164
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
165
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
166
|
+
newline();
|
|
167
|
+
const installResult = await withSpinner("Installing dependencies...", async () => installDependencies(projectPath), { successText: "Dependencies installed" });
|
|
168
|
+
if (!installResult.success) {
|
|
169
|
+
newline();
|
|
170
|
+
error(`Failed to install dependencies: ${installResult.error || "Unknown error"}`);
|
|
171
|
+
info("You can install them manually:");
|
|
172
|
+
command(`cd ${outputDir}`);
|
|
173
|
+
command("npm install"); // or pnpm install, yarn install, etc.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
newline();
|
|
178
|
+
info("Next steps:");
|
|
179
|
+
command(`cd ${outputDir}`);
|
|
180
|
+
// Suggest start command based on framework
|
|
181
|
+
if (exportData.project.framework === "expo" || exportData.project.framework === "react-native") {
|
|
182
|
+
command("npm start # or expo start");
|
|
183
|
+
}
|
|
184
|
+
else if (exportData.project.framework === "next") {
|
|
185
|
+
command("npm run dev");
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
command("npm start");
|
|
189
|
+
}
|
|
190
|
+
newline();
|
|
191
|
+
success("Happy coding!");
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
error(err instanceof Error ? err.message : "Failed to clone project");
|
|
195
|
+
newline();
|
|
196
|
+
info("Troubleshooting:");
|
|
197
|
+
command("magicappdev clone --list # Check available projects");
|
|
198
|
+
command("magicappdev auth login # Verify authentication");
|
|
199
|
+
newline();
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,eAAO,MAAM,aAAa,SAkLtB,CAAC"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -10,6 +10,11 @@ import { execa } from "execa";
|
|
|
10
10
|
export const doctorCommand = new Command("doctor")
|
|
11
11
|
.description("Diagnose project issues and check environment")
|
|
12
12
|
.option("--fix", "Attempt to fix issues automatically")
|
|
13
|
+
.addHelpText("after", `
|
|
14
|
+
Examples:
|
|
15
|
+
$ magicappdev doctor
|
|
16
|
+
$ magicappdev doctor --fix
|
|
17
|
+
`)
|
|
13
18
|
.action(async () => {
|
|
14
19
|
header("Project Diagnostics");
|
|
15
20
|
const checks = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/generate/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkBH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/generate/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkBH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,eAAe,SA4IzB,CAAC"}
|
|
@@ -9,6 +9,11 @@ import { Command } from "commander";
|
|
|
9
9
|
export const generateCommand = new Command("generate")
|
|
10
10
|
.alias("g")
|
|
11
11
|
.description("Generate code from templates")
|
|
12
|
+
.addHelpText("after", `
|
|
13
|
+
Examples:
|
|
14
|
+
$ magicappdev generate component MyButton
|
|
15
|
+
$ magicappdev generate screen SettingsScreen --path ./app/settings
|
|
16
|
+
`)
|
|
12
17
|
.addCommand(new Command("component")
|
|
13
18
|
.alias("c")
|
|
14
19
|
.description("Generate a new component")
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,SAoKpB,CAAC"}
|
package/dist/commands/init.js
CHANGED
|
@@ -49,6 +49,12 @@ export const initCommand = new Command("init")
|
|
|
49
49
|
.option("--typescript", "Use TypeScript", true)
|
|
50
50
|
.option("-y, --yes", "Skip prompts and use defaults")
|
|
51
51
|
.option("--no-install", "Skip installing dependencies")
|
|
52
|
+
.addHelpText("after", `
|
|
53
|
+
Examples:
|
|
54
|
+
$ magicappdev init my-app
|
|
55
|
+
$ magicappdev init my-app --template tabs --framework expo
|
|
56
|
+
$ magicappdev init my-app -y
|
|
57
|
+
`)
|
|
52
58
|
.action(async (name, options) => {
|
|
53
59
|
logo();
|
|
54
60
|
header("Create a new project");
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magicappdev/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "CLI tool for creating and managing MagicAppDev apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
7
7
|
"MagicAppDev",
|
|
8
|
-
"app-dev"
|
|
9
|
-
"mobile",
|
|
10
|
-
"react-native"
|
|
8
|
+
"app-dev"
|
|
11
9
|
],
|
|
12
10
|
"type": "module",
|
|
13
11
|
"main": "./dist/index.js",
|
|
@@ -31,7 +29,6 @@
|
|
|
31
29
|
"dependencies": {
|
|
32
30
|
"@magicappdev/shared": "workspace:*",
|
|
33
31
|
"@magicappdev/templates": "workspace:*",
|
|
34
|
-
"agents": "^0.3.6",
|
|
35
32
|
"chalk": "^5.4.0",
|
|
36
33
|
"commander": "^14.0.2",
|
|
37
34
|
"execa": "^9.5.0",
|