Add coexistence checks to all enqueue methods to prevent loading both React and Grid.js assets simultaneously. Changes: - ReactAdmin.php: Only enqueue React assets when ?react=1 - Init.php: Skip Grid.js when React active on admin pages - Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0 - Customer.php, Product.php, License.php: Add coexistence checks Now the toggle between Classic and React versions works correctly. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
214 lines
6.7 KiB
JavaScript
214 lines
6.7 KiB
JavaScript
'use strict';
|
|
|
|
var node_stream = require('node:stream');
|
|
var node_net = require('node:net');
|
|
var node_util = require('node:util');
|
|
var chalk = require('chalk');
|
|
var spawnd = require('spawnd');
|
|
var cwd = require('cwd');
|
|
var waitOn = require('wait-on');
|
|
var findProcess = require('find-process');
|
|
var treeKill = require('tree-kill');
|
|
var prompts = require('prompts');
|
|
|
|
const DEFAULT_CONFIG = {
|
|
debug: false,
|
|
options: {},
|
|
launchTimeout: 5000,
|
|
host: undefined,
|
|
port: undefined,
|
|
protocol: "tcp",
|
|
usedPortAction: "ask",
|
|
waitOnScheme: undefined
|
|
};
|
|
const resolveConfig = (config)=>{
|
|
return {
|
|
...DEFAULT_CONFIG,
|
|
...config
|
|
};
|
|
};
|
|
const pTreeKill = node_util.promisify(treeKill);
|
|
const serverLogPrefixer = new node_stream.Transform({
|
|
transform (chunk, _encoding, callback) {
|
|
this.push(chalk.magentaBright(`[jest-dev-server] ${chunk.toString()}`));
|
|
callback();
|
|
}
|
|
});
|
|
const ERROR_TIMEOUT = "ERROR_TIMEOUT";
|
|
const ERROR_PORT_USED = "ERROR_PORT_USED";
|
|
const ERROR_NO_COMMAND = "ERROR_NO_COMMAND";
|
|
class JestDevServerError extends Error {
|
|
code;
|
|
constructor(message, options){
|
|
// @ts-ignore - cause is not part of the Error constructor (yet)
|
|
super(message, options?.cause ? {
|
|
cause: options.cause
|
|
} : undefined);
|
|
this.code = options?.code;
|
|
}
|
|
}
|
|
const logProcDetection = (name, port)=>{
|
|
console.log(chalk.blue(`🕵️ Detecting a process "${name}" running on port "${port}"`));
|
|
};
|
|
const killProc = async (proc)=>{
|
|
console.log(chalk.yellow(`Killing process ${proc.name}...`));
|
|
await pTreeKill(proc.pid);
|
|
console.log(chalk.green(`Successfully killed process ${proc.name}`));
|
|
};
|
|
const spawnServer = (config)=>{
|
|
if (!config.command) {
|
|
throw new JestDevServerError("You must define a `command`", {
|
|
code: ERROR_NO_COMMAND
|
|
});
|
|
}
|
|
const proc = spawnd.spawnd(config.command, {
|
|
shell: true,
|
|
env: process.env,
|
|
cwd: cwd(),
|
|
...config.options
|
|
});
|
|
if (config.debug) {
|
|
console.log(chalk.magentaBright("\nJest dev-server output:"));
|
|
proc.stdout.pipe(serverLogPrefixer).pipe(process.stdout);
|
|
} else {
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
proc.stdout.on("data", ()=>{});
|
|
}
|
|
return proc;
|
|
};
|
|
const outOfStin = async (run)=>{
|
|
const { stdin } = process;
|
|
const listeners = stdin.listeners("data");
|
|
const result = await run();
|
|
// @ts-ignore
|
|
listeners.forEach((listener)=>stdin.on("data", listener));
|
|
stdin.setRawMode(true);
|
|
stdin.setEncoding("utf8");
|
|
stdin.resume();
|
|
return result;
|
|
};
|
|
const checkIsPortBusy = async (config)=>{
|
|
return new Promise((resolve)=>{
|
|
const server = node_net.createServer().once("error", (err)=>{
|
|
if (err.code === "EADDRINUSE") {
|
|
resolve(true);
|
|
} else {
|
|
resolve(false);
|
|
}
|
|
}).once("listening", ()=>{
|
|
server.once("close", ()=>resolve(false)).close();
|
|
}).listen(config.port, config.host);
|
|
});
|
|
};
|
|
const usedPortHandlers = {
|
|
error: (port)=>{
|
|
throw new JestDevServerError(`Port ${port} is in use`, {
|
|
code: ERROR_PORT_USED
|
|
});
|
|
},
|
|
kill: async (port)=>{
|
|
console.log("");
|
|
console.log(`Killing process listening to ${port}. On linux, this may require you to enter your password.`);
|
|
const [portProcess] = await findProcess("port", port);
|
|
logProcDetection(portProcess.name, port);
|
|
await killProc(portProcess);
|
|
return true;
|
|
},
|
|
ask: async (port)=>{
|
|
console.log("");
|
|
const answers = await outOfStin(()=>prompts({
|
|
type: "confirm",
|
|
name: "kill",
|
|
message: `Another process is listening on ${port}. Should I kill it for you? On linux, this may require you to enter your password.`,
|
|
initial: true
|
|
}));
|
|
if (answers.kill) {
|
|
const [portProcess] = await findProcess("port", port);
|
|
logProcDetection(portProcess.name, port);
|
|
await killProc(portProcess);
|
|
return true;
|
|
}
|
|
process.exit(1);
|
|
},
|
|
ignore: (port)=>{
|
|
console.log("");
|
|
console.log(`Port ${port} is already used. Assuming server is already running.`);
|
|
return false;
|
|
}
|
|
};
|
|
const handleUsedPort = async (config)=>{
|
|
if (config.port === undefined) return true;
|
|
if (!config.usedPortAction) {
|
|
throw new JestDevServerError(`Port ${config.port} is in use, but no action was provided to handle it. Please provide a "usedPortAction" in your config.`);
|
|
}
|
|
const isPortBusy = await checkIsPortBusy(config);
|
|
if (isPortBusy) {
|
|
const usedPortHandler = usedPortHandlers[config.usedPortAction];
|
|
return await usedPortHandler(config.port);
|
|
}
|
|
return true;
|
|
};
|
|
const checkIsTimeoutError = (err)=>{
|
|
return Boolean(err?.message?.startsWith("Timed out waiting for"));
|
|
};
|
|
const waitForServerToBeReady = async (config)=>{
|
|
if (config.port === undefined) return;
|
|
const { launchTimeout, protocol, host, port, path, waitOnScheme } = config;
|
|
let resource = `${host ?? "0.0.0.0"}:${port}`;
|
|
if (path) {
|
|
resource = `${resource}/${path}`;
|
|
}
|
|
let url;
|
|
if (protocol === "tcp" || protocol === "socket") {
|
|
url = `${protocol}:${resource}`;
|
|
} else {
|
|
url = `${protocol}://${resource}`;
|
|
}
|
|
const opts = {
|
|
resources: [
|
|
url
|
|
],
|
|
timeout: launchTimeout,
|
|
...waitOnScheme
|
|
};
|
|
try {
|
|
await waitOn(opts);
|
|
} catch (err) {
|
|
if (checkIsTimeoutError(err)) {
|
|
throw new JestDevServerError(`Server has taken more than ${launchTimeout}ms to start.`, {
|
|
code: ERROR_TIMEOUT
|
|
});
|
|
}
|
|
throw err;
|
|
}
|
|
};
|
|
const setupJestServer = async (providedConfig)=>{
|
|
const config = resolveConfig(providedConfig);
|
|
const shouldRunServer = await handleUsedPort(config);
|
|
if (shouldRunServer) {
|
|
const proc = spawnServer(config);
|
|
await waitForServerToBeReady(config);
|
|
return proc;
|
|
}
|
|
return null;
|
|
};
|
|
async function setup(providedConfigs) {
|
|
const configs = Array.isArray(providedConfigs) ? providedConfigs : [
|
|
providedConfigs
|
|
];
|
|
const procs = await Promise.all(configs.map((config)=>setupJestServer(config)));
|
|
return procs.filter(Boolean);
|
|
}
|
|
async function teardown(procs) {
|
|
if (procs.length) {
|
|
await Promise.all(procs.map((proc)=>proc.destroy()));
|
|
}
|
|
}
|
|
|
|
exports.ERROR_NO_COMMAND = ERROR_NO_COMMAND;
|
|
exports.ERROR_PORT_USED = ERROR_PORT_USED;
|
|
exports.ERROR_TIMEOUT = ERROR_TIMEOUT;
|
|
exports.JestDevServerError = JestDevServerError;
|
|
exports.setup = setup;
|
|
exports.teardown = teardown;
|