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