From 4117bc0849d728ea3412e2198041d26b25e2ee7e Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Sun, 18 Jan 2026 13:18:38 +0100 Subject: [PATCH 01/28] install config --- app/utils/local.js | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/app/utils/local.js b/app/utils/local.js index ce34bfc0..ebbc2d4e 100644 --- a/app/utils/local.js +++ b/app/utils/local.js @@ -5,6 +5,7 @@ import child_process from "child_process" import WebSocket from "ws" // Third party imports +import config from "config" import { getPort } from "get-port-please" import isElectron from "is-electron" import pTimeout from "p-timeout" diff --git a/package.json b/package.json index dc5cf654..863ef3b4 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@vueuse/nuxt": "13.1.0", "@vueuse/rxjs": "^14.1.0", "ajv": "8.17.1", + "config": "4.2.0", "dexie": "3.2.4", "get-port-please": "3.2.0", "is-electron": "2.2.2", From 448cca42d01d6a0f739b3be559de26cdc11121dc Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Mon, 19 Jan 2026 14:24:52 +0100 Subject: [PATCH 02/28] test dynamic imports [skip ci] --- app/stores/app.js | 2 ++ app/utils/local.js | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/stores/app.js b/app/stores/app.js index a17a2aec..2aa63cd2 100644 --- a/app/stores/app.js +++ b/app/stores/app.js @@ -144,6 +144,8 @@ export const useAppStore = defineStore("app", () => { loadedExtensions.value.set(extensionId, extensionData) console.log(`[AppStore] Extension loaded successfully: ${extensionId}`) + const config = await import("config") + config.extensions.push({ id: extensionId, path }) return extensionModule } else { diff --git a/app/utils/local.js b/app/utils/local.js index ebbc2d4e..ce34bfc0 100644 --- a/app/utils/local.js +++ b/app/utils/local.js @@ -5,7 +5,6 @@ import child_process from "child_process" import WebSocket from "ws" // Third party imports -import config from "config" import { getPort } from "get-port-please" import isElectron from "is-electron" import pTimeout from "p-timeout" From 69d30584c16d8dc0eeae292dcd0d1a6b29ff4c87 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Tue, 20 Jan 2026 16:52:09 +0100 Subject: [PATCH 03/28] save [skip ci] --- app/stores/app.js | 3 --- app/utils/config.js | 35 +++++++++++++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 app/utils/config.js diff --git a/app/stores/app.js b/app/stores/app.js index 2aa63cd2..b2da9157 100644 --- a/app/stores/app.js +++ b/app/stores/app.js @@ -144,9 +144,6 @@ export const useAppStore = defineStore("app", () => { loadedExtensions.value.set(extensionId, extensionData) console.log(`[AppStore] Extension loaded successfully: ${extensionId}`) - const config = await import("config") - config.extensions.push({ id: extensionId, path }) - return extensionModule } else { throw new Error("Extension must export an install function") diff --git a/app/utils/config.js b/app/utils/config.js new file mode 100644 index 00000000..ee838246 --- /dev/null +++ b/app/utils/config.js @@ -0,0 +1,35 @@ +import Conf from "conf" + +const projectConfigSchema = { + name: { + type: "string", + minLength: 1, + required: true, + }, + path: { + type: "string", + format: "url", + required: true, + }, + additionalProperties: false, + required: ["name", "path"], +} + +function createProjectConfig(projectName) { + console.log(createProjectConfig.name, { config }) + + const config = new Conf({ projectName, schema: projectConfigSchema }) + console.log(createProjectConfig.name, { config }) + return config +} + +function getProjectConfig(projectName) { + console.log(getProjectConfig.name, { projectName }) + + const config = config.get(projectName) + console.log(getProjectConfig.name, { config }) + + return config +} + +export { createProjectConfig, getProjectConfig } diff --git a/package.json b/package.json index a06262c3..4c84f4b3 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@vueuse/nuxt": "13.1.0", "@vueuse/rxjs": "^14.1.0", "ajv": "8.17.1", - "config": "4.2.0", + "conf": "15.0.2", "dexie": "4.2.1", "get-port-please": "3.2.0", "is-electron": "2.2.2", From 7947bee22a0c8db507ecfd7572c9ca3077a4fd6f Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 30 Jan 2026 10:58:36 +0100 Subject: [PATCH 04/28] save wip --- app/utils/config.js | 35 ------------ app/utils/extension.js | 122 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 35 deletions(-) delete mode 100644 app/utils/config.js create mode 100644 app/utils/extension.js diff --git a/app/utils/config.js b/app/utils/config.js deleted file mode 100644 index ee838246..00000000 --- a/app/utils/config.js +++ /dev/null @@ -1,35 +0,0 @@ -import Conf from "conf" - -const projectConfigSchema = { - name: { - type: "string", - minLength: 1, - required: true, - }, - path: { - type: "string", - format: "url", - required: true, - }, - additionalProperties: false, - required: ["name", "path"], -} - -function createProjectConfig(projectName) { - console.log(createProjectConfig.name, { config }) - - const config = new Conf({ projectName, schema: projectConfigSchema }) - console.log(createProjectConfig.name, { config }) - return config -} - -function getProjectConfig(projectName) { - console.log(getProjectConfig.name, { projectName }) - - const config = config.get(projectName) - console.log(getProjectConfig.name, { config }) - - return config -} - -export { createProjectConfig, getProjectConfig } diff --git a/app/utils/extension.js b/app/utils/extension.js new file mode 100644 index 00000000..0341fcb5 --- /dev/null +++ b/app/utils/extension.js @@ -0,0 +1,122 @@ +// Node.js imports +import fs from "fs" +import path from "path" +import unzipper from "unzipper" + +// Third party imports +import Conf from "conf" + +const projectConfigSchema = { + name: { + type: "string", + minLength: 1, + required: true, + }, + path: { + type: "string", + format: "url", + required: true, + }, + additionalProperties: false, + required: ["name", "path"], +} + +function createProjectConfig(projectName) { + console.log(createProjectConfig.name, { config }) + + const config = new Conf({ projectName, schema: projectConfigSchema }) + console.log(createProjectConfig.name, { config }) + return config +} + +function getProjectConfig(projectName) { + console.log(getProjectConfig.name, { projectName }) + + const config = config.get(projectName) + console.log(getProjectConfig.name, { config }) + + return config +} + +// async function importExtension(vextFilePath, extensionsFolder) { +// // Validate file exists +// if (!fs.existsSync(vextFilePath)) { +// throw new Error("File does not exist") +// } + +// // Get and sanitize filename +// const filename = path.basename(vextFilePath) + +// if (!filename.toLowerCase().endsWith(".vext")) { +// throw new Error("File must be a .vext") +// } + +// // Create extensions directory +// await fs.promises.mkdir(extensionsFolder, { recursive: true }) + +// // Extract extension name +// const extensionName = filename.includes("-") +// ? filename.substring(0, filename.lastIndexOf("-")) +// : filename.replace(".vext", "") + +// const extensionPath = path.join(extensionsFolder, extensionName) + +// // Remove existing extension if present +// if (fs.existsSync(extensionPath)) { +// await fs.promises.rm(extensionPath, { recursive: true, force: true }) +// } + +// await fs.promises.mkdir(extensionPath, { recursive: true }) + +// // Extract the .vext file (which is a zip archive) +// await new Promise((resolve, reject) => { +// fs.createReadStream(vextFilePath) +// .pipe(unzipper.Extract({ path: extensionPath })) +// .on("close", resolve) +// .on("error", reject) +// }) + +// // Look for the backend executable and frontend JS +// let backendExecutable = null +// let frontendFile = null + +// const files = await fs.promises.readdir(extensionPath) + +// for (const file of files) { +// const filePath = path.join(extensionPath, file) +// const stats = await fs.promises.stat(filePath) + +// if (stats.isFile()) { +// if (file.endsWith(".es.js")) { +// frontendFile = filePath +// } else if (!file.endsWith(".js") && !file.endsWith(".css")) { +// backendExecutable = filePath +// // Make executable (chmod 755) +// await fs.promises.chmod(backendExecutable, 0o755) +// } +// } +// } + +// if (!frontendFile) { +// throw new Error("Invalid .vext file: missing frontend JavaScript") +// } + +// if (!backendExecutable) { +// throw new Error("Invalid .vext file: missing backend executable") +// } + +// // Read frontend content +// const frontendContent = await fs.promises.readFile(frontendFile, "utf-8") + +// return { +// extension_name: extensionName, +// frontend_content: frontendContent, +// backend_path: backendExecutable, +// } +// } + +export { + createProjectConfig, + // importExtension, + getProjectConfig, +} From 5cacd6f8b9b61e3790ea0390d121dc24a5a94379 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 11 Feb 2026 09:25:50 +0100 Subject: [PATCH 05/28] cleanup imports --- app/utils/extension.js | 86 +----------------------------------------- 1 file changed, 1 insertion(+), 85 deletions(-) diff --git a/app/utils/extension.js b/app/utils/extension.js index 0341fcb5..85c9e6ec 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -1,7 +1,4 @@ // Node.js imports -import fs from "fs" -import path from "path" -import unzipper from "unzipper" // Third party imports import Conf from "conf" @@ -38,85 +35,4 @@ function getProjectConfig(projectName) { return config } -// async function importExtension(vextFilePath, extensionsFolder) { -// // Validate file exists -// if (!fs.existsSync(vextFilePath)) { -// throw new Error("File does not exist") -// } - -// // Get and sanitize filename -// const filename = path.basename(vextFilePath) - -// if (!filename.toLowerCase().endsWith(".vext")) { -// throw new Error("File must be a .vext") -// } - -// // Create extensions directory -// await fs.promises.mkdir(extensionsFolder, { recursive: true }) - -// // Extract extension name -// const extensionName = filename.includes("-") -// ? filename.substring(0, filename.lastIndexOf("-")) -// : filename.replace(".vext", "") - -// const extensionPath = path.join(extensionsFolder, extensionName) - -// // Remove existing extension if present -// if (fs.existsSync(extensionPath)) { -// await fs.promises.rm(extensionPath, { recursive: true, force: true }) -// } - -// await fs.promises.mkdir(extensionPath, { recursive: true }) - -// // Extract the .vext file (which is a zip archive) -// await new Promise((resolve, reject) => { -// fs.createReadStream(vextFilePath) -// .pipe(unzipper.Extract({ path: extensionPath })) -// .on("close", resolve) -// .on("error", reject) -// }) - -// // Look for the backend executable and frontend JS -// let backendExecutable = null -// let frontendFile = null - -// const files = await fs.promises.readdir(extensionPath) - -// for (const file of files) { -// const filePath = path.join(extensionPath, file) -// const stats = await fs.promises.stat(filePath) - -// if (stats.isFile()) { -// if (file.endsWith(".es.js")) { -// frontendFile = filePath -// } else if (!file.endsWith(".js") && !file.endsWith(".css")) { -// backendExecutable = filePath -// // Make executable (chmod 755) -// await fs.promises.chmod(backendExecutable, 0o755) -// } -// } -// } - -// if (!frontendFile) { -// throw new Error("Invalid .vext file: missing frontend JavaScript") -// } - -// if (!backendExecutable) { -// throw new Error("Invalid .vext file: missing backend executable") -// } - -// // Read frontend content -// const frontendContent = await fs.promises.readFile(frontendFile, "utf-8") - -// return { -// extension_name: extensionName, -// frontend_content: frontendContent, -// backend_path: backendExecutable, -// } -// } - -export { - createProjectConfig, - // importExtension, - getProjectConfig, -} +export { createProjectConfig, getProjectConfig } From 2c62ee432b961de210ca12d6cfd6c8bd00687272 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 11 Feb 2026 18:01:03 +0100 Subject: [PATCH 06/28] wip unzip extension [skip ci] --- app/components/Step.vue | 4 +- app/stores/app.js | 2 +- app/utils/extension.js | 109 ++++++++++++++++++++++++++++++++-------- app/utils/local.js | 39 ++++++++++++++ package.json | 3 +- 5 files changed, 132 insertions(+), 25 deletions(-) diff --git a/app/components/Step.vue b/app/components/Step.vue index e71ebccc..8b5b04c6 100644 --- a/app/components/Step.vue +++ b/app/components/Step.vue @@ -56,7 +56,7 @@ color="transparent" class="d-flex flex-column justify-center ps-2" > - {{ steps[step_index].step_title }} - +

