Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
153ff83
test log
JulienChampagnol Jun 18, 2026
73de597
fix(Perf): better microservices launch
JulienChampagnol Jun 19, 2026
bf34b61
fix(Perf): better microservices launch
JulienChampagnol Jun 19, 2026
8b8aff2
Merge branch 'fix/perf_run_microservices' of https://github.com/Geode…
JulienChampagnol Jun 19, 2026
6f45181
cleanup
JulienChampagnol Jun 19, 2026
36ab4fc
cleanup
JulienChampagnol Jun 19, 2026
7ed999f
Apply prepare changes
JulienChampagnol Jun 19, 2026
8aa8ef7
oxlint
JulienChampagnol Jun 19, 2026
078b044
Merge branch 'fix/perf_run_microservices' of https://github.com/Geode…
JulienChampagnol Jun 19, 2026
0504815
Apply prepare changes
JulienChampagnol Jun 19, 2026
14f2163
oxlint
JulienChampagnol Jun 22, 2026
ec00239
Apply prepare changes
JulienChampagnol Jun 22, 2026
3e46cae
broken import
JulienChampagnol Jun 22, 2026
914658e
Apply prepare changes
JulienChampagnol Jun 22, 2026
8e7d197
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
JulienChampagnol Jun 23, 2026
06f847e
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
JulienChampagnol Jun 24, 2026
beebc26
CollapseOrExpandAll
JulienChampagnol Jun 24, 2026
2fec093
runBrowser getAvailablePort
JulienChampagnol Jun 25, 2026
a19ca64
Apply prepare changes
JulienChampagnol Jun 25, 2026
ad3b064
cleanup
JulienChampagnol Jun 25, 2026
bcf7f3d
Merge branch 'fix/perf_run_microservices' of https://github.com/Geode…
JulienChampagnol Jun 25, 2026
aeac953
env
JulienChampagnol Jun 25, 2026
5f26611
Apply prepare changes
JulienChampagnol Jun 25, 2026
057127f
getPort
JulienChampagnol Jun 25, 2026
10ab62e
Merge branch 'fix/perf_run_microservices' of https://github.com/Geode…
JulienChampagnol Jun 25, 2026
fbf16c0
await getAvailablePort();
JulienChampagnol Jun 25, 2026
97d88e9
Apply prepare changes
JulienChampagnol Jun 25, 2026
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
76 changes: 39 additions & 37 deletions app/utils/local/microservices.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,18 @@ 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 { commandExistsSync, getAvailablePort, 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({
host: "localhost",
random: true,
});
function resolveCommand(execPath, execName) {
const command = commandExistsSync(execName) ? execName : executablePath(execPath, execName);
return command;
}

async function runScript(
Expand All @@ -30,33 +26,31 @@ async function runScript(
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 +67,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 +93,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 All @@ -121,4 +123,4 @@ function addMicroserviceMetadatas(projectFolderPath, serviceObj) {
);
}

export { addMicroserviceMetadatas, getAvailablePort, runBack, runViewer };
export { addMicroserviceMetadatas, runBack, runViewer };
100 changes: 91 additions & 9 deletions app/utils/local/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,100 @@ 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";

// Third party imports
import { getPort } from "get-port-please";

// Local imports
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 getAvailablePort() {
return getPort({
host: "localhost",
random: true,
});
}

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 Expand Up @@ -49,11 +125,17 @@ async function waitNuxt(nuxtProcess) {
async function runBrowser(scriptName) {
process.env.MODE = appMode.BROWSER;

const port = await getAvailablePort();

const nuxtProcess = child_process.spawn("npm", ["run", scriptName], {
shell: true,
FORCE_COLOR: true,
env: {
...process.env,
PORT: port,
},
});
return await waitNuxt(nuxtProcess);
}

export { runBrowser, waitForReady, commandExistsSync };
export { commandExistsSync, getAvailablePort, runBrowser, waitForReady };
Loading