openmagic 0.41.2 → 0.43.0
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.js +290 -213
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +1 -1
- package/dist/toolbar/index.global.js.map +1 -1
- package/package.json +4 -2
package/dist/cli.js
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc from "picocolors";
|
|
6
6
|
import open from "open";
|
|
7
7
|
import { resolve as resolve3, join as join6 } from "path";
|
|
8
8
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
9
9
|
import { spawn as spawn5, execSync as execSync2 } from "child_process";
|
|
10
10
|
import http2 from "http";
|
|
11
|
-
import { createInterface } from "readline";
|
|
11
|
+
import { createInterface, clearLine, cursorTo } from "readline";
|
|
12
|
+
import { createSpinner } from "nanospinner";
|
|
13
|
+
import terminalLink from "terminal-link";
|
|
12
14
|
|
|
13
15
|
// src/proxy.ts
|
|
14
16
|
import http from "http";
|
|
@@ -2868,13 +2870,106 @@ process.emitWarning = function(warning, ...args) {
|
|
|
2868
2870
|
if (typeof warning === "string" && warning.includes("util._extend")) return;
|
|
2869
2871
|
return origEmitWarning.call(process, warning, ...args);
|
|
2870
2872
|
};
|
|
2873
|
+
var INDENT = " ";
|
|
2874
|
+
var LABEL_WIDTH = 10;
|
|
2875
|
+
var activeStatusLine = false;
|
|
2876
|
+
function clearActiveStatus() {
|
|
2877
|
+
if (!activeStatusLine || !process.stdout.isTTY) return;
|
|
2878
|
+
clearLine(process.stdout, 0);
|
|
2879
|
+
cursorTo(process.stdout, 0);
|
|
2880
|
+
activeStatusLine = false;
|
|
2881
|
+
}
|
|
2882
|
+
function writeLine(line = "") {
|
|
2883
|
+
clearActiveStatus();
|
|
2884
|
+
process.stdout.write(line ? `${line}
|
|
2885
|
+
` : "\n");
|
|
2886
|
+
}
|
|
2887
|
+
function formatInfo(message) {
|
|
2888
|
+
return pc.dim(`${INDENT}${message}`);
|
|
2889
|
+
}
|
|
2890
|
+
function formatPending(message) {
|
|
2891
|
+
return pc.dim(`${INDENT}\u25CF ${message}`);
|
|
2892
|
+
}
|
|
2893
|
+
function formatSuccess(message) {
|
|
2894
|
+
return pc.green(`${INDENT}\u2713 ${message}`);
|
|
2895
|
+
}
|
|
2896
|
+
function formatReady(seconds = process.uptime()) {
|
|
2897
|
+
return pc.green(`${INDENT}\u2713 Ready in ${seconds.toFixed(1)}s`);
|
|
2898
|
+
}
|
|
2899
|
+
function formatWarning(message) {
|
|
2900
|
+
return pc.yellow(`${INDENT}\u25B2 ${message}`);
|
|
2901
|
+
}
|
|
2902
|
+
function formatError(message) {
|
|
2903
|
+
return pc.red(`${INDENT}\u2717 ${message}`);
|
|
2904
|
+
}
|
|
2905
|
+
function printInfo(message) {
|
|
2906
|
+
writeLine(formatInfo(message));
|
|
2907
|
+
}
|
|
2908
|
+
function printSuccess(message) {
|
|
2909
|
+
writeLine(formatSuccess(message));
|
|
2910
|
+
}
|
|
2911
|
+
function printWarning(message) {
|
|
2912
|
+
writeLine(formatWarning(message));
|
|
2913
|
+
}
|
|
2914
|
+
function printError(message) {
|
|
2915
|
+
writeLine(formatError(message));
|
|
2916
|
+
}
|
|
2917
|
+
function printDetail(message, formatter = pc.dim) {
|
|
2918
|
+
writeLine(formatter(`${INDENT} ${message}`));
|
|
2919
|
+
}
|
|
2920
|
+
function printCommand(message) {
|
|
2921
|
+
printDetail(message, pc.cyan);
|
|
2922
|
+
}
|
|
2923
|
+
function printLocation(label, value, brightValue = false) {
|
|
2924
|
+
const prefix = pc.dim(`${INDENT}\u279C ${`${label}:`.padEnd(LABEL_WIDTH)}`);
|
|
2925
|
+
const linked = value.startsWith("http") ? terminalLink(value, value) : value;
|
|
2926
|
+
const renderedValue = brightValue ? pc.bold(pc.white(linked)) : pc.dim(linked);
|
|
2927
|
+
writeLine(`${prefix}${renderedValue}`);
|
|
2928
|
+
}
|
|
2929
|
+
var activeSpinner = null;
|
|
2930
|
+
function startInlineStatus(message) {
|
|
2931
|
+
clearActiveStatus();
|
|
2932
|
+
activeSpinner = createSpinner(message).start();
|
|
2933
|
+
}
|
|
2934
|
+
function finishInlineStatus(message) {
|
|
2935
|
+
if (activeSpinner) {
|
|
2936
|
+
activeSpinner.success({ text: message });
|
|
2937
|
+
activeSpinner = null;
|
|
2938
|
+
} else writeLine(formatSuccess(message));
|
|
2939
|
+
}
|
|
2940
|
+
function warnInlineStatus(message) {
|
|
2941
|
+
if (activeSpinner) {
|
|
2942
|
+
activeSpinner.warn({ text: message });
|
|
2943
|
+
activeSpinner = null;
|
|
2944
|
+
} else writeLine(formatWarning(message));
|
|
2945
|
+
}
|
|
2946
|
+
function failInlineStatus(message) {
|
|
2947
|
+
if (activeSpinner) {
|
|
2948
|
+
activeSpinner.error({ text: message });
|
|
2949
|
+
activeSpinner = null;
|
|
2950
|
+
} else writeLine(formatError(message));
|
|
2951
|
+
}
|
|
2952
|
+
function finishInlineReady() {
|
|
2953
|
+
const msg = `Ready in ${process.uptime().toFixed(1)}s`;
|
|
2954
|
+
if (activeSpinner) {
|
|
2955
|
+
activeSpinner.success({ text: msg });
|
|
2956
|
+
activeSpinner = null;
|
|
2957
|
+
} else writeLine(formatReady());
|
|
2958
|
+
}
|
|
2959
|
+
function getDetectedFrameworkLabel() {
|
|
2960
|
+
if (detectedFramework) return detectedFramework;
|
|
2961
|
+
const scripts = detectDevScripts();
|
|
2962
|
+
return scripts.length > 0 ? scripts[0].framework : null;
|
|
2963
|
+
}
|
|
2871
2964
|
process.on("unhandledRejection", (err) => {
|
|
2872
|
-
|
|
2873
|
-
|
|
2965
|
+
writeLine();
|
|
2966
|
+
printError(`Unhandled error: ${err?.message || err}`);
|
|
2967
|
+
printDetail("Please report this at https://github.com/Kalmuraee/OpenMagic/issues");
|
|
2874
2968
|
});
|
|
2875
2969
|
process.on("uncaughtException", (err) => {
|
|
2876
|
-
|
|
2877
|
-
|
|
2970
|
+
writeLine();
|
|
2971
|
+
printError(`Fatal error: ${err.message}`);
|
|
2972
|
+
printDetail("Please report this at https://github.com/Kalmuraee/OpenMagic/issues");
|
|
2878
2973
|
process.exit(1);
|
|
2879
2974
|
});
|
|
2880
2975
|
try {
|
|
@@ -2886,11 +2981,13 @@ try {
|
|
|
2886
2981
|
}
|
|
2887
2982
|
const newLimit = parseInt(execSync2("ulimit -n", { encoding: "utf-8", shell: true }).trim(), 10);
|
|
2888
2983
|
if (newLimit < 4096) {
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2984
|
+
writeLine();
|
|
2985
|
+
printWarning(`File descriptor limit is ${fdLimit} (need 4096+).`);
|
|
2986
|
+
printDetail("This can cause EMFILE errors in large Next.js and Turbopack projects.");
|
|
2987
|
+
printDetail("Add this to your shell profile:");
|
|
2988
|
+
printCommand("ulimit -n 65536");
|
|
2989
|
+
printDetail("Then restart your terminal.");
|
|
2990
|
+
writeLine();
|
|
2894
2991
|
}
|
|
2895
2992
|
}
|
|
2896
2993
|
} catch {
|
|
@@ -2995,15 +3092,13 @@ function runCommand(cmd, args, cwd = process.cwd()) {
|
|
|
2995
3092
|
child.stdout?.on("data", (data) => {
|
|
2996
3093
|
const lines = data.toString().trim().split("\n");
|
|
2997
3094
|
for (const line of lines) {
|
|
2998
|
-
if (line.trim())
|
|
2999
|
-
`));
|
|
3095
|
+
if (line.trim()) writeLine(pc.dim(`${INDENT}\u2502 ${line}`));
|
|
3000
3096
|
}
|
|
3001
3097
|
});
|
|
3002
3098
|
child.stderr?.on("data", (data) => {
|
|
3003
3099
|
const lines = data.toString().trim().split("\n");
|
|
3004
3100
|
for (const line of lines) {
|
|
3005
|
-
if (line.trim())
|
|
3006
|
-
`));
|
|
3101
|
+
if (line.trim()) writeLine(pc.dim(`${INDENT}\u2502 ${line}`));
|
|
3007
3102
|
}
|
|
3008
3103
|
});
|
|
3009
3104
|
child.on("error", () => resolve4(false));
|
|
@@ -3021,20 +3116,17 @@ async function healthCheck(proxyPort, _targetPort) {
|
|
|
3021
3116
|
signal: controller.signal
|
|
3022
3117
|
});
|
|
3023
3118
|
clearTimeout(timeout);
|
|
3024
|
-
if (res.ok)
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
}
|
|
3119
|
+
if (res.ok) return null;
|
|
3120
|
+
return {
|
|
3121
|
+
message: "Proxy started, but the toolbar health check failed.",
|
|
3122
|
+
details: ["Try refreshing the page in a few seconds."]
|
|
3123
|
+
};
|
|
3029
3124
|
} catch {
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
chalk.dim(" Try refreshing the page in a few seconds.")
|
|
3035
|
-
);
|
|
3125
|
+
return {
|
|
3126
|
+
message: "Could not verify the proxy while it was starting.",
|
|
3127
|
+
details: ["Try refreshing the page in a few seconds."]
|
|
3128
|
+
};
|
|
3036
3129
|
}
|
|
3037
|
-
console.log("");
|
|
3038
3130
|
}
|
|
3039
3131
|
var detectedFramework = null;
|
|
3040
3132
|
async function validateAppHealth(targetHost, targetPort) {
|
|
@@ -3055,39 +3147,36 @@ async function validateAppHealth(targetHost, targetPort) {
|
|
|
3055
3147
|
continue;
|
|
3056
3148
|
}
|
|
3057
3149
|
if (status === 404) {
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
console.log("");
|
|
3150
|
+
printWarning("Your app returned 404 for the root path (/).");
|
|
3151
|
+
printDetail("The dev server is running, but no page matched the root path.");
|
|
3061
3152
|
if (detectedFramework === "Next.js") {
|
|
3062
3153
|
const strayLockfiles = scanParentLockfiles(process.cwd());
|
|
3063
3154
|
if (strayLockfiles.length > 0) {
|
|
3064
|
-
|
|
3155
|
+
printDetail("Found lockfiles in parent directories that can confuse Turbopack.");
|
|
3065
3156
|
for (const f of strayLockfiles) {
|
|
3066
|
-
|
|
3157
|
+
printDetail(`- ${f}`);
|
|
3067
3158
|
}
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
console.log(chalk.cyan(" turbopack: { root: __dirname }"));
|
|
3159
|
+
printDetail("Fix it by removing those lockfiles, or add this to next.config:");
|
|
3160
|
+
printCommand("turbopack: { root: __dirname }");
|
|
3071
3161
|
} else {
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3162
|
+
printDetail("Common Next.js causes:");
|
|
3163
|
+
printDetail("- Missing src/app/page.tsx (App Router) or pages/index.tsx");
|
|
3164
|
+
printDetail("- Middleware redirecting all routes to an auth provider");
|
|
3075
3165
|
}
|
|
3076
3166
|
} else if (detectedFramework === "Angular") {
|
|
3077
|
-
|
|
3167
|
+
printDetail("Angular hint: make sure the base href matches the proxy path.");
|
|
3078
3168
|
} else if (detectedFramework === "Vite") {
|
|
3079
|
-
|
|
3169
|
+
printDetail("Vite hint: check that index.html exists in the project root.");
|
|
3080
3170
|
} else {
|
|
3081
|
-
|
|
3171
|
+
printDetail("Check your framework's routing configuration.");
|
|
3082
3172
|
}
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
console.log("");
|
|
3173
|
+
printDetail("The toolbar is still available \u2014 navigate to a working route.");
|
|
3174
|
+
writeLine();
|
|
3086
3175
|
return false;
|
|
3087
3176
|
} else if (status >= 500) {
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3177
|
+
printWarning(`Your app returned HTTP ${status} on the root path.`);
|
|
3178
|
+
printDetail("There may be a server-side error. Check your dev server output.");
|
|
3179
|
+
writeLine();
|
|
3091
3180
|
if (attempt < 4) {
|
|
3092
3181
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
3093
3182
|
continue;
|
|
@@ -3112,12 +3201,9 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
3112
3201
|
"-r, --root <paths...>",
|
|
3113
3202
|
"Project root directories (defaults to cwd)"
|
|
3114
3203
|
).option("--no-open", "Don't auto-open browser").option("--host <host>", "Dev server host", "localhost").action(async (opts) => {
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
);
|
|
3119
|
-
console.log(chalk.dim(" AI coding toolbar for any web app"));
|
|
3120
|
-
console.log("");
|
|
3204
|
+
writeLine();
|
|
3205
|
+
writeLine(`${INDENT}${pc.white("OpenMagic")} ${pc.dim(`v${VERSION2}`)}`);
|
|
3206
|
+
writeLine();
|
|
3121
3207
|
let targetPort;
|
|
3122
3208
|
let targetHost = opts.host;
|
|
3123
3209
|
if (opts.port) {
|
|
@@ -3137,18 +3223,22 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
3137
3223
|
targetHost = recheck.host;
|
|
3138
3224
|
}
|
|
3139
3225
|
}
|
|
3226
|
+
const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
|
|
3227
|
+
printSuccess(`Found ${frameworkLabel} on port ${targetPort}`);
|
|
3140
3228
|
}
|
|
3141
3229
|
} else {
|
|
3142
|
-
|
|
3230
|
+
startInlineStatus("Scanning for dev server...");
|
|
3143
3231
|
const detected = await detectDevServer();
|
|
3144
3232
|
if (detected && detected.fromScripts) {
|
|
3145
3233
|
const healthy = await isPortHealthy(detected.host, detected.port);
|
|
3146
3234
|
if (healthy) {
|
|
3147
3235
|
targetPort = detected.port;
|
|
3148
3236
|
targetHost = detected.host;
|
|
3237
|
+
const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
|
|
3238
|
+
finishInlineStatus(`Found ${frameworkLabel} on port ${detected.port}`);
|
|
3149
3239
|
} else {
|
|
3150
|
-
|
|
3151
|
-
|
|
3240
|
+
warnInlineStatus(`Dev server on port ${detected.port} is not responding`);
|
|
3241
|
+
printDetail("Cleaning up the orphaned process...");
|
|
3152
3242
|
killPortProcess(detected.port);
|
|
3153
3243
|
const freed = await waitForPortClose(detected.port, 5e3);
|
|
3154
3244
|
if (!freed) {
|
|
@@ -3179,27 +3269,33 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
3179
3269
|
targetPort = redetected.port;
|
|
3180
3270
|
targetHost = redetected.host;
|
|
3181
3271
|
} else {
|
|
3182
|
-
|
|
3183
|
-
|
|
3272
|
+
printError("Could not detect the dev server after starting.");
|
|
3273
|
+
printDetail("Try specifying the port manually:");
|
|
3274
|
+
printCommand("npx openmagic --port 3000");
|
|
3184
3275
|
process.exit(1);
|
|
3185
3276
|
}
|
|
3186
3277
|
}
|
|
3278
|
+
const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
|
|
3279
|
+
printSuccess(`Found ${frameworkLabel} on port ${targetPort}`);
|
|
3187
3280
|
}
|
|
3188
3281
|
} else if (detected && !detected.fromScripts) {
|
|
3282
|
+
finishInlineStatus(`Found dev server on port ${detected.port}`);
|
|
3189
3283
|
const answer = await ask(
|
|
3190
|
-
|
|
3284
|
+
pc.yellow(`${INDENT}\u25B2 Found a server on port ${detected.port}. Is this your project's dev server? `) + pc.dim("(Y/n) ")
|
|
3191
3285
|
);
|
|
3192
3286
|
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes" || answer === "") {
|
|
3193
3287
|
targetPort = detected.port;
|
|
3194
3288
|
targetHost = detected.host;
|
|
3195
3289
|
} else {
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3290
|
+
writeLine(formatInfo("Cancelled dev server selection."));
|
|
3291
|
+
writeLine();
|
|
3292
|
+
printInfo("Start your dev server, then run:");
|
|
3293
|
+
printCommand("npx openmagic --port <your-port>");
|
|
3294
|
+
writeLine();
|
|
3200
3295
|
process.exit(0);
|
|
3201
3296
|
}
|
|
3202
3297
|
} else {
|
|
3298
|
+
warnInlineStatus("No dev server found. Starting one...");
|
|
3203
3299
|
const started = await offerToStartDevServer();
|
|
3204
3300
|
if (!started) {
|
|
3205
3301
|
process.exit(1);
|
|
@@ -3209,34 +3305,34 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
3209
3305
|
} else {
|
|
3210
3306
|
const redetected = await detectDevServer();
|
|
3211
3307
|
if (!redetected) {
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3308
|
+
printError("Could not detect the dev server after starting.");
|
|
3309
|
+
printDetail("Try specifying the port manually:");
|
|
3310
|
+
printCommand("npx openmagic --port 3000");
|
|
3311
|
+
writeLine();
|
|
3215
3312
|
process.exit(1);
|
|
3216
3313
|
}
|
|
3217
3314
|
targetPort = redetected.port;
|
|
3218
3315
|
targetHost = redetected.host;
|
|
3219
3316
|
}
|
|
3317
|
+
const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
|
|
3318
|
+
printSuccess(`Found ${frameworkLabel} on port ${targetPort}`);
|
|
3220
3319
|
}
|
|
3221
3320
|
}
|
|
3222
3321
|
if (!detectedFramework) {
|
|
3223
3322
|
const scripts = detectDevScripts();
|
|
3224
3323
|
if (scripts.length > 0) detectedFramework = scripts[0].framework;
|
|
3225
3324
|
}
|
|
3226
|
-
console.log(
|
|
3227
|
-
chalk.green(` \u2713 Dev server running at ${targetHost}:${targetPort}`)
|
|
3228
|
-
);
|
|
3229
3325
|
if (detectedFramework === "Next.js") {
|
|
3230
3326
|
const strayLockfiles = scanParentLockfiles(process.cwd());
|
|
3231
3327
|
if (strayLockfiles.length > 0) {
|
|
3232
|
-
|
|
3233
|
-
|
|
3328
|
+
writeLine();
|
|
3329
|
+
printWarning("Lockfiles found in parent directories.");
|
|
3234
3330
|
for (const f of strayLockfiles) {
|
|
3235
|
-
|
|
3331
|
+
printDetail(`- ${f}`);
|
|
3236
3332
|
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3333
|
+
printDetail("Next.js Turbopack may use the wrong workspace root and cause 404s.");
|
|
3334
|
+
printDetail("Fix it by removing those lockfiles, or add this to next.config:");
|
|
3335
|
+
printCommand("turbopack: { root: __dirname }");
|
|
3240
3336
|
}
|
|
3241
3337
|
}
|
|
3242
3338
|
const roots = (opts.root || [process.cwd()]).map(
|
|
@@ -3245,35 +3341,41 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
3245
3341
|
const config = loadConfig();
|
|
3246
3342
|
saveConfig({ ...config, roots, targetPort });
|
|
3247
3343
|
generateSessionToken();
|
|
3248
|
-
|
|
3344
|
+
const requestedProxyPort = parseInt(opts.listen, 10);
|
|
3345
|
+
let proxyPort = requestedProxyPort;
|
|
3249
3346
|
while (await isPortOpen(proxyPort)) {
|
|
3250
3347
|
proxyPort++;
|
|
3251
|
-
if (proxyPort >
|
|
3252
|
-
|
|
3348
|
+
if (proxyPort > requestedProxyPort + 100) {
|
|
3349
|
+
printError("Could not find an available port for the OpenMagic proxy.");
|
|
3253
3350
|
process.exit(1);
|
|
3254
3351
|
}
|
|
3255
3352
|
}
|
|
3353
|
+
if (proxyPort !== requestedProxyPort) {
|
|
3354
|
+
writeLine();
|
|
3355
|
+
printWarning(`Port ${requestedProxyPort} is in use \u2014 starting on ${proxyPort}`);
|
|
3356
|
+
}
|
|
3256
3357
|
const proxyServer = createProxyServer(targetHost, targetPort, roots);
|
|
3257
3358
|
proxyServer.listen(proxyPort, "localhost", async () => {
|
|
3258
3359
|
const proxyUrl = `http://localhost:${proxyPort}`;
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
);
|
|
3265
|
-
|
|
3266
|
-
await
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
);
|
|
3276
|
-
|
|
3360
|
+
const proxyWarning = await healthCheck(proxyPort, targetPort);
|
|
3361
|
+
const frameworkLabel = getDetectedFrameworkLabel();
|
|
3362
|
+
const targetLabel = frameworkLabel ? `${targetHost}:${targetPort} (${frameworkLabel})` : `${targetHost}:${targetPort}`;
|
|
3363
|
+
printLocation("Local", proxyUrl, true);
|
|
3364
|
+
printLocation("Target", targetLabel);
|
|
3365
|
+
writeLine();
|
|
3366
|
+
startInlineStatus("Waiting for app to compile...");
|
|
3367
|
+
await validateAppHealth(targetHost, targetPort);
|
|
3368
|
+
finishInlineReady();
|
|
3369
|
+
if (proxyWarning) {
|
|
3370
|
+
writeLine();
|
|
3371
|
+
printWarning(proxyWarning.message);
|
|
3372
|
+
for (const detail of proxyWarning.details || []) {
|
|
3373
|
+
printDetail(detail);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
writeLine();
|
|
3377
|
+
printInfo("Press Ctrl+C to stop");
|
|
3378
|
+
writeLine();
|
|
3277
3379
|
if (opts.open !== false) {
|
|
3278
3380
|
open(`http://localhost:${proxyPort}`).catch(() => {
|
|
3279
3381
|
});
|
|
@@ -3283,8 +3385,8 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
3283
3385
|
const shutdown = async () => {
|
|
3284
3386
|
if (shuttingDown) return;
|
|
3285
3387
|
shuttingDown = true;
|
|
3286
|
-
|
|
3287
|
-
|
|
3388
|
+
writeLine();
|
|
3389
|
+
printInfo("Shutting down OpenMagic...");
|
|
3288
3390
|
cleanupBackups();
|
|
3289
3391
|
proxyServer.close();
|
|
3290
3392
|
const alive = childProcesses.filter((cp) => cp.exitCode === null);
|
|
@@ -3300,7 +3402,7 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
3300
3402
|
if (targetPort && await isPortOpen(targetPort)) {
|
|
3301
3403
|
const freed = await waitForPortClose(targetPort, 4e3);
|
|
3302
3404
|
if (!freed) {
|
|
3303
|
-
|
|
3405
|
+
printInfo("Force-killing remaining processes...");
|
|
3304
3406
|
if (targetPort) {
|
|
3305
3407
|
try {
|
|
3306
3408
|
const pids = execSync2(`lsof -i :${targetPort} -sTCP:LISTEN -t 2>/dev/null`, {
|
|
@@ -3363,18 +3465,16 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3363
3465
|
if (scripts.length === 0) {
|
|
3364
3466
|
const htmlPath = join6(process.cwd(), "index.html");
|
|
3365
3467
|
if (existsSync6(htmlPath)) {
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
);
|
|
3369
|
-
console.log("");
|
|
3468
|
+
printInfo("No dev scripts found, but index.html was detected.");
|
|
3469
|
+
writeLine();
|
|
3370
3470
|
const answer = await ask(
|
|
3371
|
-
|
|
3471
|
+
pc.dim(`${INDENT}Serve this directory as a static site? `) + pc.dim("(Y/n) ")
|
|
3372
3472
|
);
|
|
3373
3473
|
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
|
3374
3474
|
return false;
|
|
3375
3475
|
}
|
|
3376
3476
|
const staticPort = expectedPort || 8080;
|
|
3377
|
-
|
|
3477
|
+
startInlineStatus(`Starting static server on port ${staticPort}...`);
|
|
3378
3478
|
const staticChild = spawn5("node", ["-e", `
|
|
3379
3479
|
const http = require("http");
|
|
3380
3480
|
const fs = require("fs");
|
|
@@ -3389,7 +3489,7 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3389
3489
|
res.writeHead(200, {"Content-Type": mimes[ext] || "application/octet-stream"});
|
|
3390
3490
|
res.end(data);
|
|
3391
3491
|
});
|
|
3392
|
-
}).listen(${staticPort}, "localhost"
|
|
3492
|
+
}).listen(${staticPort}, "localhost");
|
|
3393
3493
|
`], {
|
|
3394
3494
|
cwd: process.cwd(),
|
|
3395
3495
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3398,99 +3498,90 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3398
3498
|
childProcesses.push(staticChild);
|
|
3399
3499
|
staticChild.stdout?.on("data", (d) => {
|
|
3400
3500
|
for (const line of d.toString().trim().split("\n")) {
|
|
3401
|
-
if (line.trim())
|
|
3402
|
-
`));
|
|
3501
|
+
if (line.trim()) writeLine(pc.dim(`${INDENT}\u2502 ${line}`));
|
|
3403
3502
|
}
|
|
3404
3503
|
});
|
|
3405
3504
|
const up = await waitForPort(staticPort, 5e3);
|
|
3406
3505
|
if (up) {
|
|
3407
3506
|
lastDetectedPort = staticPort;
|
|
3408
|
-
|
|
3507
|
+
finishInlineStatus(`Static server running on port ${staticPort}`);
|
|
3409
3508
|
return true;
|
|
3410
3509
|
}
|
|
3411
|
-
|
|
3510
|
+
failInlineStatus("Static server failed to start");
|
|
3412
3511
|
return false;
|
|
3413
3512
|
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
);
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
console.log(chalk.cyan(" npx openmagic --port <your-port>"));
|
|
3420
|
-
console.log("");
|
|
3513
|
+
printWarning("No dev server detected and no dev scripts were found.");
|
|
3514
|
+
writeLine();
|
|
3515
|
+
printInfo("Start your dev server manually, then run:");
|
|
3516
|
+
printCommand("npx openmagic --port <your-port>");
|
|
3517
|
+
writeLine();
|
|
3421
3518
|
return false;
|
|
3422
3519
|
}
|
|
3423
3520
|
const deps = checkDependenciesInstalled();
|
|
3424
3521
|
if (!deps.installed) {
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
);
|
|
3428
|
-
console.log("");
|
|
3522
|
+
printWarning("node_modules was not found. Dependencies need to be installed.");
|
|
3523
|
+
writeLine();
|
|
3429
3524
|
const answer = await ask(
|
|
3430
|
-
|
|
3525
|
+
pc.white(`${INDENT}Run `) + pc.cyan(deps.installCommand) + pc.white("? ") + pc.dim("(Y/n) ")
|
|
3431
3526
|
);
|
|
3432
3527
|
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3528
|
+
writeLine();
|
|
3529
|
+
printInfo(`Run ${deps.installCommand} manually, then try again.`);
|
|
3530
|
+
writeLine();
|
|
3436
3531
|
return false;
|
|
3437
3532
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3533
|
+
writeLine();
|
|
3534
|
+
printInfo(`Installing dependencies with ${deps.packageManager}...`);
|
|
3440
3535
|
const [installCmd, ...installArgs] = deps.installCommand.split(" ");
|
|
3441
3536
|
const installed = await runCommand(installCmd, installArgs);
|
|
3442
3537
|
if (!installed) {
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3538
|
+
printError("Dependency installation failed.");
|
|
3539
|
+
printDetail(`Try running ${deps.installCommand} manually.`);
|
|
3540
|
+
writeLine();
|
|
3446
3541
|
return false;
|
|
3447
3542
|
}
|
|
3448
|
-
|
|
3449
|
-
|
|
3543
|
+
printSuccess("Dependencies installed.");
|
|
3544
|
+
writeLine();
|
|
3450
3545
|
}
|
|
3451
3546
|
let chosen = scripts[0];
|
|
3452
3547
|
if (scripts.length === 1) {
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
console.log(
|
|
3458
|
-
chalk.white(` Found `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.white(` in ${projectName}`) + chalk.dim(` (${chosen.framework})`)
|
|
3548
|
+
printWarning("No dev server detected.");
|
|
3549
|
+
writeLine();
|
|
3550
|
+
writeLine(
|
|
3551
|
+
pc.white(`${INDENT}Found `) + pc.cyan(`npm run ${chosen.name}`) + pc.white(` in ${projectName}`) + pc.dim(` (${chosen.framework})`)
|
|
3459
3552
|
);
|
|
3460
|
-
|
|
3461
|
-
|
|
3553
|
+
printDetail(`\u279C ${chosen.command}`);
|
|
3554
|
+
writeLine();
|
|
3462
3555
|
const answer = await ask(
|
|
3463
|
-
|
|
3556
|
+
pc.white(`${INDENT}Start it now? `) + pc.dim("(Y/n) ")
|
|
3464
3557
|
);
|
|
3465
3558
|
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3559
|
+
writeLine();
|
|
3560
|
+
printInfo("Start your dev server first, then run OpenMagic again.");
|
|
3561
|
+
writeLine();
|
|
3469
3562
|
return false;
|
|
3470
3563
|
}
|
|
3471
3564
|
} else {
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
console.log(
|
|
3477
|
-
chalk.white(` Found ${scripts.length} dev scripts in ${projectName}:`)
|
|
3565
|
+
printWarning("No dev server detected.");
|
|
3566
|
+
writeLine();
|
|
3567
|
+
writeLine(
|
|
3568
|
+
pc.white(`${INDENT}Found ${scripts.length} dev scripts in ${projectName}:`)
|
|
3478
3569
|
);
|
|
3479
|
-
|
|
3570
|
+
writeLine();
|
|
3480
3571
|
scripts.forEach((s, i) => {
|
|
3481
|
-
|
|
3482
|
-
|
|
3572
|
+
writeLine(
|
|
3573
|
+
pc.cyan(`${INDENT}${i + 1}. `) + pc.white(`npm run ${s.name}`) + pc.dim(` (${s.framework}, port ${s.defaultPort})`)
|
|
3483
3574
|
);
|
|
3484
|
-
|
|
3575
|
+
printDetail(s.command);
|
|
3485
3576
|
});
|
|
3486
|
-
|
|
3577
|
+
writeLine();
|
|
3487
3578
|
const answer = await ask(
|
|
3488
|
-
|
|
3579
|
+
pc.white(`${INDENT}Which one should OpenMagic start? `) + pc.dim(`(1-${scripts.length}, or n to cancel) `)
|
|
3489
3580
|
);
|
|
3490
3581
|
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no" || answer === "") {
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3582
|
+
writeLine();
|
|
3583
|
+
printInfo("Start your dev server first, then run OpenMagic again.");
|
|
3584
|
+
writeLine();
|
|
3494
3585
|
return false;
|
|
3495
3586
|
}
|
|
3496
3587
|
const idx = parseInt(answer, 10) - 1;
|
|
@@ -3503,13 +3594,13 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3503
3594
|
detectedFramework = chosen.framework;
|
|
3504
3595
|
const compat = checkNodeCompatibility(chosen.framework);
|
|
3505
3596
|
if (!compat.ok) {
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3597
|
+
writeLine();
|
|
3598
|
+
printError(compat.message);
|
|
3599
|
+
writeLine();
|
|
3600
|
+
printInfo("Switch Node.js before running:");
|
|
3601
|
+
printCommand("nvm use 20");
|
|
3602
|
+
printDetail("Then re-run: npx openmagic");
|
|
3603
|
+
writeLine();
|
|
3513
3604
|
return false;
|
|
3514
3605
|
}
|
|
3515
3606
|
let port = expectedPort || chosen.defaultPort;
|
|
@@ -3517,24 +3608,19 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3517
3608
|
if (await isPortOpen(port)) {
|
|
3518
3609
|
const owned = verifyPortOwnership(port, process.cwd());
|
|
3519
3610
|
if (owned === true) {
|
|
3520
|
-
|
|
3611
|
+
printSuccess(`Dev server already running on port ${port}`);
|
|
3521
3612
|
lastDetectedPort = port;
|
|
3522
3613
|
return true;
|
|
3523
3614
|
}
|
|
3524
3615
|
const altPort = await findAvailablePort(port + 1);
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
chalk.yellow(` \u26A0 Port ${port} is already in use by another process.`)
|
|
3528
|
-
);
|
|
3529
|
-
console.log(
|
|
3530
|
-
chalk.dim(` Starting on port ${altPort} instead.`)
|
|
3531
|
-
);
|
|
3616
|
+
writeLine();
|
|
3617
|
+
printWarning(`Port ${port} is in use \u2014 starting on ${altPort}`);
|
|
3532
3618
|
port = altPort;
|
|
3533
3619
|
portChanged = true;
|
|
3534
3620
|
}
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3621
|
+
writeLine();
|
|
3622
|
+
writeLine(
|
|
3623
|
+
pc.dim(`${INDENT}\u25CF Starting `) + pc.cyan(`npm run ${chosen.name}`) + (portChanged ? pc.dim(` (port ${port})`) : "") + pc.dim("...")
|
|
3538
3624
|
);
|
|
3539
3625
|
const depsInfo = checkDependenciesInstalled();
|
|
3540
3626
|
const runCmd = depsInfo.packageManager === "yarn" ? "yarn" : depsInfo.packageManager === "pnpm" ? "pnpm" : depsInfo.packageManager === "bun" ? "bun" : "npm";
|
|
@@ -3562,21 +3648,21 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3562
3648
|
}
|
|
3563
3649
|
});
|
|
3564
3650
|
} catch (e) {
|
|
3565
|
-
|
|
3651
|
+
printError(`Failed to start: ${e.message}`);
|
|
3566
3652
|
return false;
|
|
3567
3653
|
}
|
|
3568
3654
|
childProcesses.push(child);
|
|
3569
3655
|
let childExited = false;
|
|
3570
3656
|
child.on("error", (err) => {
|
|
3571
3657
|
childExited = true;
|
|
3572
|
-
|
|
3573
|
-
|
|
3658
|
+
writeLine();
|
|
3659
|
+
printError(`Failed to start: ${err.message}`);
|
|
3574
3660
|
});
|
|
3575
3661
|
child.on("exit", (code) => {
|
|
3576
3662
|
childExited = true;
|
|
3577
3663
|
if (code !== null && code !== 0) {
|
|
3578
|
-
|
|
3579
|
-
|
|
3664
|
+
writeLine();
|
|
3665
|
+
printError(`Dev server exited with code ${code}`);
|
|
3580
3666
|
}
|
|
3581
3667
|
});
|
|
3582
3668
|
process.once("exit", () => {
|
|
@@ -3589,13 +3675,11 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3589
3675
|
}
|
|
3590
3676
|
}
|
|
3591
3677
|
});
|
|
3592
|
-
|
|
3593
|
-
chalk.dim(` Waiting for dev server on port ${port}...`)
|
|
3594
|
-
);
|
|
3678
|
+
writeLine(formatPending(`Waiting for ${chosen.framework} on port ${port}...`));
|
|
3595
3679
|
const isUp = await waitForPort(port, 6e4, () => childExited);
|
|
3596
3680
|
if (isUp) {
|
|
3597
3681
|
lastDetectedPort = port;
|
|
3598
|
-
|
|
3682
|
+
printSuccess(`${chosen.framework} is listening on port ${port}`);
|
|
3599
3683
|
return true;
|
|
3600
3684
|
}
|
|
3601
3685
|
if (!childExited) {
|
|
@@ -3604,28 +3688,23 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3604
3688
|
if (await isPortOpen(scanPort)) {
|
|
3605
3689
|
const owned = verifyPortOwnership(scanPort, process.cwd());
|
|
3606
3690
|
if (owned === false) continue;
|
|
3607
|
-
|
|
3608
|
-
chalk.green(`
|
|
3609
|
-
\u2713 Dev server found on port ${scanPort}.`)
|
|
3610
|
-
);
|
|
3691
|
+
printSuccess(`Found ${chosen.framework} on port ${scanPort}`);
|
|
3611
3692
|
lastDetectedPort = scanPort;
|
|
3612
3693
|
return true;
|
|
3613
3694
|
}
|
|
3614
3695
|
}
|
|
3615
3696
|
}
|
|
3616
3697
|
if (childExited) {
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
);
|
|
3620
|
-
console.log("");
|
|
3698
|
+
printError("Dev server failed to start");
|
|
3699
|
+
writeLine();
|
|
3621
3700
|
try {
|
|
3622
3701
|
const pkgPath = join6(process.cwd(), "package.json");
|
|
3623
3702
|
if (existsSync6(pkgPath)) {
|
|
3624
3703
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
3625
3704
|
if (pkg.engines?.node) {
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3705
|
+
printWarning(`This project requires Node.js ${pkg.engines.node}`);
|
|
3706
|
+
printDetail(`You are running Node.js ${process.version}`);
|
|
3707
|
+
writeLine();
|
|
3629
3708
|
}
|
|
3630
3709
|
}
|
|
3631
3710
|
} catch {
|
|
@@ -3633,26 +3712,24 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
3633
3712
|
if (chosen?.framework) {
|
|
3634
3713
|
const compat2 = checkNodeCompatibility(chosen.framework);
|
|
3635
3714
|
if (!compat2.ok) {
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3715
|
+
printWarning(compat2.message);
|
|
3716
|
+
printDetail("Switch with:");
|
|
3717
|
+
printCommand("nvm use 20");
|
|
3718
|
+
writeLine();
|
|
3639
3719
|
}
|
|
3640
3720
|
}
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3721
|
+
writeLine(pc.white(`${INDENT}Options:`));
|
|
3722
|
+
printDetail("1. Fix the error above and try again");
|
|
3723
|
+
printDetail("2. Start the server manually, then run:");
|
|
3724
|
+
printCommand("npx openmagic --port <your-port>");
|
|
3725
|
+
writeLine();
|
|
3646
3726
|
return false;
|
|
3647
3727
|
}
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
);
|
|
3652
|
-
|
|
3653
|
-
console.log(chalk.dim(` Or start the server manually, then run:`));
|
|
3654
|
-
console.log(chalk.cyan(` npx openmagic --port <your-port>`));
|
|
3655
|
-
console.log("");
|
|
3728
|
+
printWarning("Could not find the dev server after 60s");
|
|
3729
|
+
printDetail("Check the output above for errors.");
|
|
3730
|
+
printDetail("Or start the server manually, then run:");
|
|
3731
|
+
printCommand("npx openmagic --port <your-port>");
|
|
3732
|
+
writeLine();
|
|
3656
3733
|
return false;
|
|
3657
3734
|
}
|
|
3658
3735
|
program.parse();
|