Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
17 changes: 4 additions & 13 deletions app/components/Viewer/ObjectTree/Base/Controls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,13 @@ watch(
</v-list>
</v-menu>
<ActionButton
v-if="!isCollapsed"
tooltip="Collapse All"
icon="mdi-collapse-all-outline"
data-testid="CollapseOrExpandAll"
:tooltip="isCollapsed ? 'Expand All' : 'Collapse All'"
:icon="isCollapsed ? 'mdi-expand-all-outline' : 'mdi-collapse-all-outline'"
variant="text"
color="black"
tooltipLocation="bottom"
@click="emit('collapse-all')"
/>
<ActionButton
v-else
tooltip="Expand All"
icon="mdi-expand-all-outline"
variant="text"
color="black"
tooltipLocation="bottom"
@click="emit('expand-all')"
@click="isCollapsed ? emit('expand-all') : emit('collapse-all')"
/>
</div>
</div>
Expand Down
68 changes: 39 additions & 29 deletions app/utils/local/microservices.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import path from "node:path";
// Third party imports
import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" with { type: "json" };
import { getPort } from "get-port-please";
import pTimeout from "p-timeout";

// Local imports
import { commandExistsSync, waitForReady } from "./scripts.js";
import { microservicesMetadatasPath, projectMicroservices } from "./cleanup.js";
import { executablePath } from "./path.js";

const DEFAULT_TIMEOUT_SECONDS = 120;
const MILLISECONDS_PER_SECOND = 1000;
const DEFAULT_TIMEOUT_SECONDS = 30;

function getAvailablePort() {
return getPort({
Expand All @@ -23,40 +22,43 @@ 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
81 changes: 73 additions & 8 deletions app/utils/local/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,89 @@ 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 BYTES_PER_KIBIBYTE = 1024;
const MAX_ERROR_BUFFER_KIBIBYTES = 64;
const MAX_ERROR_BUFFER_BYTES = MAX_ERROR_BUFFER_KIBIBYTES * BYTES_PER_KIBIBYTE;

function commandExistsSync(execName) {
const envPath = process.env.PATH || "";
return envPath.split(path.delimiter).some((dir) => {
const filePath = path.join(dir, execName);
return envPath.split(path.delimiter).some((directory) => {
const filePath = path.join(directory, execName);
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
});
}

async function waitForReady(child, expectedResponse) {
for await (const [data] of on(child.stdout, "data")) {
if (data.toString().includes(expectedResponse)) {
return child;
function waitForReady(child, expectedResponse, signal) {
// oxlint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => {
const readlineStdout = readline.createInterface({ input: child.stdout });
const readlineStderr = readline.createInterface({ input: child.stderr });

let recentOutput = "";
function recordOutput(line) {
recentOutput = `${recentOutput} ${line} \n`.slice(-MAX_ERROR_BUFFER_BYTES);
}
}
throw new Error("Process closed before signal");

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

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

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

function onError(err) {
cleanup();
reject(err);
}

function 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}` : ""
}`,
),
);
}

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

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

async function waitNuxt(nuxtProcess) {
Expand Down
Loading