diff --git a/packages/electron-app/electron.vite.config.ts b/packages/electron-app/electron.vite.config.ts index 105b2d3f..1f3f7ede 100644 --- a/packages/electron-app/electron.vite.config.ts +++ b/packages/electron-app/electron.vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig, externalizeDepsPlugin } from "electron-vite" import solid from "vite-plugin-solid" import { resolve } from "path" -import fs from "fs" +import { copyMonacoPublicAssets } from "../ui/scripts/monaco-public-assets.js" const uiRoot = resolve(__dirname, "../ui") const uiSrc = resolve(uiRoot, "src") @@ -9,88 +9,28 @@ const uiRendererRoot = resolve(uiRoot, "src/renderer") const uiRendererEntry = resolve(uiRendererRoot, "index.html") const uiRendererLoadingEntry = resolve(uiRendererRoot, "loading.html") -function copyMonacoPublicAssets(opts: { warn: (message: string) => void }) { - const publicDir = resolve(uiRendererRoot, "public") - const destRoot = resolve(publicDir, "monaco/vs") - - const candidates = [ - // Workspace root hoisted deps. - resolve(__dirname, "../../node_modules/monaco-editor/min/vs"), - // UI package local deps. - resolve(uiRoot, "node_modules/monaco-editor/min/vs"), - ] - const sourceRoot = candidates.find((p) => fs.existsSync(resolve(p, "loader.js"))) - if (!sourceRoot) { - opts.warn("Monaco source directory not found; skipping copy") - return - } - - const copyRecursive = (src: string, dest: string) => { - const stat = fs.statSync(src) - if (stat.isDirectory()) { - fs.mkdirSync(dest, { recursive: true }) - for (const entry of fs.readdirSync(src)) { - copyRecursive(resolve(src, entry), resolve(dest, entry)) - } - return - } - fs.copyFileSync(src, dest) - } - - // Keep the working tree clean; these assets are generated. - try { - fs.rmSync(destRoot, { recursive: true, force: true }) - } catch { - // ignore - } - fs.mkdirSync(destRoot, { recursive: true }) - - // Copy core Monaco runtime. - for (const dir of ["base", "editor", "platform"] as const) { - const src = resolve(sourceRoot, dir) - if (fs.existsSync(src)) { - copyRecursive(src, resolve(destRoot, dir)) - } - } - - // loader.js is required. - copyRecursive(resolve(sourceRoot, "loader.js"), resolve(destRoot, "loader.js")) - - // Copy baseline rich language packages + workers. - for (const lang of ["typescript", "html", "json", "css"] as const) { - const src = resolve(sourceRoot, "language", lang) - if (fs.existsSync(src)) { - copyRecursive(src, resolve(destRoot, "language", lang)) - } - } - - // Copy baseline basic tokenizers. - for (const lang of ["python", "markdown", "cpp", "kotlin"] as const) { - const src = resolve(sourceRoot, "basic-languages", lang) - if (fs.existsSync(src)) { - copyRecursive(src, resolve(destRoot, "basic-languages", lang)) - } - } - - // Copy monaco.contribution.js entrypoints (needed by some loads). - const monacoContribution = resolve(sourceRoot, "basic-languages", "monaco.contribution.js") - if (fs.existsSync(monacoContribution)) { - copyRecursive(monacoContribution, resolve(destRoot, "basic-languages", "monaco.contribution.js")) - } - const underscoreContribution = resolve(sourceRoot, "basic-languages", "_.contribution.js") - if (fs.existsSync(underscoreContribution)) { - copyRecursive(underscoreContribution, resolve(destRoot, "basic-languages", "_.contribution.js")) - } -} - function prepareMonacoPublicAssets() { return { name: "prepare-monaco-public-assets", configureServer(server: any) { - copyMonacoPublicAssets({ warn: (msg) => server.config.logger.warn(msg) }) + copyMonacoPublicAssets({ + uiRendererRoot: uiRendererRoot, + warn: (msg: string) => server.config.logger.warn(msg), + sourceRoots: [ + resolve(__dirname, "../../node_modules/monaco-editor/min/vs"), + resolve(uiRoot, "node_modules/monaco-editor/min/vs"), + ], + }) }, buildStart(this: any) { - copyMonacoPublicAssets({ warn: (msg) => this.warn(msg) }) + copyMonacoPublicAssets({ + uiRendererRoot: uiRendererRoot, + warn: (msg: string) => this.warn(msg), + sourceRoots: [ + resolve(__dirname, "../../node_modules/monaco-editor/min/vs"), + resolve(uiRoot, "node_modules/monaco-editor/min/vs"), + ], + }) }, } } diff --git a/packages/tauri-app/scripts/dev-prep.js b/packages/tauri-app/scripts/dev-prep.js index abbb4c64..52980401 100644 --- a/packages/tauri-app/scripts/dev-prep.js +++ b/packages/tauri-app/scripts/dev-prep.js @@ -3,6 +3,7 @@ const fs = require("fs") const path = require("path") const { execSync } = require("child_process") +const { pathToFileURL } = require("url") const root = path.resolve(__dirname, "..") const workspaceRoot = path.resolve(root, "..", "..") @@ -10,6 +11,20 @@ const uiRoot = path.resolve(root, "..", "ui") const uiDist = path.resolve(uiRoot, "src", "renderer", "dist") const uiLoadingDest = path.resolve(root, "src-tauri", "resources", "ui-loading") +async function ensureMonacoAssets() { + const helperPath = path.join(uiRoot, "scripts", "monaco-public-assets.js") + const helperUrl = pathToFileURL(helperPath).href + const { copyMonacoPublicAssets } = await import(helperUrl) + copyMonacoPublicAssets({ + uiRendererRoot: path.join(uiRoot, "src", "renderer"), + warn: (msg) => console.warn(`[dev-prep] ${msg}`), + sourceRoots: [ + path.resolve(workspaceRoot, "node_modules", "monaco-editor", "min", "vs"), + path.resolve(uiRoot, "node_modules", "monaco-editor", "min", "vs"), + ], + }) +} + function ensureUiBuild() { const loadingHtml = path.join(uiDist, "loading.html") if (fs.existsSync(loadingHtml)) { @@ -42,5 +57,11 @@ function copyUiLoadingAssets() { console.log(`[dev-prep] copied loader bundle from ${uiDist}`) } -ensureUiBuild() -copyUiLoadingAssets() +;(async () => { + await ensureMonacoAssets() + ensureUiBuild() + copyUiLoadingAssets() +})().catch((err) => { + console.error("[dev-prep] failed:", err) + process.exit(1) +}) diff --git a/packages/tauri-app/scripts/prebuild.js b/packages/tauri-app/scripts/prebuild.js index f4daf3b3..fbf0a4a1 100644 --- a/packages/tauri-app/scripts/prebuild.js +++ b/packages/tauri-app/scripts/prebuild.js @@ -2,6 +2,7 @@ const fs = require("fs") const path = require("path") const { execSync } = require("child_process") +const { pathToFileURL } = require("url") const root = path.resolve(__dirname, "..") const workspaceRoot = path.resolve(root, "..", "..") @@ -37,6 +38,20 @@ const braceExpansionPath = path.join( const viteBinPath = path.join(uiRoot, "node_modules", ".bin", "vite") +async function ensureMonacoAssets() { + const helperPath = path.join(uiRoot, "scripts", "monaco-public-assets.js") + const helperUrl = pathToFileURL(helperPath).href + const { copyMonacoPublicAssets } = await import(helperUrl) + copyMonacoPublicAssets({ + uiRendererRoot: path.join(uiRoot, "src", "renderer"), + warn: (msg) => console.warn(`[prebuild] ${msg}`), + sourceRoots: [ + path.resolve(workspaceRoot, "node_modules", "monaco-editor", "min", "vs"), + path.resolve(uiRoot, "node_modules", "monaco-editor", "min", "vs"), + ], + }) +} + function ensureServerBuild() { const distPath = path.join(serverRoot, "dist") const publicPath = path.join(serverRoot, "public") @@ -223,12 +238,18 @@ function copyUiLoadingAssets() { console.log(`[prebuild] prepared UI loading assets from ${uiDist}`) } -ensureServerDevDependencies() -ensureUiDevDependencies() -ensureRollupPlatformBinary() -ensureServerDependencies() -ensureServerBuild() -ensureUiBuild() -copyServerArtifacts() -stripNodeModuleBins() -copyUiLoadingAssets() +;(async () => { + ensureServerDevDependencies() + ensureUiDevDependencies() + await ensureMonacoAssets() + ensureRollupPlatformBinary() + ensureServerDependencies() + ensureServerBuild() + ensureUiBuild() + copyServerArtifacts() + stripNodeModuleBins() + copyUiLoadingAssets() +})().catch((err) => { + console.error("[prebuild] failed:", err) + process.exit(1) +}) diff --git a/packages/ui/scripts/monaco-public-assets.d.ts b/packages/ui/scripts/monaco-public-assets.d.ts new file mode 100644 index 00000000..3573f9b7 --- /dev/null +++ b/packages/ui/scripts/monaco-public-assets.d.ts @@ -0,0 +1,7 @@ +export type CopyMonacoPublicAssetsParams = { + uiRendererRoot: string + warn?: (message: string) => void + sourceRoots?: string[] +} + +export function copyMonacoPublicAssets(params: CopyMonacoPublicAssetsParams): void diff --git a/packages/ui/scripts/monaco-public-assets.js b/packages/ui/scripts/monaco-public-assets.js new file mode 100644 index 00000000..6ee3eaf7 --- /dev/null +++ b/packages/ui/scripts/monaco-public-assets.js @@ -0,0 +1,97 @@ +import fs from "fs" +import { resolve } from "path" + +/** + * Copy Monaco's AMD `min/vs` assets into the UI renderer public folder. + * + * Monaco is loaded at runtime via `/monaco/vs/loader.js`. These assets are gitignored + * and generated on demand in dev/build so the repo stays clean. + * + * @param {object} params + * @param {string} params.uiRendererRoot Absolute path to `packages/ui/src/renderer`. + * @param {(message: string) => void} [params.warn] Warning logger. + * @param {string[]} [params.sourceRoots] Optional override list of `.../monaco-editor/min/vs` roots. + */ +export function copyMonacoPublicAssets(params) { + const uiRendererRoot = params?.uiRendererRoot + if (!uiRendererRoot) { + throw new Error("copyMonacoPublicAssets: uiRendererRoot is required") + } + + const warn = params?.warn ?? ((message) => console.warn(message)) + const publicDir = resolve(uiRendererRoot, "public") + const destRoot = resolve(publicDir, "monaco/vs") + + const candidates = + params?.sourceRoots?.length > 0 + ? params.sourceRoots + : [ + // Workspace root hoisted deps. + resolve(process.cwd(), "node_modules/monaco-editor/min/vs"), + // UI package local deps (covers non-hoisted installs). + resolve(process.cwd(), "packages/ui/node_modules/monaco-editor/min/vs"), + ] + + const sourceRoot = candidates.find((p) => fs.existsSync(resolve(p, "loader.js"))) + if (!sourceRoot) { + warn("Monaco source directory not found; skipping copy") + return + } + + const copyRecursive = (src, dest) => { + const stat = fs.statSync(src) + if (stat.isDirectory()) { + fs.mkdirSync(dest, { recursive: true }) + for (const entry of fs.readdirSync(src)) { + copyRecursive(resolve(src, entry), resolve(dest, entry)) + } + return + } + fs.copyFileSync(src, dest) + } + + // Keep the working tree clean; these assets are generated. + try { + fs.rmSync(destRoot, { recursive: true, force: true }) + } catch { + // ignore + } + fs.mkdirSync(destRoot, { recursive: true }) + + // Copy core Monaco runtime. + for (const dir of ["base", "editor", "platform"]) { + const src = resolve(sourceRoot, dir) + if (fs.existsSync(src)) { + copyRecursive(src, resolve(destRoot, dir)) + } + } + + // loader.js is required. + copyRecursive(resolve(sourceRoot, "loader.js"), resolve(destRoot, "loader.js")) + + // Copy baseline rich language packages + workers. + for (const lang of ["typescript", "html", "json", "css"]) { + const src = resolve(sourceRoot, "language", lang) + if (fs.existsSync(src)) { + copyRecursive(src, resolve(destRoot, "language", lang)) + } + } + + // Copy baseline basic tokenizers. + for (const lang of ["python", "markdown", "cpp", "kotlin"]) { + const src = resolve(sourceRoot, "basic-languages", lang) + if (fs.existsSync(src)) { + copyRecursive(src, resolve(destRoot, "basic-languages", lang)) + } + } + + // Copy monaco.contribution.js entrypoints (needed by some loads). + const monacoContribution = resolve(sourceRoot, "basic-languages", "monaco.contribution.js") + if (fs.existsSync(monacoContribution)) { + copyRecursive(monacoContribution, resolve(destRoot, "basic-languages", "monaco.contribution.js")) + } + const underscoreContribution = resolve(sourceRoot, "basic-languages", "_.contribution.js") + if (fs.existsSync(underscoreContribution)) { + copyRecursive(underscoreContribution, resolve(destRoot, "basic-languages", "_.contribution.js")) + } +} diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts index 3d18c162..599db05b 100644 --- a/packages/ui/vite.config.ts +++ b/packages/ui/vite.config.ts @@ -3,84 +3,11 @@ import { defineConfig } from "vite" import solid from "vite-plugin-solid" import { VitePWA } from "vite-plugin-pwa" import { resolve } from "path" +import { copyMonacoPublicAssets } from "./scripts/monaco-public-assets.js" const uiPackageJson = JSON.parse(fs.readFileSync(resolve(__dirname, "package.json"), "utf-8")) as { version?: string } const uiVersion = uiPackageJson.version ?? "0.0.0" -function copyMonacoPublicAssets(opts: { warn: (message: string) => void }) { - const publicDir = resolve(__dirname, "src/renderer/public") - const destRoot = resolve(publicDir, "monaco/vs") - - const candidates = [ - resolve(__dirname, "../../node_modules/monaco-editor/min/vs"), - resolve(__dirname, "node_modules/monaco-editor/min/vs"), - ] - const sourceRoot = candidates.find((p) => fs.existsSync(resolve(p, "loader.js"))) - if (!sourceRoot) { - opts.warn("Monaco source directory not found; skipping copy") - return - } - - fs.mkdirSync(destRoot, { recursive: true }) - - const copyRecursive = (src: string, dest: string) => { - const stat = fs.statSync(src) - if (stat.isDirectory()) { - fs.mkdirSync(dest, { recursive: true }) - for (const entry of fs.readdirSync(src)) { - copyRecursive(resolve(src, entry), resolve(dest, entry)) - } - return - } - fs.copyFileSync(src, dest) - } - - // Keep the working tree clean; these assets are generated. - try { - fs.rmSync(destRoot, { recursive: true, force: true }) - } catch { - // ignore - } - fs.mkdirSync(destRoot, { recursive: true }) - - // Copy core Monaco runtime. - for (const dir of ["base", "editor", "platform"] as const) { - const src = resolve(sourceRoot, dir) - if (fs.existsSync(src)) { - copyRecursive(src, resolve(destRoot, dir)) - } - } - - // loader.js is required. - copyRecursive(resolve(sourceRoot, "loader.js"), resolve(destRoot, "loader.js")) - - // Copy baseline rich language packages + workers. - for (const lang of ["typescript", "html", "json", "css"] as const) { - const src = resolve(sourceRoot, "language", lang) - if (fs.existsSync(src)) { - copyRecursive(src, resolve(destRoot, "language", lang)) - } - } - - // Copy baseline basic tokenizers. - for (const lang of ["python", "markdown", "cpp", "kotlin"] as const) { - const src = resolve(sourceRoot, "basic-languages", lang) - if (fs.existsSync(src)) { - copyRecursive(src, resolve(destRoot, "basic-languages", lang)) - } - } - - // Copy monaco.contribution.js entrypoints (needed by some loads). - const monacoContribution = resolve(sourceRoot, "basic-languages", "monaco.contribution.js") - if (fs.existsSync(monacoContribution)) { - copyRecursive(monacoContribution, resolve(destRoot, "basic-languages", "monaco.contribution.js")) - } - const underscoreContribution = resolve(sourceRoot, "basic-languages", "_.contribution.js") - if (fs.existsSync(underscoreContribution)) { - copyRecursive(underscoreContribution, resolve(destRoot, "basic-languages", "_.contribution.js")) - } -} - export default defineConfig({ root: "./src/renderer", plugins: [ @@ -90,10 +17,24 @@ export default defineConfig({ // Ensure Monaco's AMD assets exist in `root/public` for both dev server and builds. // These files are gitignored and generated on demand. configureServer(server) { - copyMonacoPublicAssets({ warn: (msg) => server.config.logger.warn(msg) }) + copyMonacoPublicAssets({ + uiRendererRoot: resolve(__dirname, "src/renderer"), + warn: (msg) => server.config.logger.warn(msg), + sourceRoots: [ + resolve(__dirname, "../../node_modules/monaco-editor/min/vs"), + resolve(__dirname, "node_modules/monaco-editor/min/vs"), + ], + }) }, buildStart() { - copyMonacoPublicAssets({ warn: (msg) => this.warn(msg) }) + copyMonacoPublicAssets({ + uiRendererRoot: resolve(__dirname, "src/renderer"), + warn: (msg) => this.warn(msg), + sourceRoots: [ + resolve(__dirname, "../../node_modules/monaco-editor/min/vs"), + resolve(__dirname, "node_modules/monaco-editor/min/vs"), + ], + }) }, }, {