{ }) finalURL = URL.createObjectURL(newBlob) } - + /* @vite-ignore */ const extensionModule = await import(finalURL) if (finalURL !== path && finalURL.startsWith("blob:")) { diff --git a/app/utils/extension.js b/app/utils/extension.js index 85c9e6ec..3e9a90e5 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -2,37 +2,104 @@ // Third party imports import Conf from "conf" +import { getPort } from "get-port-please" +import _ from "lodash" + +// Local imports +import { unzipFile } from "./local.js" const projectConfigSchema = { - name: { - type: "string", - minLength: 1, - required: true, - }, - path: { - type: "string", - format: "url", - required: true, + properties: { + projectName: { + type: "string", + minLength: 1, + }, }, additionalProperties: false, - required: ["name", "path"], + required: ["projectName"], } -function createProjectConfig(projectName) { - console.log(createProjectConfig.name, { config }) +const MIN_PORT = 5001 +const MAX_PORT = 5999 +const KILL_TIMEOUT = 2000 - const config = new Conf({ projectName, schema: projectConfigSchema }) - console.log(createProjectConfig.name, { config }) - return config +const extensionProcesses = new Map() + +function projectConf(projectName) { + const projectConfig = new Conf({ projectName }) + // schema: projectConfigSchema + console.log(projectConf.name, { projectConfig }) + return projectConfig } -function getProjectConfig(projectName) { - console.log(getProjectConfig.name, { projectName }) +function extensionsConf(projectConfig) { + const extensionsConfig = projectConfig.get("extensions") + if (_.isEqual(extensionsConfig, {})) { + projectConfig.set("extensions", {}) + } + return extensionsConfig +} + +async function runExtensions(projectName) { + const projectConfig = projectConf(projectName) + console.log(runExtensions.name, { projectConfig }) + const extensionsConfig = extensionsConf(projectConfig) + console.log(runExtensions.name, { extensionsConfig }) + + if (_.isEqual(extensionsConfig, {})) { + return Promise.resolve() + } + console.log("Found extensions config for project:", projectName) + const promiseArray = [] + + for (const extensionId of Object.keys(extensionsConfig)) { + const { archivePath } = extensionsConfig[extensionId] + console.log(runExtensions.name, { archivePath }) - const config = config.get(projectName) - console.log(getProjectConfig.name, { config }) + await unzipFile(archivePath) - return config + try { + const port = await getPort({ portRange: [MIN_PORT, MAX_PORT] }) + console.log(`[Electron] Extension ${extensionId} will use port ${port}`) + extensionProcesses.set(extensionId, { process, port }) + + process.on("error", (error) => { + console.error(`[${extensionId}] Process error:`, error) + }) + + process.on("exit", (code) => { + console.log(`[${extensionId}] Process exited with code ${code}`) + extensionProcesses.delete(extensionId) + }) + + return { success: true, port } + } catch (error) { + console.error( + `[Extensions] Failed to launch extension ${extensionId}:`, + error, + ) + return { success: false, error: error.message } + } + } } -export { createProjectConfig, getProjectConfig } +function killExtensionMicroservices() { + return [...extensionProcesses.values()].map(async ({ process, _port }) => { + if (process && !process.killed) { + process.kill() + try { + // Wait for exit or timeout + await Promise.race([once(process, "exit"), setTimeout(KILL_TIMEOUT)]) + } catch { + // Ignore errors during kill wait + } + } + }) +} + +export { + projectConf, + extensionsConf, + runExtensions, + killExtensionMicroservices, +} diff --git a/app/utils/local.js b/app/utils/local.js index 54d29628..0bd8c383 100644 --- a/app/utils/local.js +++ b/app/utils/local.js @@ -5,6 +5,8 @@ import child_process from "child_process" import WebSocket from "ws" // Third party imports + +import JSZip from "jszip" import { getPort } from "get-port-please" import isElectron from "is-electron" import pTimeout from "p-timeout" @@ -334,6 +336,42 @@ async function run_browser( }) } +async function unzipFile( + zipFilePath, + outputDir = zipFilePath.replace(/\.[^/.]+$/, ""), // Remove the file extension +) { + console.log("Unzipping file...", zipFilePath, outputDir) + try { + const data = await fs.promises.readFile(zipFilePath) + const zip = await JSZip.loadAsync(data) + await fs.promises.mkdir(outputDir, { recursive: true }) + const promises = [] + + zip.forEach((relativePath, zipEntry) => { + const outputPath = path.join(outputDir, relativePath) + + if (zipEntry.dir) { + promises.push(fs.promises.mkdir(outputPath, { recursive: true })) + } else { + promises.push( + zipEntry.async("nodebuffer").then(async (content) => { + await fs.promises.mkdir(path.dirname(outputPath), { + recursive: true, + }) + await fs.promises.writeFile(outputPath, content) + }), + ) + } + }) + + await Promise.all(promises) + console.log("Extraction complete!") + } catch (error) { + console.error("Error unzipping file:", error) + throw error + } +} + export { create_path, delete_folder_recursive, @@ -346,4 +384,5 @@ export { run_back, run_viewer, run_browser, + unzipFile, } diff --git a/package.json b/package.json index 4c84f4b3..e74401db 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,12 @@ "@vueuse/nuxt": "13.1.0", "@vueuse/rxjs": "^14.1.0", "ajv": "8.17.1", - "conf": "15.0.2", + "conf": "15.1.0", "dexie": "4.2.1", "get-port-please": "3.2.0", "is-electron": "2.2.2", "js-file-download": "0.4.12", + "jszip": "^3.10.1", "nuxt": "4.2.2", "p-timeout": "7.0.1", "pinia": "3.0.4", From 814eaba3d91f723cbb950d9290628d349a781802 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Thu, 12 Feb 2026 12:07:12 +0100 Subject: [PATCH 07/28] wip js transform archive [skip ci] --- app/utils/extension.js | 55 +++++++++++++++++++++++++++++++++++++++++- app/utils/local.js | 1 + 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/app/utils/extension.js b/app/utils/extension.js index 3e9a90e5..ec3b81ca 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -1,4 +1,6 @@ // Node.js imports +import fs from "node:fs" +import path from "node:path" // Third party imports import Conf from "conf" @@ -56,7 +58,49 @@ async function runExtensions(projectName) { const { archivePath } = extensionsConfig[extensionId] console.log(runExtensions.name, { archivePath }) - await unzipFile(archivePath) + const unzippedExtensionPath = await unzipFile(archivePath) + + // Read and validate metadata.json + const metadataPath = path.join(unzippedExtensionPath, "metadata.json") + let metadata + try { + const metadataContent = await fs.promises.readFile(metadataPath, "utf-8") + metadata = JSON.parse(metadataContent) + } catch (error) { + throw new Error("Invalid .vext file: missing metadata.json") + } + + // Look for backend executable and frontend JS + let backendExecutable = null + let frontendFile = null + + const files = await fs.promises.readdir(unzippedExtensionPath) + + for (const file of files) { + const filePath = path.join(unzippedExtensionPath, file) + const stats = await fs.promises.stat(filePath) + + if (stats.isFile()) { + if (file.endsWith(".es.js")) { + frontendFile = filePath + } else if (!file.endsWith(".js") && !file.endsWith(".css")) { + backendExecutable = filePath + // Make executable (Unix-like systems) + await fs.promises.chmod(backendExecutable, 0o755) + } + } + } + + // Validate required files + if (!frontendFile) { + throw new Error("Invalid .vext file: missing frontend JavaScript") + } + if (!backendExecutable) { + throw new Error("Invalid .vext file: missing backend executable") + } + + // Read frontend content + const frontendContent = await fs.promises.readFile(frontendFile, "utf-8") try { const port = await getPort({ portRange: [MIN_PORT, MAX_PORT] }) @@ -72,6 +116,15 @@ async function runExtensions(projectName) { extensionProcesses.delete(extensionId) }) + const test = { + extension_metadata: { + name: metadata.name, + version: metadata.version, + }, + frontend_content: frontendContent, + backend_path: backendExecutable, + } + console.log("[Extensions] test return :", { test }) return { success: true, port } } catch (error) { console.error( diff --git a/app/utils/local.js b/app/utils/local.js index 0bd8c383..6451cb0f 100644 --- a/app/utils/local.js +++ b/app/utils/local.js @@ -366,6 +366,7 @@ async function unzipFile( await Promise.all(promises) console.log("Extraction complete!") + return outputDir } catch (error) { console.error("Error unzipping file:", error) throw error From 5cdbcf56f3a2f73c00d8f8e339ec037046dac4f9 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Tue, 17 Feb 2026 09:44:57 +0100 Subject: [PATCH 08/28] unzip in projectFolderPath --- app/utils/extension.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/utils/extension.js b/app/utils/extension.js index ec3b81ca..76f670a3 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -42,7 +42,7 @@ function extensionsConf(projectConfig) { return extensionsConfig } -async function runExtensions(projectName) { +async function runExtensions(projectName, projectFolderPath) { const projectConfig = projectConf(projectName) console.log(runExtensions.name, { projectConfig }) const extensionsConfig = extensionsConf(projectConfig) @@ -58,7 +58,10 @@ async function runExtensions(projectName) { const { archivePath } = extensionsConfig[extensionId] console.log(runExtensions.name, { archivePath }) - const unzippedExtensionPath = await unzipFile(archivePath) + const unzippedExtensionPath = await unzipFile( + archivePath, + projectFolderPath, + ) // Read and validate metadata.json const metadataPath = path.join(unzippedExtensionPath, "metadata.json") From debd97b28e08947ca4e85005923b43ab7fce33d4 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Thu, 19 Feb 2026 10:37:16 +0100 Subject: [PATCH 09/28] uploadExtension --- app/utils/extension.js | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/app/utils/extension.js b/app/utils/extension.js index 76f670a3..008404df 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -36,12 +36,41 @@ function projectConf(projectName) { function extensionsConf(projectConfig) { const extensionsConfig = projectConfig.get("extensions") - if (_.isEqual(extensionsConfig, {})) { - projectConfig.set("extensions", {}) + console.log(extensionsConf.name, { extensionsConfig }) + if (_.isEqual(extensionsConfig, undefined)) { + projectConfig.set("extensions", []) } return extensionsConfig } +async function uploadExtension( + archiveFileContent, + archiveFilename, + projectName, +) { + console.log(uploadExtension.name, { + archiveFileContent, + archiveFilename, + projectName, + }) + console.log("uploadExtension", { archiveFilename, projectName }) + const projectConfig = projectConf(projectName) + console.log("uploadExtension", projectConfig.path) + const extensionsConfig = extensionsConf(projectConfig) + console.log("uploadExtension", { extensionsConfig }) + + const configFolderPath = path.dirname(projectConfig.path) + const outputPath = path.join(configFolderPath, archiveFilename) + console.log(uploadExtension.name, { outputPath }) + await fs.promises.writeFile(outputPath, archiveFileContent) + + extensionsConfig.push(outputPath) + console.log("uploadExtension", { extensionsConfig }) + projectConfig.set("extensions", extensionsConfig) + + console.log("Extension uploaded successfully", extensionsConfig) +} + async function runExtensions(projectName, projectFolderPath) { const projectConfig = projectConf(projectName) console.log(runExtensions.name, { projectConfig }) @@ -156,6 +185,7 @@ function killExtensionMicroservices() { export { projectConf, extensionsConf, + uploadExtension, runExtensions, killExtensionMicroservices, } From ab0d9d4e4fb1474bb1479ce73010ee58b4639621 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Thu, 19 Feb 2026 17:05:10 +0100 Subject: [PATCH 10/28] cleanup [skip ci] --- app/utils/extension.js | 81 ++++++++++++------------------------------ 1 file changed, 23 insertions(+), 58 deletions(-) diff --git a/app/utils/extension.js b/app/utils/extension.js index 008404df..7b896575 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -48,44 +48,29 @@ async function uploadExtension( archiveFilename, projectName, ) { - console.log(uploadExtension.name, { - archiveFileContent, - archiveFilename, - projectName, - }) - console.log("uploadExtension", { archiveFilename, projectName }) const projectConfig = projectConf(projectName) - console.log("uploadExtension", projectConfig.path) const extensionsConfig = extensionsConf(projectConfig) - console.log("uploadExtension", { extensionsConfig }) - const configFolderPath = path.dirname(projectConfig.path) const outputPath = path.join(configFolderPath, archiveFilename) - console.log(uploadExtension.name, { outputPath }) + await fs.promises.writeFile(outputPath, archiveFileContent) extensionsConfig.push(outputPath) - console.log("uploadExtension", { extensionsConfig }) projectConfig.set("extensions", extensionsConfig) - - console.log("Extension uploaded successfully", extensionsConfig) } async function runExtensions(projectName, projectFolderPath) { const projectConfig = projectConf(projectName) - console.log(runExtensions.name, { projectConfig }) + console.log("runExtensions", { projectConfig }) const extensionsConfig = extensionsConf(projectConfig) - console.log(runExtensions.name, { extensionsConfig }) + console.log("runExtensions", { extensionsConfig }) if (_.isEqual(extensionsConfig, {})) { return Promise.resolve() } - console.log("Found extensions config for project:", projectName) - const promiseArray = [] - for (const extensionId of Object.keys(extensionsConfig)) { - const { archivePath } = extensionsConfig[extensionId] - console.log(runExtensions.name, { archivePath }) + for (const archivePath of extensionsConfig) { + console.log("runExtensions", { archivePath }) const unzippedExtensionPath = await unzipFile( archivePath, @@ -102,62 +87,42 @@ async function runExtensions(projectName, projectFolderPath) { throw new Error("Invalid .vext file: missing metadata.json") } - // Look for backend executable and frontend JS - let backendExecutable = null - let frontendFile = null - - const files = await fs.promises.readdir(unzippedExtensionPath) - - for (const file of files) { - const filePath = path.join(unzippedExtensionPath, file) - const stats = await fs.promises.stat(filePath) + const { name, version, backendExecutablePath, frontendFilePath } = metadata - if (stats.isFile()) { - if (file.endsWith(".es.js")) { - frontendFile = filePath - } else if (!file.endsWith(".js") && !file.endsWith(".css")) { - backendExecutable = filePath - // Make executable (Unix-like systems) - await fs.promises.chmod(backendExecutable, 0o755) - } - } - } - - // Validate required files - if (!frontendFile) { + if (!frontendFilePath) { throw new Error("Invalid .vext file: missing frontend JavaScript") } - if (!backendExecutable) { + if (!backendExecutablePath) { throw new Error("Invalid .vext file: missing backend executable") } - - // Read frontend content - const frontendContent = await fs.promises.readFile(frontendFile, "utf-8") + const frontendFileAbsolutePath = path.resolve(frontendFilePath) + const frontendContent = await fs.promises.readFile( + frontendFileAbsolutePath, + "utf-8", + ) try { const port = await getPort({ portRange: [MIN_PORT, MAX_PORT] }) - console.log(`[Electron] Extension ${extensionId} will use port ${port}`) - extensionProcesses.set(extensionId, { process, port }) + console.log(`[Electron] Extension ${name} will use port ${port}`) + extensionProcesses.set(name, { process, port }) process.on("error", (error) => { - console.error(`[${extensionId}] Process error:`, error) + console.error(`[${name}] Process error:`, error) }) process.on("exit", (code) => { - console.log(`[${extensionId}] Process exited with code ${code}`) - extensionProcesses.delete(extensionId) + console.log(`[${name}] Process exited with code ${code}`) + extensionProcesses.delete(name) }) const test = { - extension_metadata: { - name: metadata.name, - version: metadata.version, - }, - frontend_content: frontendContent, - backend_path: backendExecutable, + success: true, + extension_metadata: { name, version }, + frontendContent, + backendPort: port, } console.log("[Extensions] test return :", { test }) - return { success: true, port } + return test } catch (error) { console.error( `[Extensions] Failed to launch extension ${extensionId}:`, From d05084d84354ae7a1f70ddd031ed857edf28bc15 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Thu, 19 Feb 2026 17:05:11 +0100 Subject: [PATCH 11/28] fixed dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e74401db..a7465cf7 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "get-port-please": "3.2.0", "is-electron": "2.2.2", "js-file-download": "0.4.12", - "jszip": "^3.10.1", + "jszip": "3.10.1", "nuxt": "4.2.2", "p-timeout": "7.0.1", "pinia": "3.0.4", From 510a9509a090b4cbac3f03194e5efbae7faa0c7e Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Fri, 20 Feb 2026 09:38:32 +0100 Subject: [PATCH 12/28] test separate conf --- app/utils/config.js | 38 ++++++++++++++++++++++++++++++++++ app/utils/extension.js | 47 +++++++----------------------------------- 2 files changed, 45 insertions(+), 40 deletions(-) create mode 100644 app/utils/config.js diff --git a/app/utils/config.js b/app/utils/config.js new file mode 100644 index 00000000..1ab792ff --- /dev/null +++ b/app/utils/config.js @@ -0,0 +1,38 @@ +// Node.js imports + +// Third party imports +import Conf from "conf" +import _ from "lodash" + +// Local imports + +const projectConfigSchema = { + properties: { + projectName: { + type: "string", + minLength: 1, + }, + }, + additionalProperties: false, + required: ["projectName"], +} + +const extensionProcesses = new Map() + +function projectConf(projectName) { + const projectConfig = new Conf({ projectName }) + // schema: projectConfigSchema + console.log(projectConf.name, { projectConfig }) + return projectConfig +} + +function extensionsConf(projectConfig) { + const extensionsConfig = projectConfig.get("extensions") + console.log(extensionsConf.name, { extensionsConfig }) + if (_.isEqual(extensionsConfig, undefined)) { + projectConfig.set("extensions", []) + } + return extensionsConfig +} + +export { projectConf, extensionsConf, extensionProcesses } diff --git a/app/utils/extension.js b/app/utils/extension.js index 7b896575..a8982d58 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -3,45 +3,12 @@ import fs from "node:fs" import path from "node:path" // Third party imports -import Conf from "conf" import { getPort } from "get-port-please" import _ from "lodash" // Local imports import { unzipFile } from "./local.js" - -const projectConfigSchema = { - properties: { - projectName: { - type: "string", - minLength: 1, - }, - }, - additionalProperties: false, - required: ["projectName"], -} - -const MIN_PORT = 5001 -const MAX_PORT = 5999 -const KILL_TIMEOUT = 2000 - -const extensionProcesses = new Map() - -function projectConf(projectName) { - const projectConfig = new Conf({ projectName }) - // schema: projectConfigSchema - console.log(projectConf.name, { projectConfig }) - return projectConfig -} - -function extensionsConf(projectConfig) { - const extensionsConfig = projectConfig.get("extensions") - console.log(extensionsConf.name, { extensionsConfig }) - if (_.isEqual(extensionsConfig, undefined)) { - projectConfig.set("extensions", []) - } - return extensionsConfig -} +import { projectConf, extensionsConf } from "./config" async function uploadExtension( archiveFileContent, @@ -87,23 +54,23 @@ async function runExtensions(projectName, projectFolderPath) { throw new Error("Invalid .vext file: missing metadata.json") } - const { name, version, backendExecutablePath, frontendFilePath } = metadata + const { name, version, backendExecutable, frontendFile } = metadata - if (!frontendFilePath) { + if (!frontendFile) { throw new Error("Invalid .vext file: missing frontend JavaScript") } - if (!backendExecutablePath) { + if (!backendExecutable) { throw new Error("Invalid .vext file: missing backend executable") } - const frontendFileAbsolutePath = path.resolve(frontendFilePath) + const frontendFilePath = path.join(unzippedExtensionPath, frontendFile) const frontendContent = await fs.promises.readFile( - frontendFileAbsolutePath, + frontendFilePath, "utf-8", ) try { const port = await getPort({ portRange: [MIN_PORT, MAX_PORT] }) - console.log(`[Electron] Extension ${name} will use port ${port}`) + console.log(`Extension ${name} will use port ${port}`) extensionProcesses.set(name, { process, port }) process.on("error", (error) => { From 45693d3e134ddc91c61f870d5b9c5d7de3385d54 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 25 Feb 2026 11:08:44 +0100 Subject: [PATCH 13/28] save wip [skip ci] --- app/utils/config.js | 45 +++--- app/utils/extension.js | 221 +++++++++++++------------- app/utils/local.js | 40 +---- app/utils/server.js | 45 ++++++ nuxt.config.js | 6 + package.json | 1 + server/api/routes/extensions.put.js | 80 ++++++++++ server/api/routes/import_extension.js | 0 8 files changed, 270 insertions(+), 168 deletions(-) create mode 100644 app/utils/server.js create mode 100644 server/api/routes/extensions.put.js create mode 100644 server/api/routes/import_extension.js diff --git a/app/utils/config.js b/app/utils/config.js index 1ab792ff..776988ee 100644 --- a/app/utils/config.js +++ b/app/utils/config.js @@ -1,4 +1,5 @@ // Node.js imports +import path from "node:path" // Third party imports import Conf from "conf" @@ -6,33 +7,37 @@ import _ from "lodash" // Local imports -const projectConfigSchema = { - properties: { - projectName: { - type: "string", - minLength: 1, - }, - }, - additionalProperties: false, - required: ["projectName"], -} - -const extensionProcesses = new Map() - function projectConf(projectName) { + console.log("projectConf", { projectName }) const projectConfig = new Conf({ projectName }) - // schema: projectConfigSchema console.log(projectConf.name, { projectConfig }) return projectConfig } -function extensionsConf(projectConfig) { - const extensionsConfig = projectConfig.get("extensions") - console.log(extensionsConf.name, { extensionsConfig }) - if (_.isEqual(extensionsConfig, undefined)) { +function confFolderPath(projectName) { + console.log("confFolderPath", { projectName }) + const projectConfig = projectConf(projectName) + console.log("confFolderPath", { projectConfig }) + return path.dirname(projectConfig.path) +} + +function extensionsConf(projectName) { + console.log("extensionsConf", { projectName }) + const projectConfig = projectConf(projectName) + if (!projectConfig.has("extensions")) { projectConfig.set("extensions", []) } - return extensionsConfig + + const extensionsConfig = projectConfig.get("extensions") + console.log("extensionsConf", { extensionsConfig }) + return { extensionsConfig, path: extensionsConfig } +} + +function addExtensionToConf(projectName, extensionPath) { + const projectConfig = projectConf(projectName) + const { extensionsConfig } = extensionsConf(projectName) + extensionsConfig.push(extensionPath) + projectConfig.set("extensions", extensionsConfig) } -export { projectConf, extensionsConf, extensionProcesses } +export { confFolderPath, projectConf, extensionsConf, addExtensionToConf } diff --git a/app/utils/extension.js b/app/utils/extension.js index a8982d58..99a47355 100644 --- a/app/utils/extension.js +++ b/app/utils/extension.js @@ -1,123 +1,126 @@ // Node.js imports -import fs from "node:fs" -import path from "node:path" +// import fs from "node:fs" +// import path from "node:path" // Third party imports -import { getPort } from "get-port-please" +// import { getPort } from "get-port-please" +import { $fetch } from "ofetch" import _ from "lodash" // Local imports -import { unzipFile } from "./local.js" -import { projectConf, extensionsConf } from "./config" - -async function uploadExtension( - archiveFileContent, - archiveFilename, - projectName, -) { - const projectConfig = projectConf(projectName) - const extensionsConfig = extensionsConf(projectConfig) - const configFolderPath = path.dirname(projectConfig.path) - const outputPath = path.join(configFolderPath, archiveFilename) - - await fs.promises.writeFile(outputPath, archiveFileContent) - - extensionsConfig.push(outputPath) - projectConfig.set("extensions", extensionsConfig) -} -async function runExtensions(projectName, projectFolderPath) { - const projectConfig = projectConf(projectName) - console.log("runExtensions", { projectConfig }) - const extensionsConfig = extensionsConf(projectConfig) - console.log("runExtensions", { extensionsConfig }) - - if (_.isEqual(extensionsConfig, {})) { - return Promise.resolve() - } - - for (const archivePath of extensionsConfig) { - console.log("runExtensions", { archivePath }) - - const unzippedExtensionPath = await unzipFile( - archivePath, - projectFolderPath, - ) - - // Read and validate metadata.json - const metadataPath = path.join(unzippedExtensionPath, "metadata.json") - let metadata - try { - const metadataContent = await fs.promises.readFile(metadataPath, "utf-8") - metadata = JSON.parse(metadataContent) - } catch (error) { - throw new Error("Invalid .vext file: missing metadata.json") - } - - const { name, version, backendExecutable, frontendFile } = metadata - - if (!frontendFile) { - throw new Error("Invalid .vext file: missing frontend JavaScript") - } - if (!backendExecutable) { - throw new Error("Invalid .vext file: missing backend executable") - } - const frontendFilePath = path.join(unzippedExtensionPath, frontendFile) - const frontendContent = await fs.promises.readFile( - frontendFilePath, - "utf-8", - ) - - try { - const port = await getPort({ portRange: [MIN_PORT, MAX_PORT] }) - console.log(`Extension ${name} will use port ${port}`) - extensionProcesses.set(name, { process, port }) - - process.on("error", (error) => { - console.error(`[${name}] Process error:`, error) - }) - - process.on("exit", (code) => { - console.log(`[${name}] Process exited with code ${code}`) - extensionProcesses.delete(name) - }) - - const test = { - success: true, - extension_metadata: { name, version }, - frontendContent, - backendPort: port, - } - console.log("[Extensions] test return :", { test }) - return test - } catch (error) { - console.error( - `[Extensions] Failed to launch extension ${extensionId}:`, - error, - ) - return { success: false, error: error.message } - } - } -} +const MIN_PORT = 5001 +const MAX_PORT = 5999 +const extensionProcesses = new Map() + +async function uploadExtension(archiveFileContent, archiveFilename) { + console.log("uploadExtension", { + archiveFileContent, + archiveFilename, + // projectName, + }) -function killExtensionMicroservices() { - return [...extensionProcesses.values()].map(async ({ process, _port }) => { - if (process && !process.killed) { - process.kill() - try { - // Wait for exit or timeout - await Promise.race([once(process, "exit"), setTimeout(KILL_TIMEOUT)]) - } catch { - // Ignore errors during kill wait - } - } + const formData = new FormData() + formData.append("file", archiveFileContent) + formData.append("filename", archiveFilename) + // formData.append("projectName", projectName) + const response = await $fetch("/api/routes/extensions", { + method: "PUT", + body: formData, }) + + console.log("uploadExtension", { response }) } +// async function runExtensions(projectName, projectFolderPath) { +// console.log("runExtensions", { projectName, projectFolderPath }) + +// const extensionsConfig = extensionsConf(projectName) + +// console.log("runExtensions", { extensionsConfig }) + +// if (_.isEqual(extensionsConfig, {})) { +// return Promise.resolve() +// } + +// for (const archivePath of extensionsConfig) { +// console.log("runExtensions", { archivePath }) + +// const unzippedExtensionPath = await unzipFile( +// archivePath, +// projectFolderPath, +// ) + +// // Read and validate metadata.json +// const metadataPath = path.join(unzippedExtensionPath, "metadata.json") +// let metadata +// try { +// const metadataContent = await fs.promises.readFile(metadataPath, "utf-8") +// metadata = JSON.parse(metadataContent) +// } catch (error) { +// throw new Error("Invalid .vext file: missing metadata.json") +// } + +// const { name, version, backendExecutable, frontendFile } = metadata + +// if (!frontendFile) { +// throw new Error("Invalid .vext file: missing frontend JavaScript") +// } +// if (!backendExecutable) { +// throw new Error("Invalid .vext file: missing backend executable") +// } +// const frontendFilePath = path.join(unzippedExtensionPath, frontendFile) +// const frontendContent = await fs.promises.readFile( +// frontendFilePath, +// "utf-8", +// ) + +// try { +// const port = await getPort({ portRange: [MIN_PORT, MAX_PORT] }) +// console.log(`Extension ${name} will use port ${port}`) +// extensionProcesses.set(name, { process, port }) + +// process.on("error", (error) => { +// console.error(`[${name}] Process error:`, error) +// }) + +// process.on("exit", (code) => { +// console.log(`[${name}] Process exited with code ${code}`) +// extensionProcesses.delete(name) +// }) + +// const test = { +// success: true, +// extension_metadata: { name, version }, +// frontendContent, +// backendPort: port, +// } +// console.log("[Extensions] test return :", { test }) +// return test +// } catch (error) { +// console.error(`[Extensions] Failed to launch extension ${name}:`, error) +// return { success: false, error: error.message } +// } +// } +// } + +// function killExtensionMicroservices() { +// return [...extensionProcesses.values()].map(async ({ process, _port }) => { +// if (process && !process.killed) { +// process.kill() +// try { +// // Wait for exit or timeout +// await Promise.race([once(process, "exit"), setTimeout(KILL_TIMEOUT)]) +// } catch { +// // Ignore errors during kill wait +// } +// } +// }) +// } + export { - projectConf, - extensionsConf, uploadExtension, - runExtensions, - killExtensionMicroservices, + // runExtensions, + // killExtensionMicroservices, + extensionProcesses, } diff --git a/app/utils/local.js b/app/utils/local.js index 5743fe1c..33efe598 100644 --- a/app/utils/local.js +++ b/app/utils/local.js @@ -4,7 +4,7 @@ import fs from "node:fs" import path from "node:path" // Third party imports -import JSZip from "jszip" + import { WebSocket } from "ws" import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" with { type: "json" } import { getPort } from "get-port-please" @@ -346,43 +346,6 @@ async function run_browser( return wait_nuxt(nuxt_process, back_port, viewer_port) } -async function unzipFile( - zipFilePath, - outputDir = zipFilePath.replace(/\.[^/.]+$/, ""), // Remove the file extension -) { - console.log("Unzipping file...", zipFilePath, outputDir) - try { - const data = await fs.promises.readFile(zipFilePath) - const zip = await JSZip.loadAsync(data) - await fs.promises.mkdir(outputDir, { recursive: true }) - const promises = [] - - zip.forEach((relativePath, zipEntry) => { - const outputPath = path.join(outputDir, relativePath) - - if (zipEntry.dir) { - promises.push(fs.promises.mkdir(outputPath, { recursive: true })) - } else { - promises.push( - zipEntry.async("nodebuffer").then(async (content) => { - await fs.promises.mkdir(path.dirname(outputPath), { - recursive: true, - }) - await fs.promises.writeFile(outputPath, content) - }), - ) - } - }) - - await Promise.all(promises) - console.log("Extraction complete!") - return outputDir - } catch (error) { - console.error("Error unzipping file:", error) - throw error - } -} - export { create_path, delete_folder_recursive, @@ -395,5 +358,4 @@ export { run_back, run_viewer, run_browser, - unzipFile, } diff --git a/app/utils/server.js b/app/utils/server.js new file mode 100644 index 00000000..122340aa --- /dev/null +++ b/app/utils/server.js @@ -0,0 +1,45 @@ +// Node imports +import fs from "node:fs" +import path from "node:path" + +// Third party imports +import JSZip from "jszip" + +async function unzipFile( + zipFilePath, + outputDir = zipFilePath.replace(/\.[^/.]+$/, ""), // Remove the file extension +) { + console.log("Unzipping file...", zipFilePath, outputDir) + try { + const data = await fs.promises.readFile(zipFilePath) + const zip = await JSZip.loadAsync(data) + await fs.promises.mkdir(outputDir, { recursive: true }) + const promises = [] + + zip.forEach((relativePath, zipEntry) => { + const outputPath = path.join(outputDir, relativePath) + + if (zipEntry.dir) { + promises.push(fs.promises.mkdir(outputPath, { recursive: true })) + } else { + promises.push( + zipEntry.async("nodebuffer").then(async (content) => { + await fs.promises.mkdir(path.dirname(outputPath), { + recursive: true, + }) + await fs.promises.writeFile(outputPath, content) + }), + ) + } + }) + + await Promise.all(promises) + console.log("Extraction complete!") + return outputDir + } catch (error) { + console.error("Error unzipping file:", error) + throw error + } +} + +export { unzipFile } diff --git a/nuxt.config.js b/nuxt.config.js index c9ef0257..7b41bfa3 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -51,4 +51,10 @@ export default defineNuxtConfig({ ], }, }, + + nitro: { + routeRules: { + "/api/routes/extensions": { bodySize: 100 * 1024 * 1024 }, // 100MB + }, + }, }) diff --git a/package.json b/package.json index a7465cf7..242339b6 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@vueuse/nuxt": "13.1.0", "@vueuse/rxjs": "^14.1.0", "ajv": "8.17.1", + "busboy": "1.6.0", "conf": "15.1.0", "dexie": "4.2.1", "get-port-please": "3.2.0", diff --git a/server/api/routes/extensions.put.js b/server/api/routes/extensions.put.js new file mode 100644 index 00000000..02557a30 --- /dev/null +++ b/server/api/routes/extensions.put.js @@ -0,0 +1,80 @@ +// Node imports +import fs from "node:fs" +import path from "node:path" +import { Readable } from "node:stream" + +// Third party imports +import busboy from "busboy" +import { defineEventHandler, getRequestHeaders, getRequestWebStream } from "h3" + +// Local imports +import { + confFolderPath, + addExtensionToConf, +} from "../../../app/utils/config.js" +// import { useRuntimeConfig } from "#imports" + +const CODE_201 = 201 + +export default defineEventHandler(async (event) => { + console.log("[TIME] defineEventHandler", Date.now()) + // const config = useRuntimeConfig(event).public + // const { APP_NAME } = config + // console.log("defineEventHandler", { APP_NAME }) + const projectName = "vease" + + const configFolderPath = confFolderPath(projectName) + // const params = await readMultipartFormData(event) + console.log("[TIME] readMultipartFormData", Date.now()) + + const headers = getRequestHeaders(event) + + await new Promise((resolve, reject) => { + const bb = busboy({ headers }) + + bb.on("file", (name, stream, info) => { + const outputPath = path.join(confFolderPath("vease"), info.filename) + stream.pipe(fs.createWriteStream(outputPath)) + }) + + bb.on("close", resolve) + bb.on("error", reject) + + const rawStream = getRequestWebStream(event) + // pipe web stream to busboy + Readable.fromWeb(rawStream).pipe(bb) + }) + + // const fileData = params.find((item) => item.name === "fileContent") + // const nameData = params.find((item) => item.name === "filename") + + // const fileContent = fileData?.data + // const filename = nameData?.data.toString() // Convert Buffer to string + + // const fileContent = params.find((item) => { + // if (item.name === "fileContent") { + // return item.data + // } + // }) + // const filename = params.find((item) => { + // if (item.name === "filename") { + // return item.data.toString() + // } + // }) + // console.log("filename", { filename }) + // console.log("defineEventHandler", { + // projectName, + // fileContent, + // filename, + // }) + const outputPath = path.join(configFolderPath, filename) + console.log("[TIME] fs.promises.writeFile", Date.now()) + + await fs.promises.writeFile(outputPath, fileContent) + console.log("[TIME] fs.awaitpromises.writeFile", Date.now()) + + await addExtensionToConf(projectName, outputPath) + console.log("[TIME] addExtensionToConf", Date.now()) + + return { statusCode: CODE_201 } +}) diff --git a/server/api/routes/import_extension.js b/server/api/routes/import_extension.js new file mode 100644 index 00000000..e69de29b From 93a685096f04c67afb6f11a78a9a638f4c1c5dd7 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Thu, 26 Feb 2026 15:56:33 +0100 Subject: [PATCH 14/28] wip launch microservices [skip ci] --- app/components/FileUploader.vue | 6 +- app/stores/app.js | 50 ++++++ app/stores/geode.js | 32 +++- app/stores/infra.js | 16 +- app/stores/lambda.js | 3 +- app/utils/extension.js | 148 +++++---------- app/utils/local.js | 168 +++++++++--------- {app => internal}/utils/upload_file.js | 13 +- nuxt.config.js | 18 +- server/api/routes/extensions.put.js | 80 --------- server/api/routes/extensions/import.post.js | 92 ++++++++++ server/api/routes/extensions/index.put.js | 98 ++++++++++ server/api/routes/import_extension.js | 0 server/api/routes/microservices/index.get.js | 20 +++ .../microservices/run_back.post copy.js | 12 ++ .../api/routes/microservices/run_back.post.js | 10 ++ tests/integration/setup.js | 39 +--- tests/unit/stores/Lambda.nuxt.test.js | 4 +- 18 files changed, 482 insertions(+), 327 deletions(-) rename {app => internal}/utils/upload_file.js (82%) delete mode 100644 server/api/routes/extensions.put.js create mode 100644 server/api/routes/extensions/import.post.js create mode 100644 server/api/routes/extensions/index.put.js delete mode 100644 server/api/routes/import_extension.js create mode 100644 server/api/routes/microservices/index.get.js create mode 100644 server/api/routes/microservices/run_back.post copy.js create mode 100644 server/api/routes/microservices/run_back.post.js diff --git a/app/components/FileUploader.vue b/app/components/FileUploader.vue index b36ae79d..4fd089b3 100644 --- a/app/components/FileUploader.vue +++ b/app/components/FileUploader.vue @@ -1,7 +1,7 @@