Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 38 additions & 28 deletions app/utils/local/microservices.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import { commandExistsSync, waitForReady } from "./scripts.js";
import { microservicesMetadatasPath, projectMicroservices } from "./cleanup.js";
import { executablePath } from "./path.js";

const DEFAULT_TIMEOUT_SECONDS = 60;
const MILLISECONDS_PER_SECOND = 1000;
const DEFAULT_TIMEOUT_SECONDS = 30;
const MAX_ERROR_BUFFER_BYTES = 64 * 1024;

function getAvailablePort() {
return getPort({
Expand All @@ -23,40 +24,41 @@ function getAvailablePort() {
});
}

function resolveCommand(execPath, execName) {
const command = commandExistsSync(execName) ? execName : executablePath(execPath, execName);
return command;
}

async function runScript(
execPath,
execName,
args,
expectedResponse,
timeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
) {
let command = "";
if (commandExistsSync(execName)) {
command = execName;
} else {
command = path.join(executablePath(execPath, execName));
}
const command = resolveCommand(execPath, execName);
console.log("runScript", command, args);

const child = child_process.spawn(process.platform === "win32" ? command : `"${command}"`, args, {
encoding: "utf8",
shell: true,
const child = child_process.spawn(command, args, {
stdio: ["ignore", "pipe", "pipe"],
});
child.stdout.on("data", (data) => console.log(`[${execName}] ${data.toString()}`));
child.stderr.on("data", (data) => console.log(`[${execName}] ${data.toString()}`));

child.on("close", (code) => console.log(`[${execName}] exited with code ${code}`));
child.on("kill", () => {
console.log(`[${execName}] process killed`);
});
child.name = command.replace(/^.*[\\/]/u, "");

child.on("spawn", () => {
console.log(`[${child.name}] spawned, pid=${child.pid}`);
});

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutSeconds * MILLISECONDS_PER_SECOND);
if (typeof timer.unref === "function") timer.unref();

try {
return await pTimeout(waitForReady(child, expectedResponse), {
milliseconds: timeoutSeconds * MILLISECONDS_PER_SECOND,
message: `Timed out after ${timeoutSeconds} seconds`,
});
const result = await waitForReady(child, expectedResponse, controller.signal);
clearTimeout(timer);
return result;
} catch (error) {
clearTimeout(timer);
child.kill();
throw error;
}
Expand All @@ -73,11 +75,16 @@ async function runBack(execName, execPath, args = {}) {
}
const port = await getAvailablePort();
const backArgs = [
`--port ${port}`,
`--data_folder_path ${projectFolderPath}`,
`--upload_folder_path ${uploadFolderPath}`,
`--allowed_origin http://localhost:*`,
`--timeout ${0}`,
"--port",
String(port),
"--data_folder_path",
projectFolderPath,
"--upload_folder_path",
uploadFolderPath,
"--allowed_origin",
"http://localhost:*",
"--timeout",
"0",
];
if (process.env.NODE_ENV === "development" || !process.env.NODE_ENV) {
backArgs.push("--debug");
Expand All @@ -94,9 +101,12 @@ async function runViewer(execName, execPath, args = {}) {
}
const port = await getAvailablePort();
const viewerArgs = [
`--port ${port}`,
`--data_folder_path ${projectFolderPath}`,
`--timeout ${0}`,
"--port",
String(port),
"--data_folder_path",
projectFolderPath,
"--timeout",
"0",
];
console.log("runViewer", execPath, execName, viewerArgs);
await runScript(execPath, execName, viewerArgs, "Starting factory");
Expand Down
72 changes: 64 additions & 8 deletions app/utils/local/scripts.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Node imports
import child_process from "node:child_process";
import fs from "node:fs";
import { on } from "node:events";
import path from "node:path";
import readline from "node:readline";

import { appMode } from "./app_mode.js";

const MAX_ERROR_BUFFER_BYTES = 64 * 1024;

Check failure on line 9 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(no-magic-numbers)

No magic number: 64

function commandExistsSync(execName) {
const envPath = process.env.PATH || "";
return envPath.split(path.delimiter).some((dir) => {
Expand All @@ -14,13 +16,67 @@
});
}

async function waitForReady(child, expectedResponse) {
for await (const [data] of on(child.stdout, "data")) {
if (data.toString().includes(expectedResponse)) {
return child;
}
}
throw new Error("Process closed before signal");
function waitForReady(child, expectedResponse, signal) {
return new Promise((resolve, reject) => {
const readlineStdout = readline.createInterface({ input: child.stdout });
const readlineStderr = readline.createInterface({ input: child.stderr });

let recentOutput = "";
const recordOutput = (line) => {
recentOutput = (recentOutput + line + "\n").slice(-MAX_ERROR_BUFFER_BYTES);
};

Check failure on line 27 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(func-style)

Expected a function declaration.

const cleanup = () => {
readlineStdout.removeAllListeners();
readlineStdout.close();
readlineStderr.removeAllListeners();
readlineStderr.close();
child.removeListener("error", onError);
child.removeListener("close", onClose);
if (signal) signal.removeEventListener("abort", onAbort);

Check failure on line 36 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(curly)

Expected { after 'if' condition.
};

Check failure on line 37 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(func-style)

Expected a function declaration.

const onLine = (line) => {
console.log(`[${child.name}] ${line}`);
recordOutput(line);
if (line.includes(expectedResponse)) {
cleanup();
resolve(child);
}
};

Check failure on line 46 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(func-style)

Expected a function declaration.

const onErrLine = (line) => {
console.log(`[${child.name}] ${line}`);
recordOutput(line);
};

Check failure on line 51 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(func-style)

Expected a function declaration.

const onError = (err) => {
cleanup();
reject(err);
};

Check failure on line 56 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(func-style)

Expected a function declaration.

const onClose = (code) => {
console.log(`[${child.name}] exited with code ${code}`);
cleanup();
reject(
new Error(
`[${child.name}] exited with code ${code} before becoming ready.` +
(recentOutput ? `\nRecent output:\n${recentOutput}` : ""),
),
);
};

Check failure on line 67 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(func-style)

Expected a function declaration.

const onAbort = () => {
cleanup();
reject(new Error(`[${child.name}] timed out waiting for "${expectedResponse}"`));
};

Check failure on line 72 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(func-style)

Expected a function declaration.

readlineStdout.on("line", onLine);
readlineStderr.on("line", onErrLine);
child.once("error", onError);
child.once("close", onClose);
if (signal) signal.addEventListener("abort", onAbort, { once: true });

Check failure on line 78 in app/utils/local/scripts.js

View workflow job for this annotation

GitHub Actions / test / oxlint

eslint(curly)

Expected { after 'if' condition.
});
}

async function waitNuxt(nuxtProcess) {
Expand Down
Loading