From 03f40e0b4c8a9a3f33c4c45cd98d9a3f2a2e154b Mon Sep 17 00:00:00 2001 From: ethan Date: Thu, 4 Jun 2026 18:27:22 +0900 Subject: [PATCH 1/7] Ship local-first desktop distribution path The web app is moving from hosted editing toward a desktop download surface, so this commit adds Electron packaging, a Vercel-ready web profile, and a GitHub Releases pipeline that builds OS-specific installers from tagged commits. The desktop package intentionally excludes production node_modules and removes user-facing LLM, remote share, Google Workspace, and remote TeX runtime paths from the app bundle. Constraint: Desktop releases need Windows, macOS, and Linux artifacts, which cannot be produced reliably from the local Windows session alone. Constraint: Vercel should serve the distribution page, not the full hosted editor experience. Rejected: Upload only the local win-unpacked folder | it would not provide macOS/Linux artifacts or stable installer download URLs. Rejected: Keep LLM and workspace runtimes hidden only by UI | those chunks and service clients would still be reachable in the desktop bundle. Confidence: medium Scope-risk: broad Directive: Do not re-enable remote AI, share, workspace, or TeX service calls in desktop builds without an explicit product decision and release security review. Tested: npm run typecheck; npm run build:web; npm run build; npm run electron:build; dist bundle-report banned chunk scan; app.asar package listing scan after node_modules exclusion; GitHub action tag checks via gh api Not-tested: Full OS installer release workflow has not completed yet; local electron-builder exits during Windows winCodeSign helper extraction because the current Windows session lacks symlink privilege --- .env.example | 9 + .github/workflows/deploy-full-stack.yml | 1 + .github/workflows/deploy-web.yml | 1 + .github/workflows/release-desktop.yml | 113 + .gitignore | 5 + e2e/graph-regression.spec.ts | 121 +- e2e/mobile-toolbar-formatting.spec.ts | 7 +- e2e/responsive-layout.spec.ts | 8 +- electron/main.ts | 112 + electron/preload.ts | 10 + package-lock.json | 2893 ++++++++++++++++- package.json | 56 +- scripts/electron-dev.mjs | 62 + src/App.tsx | 36 +- .../editor/ChangeMonitoringPanel.tsx | 52 +- .../editor/ConsistencyIssuesPanel.tsx | 10 +- src/components/editor/DocumentImpactPanel.tsx | 26 +- src/components/editor/EditorHeader.tsx | 26 +- src/components/editor/EditorWorkspace.tsx | 28 - .../editor/FileSidebarKnowledgePanels.tsx | 64 +- src/components/editor/PreviewRuntime.tsx | 25 +- .../editor/SuggestionQueuePanel.tsx | 8 +- src/components/editor/sidebarFeatureTypes.ts | 4 +- src/content/webGuideContentStub.ts | 24 + src/hooks/useDocumentIO.ts | 27 +- src/hooks/useTexValidation.ts | 235 +- src/hooks/useVersionHistory.ts | 51 +- src/i18n/messages/en.ts | 46 +- src/i18n/messages/ko.ts | 46 +- src/lib/ai/tocGeneration.ts | 2 +- src/lib/appProfile.ts | 18 +- src/lib/editor/userProfiles.ts | 5 +- src/lib/editor/webEditorPreferences.ts | 15 +- src/lib/history/versionHistoryActions.ts | 42 +- src/lib/latex/importLatexToDocsy.ts | 2 +- src/lib/release/downloadLinks.ts | 44 + src/lib/runtime/desktopShell.ts | 2 + src/pages/Index.tsx | 682 ++-- src/pages/Landing.tsx | 179 +- src/pages/WebRedirect.tsx | 5 + src/test/editorWorkspace.test.tsx | 19 - src/vite-env.d.ts | 15 + tsconfig.electron.json | 7 + vercel.json | 12 + vite.config.ts | 22 +- 45 files changed, 4082 insertions(+), 1095 deletions(-) create mode 100644 .github/workflows/release-desktop.yml create mode 100644 electron/main.ts create mode 100644 electron/preload.ts create mode 100644 scripts/electron-dev.mjs create mode 100644 src/content/webGuideContentStub.ts create mode 100644 src/lib/release/downloadLinks.ts create mode 100644 src/lib/runtime/desktopShell.ts create mode 100644 src/pages/WebRedirect.tsx create mode 100644 tsconfig.electron.json create mode 100644 vercel.json diff --git a/.env.example b/.env.example index b4e632b..58ca622 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,14 @@ # Local development VITE_APP_PROFILE=desktop +VITE_DESKTOP_RELEASES_URL= +VITE_DOWNLOAD_WINDOWS_URL= +VITE_DOWNLOAD_MACOS_URL= +VITE_DOWNLOAD_LINUX_URL= + +# Current desktop/web profiles keep LLM, remote share, Google Workspace, and +# remote TeX calls disabled by default. The server values below are retained for +# legacy service development only; do not set them in Vercel unless those remote +# services are intentionally re-enabled. GOOGLE_GENAI_USE_VERTEXAI=true GOOGLE_CLOUD_PROJECT=urban-dds GOOGLE_CLOUD_LOCATION=asia-northeast3 diff --git a/.github/workflows/deploy-full-stack.yml b/.github/workflows/deploy-full-stack.yml index 802ed8b..703ec43 100644 --- a/.github/workflows/deploy-full-stack.yml +++ b/.github/workflows/deploy-full-stack.yml @@ -701,6 +701,7 @@ jobs: printf "VITE_APP_PROFILE=web\nVITE_AI_API_BASE_URL=%s\nVITE_APP_BUILD_ID=%s\n" "${AI_API_BASE_URL}" "${FRONTEND_BUILD_ID}" > .env.web.local npm ci + npm run typecheck:app npm run build:web - name: Verify frontend file diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index d94882b..ac008dc 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -117,6 +117,7 @@ jobs: printf "VITE_APP_PROFILE=web\nVITE_AI_API_BASE_URL=%s\nVITE_APP_BUILD_ID=%s\n" "${AI_API_BASE_URL}" "${FRONTEND_BUILD_ID}" > .env.web.local npm ci + npm run typecheck:app npm run build:web - name: Verify frontend file diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml new file mode 100644 index 0000000..c4bad94 --- /dev/null +++ b/.github/workflows/release-desktop.yml @@ -0,0 +1,113 @@ +name: Release Desktop Apps + +on: + push: + tags: + - "desktop-v*" + workflow_dispatch: + inputs: + tag: + description: "Release tag to create or update" + required: true + type: string + +permissions: + contents: write + +jobs: + build: + name: Build ${{ matrix.label }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - label: Windows + os: windows-latest + - label: macOS + os: macos-15-intel + - label: Linux + os: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + cache: npm + node-version: 22 + + - name: Install Linux packaging dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libarchive-tools rpm + + - name: Install dependencies + run: npm ci + + - name: Typecheck + run: npm run typecheck + + - name: Build desktop renderer and Electron entry + run: | + npm run build + npm run electron:build + + - name: Package desktop app + env: + CSC_IDENTITY_AUTO_DISCOVERY: "false" + run: npx electron-builder --publish never + + - name: Collect release assets + shell: bash + run: | + set -euo pipefail + mkdir -p desktop-artifacts + find release -maxdepth 1 -type f \( \ + -name "*.AppImage" -o \ + -name "*.deb" -o \ + -name "*.dmg" -o \ + -name "*.exe" -o \ + -name "*.zip" \ + \) -exec cp {} desktop-artifacts/ \; + ls -la desktop-artifacts + test "$(find desktop-artifacts -type f | wc -l)" -gt 0 + + - name: Upload release assets + uses: actions/upload-artifact@v7 + with: + name: desktop-${{ runner.os }} + path: desktop-artifacts/* + if-no-files-found: error + + publish: + name: Publish GitHub Release + needs: build + runs-on: ubuntu-latest + steps: + - name: Download release assets + uses: actions/download-artifact@v8 + with: + merge-multiple: true + path: release-assets + + - name: Publish release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_REPO: ${{ github.repository }} + RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }} + run: | + set -euo pipefail + ls -la release-assets + if gh release view "${RELEASE_TAG}" --repo "${RELEASE_REPO}" >/dev/null 2>&1; then + gh release upload "${RELEASE_TAG}" release-assets/* --repo "${RELEASE_REPO}" --clobber + else + gh release create "${RELEASE_TAG}" release-assets/* \ + --repo "${RELEASE_REPO}" \ + --latest \ + --title "Docsy Desktop ${RELEASE_TAG}" \ + --notes "Local-first Docsy desktop builds for Windows, macOS, and Linux." + fi diff --git a/.gitignore b/.gitignore index 85966fb..23c55ae 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ lerna-debug.log* node_modules dist server-dist +electron-dist +/release dist-ssr .vite coverage @@ -43,6 +45,7 @@ Thumbs.db # Local/temporary files .data/ +.playwright-cli/ *.local *.tmp *.temp @@ -54,6 +57,8 @@ temp-*.ts temp-*.tsx test-results output/gcp-monitoring/ +output/playwright/ output/readme-bundles/ +output/web-profile-test/ playwright-report .playwright-*.png diff --git a/e2e/graph-regression.spec.ts b/e2e/graph-regression.spec.ts index c13e82b..7c74f08 100644 --- a/e2e/graph-regression.spec.ts +++ b/e2e/graph-regression.spec.ts @@ -1,32 +1,68 @@ -import { expect, test, type Page } from "@playwright/test"; +import { expect, test, type Page, type Route } from "@playwright/test"; const AUTOSAVE_KEY = "docsy-autosave-v2"; +const AUTOSAVE_V3_DB_NAME = "docsy-autosave-v3"; +const AUTOSAVE_V3_POINTER_KEY = "docsy-autosave-v3-pointer"; const KNOWLEDGE_DB_NAME = "docsy-knowledge-index"; const KNOWLEDGE_FALLBACK_KEY = "docsy-knowledge-index-fallback-v2"; const SOURCE_SNAPSHOT_KEY = "docsy.source-snapshots.v1"; const UI_LANGUAGE_KEY = "docsy-ui-language"; +const USER_PROFILE_KEY = "docsy:web:user-profile"; + +const fulfillJson = (route: Route, payload: unknown) => + route.fulfill({ + body: JSON.stringify(payload), + contentType: "application/json", + status: 200, + }); + +const installGraphApiMocks = async (page: Page) => { + const context = page.context(); + + await context.route("**/api/auth/session", (route) => + fulfillJson(route, { connected: false, provider: null, user: null })); + await context.route("**/api/ai/health", (route) => + fulfillJson(route, { configured: false, model: "e2e", ok: true })); + await context.route("**/api/ai/propose-action", (route) => + fulfillJson(route, { + action: "open_patch_review", + confidence: 0.9, + payload: { title: "Open patch review" }, + reason: "E2E deterministic graph suggestion.", + })); + await context.route("**/api/ai/autosave-diff-summary", (route) => + fulfillJson(route, { requestId: "e2e-autosave-summary", summary: "E2E autosave summary." })); +}; const clearGraphState = async (page: Page) => { - await page.goto("/editor"); + await installGraphApiMocks(page); + await page.goto("/", { waitUntil: "domcontentloaded" }); await page.evaluate(async ({ autosaveKey, + autosaveV3DbName, + autosaveV3PointerKey, dbName, knowledgeFallbackKey, sourceSnapshotKey, uiLanguageKey, + userProfileKey, }) => { localStorage.clear(); sessionStorage.clear(); localStorage.removeItem(knowledgeFallbackKey); localStorage.removeItem(sourceSnapshotKey); + localStorage.removeItem(autosaveV3PointerKey); localStorage.setItem(uiLanguageKey, "en"); + localStorage.setItem(userProfileKey, "advanced"); - await new Promise((resolve) => { - const request = indexedDB.deleteDatabase(dbName); - request.onsuccess = () => resolve(); - request.onerror = () => resolve(); - request.onblocked = () => resolve(); - }); + for (const databaseName of [dbName, autosaveV3DbName]) { + await new Promise((resolve) => { + const request = indexedDB.deleteDatabase(databaseName); + request.onsuccess = () => resolve(); + request.onerror = () => resolve(); + request.onblocked = () => resolve(); + }); + } const now = Date.now(); const documents = [ @@ -85,15 +121,46 @@ const clearGraphState = async (page: Page) => { })); }, { autosaveKey: AUTOSAVE_KEY, + autosaveV3DbName: AUTOSAVE_V3_DB_NAME, + autosaveV3PointerKey: AUTOSAVE_V3_POINTER_KEY, dbName: KNOWLEDGE_DB_NAME, knowledgeFallbackKey: KNOWLEDGE_FALLBACK_KEY, sourceSnapshotKey: SOURCE_SNAPSHOT_KEY, uiLanguageKey: UI_LANGUAGE_KEY, + userProfileKey: USER_PROFILE_KEY, }); }; const getPrimaryEditor = (page: Page) => page.locator(".ProseMirror").first(); +const closePatchReviewIfOpen = async (page: Page, timeout = 30000) => { + const patchReviewDialog = page.getByTestId("patch-review-dialog"); + + if (await patchReviewDialog.isVisible({ timeout }).catch(() => false)) { + await page.keyboard.press("Escape"); + await expect(patchReviewDialog).toHaveCount(0); + } +}; + +const openKnowledgePanel = async (page: Page) => { + const knowledgeButton = page.locator("button").filter({ hasText: "Knowledge" }).first(); + + if (!(await knowledgeButton.isVisible({ timeout: 1000 }).catch(() => false))) { + await page.getByRole("button", { name: "Toggle sidebar" }).click(); + } + + await knowledgeButton.evaluate((element) => { + (element as HTMLElement).click(); + }); +}; + +const openSuggestionQueuePatchReview = async (page: Page) => { + const openReviewAction = page.locator('[data-testid^="suggestion-queue-open-review-"]').first(); + + await expect(openReviewAction).toBeVisible({ timeout: 60000 }); + await openReviewAction.click({ force: true }); +}; + test.describe("workspace graph regressions", () => { test.beforeEach(async ({ page }) => { await clearGraphState(page); @@ -124,12 +191,13 @@ test.describe("workspace graph regressions", () => { }); test("issues mode narrows the graph to issue-linked documents", async ({ page }) => { - await page.getByRole("button", { name: "Issues graph" }).click(); + await page.getByRole("button", { name: "Graph view filter menu" }).click(); + await page.getByRole("menuitemradio", { name: "Issues graph" }).click(); await expect(page.getByTestId("graph-canvas-node-doc:doc-alpha")).toBeVisible(); await expect(page.getByTestId("graph-canvas-node-doc:doc-beta")).toBeVisible(); await expect(page.getByTestId("graph-canvas-node-doc:doc-gamma")).toHaveCount(0); - await expect(page.getByRole("button", { name: "Broken reference", exact: true })).toBeVisible(); + await expect(page.getByText("Broken reference").first()).toBeVisible(); }); test("graph context can create a queue item and open patch review", async ({ page }) => { @@ -140,16 +208,15 @@ test.describe("workspace graph regressions", () => { await page.getByRole("button", { name: "Suggest update" }).click(); await expect(page).toHaveURL(/\/editor/); + await closePatchReviewIfOpen(page); - await page.getByRole("button", { name: "Toggle sidebar" }).click(); - await page.getByRole("button", { name: "Knowledge" }).click(); + await openKnowledgePanel(page); - await expect(page.getByRole("heading", { name: "Suggestion Queue" })).toBeVisible(); + await expect(page.getByText("Suggestion Queue", { exact: true }).first()).toBeVisible(); await expect(page.getByText("alpha").first()).toBeVisible(); await expect(page.getByText("beta").first()).toBeVisible(); - await expect(page.getByText("Editor is not ready yet.").first()).toBeVisible(); - await page.getByRole("button", { name: "Retry", exact: true }).click(); + await openSuggestionQueuePatchReview(page); const patchReviewDialog = page.getByRole("dialog"); await expect(patchReviewDialog.getByText("Patch Review", { exact: true })).toBeVisible({ timeout: 30000 }); await expect(patchReviewDialog.getByText("Patches", { exact: true })).toBeVisible(); @@ -169,15 +236,14 @@ test.describe("workspace graph regressions", () => { await expect(page.getByText("Context chain")).toBeVisible(); await page.getByRole("button", { name: "Suggest update" }).click(); await expect(page).toHaveURL(/\/editor/); + await closePatchReviewIfOpen(page); - await page.getByRole("button", { name: "Toggle sidebar" }).click(); - await page.getByRole("button", { name: "Knowledge" }).click(); - await expect(page.getByRole("heading", { name: "Suggestion Queue" })).toBeVisible(); + await openKnowledgePanel(page); + await expect(page.getByText("Suggestion Queue", { exact: true }).first()).toBeVisible(); - const suggestionQueueSection = page.locator("section").filter({ - has: page.getByRole("heading", { name: "Suggestion Queue" }), + await page.locator('[data-testid^="suggestion-queue-open-graph-"]').first().evaluate((element) => { + (element as HTMLElement).click(); }); - await suggestionQueueSection.getByRole("button", { name: "Explore", exact: true }).click(); await expect(page).toHaveURL(/\/editor\/graph/); await expect(page.getByText("Context chain")).toBeVisible(); @@ -191,20 +257,21 @@ test.describe("workspace graph regressions", () => { await expect(page.getByText("Context chain")).toBeVisible(); await page.getByRole("button", { name: "Suggest update" }).click(); await expect(page).toHaveURL(/\/editor/); + await closePatchReviewIfOpen(page); - await page.getByRole("button", { name: "Toggle sidebar" }).click(); - await page.getByRole("button", { name: "Knowledge" }).click(); - await expect(page.getByRole("heading", { name: "Suggestion Queue" })).toBeVisible(); - await expect(page.getByText("Editor is not ready yet.").first()).toBeVisible(); + await openKnowledgePanel(page); + await expect(page.getByText("Suggestion Queue", { exact: true }).first()).toBeVisible(); - await page.getByRole("button", { name: "Retry", exact: true }).click(); + await openSuggestionQueuePatchReview(page); const patchReviewDialog = page.getByRole("dialog"); await expect(patchReviewDialog.getByText("Patch Review", { exact: true })).toBeVisible({ timeout: 30000 }); await page.keyboard.press("Escape"); await expect(patchReviewDialog).toHaveCount(0); - await page.getByRole("button", { name: /beta\s*\.md/i }).click(); + await page.locator('[data-testid^="suggestion-queue-target-"]').first().evaluate((element) => { + (element as HTMLElement).click(); + }); await expect(getPrimaryEditor(page)).toBeVisible(); await expect(getPrimaryEditor(page)).toContainText("Reference target."); diff --git a/e2e/mobile-toolbar-formatting.spec.ts b/e2e/mobile-toolbar-formatting.spec.ts index 47aea83..65926c4 100644 --- a/e2e/mobile-toolbar-formatting.spec.ts +++ b/e2e/mobile-toolbar-formatting.spec.ts @@ -3,6 +3,7 @@ import { expect, test } from "@playwright/test"; const AUTOSAVE_KEY = "docsy-autosave-v2"; const DOCUMENT_TOOLS_KEY = "docsy:web:document-tools-enabled"; const UI_LANGUAGE_KEY = "docsy-ui-language"; +const USER_PROFILE_KEY = "docsy:web:user-profile"; const createMobileEditorState = () => { const now = Date.now(); @@ -33,10 +34,11 @@ test("mobile toolbar preserves selection for bold and exposes the mobile format await page.setViewportSize({ width: 390, height: 844 }); await page.goto("/", { waitUntil: "domcontentloaded" }); - await page.evaluate(({ autosaveKey, autosaveState, documentToolsKey, uiLanguageKey }) => { + await page.evaluate(({ autosaveKey, autosaveState, documentToolsKey, uiLanguageKey, userProfileKey }) => { localStorage.clear(); sessionStorage.clear(); localStorage.setItem(uiLanguageKey, "en"); + localStorage.setItem(userProfileKey, "advanced"); localStorage.setItem(documentToolsKey, "true"); localStorage.setItem(autosaveKey, JSON.stringify(autosaveState)); }, { @@ -44,6 +46,7 @@ test("mobile toolbar preserves selection for bold and exposes the mobile format autosaveState, documentToolsKey: DOCUMENT_TOOLS_KEY, uiLanguageKey: UI_LANGUAGE_KEY, + userProfileKey: USER_PROFILE_KEY, }); await page.goto("/editor?e2e=1", { waitUntil: "domcontentloaded" }); @@ -81,7 +84,7 @@ test("mobile toolbar preserves selection for bold and exposes the mobile format }); }).toBeTruthy(); - await page.getByRole("button", { name: "More" }).click(); + await page.getByTestId("toolbar-mobile-more").getByRole("button", { name: "More" }).click(); const mobileSheet = page.getByTestId("toolbar-mobile-sheet"); await expect(mobileSheet).toBeVisible(); const mobileSheetScroll = page.getByTestId("toolbar-mobile-sheet-scroll"); diff --git a/e2e/responsive-layout.spec.ts b/e2e/responsive-layout.spec.ts index 570ec94..6bb9d09 100644 --- a/e2e/responsive-layout.spec.ts +++ b/e2e/responsive-layout.spec.ts @@ -1,7 +1,9 @@ import { expect, test, type Page } from "@playwright/test"; const AUTOSAVE_KEY = "docsy-autosave-v2"; +const DOCUMENT_TOOLS_KEY = "docsy:web:document-tools-enabled"; const UI_LANGUAGE_KEY = "docsy-ui-language"; +const USER_PROFILE_KEY = "docsy:web:user-profile"; type Locale = "en" | "ko"; @@ -82,6 +84,8 @@ const seedEditorState = async (page: Page, locale: Locale) => { localStorage.clear(); sessionStorage.clear(); localStorage.setItem(args.uiLanguageKey, args.locale); + localStorage.setItem(args.userProfileKey, "advanced"); + localStorage.setItem(args.documentToolsKey, "true"); const now = Date.now(); const documents = [ @@ -139,7 +143,9 @@ const seedEditorState = async (page: Page, locale: Locale) => { })); }, { autosaveKey: AUTOSAVE_KEY, + documentToolsKey: DOCUMENT_TOOLS_KEY, uiLanguageKey: UI_LANGUAGE_KEY, + userProfileKey: USER_PROFILE_KEY, locale, }); }; @@ -289,7 +295,7 @@ test.describe("responsive overlap checks", () => { await page.goto("/editor?e2e=1", { waitUntil: "domcontentloaded" }); await expect(page.locator("header input").first()).toBeVisible(); - await page.getByRole("button", { name: /Patch Review|패치 검토/ }).first().click(); + await page.locator('[data-visual-target="header-open-patch-review"]').first().click(); const dialog = page.getByTestId("patch-review-dialog"); await expect(dialog).toBeVisible(); diff --git a/electron/main.ts b/electron/main.ts new file mode 100644 index 0000000..da75998 --- /dev/null +++ b/electron/main.ts @@ -0,0 +1,112 @@ +import { app, BrowserWindow, Menu, shell, session } from "electron"; +import path from "node:path"; + +const APP_HASH_ROUTE = "/editor"; +const BLOCKED_REMOTE_SERVICE_PATH_PATTERNS = [ + /\/api\/ai(?:\/|$)/i, + /\/api\/auth\/google(?:\/|$)/i, + /\/api\/internal\/ai(?:\/|$)/i, + /\/api\/share(?:\/|$)/i, + /\/api\/tex(?:\/|$)/i, + /\/api\/workspace(?:\/|$)/i, +]; +const BLOCKED_REMOTE_SERVICE_HOST_PATTERNS = [ + /(^|\.)generativelanguage\.googleapis\.com$/i, + /(^|\.)aiplatform\.googleapis\.com$/i, + /(^|\.)googleapis\.com$/i, +]; + +const isAllowedExternalUrl = (url: string) => { + try { + const parsed = new URL(url); + return parsed.protocol === "https:" || parsed.protocol === "http:"; + } catch { + return false; + } +}; + +const shouldBlockRequest = (url: string) => { + try { + const parsed = new URL(url); + return BLOCKED_REMOTE_SERVICE_HOST_PATTERNS.some((pattern) => pattern.test(parsed.hostname)) + || BLOCKED_REMOTE_SERVICE_PATH_PATTERNS.some((pattern) => pattern.test(parsed.pathname)); + } catch { + return false; + } +}; + +const installNetworkGuards = () => { + session.defaultSession.webRequest.onBeforeRequest((details, callback) => { + callback({ cancel: shouldBlockRequest(details.url) }); + }); +}; + +const createMainWindow = async () => { + const mainWindow = new BrowserWindow({ + backgroundColor: "#0f172a", + height: 900, + minHeight: 720, + minWidth: 1080, + show: false, + title: "Docsy", + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + preload: path.join(__dirname, "preload.cjs"), + sandbox: true, + webSecurity: true, + }, + width: 1440, + }); + + mainWindow.once("ready-to-show", () => { + mainWindow.show(); + }); + + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + if (isAllowedExternalUrl(url)) { + void shell.openExternal(url); + } + + return { action: "deny" }; + }); + + mainWindow.webContents.on("will-navigate", (event, url) => { + const currentUrl = mainWindow.webContents.getURL(); + + if (url !== currentUrl && isAllowedExternalUrl(url)) { + event.preventDefault(); + void shell.openExternal(url); + } + }); + + const rendererUrl = process.env.ELECTRON_RENDERER_URL?.trim(); + + if (!app.isPackaged && rendererUrl) { + await mainWindow.loadURL(`${rendererUrl.replace(/\/$/, "")}/#${APP_HASH_ROUTE}`); + mainWindow.webContents.openDevTools({ mode: "detach" }); + return; + } + + await mainWindow.loadFile(path.join(__dirname, "..", "dist", "index.html"), { + hash: APP_HASH_ROUTE, + }); +}; + +app.whenReady().then(async () => { + Menu.setApplicationMenu(null); + installNetworkGuards(); + await createMainWindow(); + + app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) { + void createMainWindow(); + } + }); +}); + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit(); + } +}); diff --git a/electron/preload.ts b/electron/preload.ts new file mode 100644 index 0000000..aad1982 --- /dev/null +++ b/electron/preload.ts @@ -0,0 +1,10 @@ +import { contextBridge } from "electron"; + +contextBridge.exposeInMainWorld("docsyDesktop", { + isDesktop: true, + platform: process.platform, + versions: { + chrome: process.versions.chrome, + electron: process.versions.electron, + }, +}); diff --git a/package-lock.json b/package-lock.json index 1096b9d..c50ef6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,6 +109,8 @@ "@types/react-dom": "^18.3.7", "@vitejs/plugin-react-swc": "^3.11.0", "autoprefixer": "^10.4.21", + "electron": "^42.3.3", + "electron-builder": "^26.8.1", "esbuild": "^0.25.10", "eslint": "^9.32.0", "eslint-plugin-react-hooks": "^5.2.0", @@ -446,6 +448,346 @@ "node": ">=20.19.0" } }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@develar/schema-utils/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@develar/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@develar/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@electron/asar/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", + "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.4.tgz", + "integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -2020,6 +2362,19 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -2078,6 +2433,61 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@mermaid-js/parser": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", @@ -3953,6 +4363,19 @@ "win32" ] }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@swc/core": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", @@ -4179,6 +4602,19 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tailwindcss/typography": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", @@ -4976,6 +5412,19 @@ "license": "MIT", "peer": true }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/caseless": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", @@ -5246,7 +5695,17 @@ "@types/d3-selection": "*" } }, - "node_modules/@types/deep-eql": { + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", @@ -5260,6 +5719,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -5275,6 +5744,13 @@ "@types/unist": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", @@ -5294,6 +5770,16 @@ "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -5322,6 +5808,13 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.16.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz", @@ -5331,6 +5824,18 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -5368,6 +5873,16 @@ "form-data": "^2.5.5" } }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -5399,6 +5914,25 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.38.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", @@ -5786,6 +6320,33 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -5891,6 +6452,246 @@ "node": ">= 8" } }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", + "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz", + "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.3", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.8.1", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.3", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.8.1", + "electron-builder-squirrel-windows": "26.8.1" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/app-builder-lib/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/app-builder-lib/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -5934,6 +6735,17 @@ "node": ">=8" } }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -5944,6 +6756,34 @@ "node": ">=12" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -5959,6 +6799,16 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -6075,6 +6925,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -6131,18 +6990,129 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz", + "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.12", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", + "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", "dev": true, "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, "engines": { "node": ">=8" } @@ -6314,6 +7284,39 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -6325,6 +7328,73 @@ "url": "https://polar.sh/cva" } }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -6391,6 +7461,19 @@ "node": ">=8" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -6455,6 +7538,16 @@ "node": ">= 6" } }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6468,6 +7561,14 @@ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -6477,12 +7578,32 @@ "layout-base": "^1.0.0" } }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "license": "MIT" }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7156,6 +8277,35 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -7173,6 +8323,54 @@ "dev": true, "license": "MIT" }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", @@ -7200,6 +8398,14 @@ "node": ">=6" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -7231,12 +8437,93 @@ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "license": "MIT" }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dmg-builder": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", + "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dmg-license/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/dmg-license/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -7264,6 +8551,35 @@ "@types/trusted-types": "^2.0.7" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -7302,15 +8618,330 @@ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", "dependencies": { - "safe-buffer": "^5.0.1" + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "42.3.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-42.3.3.tgz", + "integrity": "sha512-0MwYp9wTb7TrtTalOYqeW+suqd9T/Znstr/nDLKqFGIjHdBZX339guo3mQqTPURRZ/UQmYM4uMpzKpI5wLptfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-builder": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz", + "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.8.1", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz", + "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-builder/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-builder/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-builder/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-builder/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-builder/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/electron-builder/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-builder/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", + "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/electron-publish/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.192", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", + "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "24.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.0.tgz", + "integrity": "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.192", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", - "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==", + "node_modules/electron/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, - "license": "ISC" + "license": "MIT" }, "node_modules/embla-carousel": { "version": "8.6.0", @@ -7368,6 +8999,26 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -7420,6 +9071,14 @@ "node": ">= 0.4" } }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -7737,12 +9396,51 @@ "node": ">=12.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7860,6 +9558,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -7896,6 +9604,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -8032,6 +9773,28 @@ } } }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -8145,6 +9908,22 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-tsconfig": { "version": "4.13.6", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", @@ -8214,6 +9993,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/globals": { "version": "15.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", @@ -8227,6 +10025,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/google-auth-library": { "version": "10.6.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.1.tgz", @@ -8287,6 +10103,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -8394,6 +10243,20 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -8442,6 +10305,39 @@ "node": ">=12.0.0" } }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -8484,6 +10380,13 @@ "node": ">=8.0.0" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -8498,6 +10401,20 @@ "node": ">= 14" } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -8511,6 +10428,24 @@ "node": ">= 14" } }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -8523,6 +10458,28 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -8570,6 +10527,18 @@ "node": ">=8" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -8680,6 +10649,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8701,6 +10683,24 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -8791,12 +10791,46 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } }, "node_modules/jwa": { "version": "2.0.1", @@ -8882,6 +10916,13 @@ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "license": "MIT" }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -9021,6 +11062,16 @@ "vite": ">=5.0.0 <8.0.0" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lowlight": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", @@ -9112,6 +11163,20 @@ "node": ">= 20" } }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -9217,6 +11282,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -9240,6 +11315,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -9249,6 +11334,33 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mlly": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz", @@ -9328,6 +11440,37 @@ "react-dom": "^16.8 || ^17 || ^18" } }, + "node_modules/node-abi": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.31.0.tgz", + "integrity": "sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -9366,6 +11509,77 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-gyp": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", + "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", + "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -9373,6 +11587,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9392,6 +11622,19 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9410,6 +11653,17 @@ "node": ">= 6" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9443,6 +11697,16 @@ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", "license": "MIT" }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9564,6 +11828,16 @@ "node": ">=14.0.0" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -9611,6 +11885,28 @@ "node": ">= 14.16" } }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -9705,6 +12001,21 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -9873,6 +12184,36 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9932,6 +12273,50 @@ "license": "MIT", "peer": true }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9949,6 +12334,35 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/prosemirror-changeset": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", @@ -10180,6 +12594,17 @@ "node": ">=12.0.0" } }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10236,6 +12661,19 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -10439,6 +12877,19 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -10544,6 +12995,24 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "license": "ISC" }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -10561,6 +13030,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -10580,6 +13056,19 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -10627,6 +13116,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -10751,6 +13259,26 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -10786,6 +13314,31 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -10832,6 +13385,47 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sonner": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", @@ -10842,6 +13436,16 @@ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -10851,6 +13455,25 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -10858,6 +13481,16 @@ "dev": true, "license": "MIT" }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", @@ -11077,6 +13710,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11165,6 +13811,23 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar": { + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/teeny-request": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.0.tgz", @@ -11219,6 +13882,70 @@ "node": ">= 6" } }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -11249,6 +13976,26 @@ "node": ">=0.8" } }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -11369,6 +14116,26 @@ "dev": true, "license": "MIT" }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11407,6 +14174,16 @@ "node": ">=20" } }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -11929,6 +14706,20 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -11979,9 +14770,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.1.tgz", - "integrity": "sha512-5xoBibbmnjlcR3jdqtY2Lnx7WbrD/tHlT01TmvqZUFVc9Q1w4+j5hbnapTqbcXITMH1ovjq/W7BkqBilHiVAaA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.1.tgz", + "integrity": "sha512-UDdpiex+mzigiyrXrGbiUaF4HzTNhKbh2vRNFaTMzcqmLIPrZxaCtwo/1TMSuWoM1Xz3WiTo9KdgI3kRqYzJGg==", "dev": true, "license": "MIT", "engines": { @@ -11994,6 +14785,16 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -12087,6 +14888,13 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12128,6 +14936,22 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/victory-vendor": { "version": "36.9.2", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", @@ -13041,6 +15865,16 @@ "node": ">=18" } }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -13054,6 +15888,16 @@ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "license": "ISC" }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/yaml": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", @@ -13194,6 +16038,17 @@ "node": ">=8" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b94613c..487ab6c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,13 @@ "name": "vite_react_shadcn_ts", "private": true, "version": "0.0.0", + "description": "Local-first desktop document editor and public download site.", + "author": { + "name": "Docsy Maintainers", + "email": "dailykim149656-source@users.noreply.github.com" + }, "type": "module", + "main": "electron-dist/main.cjs", "scripts": { "ai:server": "node --env-file-if-exists=.env.local --import tsx server/aiServer.ts", "bundle:readme-docs": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package-readme-bundle.ps1", @@ -13,6 +19,11 @@ "build:dev": "vite build --mode development", "capture:marketing": "node scripts/capture-marketing-surfaces.mjs", "check:public-deploy": "node scripts/validate-public-deploy-config.mjs", + "desktop:dev": "node scripts/electron-dev.mjs", + "desktop:dist": "npm run build && npm run electron:build && electron-builder --publish never", + "desktop:dist:dir": "npm run build && npm run electron:build && electron-builder --dir --publish never", + "electron:build": "esbuild electron/main.ts electron/preload.ts --bundle --platform=node --format=cjs --target=node22 --outdir=electron-dist --out-extension:.js=.cjs --external:electron", + "electron:start": "npm run electron:build && electron .", "lint": "eslint .", "measure:release-closeout": "node scripts/measure-release-closeout-performance.mjs", "monitor:gcp:cloud-run": "node scripts/gcp/apply-cloud-run-monitoring.mjs", @@ -21,12 +32,15 @@ "test:e2e": "playwright test", "test:watch": "vitest", "tex:server": "node --env-file-if-exists=.env.local --import tsx server/texService.ts", + "typecheck": "npm run typecheck:app && npm run typecheck:server && npm run typecheck:electron", + "typecheck:app": "tsc -p tsconfig.app.json --noEmit", + "typecheck:electron": "tsc -p tsconfig.electron.json --noEmit", "typecheck:server": "tsc -p tsconfig.server.json --noEmit" }, "dependencies": { + "@google-cloud/firestore": "^8.3.0", "@google-cloud/storage": "^7.17.2", "@google-cloud/tasks": "^5.5.0", - "@google-cloud/firestore": "^8.3.0", "@google/genai": "^1.44.0", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "^1.2.11", @@ -115,7 +129,6 @@ "zod": "^3.25.76" }, "devDependencies": { - "esbuild": "^0.25.10", "@eslint/js": "^9.32.0", "@playwright/test": "^1.55.0", "@tailwindcss/typography": "^0.5.16", @@ -126,6 +139,9 @@ "@types/react-dom": "^18.3.7", "@vitejs/plugin-react-swc": "^3.11.0", "autoprefixer": "^10.4.21", + "electron": "^42.3.3", + "electron-builder": "^26.8.1", + "esbuild": "^0.25.10", "eslint": "^9.32.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", @@ -137,5 +153,41 @@ "typescript-eslint": "^8.38.0", "vite": "5.4.21", "vitest": "^3.2.4" + }, + "build": { + "appId": "dev.docsy.editor", + "productName": "Docsy", + "artifactName": "Docsy-${os}-${arch}.${ext}", + "asar": true, + "directories": { + "output": "release" + }, + "files": [ + "dist/**/*", + "electron-dist/**/*", + "package.json", + "!node_modules/**/*" + ], + "mac": { + "category": "public.app-category.productivity", + "target": [ + "dmg", + "zip" + ] + }, + "win": { + "target": [ + "nsis" + ] + }, + "linux": { + "category": "Office", + "maintainer": "dailykim149656-source@users.noreply.github.com", + "artifactName": "Docsy-linux-x64.${ext}", + "target": [ + "AppImage", + "deb" + ] + } } } diff --git a/scripts/electron-dev.mjs b/scripts/electron-dev.mjs new file mode 100644 index 0000000..ec8edb3 --- /dev/null +++ b/scripts/electron-dev.mjs @@ -0,0 +1,62 @@ +import { spawn } from "node:child_process"; +import process from "node:process"; + +const rendererUrl = "http://localhost:8080"; +const waitForRenderer = async () => { + const deadline = Date.now() + 30_000; + + while (Date.now() < deadline) { + try { + const response = await fetch(rendererUrl); + + if (response.ok) { + return; + } + } catch { + // Vite is still starting. + } + + await new Promise((resolve) => setTimeout(resolve, 500)); + } + + throw new Error(`Timed out waiting for ${rendererUrl}`); +}; + +const spawnChild = (command, args, options = {}) => { + const child = spawn(command, args, { + env: { + ...process.env, + ...options.env, + }, + shell: process.platform === "win32", + stdio: "inherit", + }); + + child.on("exit", (code) => { + if (!options.allowExit) { + process.exitCode = code ?? 0; + } + }); + + return child; +}; + +const vite = spawnChild("npm", ["run", "dev"], { + allowExit: true, +}); + +await waitForRenderer(); + +const electron = spawnChild("npm", ["run", "electron:start"], { + env: { + ELECTRON_RENDERER_URL: rendererUrl, + }, +}); + +const shutdown = () => { + vite.kill(); + electron.kill(); +}; + +process.on("SIGINT", shutdown); +process.on("SIGTERM", shutdown); diff --git a/src/App.tsx b/src/App.tsx index 2ffa881..2331137 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,17 +4,25 @@ import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { I18nProvider } from "@/i18n/I18nProvider"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, HashRouter, Navigate, Routes, Route } from "react-router-dom"; +import { isDesktopShell } from "@/lib/runtime/desktopShell"; +import { isWebProfile } from "@/lib/appProfile"; -const Landing = lazy(() => import("./pages/Landing")); -const Guide = lazy(() => import("./pages/Guide")); -const Index = lazy(() => import("./pages/Index")); -const Privacy = lazy(() => import("./pages/Privacy")); -const Terms = lazy(() => import("./pages/Terms")); -const WorkspaceGraph = lazy(() => import("./pages/WorkspaceGraph")); -const NotFound = lazy(() => import("./pages/NotFound")); +const isWebBuildProfile = import.meta.env.VITE_APP_PROFILE?.trim().toLowerCase() === "web"; +const Landing = lazy(() => import("@/pages/Landing")); +const Guide = isWebBuildProfile ? null : lazy(() => import("@/pages/Guide")); +const Index = isWebBuildProfile ? null : lazy(() => import("@/pages/Index")); +const Privacy = lazy(() => import("@/pages/Privacy")); +const Terms = lazy(() => import("@/pages/Terms")); +const WorkspaceGraph = isWebBuildProfile ? null : lazy(() => import("@/pages/WorkspaceGraph")); +const NotFound = lazy(() => import("@/pages/NotFound")); const queryClient = new QueryClient(); +const Router = isDesktopShell() ? HashRouter : BrowserRouter; +const WebEditorRoute = () => ; +const GuideRoute = () => (Guide ? : ); +const EditorRoute = () => (Index ? : ); +const GraphRoute = () => (WorkspaceGraph ? : ); const RouteFallback = () => (
@@ -28,22 +36,22 @@ const App = () => ( - + }> } /> } /> - } /> + : } /> } /> } /> - } /> - } /> - } /> + : } /> + : } /> + : } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> - + diff --git a/src/components/editor/ChangeMonitoringPanel.tsx b/src/components/editor/ChangeMonitoringPanel.tsx index 790efa4..109204b 100644 --- a/src/components/editor/ChangeMonitoringPanel.tsx +++ b/src/components/editor/ChangeMonitoringPanel.tsx @@ -18,7 +18,7 @@ interface ChangeMonitoringPanelProps { targetDocumentId: string; }) => void; onRescan: () => void; - onSuggestUpdates: (sourceDocumentId: string, targetDocumentId: string) => void; + onSuggestUpdates?: (sourceDocumentId: string, targetDocumentId: string) => void; } const formatTimestamp = (timestamp: number | null) => @@ -114,20 +114,22 @@ const ChangeMonitoringPanel = ({

- + {onSuggestUpdates && ( + + )} )} - + {onSuggestUpdates && ( + + )}
); diff --git a/src/components/editor/ConsistencyIssuesPanel.tsx b/src/components/editor/ConsistencyIssuesPanel.tsx index 88659df..0bf7a22 100644 --- a/src/components/editor/ConsistencyIssuesPanel.tsx +++ b/src/components/editor/ConsistencyIssuesPanel.tsx @@ -31,7 +31,7 @@ interface ConsistencyIssuesPanelProps { sourceDocumentId: string; targetDocumentId: string; }) => void; - onSuggestUpdates: (request: ConsistencySuggestionRequest) => Promise | unknown; + onSuggestUpdates?: (request: ConsistencySuggestionRequest) => Promise | unknown; suggestableDocumentIds: string[]; } @@ -113,6 +113,10 @@ const ConsistencyIssuesPanel = ({ ); const handleSuggestUpdates = (issue: KnowledgeConsistencyIssue) => { + if (!onSuggestUpdates) { + return; + } + setSuggestingIssueId(issue.id); const request: ConsistencySuggestionRequest = { issueId: issue.id, @@ -156,7 +160,7 @@ const ConsistencyIssuesPanel = ({ ) : (
{orderedIssues.map((issue) => { - const canSuggest = suggestableSet.has(issue.relatedDocumentId); + const canSuggest = Boolean(onSuggestUpdates) && suggestableSet.has(issue.relatedDocumentId); const isExpanded = expandedIssueId === issue.id; const isSuggesting = suggestingIssueId === issue.id; const previewDeltas = issue.comparison.deltas.slice(0, 3); @@ -270,7 +274,7 @@ const ConsistencyIssuesPanel = ({ )}
- {!canSuggest && ( + {onSuggestUpdates && !canSuggest && (
{t("knowledge.consistencySuggestionUnavailable")}
diff --git a/src/components/editor/DocumentImpactPanel.tsx b/src/components/editor/DocumentImpactPanel.tsx index 81ad03e..a19d139 100644 --- a/src/components/editor/DocumentImpactPanel.tsx +++ b/src/components/editor/DocumentImpactPanel.tsx @@ -13,7 +13,7 @@ interface DocumentImpactPanelProps { sourceDocumentId: string; targetDocumentId: string; }) => void; - onSuggestUpdates: (documentId: string) => void; + onSuggestUpdates?: (documentId: string) => void; suggestableDocumentIds: string[]; } @@ -98,7 +98,7 @@ const DocumentImpactPanel = ({ ) : (
{impact.relatedDocuments.slice(0, 5).map((document, index) => { - const canSuggestUpdates = suggestableDocumentIds.includes(document.documentId); + const canSuggestUpdates = Boolean(onSuggestUpdates) && suggestableDocumentIds.includes(document.documentId); const emphasisClass = index === 0 ? "border-primary/40 bg-primary/5" : index < 3 @@ -156,16 +156,18 @@ const DocumentImpactPanel = ({ {t("knowledge.open")} - + {onSuggestUpdates && ( + + )}
diff --git a/src/components/editor/EditorHeader.tsx b/src/components/editor/EditorHeader.tsx index 936d046..cd97758 100644 --- a/src/components/editor/EditorHeader.tsx +++ b/src/components/editor/EditorHeader.tsx @@ -52,12 +52,12 @@ interface EditorHeaderProps { importState: DocumentImportState; onToggleTheme: () => void; onSaveDocsy: () => void; - onOpenShare: () => void; + onOpenShare?: () => void; onCopyMd: () => void; onCopyHtml: () => void; onCopyJson: () => void; onCopyYaml: () => void; - onCopyShareLink: () => void; + onCopyShareLink?: () => void; onSaveMd: () => void; onSaveTex: () => void; onSaveHtml: () => void; @@ -469,15 +469,19 @@ const EditorHeader = ({ Docsy (.docsy) - - - {t("header.clipboard.share")} - - - - {t("header.clipboard.shareLink")} - - + {onOpenShare && ( + + + {t("header.clipboard.share")} + + )} + {onCopyShareLink && ( + + + {t("header.clipboard.shareLink")} + + )} + {(onOpenShare || onCopyShareLink) && } {(mode === "markdown" || mode === "latex" || mode === "html") && ( <> diff --git a/src/components/editor/EditorWorkspace.tsx b/src/components/editor/EditorWorkspace.tsx index c884eeb..25090c8 100644 --- a/src/components/editor/EditorWorkspace.tsx +++ b/src/components/editor/EditorWorkspace.tsx @@ -26,13 +26,9 @@ type KeyboardShortcutsModalProps = ComponentProps type ExportPreviewPanelProps = ComponentProps<(typeof import("@/components/editor/ExportPreviewPanel"))["default"]>; type TemplateDialogProps = ComponentProps<(typeof import("@/components/editor/TemplateDialog"))["default"]>; type PatchReviewDialogProps = ComponentProps<(typeof import("@/components/editor/PatchReviewDialog"))["default"]>; -type AiAssistantDialogProps = ComponentProps<(typeof import("@/components/editor/AiAssistantDialog"))["default"]>; -type ShareLinkDialogProps = ComponentProps<(typeof import("@/components/editor/ShareLinkDialog"))["default"]>; -const AiAssistantDialog = lazy(() => import("@/components/editor/AiAssistantDialog")); const ExportPreviewPanel = lazy(() => import("@/components/editor/ExportPreviewPanel")); const PatchReviewDialog = lazy(() => import("@/components/editor/PatchReviewDialog")); -const ShareLinkDialog = lazy(() => import("@/components/editor/ShareLinkDialog")); const TemplateDialog = lazy(() => import("@/components/editor/TemplateDialog")); const PreviewFallback = () =>
; @@ -91,8 +87,6 @@ interface EditorWorkspaceProps { previewProps: ExportPreviewPanelProps; renderEditor: () => ReactNode; shortcutsModalProps: KeyboardShortcutsModalProps; - aiAssistantDialogProps: AiAssistantDialogProps; - shareLinkDialogProps: ShareLinkDialogProps; patchReviewDialogProps: PatchReviewDialogProps; templateDialogProps: TemplateDialogProps; onFileChange: ChangeEventHandler; @@ -109,8 +103,6 @@ const EditorWorkspace = ({ previewProps, renderEditor, shortcutsModalProps, - aiAssistantDialogProps, - shareLinkDialogProps, patchReviewDialogProps, templateDialogProps, onFileChange, @@ -179,8 +171,6 @@ const EditorWorkspace = ({ previewProps={previewProps} renderEditor={renderEditor} shortcutsModalProps={shortcutsModalProps} - aiAssistantDialogProps={aiAssistantDialogProps} - shareLinkDialogProps={shareLinkDialogProps} patchReviewDialogProps={patchReviewDialogProps} templateDialogProps={templateDialogProps} onFileChange={onFileChange} @@ -203,8 +193,6 @@ interface EditorWorkspaceLayoutProps { previewProps: ExportPreviewPanelProps; renderEditor: () => ReactNode; shortcutsModalProps: KeyboardShortcutsModalProps; - aiAssistantDialogProps: AiAssistantDialogProps; - shareLinkDialogProps: ShareLinkDialogProps; patchReviewDialogProps: PatchReviewDialogProps; templateDialogProps: TemplateDialogProps; onFileChange: ChangeEventHandler; @@ -224,8 +212,6 @@ const EditorWorkspaceLayout = ({ previewProps, renderEditor, shortcutsModalProps, - aiAssistantDialogProps, - shareLinkDialogProps, patchReviewDialogProps, templateDialogProps, onFileChange, @@ -304,20 +290,6 @@ const EditorWorkspaceLayout = ({ type="file" /> - {aiAssistantDialogProps.open && ( - }> - - - - - )} - {shareLinkDialogProps.open && ( - }> - - - - - )} {patchReviewDialogProps.open && ( }> diff --git a/src/components/editor/FileSidebarKnowledgePanels.tsx b/src/components/editor/FileSidebarKnowledgePanels.tsx index caffd68..bd1a9a9 100644 --- a/src/components/editor/FileSidebarKnowledgePanels.tsx +++ b/src/components/editor/FileSidebarKnowledgePanels.tsx @@ -147,11 +147,13 @@ const FileSidebarKnowledgePanels = ({ impact={knowledgeActiveImpact} onOpenDocument={openKnowledgeDocumentById} onOpenGraph={openKnowledgeGraph} - onSuggestUpdates={(documentId) => onSuggestKnowledgeUpdates(documentId, { - queueContext: "impact", - sourceDocumentId: activeDocId, - sourceDocumentName: activeDoc.name, - })} + onSuggestUpdates={onSuggestKnowledgeUpdates + ? (documentId) => onSuggestKnowledgeUpdates(documentId, { + queueContext: "impact", + sourceDocumentId: activeDocId, + sourceDocumentName: activeDoc.name, + }) + : undefined} suggestableDocumentIds={suggestableKnowledgeDocumentIds} /> { - const context = { - issueId, - issueKind, - issuePriority, - issueReason, - queueContext: "consistency" as const, - sourceDocumentId, - sourceDocumentName, - targetDocumentName, - }; - - if (sourceDocumentId === activeDocId) { - onSuggestKnowledgeUpdates(targetDocumentId, context); - return; - } + onSuggestUpdates={onSuggestKnowledgeUpdates && onSuggestKnowledgeImpactUpdate + ? ({ issueId, issueKind, issuePriority, issueReason, sourceDocumentId, sourceDocumentName, targetDocumentId, targetDocumentName }) => { + const context = { + issueId, + issueKind, + issuePriority, + issueReason, + queueContext: "consistency" as const, + sourceDocumentId, + sourceDocumentName, + targetDocumentName, + }; - onSuggestKnowledgeImpactUpdate(sourceDocumentId, targetDocumentId, context); - }} + if (sourceDocumentId === activeDocId) { + onSuggestKnowledgeUpdates(targetDocumentId, context); + return; + } + + onSuggestKnowledgeImpactUpdate(sourceDocumentId, targetDocumentId, context); + } + : undefined} suggestableDocumentIds={suggestableKnowledgeDocumentIds} /> { - onSuggestKnowledgeImpactUpdate(sourceDocumentId, targetDocumentId, { - queueContext: "change", - sourceDocumentId, - }); - }} + onSuggestUpdates={onSuggestKnowledgeImpactUpdate + ? (sourceDocumentId, targetDocumentId) => { + onSuggestKnowledgeImpactUpdate(sourceDocumentId, targetDocumentId, { + queueContext: "change", + sourceDocumentId, + }); + } + : undefined} /> { void onVersionSnapshot(activeDoc, { exportFormat: "XeLaTeX PDF" }); }, + serviceEnabled: featureFlags.remoteTexServiceEnabled, }); - const { - generatePatchSet: generateTexAutoFixPatchSet, - isFixing: isFixingTexAutoFix, - } = useTexAutoFix({ - diagnostics: texValidation.diagnostics, - documentId: activeDoc.id, - documentName: activeDoc.name, - latexSource: activeDoc.content, - logSummary: texValidation.logSummary, - sourceType: texValidation.sourceType, - }); - const canAiFixTex = texValidation.validationEnabled + const canAiFixTex = featureFlags.llmFeaturesEnabled + && texValidation.validationEnabled && texValidation.status === "error" && texValidation.sourceType === "raw-latex" && texValidation.diagnostics.length > 0; const handleOpenTexAutoFixReview = useCallback(async () => { - const nextPatchSet = await generateTexAutoFixPatchSet(); - onLoadPatchSet(nextPatchSet); - }, [generateTexAutoFixPatchSet, onLoadPatchSet]); + void onLoadPatchSet; + }, [onLoadPatchSet]); const texValidationProps = useMemo(() => ({ canAiFix: canAiFixTex, compileMs: texValidation.compileMs, diagnostics: texValidation.diagnostics, health: texValidation.health, - isAiFixing: isFixingTexAutoFix, + isAiFixing: false, isExportingPdf: texValidation.isExportingPdf, lastValidatedAt: texValidation.lastValidatedAt, latexSource: activeDoc.mode === "latex" ? activeDoc.content : currentRenderableLatexDocument, @@ -106,7 +96,6 @@ const PreviewRuntime = ({ canAiFixTex, currentRenderableLatexDocument, handleOpenTexAutoFixReview, - isFixingTexAutoFix, onJumpToLatexLine, texValidation.compileMs, texValidation.diagnostics, diff --git a/src/components/editor/SuggestionQueuePanel.tsx b/src/components/editor/SuggestionQueuePanel.tsx index 3912799..12e4256 100644 --- a/src/components/editor/SuggestionQueuePanel.tsx +++ b/src/components/editor/SuggestionQueuePanel.tsx @@ -163,7 +163,7 @@ const SuggestionQueuePanel = ({ ); return ( -
+

@@ -233,11 +233,11 @@ const SuggestionQueuePanel = ({ )}

- -
@@ -302,6 +302,7 @@ const SuggestionQueuePanel = ({
- - + )} +
@@ -278,11 +289,11 @@ const Landing = () => { >
+ {isWebProfile && ( +
+
+ + + {t("landing.downloadsBadge")} + +

{t("landing.downloadsTitle")}

+

{t("landing.downloadsDescription")}

+
+ + {configuredDesktopDownloadLinks.length > 0 ? ( + + {configuredDesktopDownloadLinks.map((link, index) => ( + +
+ +
+
+

{link.label}

+ +
+

+ {t(`landing.downloadPlatforms.${link.id}`)} +

+
+ ))} +
+ ) : ( + + {t("landing.downloadsNotConfigured")} + + )} + + {desktopReleasesUrl && ( + + )} +
+
+ )} + + {!isWebProfile && (
{
+ )}
@@ -476,6 +565,7 @@ const Landing = () => {
+ {!isWebProfile && (
{
+ )}
@@ -626,6 +717,7 @@ const Landing = () => {
+ {!isWebProfile && (
{
+ )} + {!isWebProfile && (
{
+ )}
{
- -
-
-
{guideContent.landing.ctaTitle}
-

- {guideContent.landing.ctaDescription} -

+ {!isWebProfile && ( + + )}
+ {!isWebProfile && ( +
+
{guideContent.landing.ctaTitle}
+

+ {guideContent.landing.ctaDescription} +

+
+ )}
diff --git a/src/pages/WebRedirect.tsx b/src/pages/WebRedirect.tsx new file mode 100644 index 0000000..312160c --- /dev/null +++ b/src/pages/WebRedirect.tsx @@ -0,0 +1,5 @@ +import { Navigate } from "react-router-dom"; + +const WebRedirect = () => ; + +export default WebRedirect; diff --git a/src/test/editorWorkspace.test.tsx b/src/test/editorWorkspace.test.tsx index 0e9c76e..acbe23b 100644 --- a/src/test/editorWorkspace.test.tsx +++ b/src/test/editorWorkspace.test.tsx @@ -8,25 +8,6 @@ import { I18nContext } from "@/i18n/I18nProvider"; const renderWorkspace = (overrides: Record = {}) => { const baseProps: Record = { activeMode: "markdown", - aiAssistantDialogProps: { - busyAction: null, - compareCandidates: [], - comparePreview: null, - onCompare: vi.fn(), - onExtractProcedure: vi.fn(), - onGenerateSection: vi.fn(), - onGenerateToc: vi.fn(), - onLoadTocPatch: vi.fn(), - onOpenChange: vi.fn(), - onSuggestUpdates: vi.fn(), - onSummarize: vi.fn(), - open: false, - procedureResult: null, - richTextAvailable: false, - summaryResult: null, - tocPreview: null, - updateSuggestionPreview: null, - }, fileInputRef: createRef(), findReplaceProps: { editor: null, diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 1166dfd..73f972d 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -4,6 +4,21 @@ interface ImportMetaEnv { readonly VITE_AI_API_BASE_URL?: string; readonly VITE_APP_BUILD_ID?: string; readonly VITE_APP_PROFILE?: "desktop" | "web"; + readonly VITE_DESKTOP_RELEASES_URL?: string; + readonly VITE_DOWNLOAD_LINUX_URL?: string; + readonly VITE_DOWNLOAD_MACOS_URL?: string; + readonly VITE_DOWNLOAD_WINDOWS_URL?: string; +} + +interface Window { + docsyDesktop?: { + isDesktop: true; + platform: string; + versions: { + chrome?: string; + electron?: string; + }; + }; } interface ImportMeta { diff --git a/tsconfig.electron.json b/tsconfig.electron.json new file mode 100644 index 0000000..b543d2e --- /dev/null +++ b/tsconfig.electron.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.node.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["electron/**/*.ts"] +} diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..ec97836 --- /dev/null +++ b/vercel.json @@ -0,0 +1,12 @@ +{ + "buildCommand": "npm run build:web", + "framework": "vite", + "installCommand": "npm ci", + "outputDirectory": "dist", + "rewrites": [ + { + "destination": "/index.html", + "source": "/(.*)" + } + ] +} diff --git a/vite.config.ts b/vite.config.ts index be5f00f..7567abf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -69,6 +69,9 @@ const bundleReportPlugin = () => ({ // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { + const webPageStub = path.resolve(__dirname, "./src/pages/WebRedirect.tsx"); + const webGuideContentStub = path.resolve(__dirname, "./src/content/webGuideContentStub.ts"); + return ({ server: { host: "::", @@ -87,6 +90,12 @@ export default defineConfig(({ mode }) => { plugins: [react(), bundleReportPlugin(), mode === "development" && componentTagger()].filter(Boolean), resolve: { alias: { + ...(mode === "web" ? { + "@/content/guideContent": webGuideContentStub, + "@/pages/Guide": webPageStub, + "@/pages/Index": webPageStub, + "@/pages/WorkspaceGraph": webPageStub, + } : {}), "@": path.resolve(__dirname, "./src"), }, }, @@ -101,19 +110,6 @@ export default defineConfig(({ mode }) => { }, output: { manualChunks(id) { - if (matchesSource(id, [ - "src/lib/ai/autosaveSummaryClient.ts", - ])) { - return "ai-history"; - } - - if (matchesSource(id, [ - "src/lib/ai/httpClient.ts", - "src/lib/ai/texClient.ts", - ])) { - return "ai-shared"; - } - if (!id.includes("node_modules")) { return; } From c2634ea51fa901d73808f27edfea8c9e4c0fd8db Mon Sep 17 00:00:00 2001 From: ethan Date: Thu, 4 Jun 2026 19:16:05 +0900 Subject: [PATCH 2/7] Keep Vercel deploy uploads web-only The Vercel CLI deploy packages the working tree, so local Electron build outputs can accidentally be uploaded alongside the web source. This commit excludes desktop artifacts, local build outputs, test output, logs, and local env files from Vercel uploads while preserving the release page build path. Constraint: Vercel preview deploy rejected a local upload because Electron release files exceeded the 100 MB file limit. Rejected: Delete local release artifacts before every deploy | it is easy to forget and does not protect future deploys. Confidence: high Scope-risk: narrow Directive: Keep desktop packaging output out of Vercel uploads; the web profile should link to GitHub Releases instead of shipping binaries through Vercel. Tested: git diff --check; prior npm run build:web success before adding ignore-only deploy config. Not-tested: Vercel deploy rerun is pending for this commit. --- .gitignore | 2 ++ .vercelignore | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 .vercelignore diff --git a/.gitignore b/.gitignore index 23c55ae..058148c 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,5 @@ output/readme-bundles/ output/web-profile-test/ playwright-report .playwright-*.png + +.vercel diff --git a/.vercelignore b/.vercelignore new file mode 100644 index 0000000..48731ed --- /dev/null +++ b/.vercelignore @@ -0,0 +1,16 @@ +/release +/dist +/electron-dist +/server-dist +/node_modules +/.omx +/.playwright-cli +/test-results +/output +*.har +*.log +*.err +*.out +.env +.env.* +!.env.example From 7c416b1071b0dd3973ff3906b268b1036243b704 Mon Sep 17 00:00:00 2001 From: ethan Date: Thu, 4 Jun 2026 19:41:24 +0900 Subject: [PATCH 3/7] Build Vercel web profile without env files The production Vercel deploy was using npm run build:web, but Vercel uploads excluded .env.web, so the app could still resolve to the desktop profile when VITE_APP_PROFILE was missing. This makes Vite mode web authoritative and reuses the same profile flag for route-level lazy loading. Constraint: Vercel deploy uploads should not include local .env files. Rejected: Re-include .env.web in Vercel uploads | it would keep deploy correctness dependent on an ignored local file instead of the build mode. Confidence: high Scope-risk: narrow Directive: Treat vite --mode web as sufficient to produce the public download page. Tested: npm run typecheck; npm run build:web; dist Landing chunk contains GitHub Release download URLs; dist bundle report has no editor page runtime chunks. Not-tested: Production Vercel redeploy is pending for this commit. --- src/App.tsx | 7 +++---- src/lib/appProfile.ts | 6 +++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 2331137..52be82f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,13 +8,12 @@ import { BrowserRouter, HashRouter, Navigate, Routes, Route } from "react-router import { isDesktopShell } from "@/lib/runtime/desktopShell"; import { isWebProfile } from "@/lib/appProfile"; -const isWebBuildProfile = import.meta.env.VITE_APP_PROFILE?.trim().toLowerCase() === "web"; const Landing = lazy(() => import("@/pages/Landing")); -const Guide = isWebBuildProfile ? null : lazy(() => import("@/pages/Guide")); -const Index = isWebBuildProfile ? null : lazy(() => import("@/pages/Index")); +const Guide = isWebProfile ? null : lazy(() => import("@/pages/Guide")); +const Index = isWebProfile ? null : lazy(() => import("@/pages/Index")); const Privacy = lazy(() => import("@/pages/Privacy")); const Terms = lazy(() => import("@/pages/Terms")); -const WorkspaceGraph = isWebBuildProfile ? null : lazy(() => import("@/pages/WorkspaceGraph")); +const WorkspaceGraph = isWebProfile ? null : lazy(() => import("@/pages/WorkspaceGraph")); const NotFound = lazy(() => import("@/pages/NotFound")); const queryClient = new QueryClient(); diff --git a/src/lib/appProfile.ts b/src/lib/appProfile.ts index 3a4c43d..253ea25 100644 --- a/src/lib/appProfile.ts +++ b/src/lib/appProfile.ts @@ -18,7 +18,11 @@ export interface FeatureFlags { const resolveProfile = (): AppProfile => { const configured = import.meta.env.VITE_APP_PROFILE?.trim().toLowerCase(); - return configured === "web" ? "web" : "desktop"; + if (configured === "web" || configured === "desktop") { + return configured; + } + + return import.meta.env.MODE === "web" ? "web" : "desktop"; }; export const appProfile = resolveProfile(); From 4dc79044b5a2f757c2f3d021e04778d8690147c5 Mon Sep 17 00:00:00 2001 From: ethan Date: Thu, 4 Jun 2026 21:33:01 +0900 Subject: [PATCH 4/7] Make desktop release local-first and file-url safe Desktop builds were still tied to cloud-backed workspace and TeX paths, while packaged renderer assets used absolute URLs that fail from Electron file:// installs. This moves persistence, queues, and TeX artifacts to local file-backed behavior, normalizes deployment defaults, removes unused Google cloud dependencies, and adds focused regressions for local queues, stores, and Vite desktop asset paths. Constraint: Desktop editing must work without remote DB, Cloud Tasks, GCS, or bundled cloud service credentials Rejected: Keep Firestore, GCS, and Cloud Tasks behind environment toggles | user requested all DB/server behavior to use local storage Rejected: Only reinstall the desktop app | GitHub source and release assets would remain stale Confidence: high Scope-risk: moderate Directive: Do not reintroduce remote DB/storage/queue defaults for desktop without explicit project-owner approval Tested: npm run typecheck; npm run build:server; npm run build; npm run desktop:dist; targeted Vitest local DB suite; changed-file ESLint; git diff --check; installed Docsy visual launch Not-tested: Full npm run lint and full npm run test still fail on pre-existing unrelated repo-wide lint and feature-flag test debt --- .env.example | 23 +- README.md | 8 +- cloudbuild.ai.yaml | 2 +- cloudbuild.tex.yaml | 59 +- package-lock.json | 1265 +---------------- package.json | 7 +- scripts/deploy-ai-cloud-run.ps1 | 2 +- scripts/gcp/ensure-tex-runtime-resources.sh | 135 +- scripts/resolve-public-deploy-env.mjs | 11 +- .../modules/config/publicDeploymentConfig.js | 21 +- server/modules/http/aiDiagnostics.ts | 2 +- server/modules/tex/artifactStorage.ts | 124 +- server/modules/tex/jobQueue.ts | 60 +- server/modules/tex/jobStore.ts | 118 +- server/modules/workspace/repository.ts | 281 +--- src/test/aiDiagnostics.test.ts | 4 +- src/test/authCallbackSessionRotation.test.ts | 2 +- src/test/authConnectRoute.test.ts | 2 +- src/test/landingPage.test.tsx | 4 +- src/test/publicDeploymentConfig.test.ts | 16 +- .../resolveWorkspaceSessionForAgent.test.ts | 6 +- src/test/setup.ts | 62 +- src/test/texJobQueue.test.ts | 46 + src/test/texJobStore.test.ts | 37 +- src/test/texPreviewStorage.test.ts | 32 +- src/test/viteConfig.test.ts | 28 + src/test/workspaceRepository.test.ts | 59 +- vite.config.ts | 1 + 28 files changed, 303 insertions(+), 2114 deletions(-) create mode 100644 src/test/texJobQueue.test.ts create mode 100644 src/test/viteConfig.test.ts diff --git a/.env.example b/.env.example index 58ca622..e4463a4 100644 --- a/.env.example +++ b/.env.example @@ -66,21 +66,15 @@ GOOGLE_WORKSPACE_SCOPE_PROFILE=restricted GOOGLE_WORKSPACE_SCOPES= WORKSPACE_STATE_PATH= WORKSPACE_DB_PATH= -# Local development defaults to a file-backed workspace repository. -# Cloud Run defaults to Firestore so OAuth/session state is shared across instances. -# Override only if you need to force a backend explicitly. +# Docsy now uses the local file-backed workspace repository in every runtime. +# Set WORKSPACE_STATE_PATH or WORKSPACE_DB_PATH to a writable persistent local +# path when the runtime should keep OAuth/session state across restarts. # WORKSPACE_REPOSITORY_BACKEND=file -# Optional Firestore namespace overrides for deployed workspace state. -# WORKSPACE_FIRESTORE_ROOT_COLLECTION=docsyWorkspace -# WORKSPACE_FIRESTORE_ROOT_DOCUMENT=state # TeX service hardening TEX_MAX_REQUEST_BYTES=400000 -TEX_PREVIEW_BUCKET= +TEX_PREVIEW_PUBLIC_BASE_URL= TEX_PREVIEW_URL_TTL_SECONDS=900 -TEX_TASK_QUEUE= -TEX_TASK_LOCATION= -TEX_JOB_WORKER_URL= # Keep full LaTeX document wrappers enabled for deployed LaTeX mode. # Public/demo deployments should accept arbitrary installed packages and only # block dangerous file/process primitives by default. @@ -89,9 +83,6 @@ TEX_ALLOW_ALL_PACKAGES=true TEX_ALLOW_RAW_DOCUMENT=true TEX_ALLOW_RESTRICTED_COMMANDS=false TEX_ALLOWED_PACKAGES=amsmath,amssymb,amsthm,array,booktabs,caption,enumitem,etoolbox,fancyhdr,float,fontspec,geometry,graphicx,hyperref,inputenc,latexsym,listings,longtable,makecell,mathtools,multirow,setspace,soul,tabularx,tcolorbox,titlesec,ulem,xcolor,xeCJK -# For Cloud Run preview delivery, set TEX_PREVIEW_BUCKET to a GCS bucket that the -# TeX service account can write to and sign read URLs for. Signed preview URLs -# expire after TEX_PREVIEW_URL_TTL_SECONDS (default 15 minutes). -# For async preview/export, set TEX_TASK_QUEUE, TEX_TASK_LOCATION, and -# TEX_JOB_WORKER_URL on the interactive TeX service so it can enqueue Cloud Tasks -# for the dedicated worker service. +# Signed local preview/download URLs expire after TEX_PREVIEW_URL_TTL_SECONDS +# (default 15 minutes). Set TEX_PREVIEW_PUBLIC_BASE_URL only when the service +# must advertise a public local artifact origin instead of loopback. diff --git a/README.md b/README.md index e96edb1..3710fd2 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Current submission priorities: ## Architecture Diagram -Docsy's deployed architecture uses a React + Vite frontend served from Firebase Hosting and a separate Node.js AI backend deployed on Cloud Run. The backend accesses Gemini on Vertex AI through the Google GenAI SDK, stores shared Google Workspace session and state data in Firestore, and brokers Google Docs/Drive plus LaTeX service requests without exposing credentials to the browser. +Docsy's deployed architecture uses a React + Vite frontend served from Firebase Hosting and a separate Node.js AI backend deployed on Cloud Run. The backend accesses Gemini on Vertex AI through the Google GenAI SDK, stores Google Workspace session and state data in the local file-backed repository, and brokers Google Docs/Drive plus LaTeX service requests without exposing credentials to the browser. For hackathon submission, export the diagram below as a PNG and upload it via `File Upload`. @@ -70,8 +70,8 @@ flowchart LR GEM -->|"structured JSON action
patch proposal"| API API -->|"assistant response
patch data"| FE - API -->|"session + workspace state"| DB["Firestore"] - DB -->|"shared state lookup"| API + API -->|"session + workspace state"| DB["Local state file"] + DB -->|"local state lookup"| API API -->|"OAuth + Docs/Drive operations"| GW["Google OAuth + Google Docs/Drive"] GW -->|"auth callback + document data"| API @@ -201,7 +201,7 @@ Google OAuth production guard: - set `GOOGLE_OAUTH_PUBLISHING_STATUS=testing|production` - set `GOOGLE_WORKSPACE_SCOPE_PROFILE=restricted|reduced` -- deployed Google Workspace state now defaults to Firestore on Cloud Run so OAuth/session state is shared across instances +- deployed Google Workspace state now uses the local file-backed repository; set `WORKSPACE_STATE_PATH` or `WORKSPACE_DB_PATH` to a writable persistent local path when state must survive restarts - Firebase Hosting rewrites only forward the `__session` cookie to Cloud Run, so hosted workspace auth must use that cookie name in deployed HTTPS environments - the current deployed auth contract is documented in [docs/current-workspace-auth-contract-2026-03-16.md](docs/current-workspace-auth-contract-2026-03-16.md) - run `npm run check:public-deploy` before public deploys to validate custom-domain and OAuth settings diff --git a/cloudbuild.ai.yaml b/cloudbuild.ai.yaml index 9176cda..326c532 100644 --- a/cloudbuild.ai.yaml +++ b/cloudbuild.ai.yaml @@ -75,7 +75,7 @@ substitutions: _GOOGLE_WORKSPACE_SCOPE_PROFILE: "restricted" _WORKSPACE_FRONTEND_ORIGIN: "" _PUBLIC_DEPLOY_EXPECTED_FRONTEND_ORIGIN: "" - _WORKSPACE_REPOSITORY_BACKEND: "firestore" + _WORKSPACE_REPOSITORY_BACKEND: "file" _GOOGLE_WORKSPACE_SCOPES: "" _TEX_ALLOW_ALL_PACKAGES: "true" _TEX_ALLOW_RAW_DOCUMENT: "true" diff --git a/cloudbuild.tex.yaml b/cloudbuild.tex.yaml index 765c64a..2b1e378 100644 --- a/cloudbuild.tex.yaml +++ b/cloudbuild.tex.yaml @@ -19,49 +19,7 @@ steps: args: - -ceu - | - if [ -z "${_TEX_PREVIEW_BUCKET}" ] || [ "${_TEX_PREVIEW_BUCKET}" = "__SET_ME__" ]; then - echo "_TEX_PREVIEW_BUCKET must be configured for Cloud Run deployments." >&2 - exit 1 - fi - - if [ -z "${_TEX_TASK_QUEUE}" ] || [ -z "${_TEX_TASK_LOCATION}" ]; then - echo "Cloud Tasks queue is not configured. TeX service will use local background processing." - elif ! gcloud tasks queues describe "${_TEX_TASK_QUEUE}" --location="${_TEX_TASK_LOCATION}" >/dev/null 2>&1; then - gcloud tasks queues create "${_TEX_TASK_QUEUE}" --location="${_TEX_TASK_LOCATION}" - fi - - cat > /workspace/tex-worker-env.yaml < /workspace/tex-service-env.yaml <=18" - } - }, - "node_modules/@google-cloud/paginator": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", - "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", - "license": "Apache-2.0", - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@google-cloud/projectify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", - "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@google-cloud/promisify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", - "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", - "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", - "license": "Apache-2.0", - "dependencies": { - "@google-cloud/paginator": "^5.0.0", - "@google-cloud/projectify": "^4.0.0", - "@google-cloud/promisify": "<4.1.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "duplexify": "^4.1.3", - "fast-xml-parser": "^5.3.4", - "gaxios": "^6.0.2", - "google-auth-library": "^9.6.3", - "html-entities": "^2.5.2", - "mime": "^3.0.0", - "p-limit": "^3.0.1", - "retry-request": "^7.0.0", - "teeny-request": "^9.0.0", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@google-cloud/storage/node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/gaxios/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@google-cloud/storage/node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/google-auth-library": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", - "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@google-cloud/storage/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/@google-cloud/storage/node_modules/retry-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", - "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", - "license": "MIT", - "dependencies": { - "@types/request": "^2.48.8", - "extend": "^3.0.2", - "teeny-request": "^9.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/teeny-request": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", - "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", - "license": "Apache-2.0", - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.9", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/teeny-request/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@google-cloud/storage/node_modules/teeny-request/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@google-cloud/storage/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/@google-cloud/storage/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@google-cloud/storage/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/@google-cloud/storage/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/@google-cloud/tasks": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@google-cloud/tasks/-/tasks-5.5.2.tgz", - "integrity": "sha512-F934h4rI3OLlEVgzthDzE5RDQqT2brlq+BeD15eHNBi4sGMTdaz/b4y3eMFxJvB2hZk5mEKlLnsb9Pt2EcmNSQ==", - "license": "Apache-2.0", - "dependencies": { - "google-gax": "^4.0.4" - }, - "engines": { - "node": ">=v14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@google-cloud/tasks/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@google-cloud/tasks/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@google-cloud/tasks/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/tasks/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/@google-cloud/tasks/node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/google-auth-library": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", - "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/google-gax": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", - "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.13", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.3.0", - "node-fetch": "^2.7.0", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.2", - "protobufjs": "^7.3.2", - "retry-request": "^7.0.0", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@google-cloud/tasks/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/@google-cloud/tasks/node_modules/proto3-json-serializer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", - "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", - "license": "Apache-2.0", - "dependencies": { - "protobufjs": "^7.2.5" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@google-cloud/tasks/node_modules/retry-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", - "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", - "license": "MIT", - "dependencies": { - "@types/request": "^2.48.8", - "extend": "^3.0.2", - "teeny-request": "^9.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@google-cloud/tasks/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@google-cloud/tasks/node_modules/teeny-request": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", - "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", - "license": "Apache-2.0", - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.9", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/tasks/node_modules/teeny-request/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@google-cloud/tasks/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/@google-cloud/tasks/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@google-cloud/tasks/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/@google-cloud/tasks/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/@google-cloud/tasks/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@google-cloud/tasks/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/tasks/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/tasks/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/@google/genai": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.44.0.tgz", @@ -2114,145 +1483,6 @@ } } }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.3", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@grpc/proto-loader/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/proto-loader/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@grpc/proto-loader/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/@grpc/proto-loader/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/proto-loader/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/proto-loader/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@grpc/proto-loader/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@grpc/proto-loader/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", @@ -2423,16 +1653,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -2538,15 +1758,6 @@ "node": ">= 8" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5395,15 +4606,6 @@ "url": "https://github.com/sponsors/ueberdosis" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -5425,12 +4627,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/caseless": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", - "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", - "license": "MIT" - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -5786,12 +4982,6 @@ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "license": "MIT" }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT" - }, "node_modules/@types/markdown-it": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", @@ -5861,18 +5051,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/request": { - "version": "2.48.13", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", - "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", - "license": "MIT", - "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.5" - } - }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -5889,12 +5067,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -6347,18 +5519,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -6726,15 +5886,6 @@ "dequal": "^2.0.3" } }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -6781,22 +5932,14 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "license": "MIT", - "dependencies": { - "retry": "0.13.1" + "node": ">=0.12.0" } }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -7121,6 +6264,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7521,6 +6665,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -8384,6 +7529,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -8584,6 +7730,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -8594,18 +7741,6 @@ "node": ">= 0.4" } }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -8981,6 +8116,7 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -9023,6 +8159,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9032,6 +8169,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9048,6 +8186,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -9060,6 +8199,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9142,6 +8282,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9371,15 +8512,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -9514,41 +8646,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-builder": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz", - "integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.1.3" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.5.tgz", - "integrity": "sha512-NLY+V5NNbdmiEszx9n14mZBseJTC50bRq1VHsaxOmR72JDuZt+5J1Co+dC/4JPnyq+WrIHNM69r0sqf7BMb3Mg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "fast-xml-builder": "^1.1.3", - "path-expression-matcher": "^1.1.3", - "strnum": "^2.1.2" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -9703,23 +8800,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -9818,12 +8898,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "license": "MIT" - }, "node_modules/gaxios": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", @@ -9866,6 +8940,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -9899,6 +8974,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -10060,28 +9136,6 @@ "node": ">=18" } }, - "node_modules/google-gax": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.6.tgz", - "integrity": "sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.12.6", - "@grpc/proto-loader": "^0.8.0", - "duplexify": "^4.1.3", - "google-auth-library": "^10.1.0", - "google-logging-utils": "^1.1.1", - "node-fetch": "^3.3.2", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^3.0.0", - "protobufjs": "^7.5.3", - "retry-request": "^8.0.0", - "rimraf": "^5.0.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/google-logging-utils": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", @@ -10095,6 +9149,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10143,90 +9198,6 @@ "dev": true, "license": "MIT" }, - "node_modules/gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", - "license": "MIT", - "dependencies": { - "gaxios": "^6.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/gtoken/node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/gtoken/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/gtoken/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/gtoken/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/gtoken/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/gtoken/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -10261,6 +9232,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10273,6 +9245,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -10351,22 +9324,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -10543,6 +9500,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/input-otp": { @@ -10637,18 +9595,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isbinaryfile": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", @@ -10998,12 +9944,6 @@ "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -11181,6 +10121,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11249,22 +10190,11 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11274,6 +10204,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -11668,6 +10599,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -11711,6 +10643,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -11813,21 +10746,6 @@ "node": ">=8" } }, - "node_modules/path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -12558,18 +11476,6 @@ "prosemirror-transform": "^1.1.0" } }, - "node_modules/proto3-json-serializer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", - "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", - "license": "Apache-2.0", - "dependencies": { - "protobufjs": "^7.4.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/protobufjs": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", @@ -12899,20 +11805,6 @@ "pify": "^2.3.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -13078,19 +11970,6 @@ "node": ">= 4" } }, - "node_modules/retry-request": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.2.tgz", - "integrity": "sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==", - "license": "MIT", - "dependencies": { - "extend": "^3.0.2", - "teeny-request": "^10.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -13498,30 +12377,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "license": "MIT", - "dependencies": { - "stubs": "^3.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -13664,24 +12519,6 @@ "dev": true, "license": "MIT" }, - "node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", - "license": "MIT" - }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", @@ -13828,60 +12665,6 @@ "node": ">=18" } }, - "node_modules/teeny-request": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.0.tgz", - "integrity": "sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw==", - "license": "Apache-2.0", - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^3.3.2", - "stream-events": "^1.0.5" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/teeny-request/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/teeny-request/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/teeny-request/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", @@ -15832,6 +14615,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/ws": { @@ -16053,6 +14837,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 487ab6c..956b0ed 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "capture:marketing": "node scripts/capture-marketing-surfaces.mjs", "check:public-deploy": "node scripts/validate-public-deploy-config.mjs", "desktop:dev": "node scripts/electron-dev.mjs", - "desktop:dist": "npm run build && npm run electron:build && electron-builder --publish never", - "desktop:dist:dir": "npm run build && npm run electron:build && electron-builder --dir --publish never", + "desktop:dist": "npm run build && npm run electron:build && electron-builder --publish never --config.win.signAndEditExecutable=false", + "desktop:dist:dir": "npm run build && npm run electron:build && electron-builder --dir --publish never --config.win.signAndEditExecutable=false", "electron:build": "esbuild electron/main.ts electron/preload.ts --bundle --platform=node --format=cjs --target=node22 --outdir=electron-dist --out-extension:.js=.cjs --external:electron", "electron:start": "npm run electron:build && electron .", "lint": "eslint .", @@ -38,9 +38,6 @@ "typecheck:server": "tsc -p tsconfig.server.json --noEmit" }, "dependencies": { - "@google-cloud/firestore": "^8.3.0", - "@google-cloud/storage": "^7.17.2", - "@google-cloud/tasks": "^5.5.0", "@google/genai": "^1.44.0", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "^1.2.11", diff --git a/scripts/deploy-ai-cloud-run.ps1 b/scripts/deploy-ai-cloud-run.ps1 index f4adde9..310c217 100644 --- a/scripts/deploy-ai-cloud-run.ps1 +++ b/scripts/deploy-ai-cloud-run.ps1 @@ -47,7 +47,7 @@ $substitutions = @( "_GOOGLE_WORKSPACE_SCOPE_PROFILE=$GoogleWorkspaceScopeProfile" "_WORKSPACE_FRONTEND_ORIGIN=$workspaceOrigin" "_PUBLIC_DEPLOY_EXPECTED_FRONTEND_ORIGIN=https://docsy.cyou" - "_WORKSPACE_REPOSITORY_BACKEND=firestore" + "_WORKSPACE_REPOSITORY_BACKEND=file" "_GOOGLE_WORKSPACE_SCOPES=$GoogleWorkspaceScopes" "_AI_DIAGNOSTICS_TOKEN_SECRET=$DiagnosticsTokenSecret" "_GOOGLE_CLIENT_SECRET_SECRET=$GoogleClientSecretSecret" diff --git a/scripts/gcp/ensure-tex-runtime-resources.sh b/scripts/gcp/ensure-tex-runtime-resources.sh index becf879..a0b7206 100644 --- a/scripts/gcp/ensure-tex-runtime-resources.sh +++ b/scripts/gcp/ensure-tex-runtime-resources.sh @@ -1,136 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -require_env() { - local name="$1" - local value="${!name:-}" - if [[ -z "${value}" ]]; then - echo "${name} is required." >&2 - exit 1 - fi -} +echo "Docsy TeX uses local job processing and local artifact storage." +echo "No GCS bucket or Cloud Tasks queue is required." -require_env "PROJECT_ID" -require_env "GCP_REGION" - -CLOUD_TASKS_SERVICE="cloudtasks.googleapis.com" -RUN_SERVICE_ACCOUNT="${RUN_SERVICE_ACCOUNT:-}" -if [[ -z "${RUN_SERVICE_ACCOUNT}" ]]; then - PROJECT_NUMBER="$(gcloud projects describe "${PROJECT_ID}" --format='value(projectNumber)')" - if [[ -z "${PROJECT_NUMBER}" ]]; then - echo "Unable to resolve project number for ${PROJECT_ID}." >&2 - exit 1 - fi - - RUN_SERVICE_ACCOUNT="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" -fi - -TEX_PREVIEW_BUCKET="${TEX_PREVIEW_BUCKET:-${PROJECT_ID}-docsy-tex-preview}" -TEX_TASK_QUEUE="${TEX_TASK_QUEUE:-tex-compile}" -TEX_TASK_LOCATION="${TEX_TASK_LOCATION:-${GCP_REGION}}" - -echo "Ensuring TeX runtime resources:" -echo " project: ${PROJECT_ID}" -echo " region: ${GCP_REGION}" -echo " preview_bucket: ${TEX_PREVIEW_BUCKET}" -echo " task_queue: ${TEX_TASK_QUEUE}" -echo " task_location: ${TEX_TASK_LOCATION}" -echo " runtime_service_account: ${RUN_SERVICE_ACCOUNT}" - -if ! gcloud storage buckets describe "gs://${TEX_PREVIEW_BUCKET}" --project "${PROJECT_ID}" >/dev/null 2>&1; then - gcloud storage buckets create "gs://${TEX_PREVIEW_BUCKET}" \ - --project "${PROJECT_ID}" \ - --location "${GCP_REGION}" \ - --uniform-bucket-level-access -fi - -LIFECYCLE_FILE="$(mktemp)" -cat > "${LIFECYCLE_FILE}" <<'EOF' -{ - "rule": [ - { - "action": { "type": "Delete" }, - "condition": { "age": 1 } - } - ] -} +cat </dev/null -rm -f "${LIFECYCLE_FILE}" - -gcloud storage buckets add-iam-policy-binding "gs://${TEX_PREVIEW_BUCKET}" \ - --member="serviceAccount:${RUN_SERVICE_ACCOUNT}" \ - --role="roles/storage.objectAdmin" \ - >/dev/null - -USE_CLOUD_TASKS="true" -TASKS_SERVICE_STATE_OUTPUT="" -if TASKS_SERVICE_STATE_OUTPUT="$(gcloud services list --enabled --project "${PROJECT_ID}" --filter="config.name:${CLOUD_TASKS_SERVICE}" --format='value(config.name)' 2>&1)"; then - if [[ "$(printf '%s' "${TASKS_SERVICE_STATE_OUTPUT}" | tr -d '\r')" == "${CLOUD_TASKS_SERVICE}" ]]; then - echo " cloud_tasks_api: enabled" - else - echo " cloud_tasks_api: disabled" - ENABLE_OUTPUT="" - if ! ENABLE_OUTPUT="$(gcloud services enable "${CLOUD_TASKS_SERVICE}" --project "${PROJECT_ID}" 2>&1 >/dev/null)"; then - echo "Warning: ${CLOUD_TASKS_SERVICE} is disabled and could not be enabled by this deploy principal." >&2 - echo "TeX service will fall back to local background processing." >&2 - echo "${ENABLE_OUTPUT}" >&2 - USE_CLOUD_TASKS="false" - TEX_TASK_QUEUE="" - TEX_TASK_LOCATION="" - else - echo " cloud_tasks_api: enabled" - fi - fi -else - echo "Warning: unable to inspect ${CLOUD_TASKS_SERVICE} state for ${PROJECT_ID}." >&2 - echo "${TASKS_SERVICE_STATE_OUTPUT}" >&2 - echo "TeX service will fall back to local background processing." >&2 - USE_CLOUD_TASKS="false" - TEX_TASK_QUEUE="" - TEX_TASK_LOCATION="" -fi - -if [[ "${USE_CLOUD_TASKS}" == "true" ]]; then - if ! gcloud tasks queues describe "${TEX_TASK_QUEUE}" --location "${TEX_TASK_LOCATION}" >/dev/null 2>&1; then - CREATE_OUTPUT="" - if ! CREATE_OUTPUT="$(gcloud tasks queues create "${TEX_TASK_QUEUE}" --location "${TEX_TASK_LOCATION}" 2>&1 >/dev/null)"; then - echo "Warning: unable to create Cloud Tasks queue ${TEX_TASK_QUEUE} in ${TEX_TASK_LOCATION}." >&2 - echo "TeX service will fall back to local background processing." >&2 - echo "${CREATE_OUTPUT}" >&2 - USE_CLOUD_TASKS="false" - TEX_TASK_QUEUE="" - TEX_TASK_LOCATION="" - fi - fi -fi - -if [[ "${USE_CLOUD_TASKS}" == "true" ]]; then - BIND_OUTPUT="" - if ! BIND_OUTPUT="$(gcloud tasks queues add-iam-policy-binding "${TEX_TASK_QUEUE}" \ - --location "${TEX_TASK_LOCATION}" \ - --member="serviceAccount:${RUN_SERVICE_ACCOUNT}" \ - --role="roles/cloudtasks.enqueuer" \ - 2>&1 >/dev/null)"; then - echo "Warning: unable to grant roles/cloudtasks.enqueuer on ${TEX_TASK_QUEUE} to ${RUN_SERVICE_ACCOUNT}." >&2 - echo "TeX service will fall back to local background processing." >&2 - echo "${BIND_OUTPUT}" >&2 - USE_CLOUD_TASKS="false" - TEX_TASK_QUEUE="" - TEX_TASK_LOCATION="" - fi -fi - -if [[ "${USE_CLOUD_TASKS}" != "true" ]]; then - echo " cloud_tasks_mode: disabled" -else - echo " cloud_tasks_mode: queue" -fi - -if [[ -n "${GITHUB_OUTPUT:-}" ]]; then - { - echo "preview_bucket=${TEX_PREVIEW_BUCKET}" - echo "task_queue=${TEX_TASK_QUEUE}" - echo "task_location=${TEX_TASK_LOCATION}" - } >> "${GITHUB_OUTPUT}" -fi diff --git a/scripts/resolve-public-deploy-env.mjs b/scripts/resolve-public-deploy-env.mjs index 78956e1..e744466 100644 --- a/scripts/resolve-public-deploy-env.mjs +++ b/scripts/resolve-public-deploy-env.mjs @@ -9,12 +9,19 @@ const DEFAULTS = { texServiceName: "docsy-tex", texWorkerServiceName: "docsy-tex-worker", vertexLocation: "asia-northeast3", - workspaceRepositoryBackend: "firestore", + workspaceRepositoryBackend: "file", workspaceScopeProfile: "restricted", }; const shellEscape = (value) => `'${String(value ?? "").replace(/'/g, `'\\''`)}'`; +const normalizeWorkspaceRepositoryBackend = (value) => { + const normalized = value.trim().toLowerCase(); + return normalized === "file" || normalized === "local" + ? "file" + : DEFAULTS.workspaceRepositoryBackend; +}; + const readArgs = (argv) => { const parsed = { aiUrl: process.env.DOCSY_AI_SERVICE_URL?.trim() || "", @@ -75,7 +82,7 @@ const resolveDeployEnv = ({ aiUrl, target }) => { const workspaceScopeProfile = configuredWorkspaceScopeProfile || DEFAULTS.workspaceScopeProfile; const workspaceScopes = configuredWorkspaceScopes; const aiMaxRequestBytes = configuredAiMaxRequestBytes || DEFAULTS.aiMaxRequestBytes; - const workspaceRepositoryBackend = configuredWorkspaceRepositoryBackend || DEFAULTS.workspaceRepositoryBackend; + const workspaceRepositoryBackend = normalizeWorkspaceRepositoryBackend(configuredWorkspaceRepositoryBackend); let aiApiBaseUrl = configuredApiBaseUrl || frontendOrigin; if (!aiApiBaseUrl && target === "web" && aiUrl) { diff --git a/server/modules/config/publicDeploymentConfig.js b/server/modules/config/publicDeploymentConfig.js index 1ee2196..ecdb74d 100644 --- a/server/modules/config/publicDeploymentConfig.js +++ b/server/modules/config/publicDeploymentConfig.js @@ -205,6 +205,10 @@ const pushDynamicDnsMessage = ({ bucket, fieldName, hostname, publishingStatus } const normalizeWorkspaceRepositoryBackend = (value) => { const normalized = value?.trim().toLowerCase() || ""; + if (normalized === "local") { + return "file"; + } + return normalized === "file" || normalized === "firestore" ? normalized : ""; }; @@ -227,7 +231,8 @@ export const readPublicDeploymentConfig = (env = process.env) => { const browserApiBaseUrl = normalizeConfiguredOrigin(env.VITE_AI_API_BASE_URL); const frontendOrigin = normalizeConfiguredOrigin(env.WORKSPACE_FRONTEND_ORIGIN); const googleClientId = env.GOOGLE_CLIENT_ID?.trim() || ""; - const workspaceRepositoryBackend = normalizeWorkspaceRepositoryBackend(env.WORKSPACE_REPOSITORY_BACKEND); + const explicitWorkspaceRepositoryBackend = normalizeWorkspaceRepositoryBackend(env.WORKSPACE_REPOSITORY_BACKEND); + const workspaceRepositoryBackend = explicitWorkspaceRepositoryBackend || "file"; const redirectUri = normalizeConfiguredGoogleOAuthRedirectUri( env.GOOGLE_OAUTH_REDIRECT_URI, frontendOrigin, @@ -260,7 +265,7 @@ export const readPublicDeploymentConfig = (env = process.env) => { explicitScopes, frontendOrigin, googleClientId, - hasExplicitWorkspaceRepositoryBackend: Boolean(workspaceRepositoryBackend), + hasExplicitWorkspaceRepositoryBackend: Boolean(explicitWorkspaceRepositoryBackend), hasBrowserApiBaseUrl: Boolean(env.VITE_AI_API_BASE_URL?.trim()), oauthEnabled: Boolean(googleClientId || redirectUri || frontendOrigin), publishingStatus, @@ -506,12 +511,12 @@ export const validatePublicDeploymentConfig = (config, options = {}) => { notes.push(`Public deploy expectation pins the hosted frontend origin to ${expectedOrigin}.`); } - if (hasNonLocalDeploymentTarget && config.oauthEnabled) { - if (config.workspaceRepositoryBackend === "file") { - errors.push("WORKSPACE_REPOSITORY_BACKEND=file is not supported for deployed Google Workspace OAuth. Cloud Run instances do not share local filesystem state; use Firestore instead."); - } else { - notes.push("Deployed Google Workspace state should use the Firestore repository backend. Enable firestore.googleapis.com and create a Firestore database in the target GCP project before rollout."); - } + if (config.workspaceRepositoryBackend === "firestore") { + errors.push("WORKSPACE_REPOSITORY_BACKEND=firestore is no longer supported. Docsy now uses the local file-backed workspace repository; set WORKSPACE_REPOSITORY_BACKEND=file or leave it unset."); + } + + if (hasNonLocalDeploymentTarget && config.oauthEnabled && config.workspaceRepositoryBackend !== "firestore") { + notes.push("Google Workspace state uses the local file-backed repository. Set WORKSPACE_STATE_PATH or WORKSPACE_DB_PATH to a writable local persistence path for this runtime."); } if (frontendUrl && redirectUrl) { diff --git a/server/modules/http/aiDiagnostics.ts b/server/modules/http/aiDiagnostics.ts index 1420615..17bcd14 100644 --- a/server/modules/http/aiDiagnostics.ts +++ b/server/modules/http/aiDiagnostics.ts @@ -54,7 +54,7 @@ export const buildInternalAiHealthPayload = ({ runtimeRevision: string | null; runtimeService: string | null; workspaceAuthSuccessCriterion: string; - workspaceRepositoryBackend: "file" | "firestore"; + workspaceRepositoryBackend: "file"; workspaceSessionCookieAcceptedNames: readonly string[]; workspaceSessionCookieSecureName: string; workspaceSessionCookieSecureSameSite: "Lax" | "None" | "Strict"; diff --git a/server/modules/tex/artifactStorage.ts b/server/modules/tex/artifactStorage.ts index fe01db8..dc9653c 100644 --- a/server/modules/tex/artifactStorage.ts +++ b/server/modules/tex/artifactStorage.ts @@ -1,7 +1,6 @@ import { Buffer } from "node:buffer"; import { randomUUID } from "node:crypto"; import type { IncomingMessage } from "node:http"; -import { Storage } from "@google-cloud/storage"; import { HttpError } from "../http/http"; export type TexArtifactMode = "export" | "preview"; @@ -15,23 +14,16 @@ interface StoredLocalArtifact { export interface StoredTexArtifact { expiresAt: number; - storageBackend: "gcs" | "local" | "unavailable"; + storageBackend: "local" | "unavailable"; url?: string; } const DEFAULT_ARTIFACT_TTL_SECONDS = 15 * 60; const DEFAULT_LOCAL_ARTIFACT_CAP = 24; -const DEFAULT_OBJECT_PREFIX = "tex-artifacts"; -const GCS_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; -const GCS_CLEANUP_SCAN_LIMIT = 50; const localArtifactStore = new Map(); -let cachedStorageClient: Storage | null = null; -let lastGcsCleanupStartedAt = 0; -const getArtifactBucketName = () => process.env.TEX_PREVIEW_BUCKET?.trim() || ""; const getArtifactPublicBaseUrl = () => process.env.TEX_PREVIEW_PUBLIC_BASE_URL?.trim().replace(/\/$/, "") || ""; -const getArtifactObjectPrefix = () => process.env.TEX_ARTIFACT_OBJECT_PREFIX?.trim().replace(/^\/+|\/+$/g, "") || DEFAULT_OBJECT_PREFIX; const getArtifactTtlSeconds = () => { const configured = Number(process.env.TEX_PREVIEW_URL_TTL_SECONDS || DEFAULT_ARTIFACT_TTL_SECONDS); return Number.isFinite(configured) && configured > 0 @@ -73,14 +65,6 @@ const pruneExpiredLocalArtifacts = () => { } }; -const getStorageClient = () => { - if (!cachedStorageClient) { - cachedStorageClient = new Storage(); - } - - return cachedStorageClient; -}; - const getArtifactFileName = (mode: TexArtifactMode, documentName?: string) => { const baseName = (documentName || (mode === "preview" ? "preview" : "document")) .replace(/[^a-zA-Z0-9._-]+/g, "-") @@ -95,53 +79,6 @@ const buildContentDisposition = (mode: TexArtifactMode, documentName?: string) = return `${mode === "preview" ? "inline" : "attachment"}; filename="${fileName}"`; }; -const buildGcsObjectName = (mode: TexArtifactMode) => { - const timestamp = Date.now(); - return `${getArtifactObjectPrefix()}/${mode}/${timestamp}-${randomUUID()}.pdf`; -}; - -const parseArtifactTimestamp = (objectName: string) => { - const fileName = objectName.split("/").pop() || ""; - const prefix = fileName.split("-", 1)[0] || ""; - const parsed = Number.parseInt(prefix, 10); - return Number.isFinite(parsed) ? parsed : null; -}; - -const maybeCleanupExpiredGcsArtifacts = async () => { - const bucketName = getArtifactBucketName(); - - if (!bucketName) { - return; - } - - const now = Date.now(); - if (lastGcsCleanupStartedAt > now - GCS_CLEANUP_INTERVAL_MS) { - return; - } - - lastGcsCleanupStartedAt = now; - const cutoff = now - getArtifactTtlMs(); - - try { - const [files] = await getStorageClient().bucket(bucketName).getFiles({ - autoPaginate: false, - maxResults: GCS_CLEANUP_SCAN_LIMIT, - prefix: `${getArtifactObjectPrefix()}/`, - }); - - await Promise.allSettled( - files - .filter((file) => { - const createdAt = parseArtifactTimestamp(file.name); - return createdAt !== null && createdAt < cutoff; - }) - .map((file) => file.delete({ ignoreNotFound: true })), - ); - } catch (error) { - console.warn("[TeX Artifact] Failed to cleanup expired PDF artifacts.", error); - } -}; - const buildLocalArtifactBaseUrl = (request: IncomingMessage) => { const configured = getArtifactPublicBaseUrl(); if (configured) { @@ -199,61 +136,6 @@ const storeLocalArtifact = ({ }; }; -const uploadArtifactToGcs = async ({ - documentName, - mode, - pdfBuffer, -}: { - documentName?: string; - mode: TexArtifactMode; - pdfBuffer: Buffer; -}): Promise => { - const bucketName = getArtifactBucketName(); - const expiresAt = Date.now() + getArtifactTtlMs(); - - if (!bucketName) { - return { - expiresAt, - storageBackend: "unavailable", - }; - } - - await maybeCleanupExpiredGcsArtifacts(); - - const bucket = getStorageClient().bucket(bucketName); - const objectName = buildGcsObjectName(mode); - const file = bucket.file(objectName); - const contentDisposition = buildContentDisposition(mode, documentName); - - await file.save(pdfBuffer, { - contentType: "application/pdf", - metadata: { - cacheControl: `private, max-age=${getArtifactTtlSeconds()}`, - contentDisposition, - }, - resumable: false, - validation: false, - }).catch((error) => { - throw new HttpError(503, error instanceof Error ? error.message : "Unable to upload compiled PDF artifact."); - }); - - const [url] = await file.getSignedUrl({ - action: "read", - expires: expiresAt, - responseDisposition: contentDisposition, - responseType: "application/pdf", - version: "v4", - }).catch((error) => { - throw new HttpError(503, error instanceof Error ? error.message : "Unable to sign compiled PDF artifact URL."); - }); - - return { - expiresAt, - storageBackend: "gcs", - url, - }; -}; - export const storeTexArtifactPdf = async ({ documentName, mode, @@ -265,10 +147,6 @@ export const storeTexArtifactPdf = async ({ pdfBuffer: Buffer; request: IncomingMessage; }): Promise => { - if (getArtifactBucketName()) { - return uploadArtifactToGcs({ documentName, mode, pdfBuffer }); - } - return storeLocalArtifact({ documentName, mode, pdfBuffer, request }); }; diff --git a/server/modules/tex/jobQueue.ts b/server/modules/tex/jobQueue.ts index fd5db41..e4410cf 100644 --- a/server/modules/tex/jobQueue.ts +++ b/server/modules/tex/jobQueue.ts @@ -1,25 +1,3 @@ -import { Buffer } from "node:buffer"; -import { CloudTasksClient } from "@google-cloud/tasks"; - -let cachedTasksClient: CloudTasksClient | null = null; - -const getTasksClient = () => { - if (!cachedTasksClient) { - cachedTasksClient = new CloudTasksClient(); - } - - return cachedTasksClient; -}; - -const getCloudProject = () => process.env.GOOGLE_CLOUD_PROJECT?.trim() || ""; -const getTexTaskQueue = () => process.env.TEX_TASK_QUEUE?.trim() || ""; -const getTexTaskLocation = () => process.env.TEX_TASK_LOCATION?.trim() || ""; -const getTexJobWorkerUrl = () => process.env.TEX_JOB_WORKER_URL?.trim().replace(/\/$/, "") || ""; -const getTexAuthToken = () => process.env.TEX_SERVICE_AUTH_TOKEN?.trim() || ""; - -const canUseCloudTasks = () => - Boolean(getCloudProject() && getTexTaskQueue() && getTexTaskLocation() && getTexJobWorkerUrl()); - export const enqueueTexJob = async ({ jobId, processLocally, @@ -27,41 +5,13 @@ export const enqueueTexJob = async ({ jobId: string; processLocally?: () => Promise; }) => { - if (!canUseCloudTasks()) { - if (!processLocally) { - return; - } - - queueMicrotask(() => { - void processLocally().catch((error) => { - console.error(`[TeX Job Queue] Local background processing failed jobId=${jobId}`, error); - }); - }); + if (!processLocally) { return; } - const taskClient = getTasksClient(); - const parent = taskClient.queuePath(getCloudProject(), getTexTaskLocation(), getTexTaskQueue()); - const payload = Buffer.from(JSON.stringify({ jobId })).toString("base64"); - const headers: Record = { - "Content-Type": "application/json", - }; - const authToken = getTexAuthToken(); - - if (authToken) { - headers["X-Docsy-Tex-Token"] = authToken; - } - - await taskClient.createTask({ - parent, - task: { - dispatchDeadline: { seconds: 900 }, - httpRequest: { - body: payload, - headers, - httpMethod: "POST", - url: `${getTexJobWorkerUrl()}/tasks/tex-jobs`, - }, - }, + queueMicrotask(() => { + void processLocally().catch((error) => { + console.error(`[TeX Job Queue] Local background processing failed jobId=${jobId}`, error); + }); }); }; diff --git a/server/modules/tex/jobStore.ts b/server/modules/tex/jobStore.ts index e716553..9beb0b3 100644 --- a/server/modules/tex/jobStore.ts +++ b/server/modules/tex/jobStore.ts @@ -2,8 +2,6 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { randomUUID } from "node:crypto"; import { homedir } from "node:os"; import path from "node:path"; -import { Firestore } from "@google-cloud/firestore"; -import { resolveWorkspaceRepositoryBackend, stripUndefinedDeep } from "../workspace/repository"; import type { TexDiagnostic, TexJobMode, TexJobStatus, TexSourceType } from "@/types/tex"; export interface TexJobRecord { @@ -64,7 +62,6 @@ const DEFAULT_STATE: TexJobStoreState = { }; const DEFAULT_CLOUD_RUN_STATE_PATH = path.posix.join("/tmp", "docsy-tex-jobs.json"); const DEFAULT_LOCAL_STATE_PATH = path.join(homedir(), ".docsy", "tex-jobs.json"); -const DEFAULT_FIRESTORE_COLLECTION = "texJobs"; const getDefaultStatePath = () => (process.env.K_SERVICE || process.env.K_REVISION || process.env.CLOUD_RUN_JOB) @@ -235,124 +232,11 @@ class FileTexJobStore implements TexJobStore { } } -class FirestoreTexJobStore implements TexJobStore { - constructor( - private readonly firestore: Firestore, - private readonly collectionName: string, - ) {} - - private getCollection() { - return this.firestore.collection(this.collectionName); - } - - async pruneExpired(now = Date.now()) { - const expired = await this.getCollection().where("ttlAt", "<=", now).get(); - if (expired.empty) { - return; - } - - const batch = this.firestore.batch(); - for (const snapshot of expired.docs) { - batch.delete(snapshot.ref); - } - await batch.commit(); - } - - async createJob(input: CreateTexJobInput) { - const now = Date.now(); - const record: TexJobRecord = { - contentHash: input.contentHash, - createdAt: now, - documentName: input.documentName, - jobId: randomUUID(), - latex: input.latex, - mode: input.mode, - sourceType: input.sourceType, - status: "queued", - ttlAt: now + getJobTtlMs(), - updatedAt: now, - }; - - await this.getCollection().doc(record.jobId).set(stripUndefinedDeep(record)); - return record; - } - - async getJob(jobId: string) { - const snapshot = await this.getCollection().doc(jobId).get(); - if (!snapshot.exists) { - return null; - } - - return sanitizeTexJobRecord(snapshot.data() as Partial); - } - - async claimJob(jobId: string) { - return this.firestore.runTransaction(async (transaction) => { - const recordRef = this.getCollection().doc(jobId); - const snapshot = await transaction.get(recordRef); - - if (!snapshot.exists) { - return null; - } - - const record = sanitizeTexJobRecord(snapshot.data() as Partial); - if (!record || record.status !== "queued") { - return null; - } - - const nextRecord: TexJobRecord = { - ...record, - status: "running", - updatedAt: Date.now(), - }; - - transaction.set(recordRef, stripUndefinedDeep(nextRecord)); - return nextRecord; - }); - } - - async completeJob(jobId: string, status: Extract, result: CompleteTexJobInput) { - const recordRef = this.getCollection().doc(jobId); - const currentRecord = await this.getJob(jobId); - - if (!currentRecord) { - return null; - } - - const nextRecord: TexJobRecord = { - ...currentRecord, - compileMs: result.compileMs, - diagnostics: result.diagnostics, - downloadUrl: result.downloadUrl, - error: result.error, - expiresAt: result.expiresAt, - logSummary: result.logSummary, - previewUrl: result.previewUrl, - status, - updatedAt: Date.now(), - }; - - await recordRef.set(stripUndefinedDeep(nextRecord)); - return nextRecord; - } -} - -const createFirestoreStore = () => { - const projectId = process.env.GOOGLE_CLOUD_PROJECT?.trim() || undefined; - const firestore = projectId - ? new Firestore({ ignoreUndefinedProperties: true, projectId }) - : new Firestore({ ignoreUndefinedProperties: true }); - - return new FirestoreTexJobStore(firestore, DEFAULT_FIRESTORE_COLLECTION); -}; - let jobStoreInstance: TexJobStore | null = null; export const getTexJobStore = (): TexJobStore => { if (!jobStoreInstance) { - jobStoreInstance = resolveWorkspaceRepositoryBackend() === "firestore" - ? createFirestoreStore() - : new FileTexJobStore(); + jobStoreInstance = new FileTexJobStore(); } return jobStoreInstance; diff --git a/server/modules/workspace/repository.ts b/server/modules/workspace/repository.ts index ad56cda..766242f 100644 --- a/server/modules/workspace/repository.ts +++ b/server/modules/workspace/repository.ts @@ -2,7 +2,6 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { randomUUID } from "node:crypto"; import { homedir } from "node:os"; import path from "node:path"; -import { Firestore } from "@google-cloud/firestore"; export interface GoogleWorkspaceTokens { accessToken?: string; @@ -111,8 +110,6 @@ const DEFAULT_REPOSITORY_STATE: WorkspaceRepositoryState = { const DEFAULT_CLOUD_RUN_STATE_PATH = path.posix.join("/tmp", "docsy-workspace-state.json"); const DEFAULT_LOCAL_STATE_PATH = path.join(homedir(), ".docsy", "workspace-state.json"); -const DEFAULT_FIRESTORE_ROOT_COLLECTION = "docsyWorkspace"; -const DEFAULT_FIRESTORE_ROOT_DOCUMENT = "state"; const isCloudRunEnvironment = (env = process.env) => Boolean(env.K_SERVICE || env.K_REVISION || env.CLOUD_RUN_JOB); @@ -120,6 +117,8 @@ const isCloudRunEnvironment = (env = process.env) => const isTestEnvironment = (env = process.env) => env.NODE_ENV === "test" || env.VITEST === "true"; +let didWarnIgnoredWorkspaceRepositoryBackend = false; + const isPathInsideDirectory = (candidatePath: string, directoryPath: string) => { const relativePath = path.relative(directoryPath, candidatePath); return relativePath === "" @@ -132,24 +131,6 @@ const getDefaultRepositoryFilePath = (env = process.env) => const sortImportedDocuments = (documents: WorkspaceImportedDocumentRecord[]) => documents.sort((left, right) => right.updatedAt - left.updatedAt || left.fileName.localeCompare(right.fileName)); -export const stripUndefinedDeep = (value: unknown): unknown => { - if (Array.isArray(value)) { - return value - .map((entry) => stripUndefinedDeep(entry)) - .filter((entry) => entry !== undefined); - } - - if (value && typeof value === "object") { - return Object.fromEntries( - Object.entries(value) - .map(([key, entry]) => [key, stripUndefinedDeep(entry)]) - .filter(([, entry]) => entry !== undefined), - ); - } - - return value === undefined ? undefined : value; -}; - const sanitizeWorkspaceTokens = (tokens: Partial | null | undefined): GoogleWorkspaceTokens => ({ expiryDate: typeof tokens?.expiryDate === "number" ? tokens.expiryDate : undefined, refreshToken: typeof tokens?.refreshToken === "string" ? tokens.refreshToken : undefined, @@ -284,14 +265,15 @@ export const assertSafeWorkspaceRepositoryPath = ( } }; -export const resolveWorkspaceRepositoryBackend = (env = process.env) => { +export const resolveWorkspaceRepositoryBackend = (env = process.env): "file" => { const configured = env.WORKSPACE_REPOSITORY_BACKEND?.trim().toLowerCase(); - if (configured === "file" || configured === "firestore") { - return configured; + if (configured && configured !== "file" && configured !== "local" && !didWarnIgnoredWorkspaceRepositoryBackend) { + console.warn(`Workspace repository backend "${configured}" is ignored. Docsy now uses the local file-backed repository.`); + didWarnIgnoredWorkspaceRepositoryBackend = true; } - return isCloudRunEnvironment(env) ? "firestore" : "file"; + return "file"; }; const getRepositoryFilePath = () => { @@ -557,258 +539,11 @@ class FileWorkspaceRepository implements WorkspaceRepository { } } -class FirestoreWorkspaceRepository implements WorkspaceRepository { - constructor( - private readonly firestore: Firestore, - private readonly rootCollection: string, - private readonly rootDocument: string, - ) {} - - private getRootDocumentRef() { - return this.firestore.collection(this.rootCollection).doc(this.rootDocument); - } - - private getAuthStatesCollection() { - return this.getRootDocumentRef().collection("authStates"); - } - - private getConnectionsCollection() { - return this.getRootDocumentRef().collection("connections"); - } - - private getSessionsCollection() { - return this.getRootDocumentRef().collection("sessions"); - } - - private getImportedDocumentsCollection() { - return this.getRootDocumentRef().collection("importedDocuments"); - } - - private getSharedDocumentsCollection() { - return this.getRootDocumentRef().collection("sharedDocuments"); - } - - async pruneExpired(now = Date.now()) { - const [expiredAuthStates, expiredSessions, expiredSharedDocuments] = await Promise.all([ - this.getAuthStatesCollection().where("expiresAt", "<=", now).get(), - this.getSessionsCollection().where("expiresAt", "<=", now).get(), - this.getSharedDocumentsCollection().where("expiresAt", "<=", now).get(), - ]); - const batch = this.firestore.batch(); - let writeCount = 0; - - for (const snapshot of [...expiredAuthStates.docs, ...expiredSessions.docs, ...expiredSharedDocuments.docs]) { - batch.delete(snapshot.ref); - writeCount += 1; - } - - if (writeCount > 0) { - await batch.commit(); - } - } - - async saveAuthState(record: WorkspaceAuthStateRecord) { - const sanitizedRecord = sanitizeWorkspaceAuthStateRecord(record); - - if (!sanitizedRecord) { - throw new Error(`Workspace auth state record is invalid for state=${record.state}`); - } - - await this.getAuthStatesCollection().doc(sanitizedRecord.state).set(stripUndefinedDeep(sanitizedRecord)); - } - - async consumeAuthState(stateId: string) { - return this.firestore.runTransaction(async (transaction) => { - const recordRef = this.getAuthStatesCollection().doc(stateId); - const snapshot = await transaction.get(recordRef); - - if (!snapshot.exists) { - return null; - } - - const record = sanitizeWorkspaceAuthStateRecord(snapshot.data() as Partial); - - if (!record) { - transaction.delete(recordRef); - return null; - } - - transaction.delete(recordRef); - return record; - }); - } - - async upsertConnection(record: WorkspaceConnectionRecord) { - const sanitizedRecord = sanitizeWorkspaceConnectionRecord(record); - - if (!sanitizedRecord) { - throw new Error(`Workspace connection record is invalid for connectionId=${record.connectionId}`); - } - - await this.getConnectionsCollection().doc(sanitizedRecord.connectionId).set(stripUndefinedDeep(sanitizedRecord)); - } - - async getConnection(connectionId: string) { - const snapshot = await this.getConnectionsCollection().doc(connectionId).get(); - - if (!snapshot.exists) { - return null; - } - - return sanitizeWorkspaceConnectionRecord(snapshot.data() as Partial); - } - - async createSession(connectionId: string, absoluteTtlMs: number, idleTtlMs: number) { - const now = Date.now(); - const session: WorkspaceSessionRecord = { - connectionId, - createdAt: now, - expiresAt: now + Math.min(absoluteTtlMs, idleTtlMs), - sessionId: randomUUID(), - updatedAt: now, - }; - - await this.getSessionsCollection().doc(session.sessionId).set(stripUndefinedDeep(session)); - return session; - } - - async getSession(sessionId: string) { - const sessionSnapshot = await this.getSessionsCollection().doc(sessionId).get(); - - if (!sessionSnapshot.exists) { - return null; - } - - const session = sanitizeWorkspaceSessionRecord(sessionSnapshot.data() as Partial); - - if (!session) { - await sessionSnapshot.ref.delete(); - return null; - } - - const connection = await this.getConnection(session.connectionId); - - return { - connection, - session, - }; - } - - async touchSession(sessionId: string, absoluteTtlMs: number, idleTtlMs: number) { - return this.firestore.runTransaction(async (transaction) => { - const sessionRef = this.getSessionsCollection().doc(sessionId); - const sessionSnapshot = await transaction.get(sessionRef); - - if (!sessionSnapshot.exists) { - return null; - } - - const session = sanitizeWorkspaceSessionRecord(sessionSnapshot.data() as Partial); - - if (!session) { - transaction.delete(sessionRef); - return null; - } - - const now = Date.now(); - const absoluteExpiryAt = session.createdAt + absoluteTtlMs; - const idleExpiryAt = now + idleTtlMs; - const updatedSession: WorkspaceSessionRecord = { - ...session, - expiresAt: Math.min(absoluteExpiryAt, idleExpiryAt), - updatedAt: now, - }; - - const connectionRef = this.getConnectionsCollection().doc(updatedSession.connectionId); - const connectionSnapshot = await transaction.get(connectionRef); - const connection = connectionSnapshot.exists - ? sanitizeWorkspaceConnectionRecord(connectionSnapshot.data() as Partial) - : null; - - transaction.set(sessionRef, stripUndefinedDeep(updatedSession)); - - return { - connection, - session: updatedSession, - }; - }); - } - - async deleteSession(sessionId: string) { - await this.getSessionsCollection().doc(sessionId).delete(); - } - - async upsertImportedDocument(record: WorkspaceImportedDocumentRecord) { - const sanitizedRecord = sanitizeImportedDocumentRecord(record); - - if (!sanitizedRecord) { - throw new Error(`Imported workspace document record is invalid for documentId=${record.documentId}`); - } - - await this.getImportedDocumentsCollection().doc(sanitizedRecord.documentId).set(stripUndefinedDeep(sanitizedRecord)); - } - - async getImportedDocument(documentId: string) { - const snapshot = await this.getImportedDocumentsCollection().doc(documentId).get(); - - if (!snapshot.exists) { - return null; - } - - return sanitizeImportedDocumentRecord(snapshot.data() as Partial); - } - - async getSharedDocument(shareId: string) { - const snapshot = await this.getSharedDocumentsCollection().doc(shareId).get(); - - if (!snapshot.exists) { - return null; - } - - return sanitizeSharedDocumentRecord(snapshot.data() as Partial); - } - - async listImportedDocuments(connectionId: string) { - const snapshot = await this.getImportedDocumentsCollection() - .where("connectionId", "==", connectionId) - .get(); - - return sortImportedDocuments( - snapshot.docs - .map((documentSnapshot) => sanitizeImportedDocumentRecord(documentSnapshot.data() as Partial)) - .filter((record): record is WorkspaceImportedDocumentRecord => Boolean(record)), - ); - } - - async upsertSharedDocument(record: SharedDocumentRecord) { - const sanitizedRecord = sanitizeSharedDocumentRecord(record); - - if (!sanitizedRecord) { - throw new Error(`Shared document record is invalid for shareId=${record.shareId}`); - } - - await this.getSharedDocumentsCollection().doc(sanitizedRecord.shareId).set(stripUndefinedDeep(sanitizedRecord)); - } -} - -const createFirestoreRepository = (env = process.env) => { - const projectId = env.GOOGLE_CLOUD_PROJECT?.trim() || undefined; - const rootCollection = env.WORKSPACE_FIRESTORE_ROOT_COLLECTION?.trim() || DEFAULT_FIRESTORE_ROOT_COLLECTION; - const rootDocument = env.WORKSPACE_FIRESTORE_ROOT_DOCUMENT?.trim() || DEFAULT_FIRESTORE_ROOT_DOCUMENT; - const firestore = projectId - ? new Firestore({ ignoreUndefinedProperties: true, projectId }) - : new Firestore({ ignoreUndefinedProperties: true }); - - return new FirestoreWorkspaceRepository(firestore, rootCollection, rootDocument); -}; - let repositoryInstance: WorkspaceRepository | null = null; export const getWorkspaceRepository = (): WorkspaceRepository => { if (!repositoryInstance) { - repositoryInstance = resolveWorkspaceRepositoryBackend() === "firestore" - ? createFirestoreRepository() - : new FileWorkspaceRepository(); + repositoryInstance = new FileWorkspaceRepository(); } return repositoryInstance; diff --git a/src/test/aiDiagnostics.test.ts b/src/test/aiDiagnostics.test.ts index 2a80932..50fc2b0 100644 --- a/src/test/aiDiagnostics.test.ts +++ b/src/test/aiDiagnostics.test.ts @@ -51,7 +51,7 @@ describe("aiDiagnostics", () => { runtimeRevision: "docsy-00129-gsw", runtimeService: "docsy", workspaceAuthSuccessCriterion: "/api/auth/session returns connected=true", - workspaceRepositoryBackend: "firestore", + workspaceRepositoryBackend: "file", workspaceSessionCookieAcceptedNames: ["__session", "__Host-docsy-workspace-session", "docsy_workspace_session"], workspaceSessionCookieSecureName: "__session", workspaceSessionCookieSecureSameSite: "None", @@ -66,7 +66,7 @@ describe("aiDiagnostics", () => { runtimeRevision: "docsy-00129-gsw", runtimeService: "docsy", workspaceAuthSuccessCriterion: "/api/auth/session returns connected=true", - workspaceRepositoryBackend: "firestore", + workspaceRepositoryBackend: "file", workspaceSessionCookieSecureName: "__session", workspaceSessionCookieSecureSameSite: "None", }); diff --git a/src/test/authCallbackSessionRotation.test.ts b/src/test/authCallbackSessionRotation.test.ts index 6c0ab16..779c7ae 100644 --- a/src/test/authCallbackSessionRotation.test.ts +++ b/src/test/authCallbackSessionRotation.test.ts @@ -42,7 +42,7 @@ vi.mock("../../server/modules/workspace/repository", () => ({ saveAuthState: vi.fn(), upsertConnection: (...args: Parameters) => upsertConnectionMock(...args), }), - resolveWorkspaceRepositoryBackend: () => "firestore", + resolveWorkspaceRepositoryBackend: () => "file", })); describe("auth callback session rotation", () => { diff --git a/src/test/authConnectRoute.test.ts b/src/test/authConnectRoute.test.ts index 5c80b5d..7797696 100644 --- a/src/test/authConnectRoute.test.ts +++ b/src/test/authConnectRoute.test.ts @@ -10,7 +10,7 @@ vi.mock("../../server/modules/workspace/repository", () => ({ saveAuthState: (...args: Parameters) => saveAuthStateMock(...args), upsertConnection: vi.fn(), }), - resolveWorkspaceRepositoryBackend: () => "firestore", + resolveWorkspaceRepositoryBackend: () => "file", })); const ORIGINAL_ENV = { ...process.env }; diff --git a/src/test/landingPage.test.tsx b/src/test/landingPage.test.tsx index f5f00ec..f1272bb 100644 --- a/src/test/landingPage.test.tsx +++ b/src/test/landingPage.test.tsx @@ -70,8 +70,8 @@ describe("Landing page", () => { expect(screen.getByText("Capture the live viewport")).toBeInTheDocument(); expect(screen.getByText("Gemini chooses one UI action")).toBeInTheDocument(); expect(screen.getByText("Docsy executes and hands off to review")).toBeInTheDocument(); - expect(screen.getByText(/let Docsy observe the live UI/i)).toBeInTheDocument(); - expect(screen.getByText(/Use the guide to learn the Visual Navigator flow/i)).toBeInTheDocument(); + expect(screen.getByText(/Docsy looks at the real visible UI/i)).toBeInTheDocument(); + expect(screen.getByText(/Open the guide to learn where the Visual Navigator lives/i)).toBeInTheDocument(); expect(screen.getByText("Edit now")).toBeInTheDocument(); expect(screen.getByText("Import / export")).toBeInTheDocument(); expect(screen.getByText(".docsy")).toBeInTheDocument(); diff --git a/src/test/publicDeploymentConfig.test.ts b/src/test/publicDeploymentConfig.test.ts index 0441380..9f4edea 100644 --- a/src/test/publicDeploymentConfig.test.ts +++ b/src/test/publicDeploymentConfig.test.ts @@ -155,7 +155,7 @@ describe("publicDeploymentConfig", () => { const validation = validatePublicDeploymentConfig(config); expect(validation.errors).toEqual([]); - expect(validation.notes.some((note) => note.includes("Firestore repository backend"))).toBe(true); + expect(validation.notes.some((note) => note.includes("local file-backed repository"))).toBe(true); expect(validation.notes.some((note) => note.includes("GOOGLE_OAUTH_REDIRECT_URI matches WORKSPACE_FRONTEND_ORIGIN"))).toBe(true); expect(validation.notes.some((note) => note.includes("VITE_AI_API_BASE_URL matches WORKSPACE_FRONTEND_ORIGIN"))).toBe(true); }); @@ -196,18 +196,26 @@ describe("publicDeploymentConfig", () => { expect(validation.notes.some((note) => note.includes("Public deploy expectation pins the hosted frontend origin"))).toBe(true); }); - it("rejects deployed OAuth config when the file repository backend is forced", () => { + it("rejects deployed OAuth config when the legacy Firestore repository backend is forced", () => { const config = readPublicDeploymentConfig({ AI_ALLOWED_ORIGIN: "https://app.docsy.dev", GOOGLE_CLIENT_ID: "client-id", GOOGLE_OAUTH_PUBLISHING_STATUS: "testing", GOOGLE_OAUTH_REDIRECT_URI: "https://app.docsy.dev/api/auth/google/callback", WORKSPACE_FRONTEND_ORIGIN: "https://app.docsy.dev", - WORKSPACE_REPOSITORY_BACKEND: "file", + WORKSPACE_REPOSITORY_BACKEND: "firestore", }); const validation = validatePublicDeploymentConfig(config); - expect(validation.errors.some((error) => error.includes("WORKSPACE_REPOSITORY_BACKEND=file"))).toBe(true); + expect(validation.errors.some((error) => error.includes("WORKSPACE_REPOSITORY_BACKEND=firestore"))).toBe(true); + }); + + it("normalizes local repository aliases to the file backend", () => { + const config = readPublicDeploymentConfig({ + WORKSPACE_REPOSITORY_BACKEND: "local", + }); + + expect(config.workspaceRepositoryBackend).toBe("file"); }); it("rejects allowed origins that are not origin-only URLs", () => { diff --git a/src/test/resolveWorkspaceSessionForAgent.test.ts b/src/test/resolveWorkspaceSessionForAgent.test.ts index 69cab70..606c74b 100644 --- a/src/test/resolveWorkspaceSessionForAgent.test.ts +++ b/src/test/resolveWorkspaceSessionForAgent.test.ts @@ -10,7 +10,7 @@ vi.mock("../../server/modules/auth/sessionStore", () => ({ })); vi.mock("../../server/modules/workspace/repository", () => ({ - resolveWorkspaceRepositoryBackend: () => "firestore", + resolveWorkspaceRepositoryBackend: () => "file", })); const ORIGINAL_ENV = { @@ -67,7 +67,7 @@ describe("resolveWorkspaceSessionForAgent", () => { it("degrades to workspace disconnected when session lookup throws", async () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined); getPresentWorkspaceSessionCookieNamesMock.mockReturnValue(["__session"]); - getWorkspaceSessionMock.mockRejectedValue(new Error("Firestore unavailable\nretry later")); + getWorkspaceSessionMock.mockRejectedValue(new Error("Local workspace store unavailable\nretry later")); const { resolveWorkspaceSessionForAgent } = await import("../../server/modules/agent/resolveWorkspaceSessionForAgent"); const result = await resolveWorkspaceSessionForAgent({ @@ -82,7 +82,7 @@ describe("resolveWorkspaceSessionForAgent", () => { workspaceConnected: false, }); expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining( - "[LiveAgent] session lookup degraded requestId=req-2 cookieNames=__session backend=firestore revision=docsy-00042 message=Firestore unavailable retry later", + "[LiveAgent] session lookup degraded requestId=req-2 cookieNames=__session backend=file revision=docsy-00042 message=Local workspace store unavailable retry later", )); warnSpy.mockRestore(); }); diff --git a/src/test/setup.ts b/src/test/setup.ts index efdba50..d06e1ee 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,36 +1,38 @@ import "@testing-library/jest-dom"; -Object.defineProperty(window, "matchMedia", { - writable: true, - value: (query: string) => ({ - matches: false, - media: query, - onchange: null, - addListener: () => {}, - removeListener: () => {}, - addEventListener: () => {}, - removeEventListener: () => {}, - dispatchEvent: () => {}, - }), -}); +if (typeof window !== "undefined") { + Object.defineProperty(window, "matchMedia", { + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => {}, + }), + }); -class ResizeObserverMock { - observe() {} - unobserve() {} - disconnect() {} -} + class ResizeObserverMock { + observe() {} + unobserve() {} + disconnect() {} + } -Object.defineProperty(window, "ResizeObserver", { - writable: true, - value: ResizeObserverMock, -}); + Object.defineProperty(window, "ResizeObserver", { + writable: true, + value: ResizeObserverMock, + }); -if (!HTMLElement.prototype.getClientRects) { - HTMLElement.prototype.getClientRects = function () { - return { - item: () => null, - length: 0, - [Symbol.iterator]: function* iterator() {}, - } as DOMRectList; - }; + if (!HTMLElement.prototype.getClientRects) { + HTMLElement.prototype.getClientRects = function () { + return { + item: () => null, + length: 0, + [Symbol.iterator]: function* iterator() {}, + } as DOMRectList; + }; + } } diff --git a/src/test/texJobQueue.test.ts b/src/test/texJobQueue.test.ts new file mode 100644 index 0000000..645802e --- /dev/null +++ b/src/test/texJobQueue.test.ts @@ -0,0 +1,46 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { enqueueTexJob } from "../../server/modules/tex/jobQueue"; + +const ORIGINAL_ENV = { + GOOGLE_CLOUD_PROJECT: process.env.GOOGLE_CLOUD_PROJECT, + TEX_JOB_WORKER_URL: process.env.TEX_JOB_WORKER_URL, + TEX_TASK_LOCATION: process.env.TEX_TASK_LOCATION, + TEX_TASK_QUEUE: process.env.TEX_TASK_QUEUE, +}; + +const restoreEnv = (key: keyof typeof ORIGINAL_ENV) => { + const value = ORIGINAL_ENV[key]; + + if (value === undefined) { + delete process.env[key]; + return; + } + + process.env[key] = value; +}; + +afterEach(() => { + restoreEnv("GOOGLE_CLOUD_PROJECT"); + restoreEnv("TEX_JOB_WORKER_URL"); + restoreEnv("TEX_TASK_LOCATION"); + restoreEnv("TEX_TASK_QUEUE"); +}); + +describe("tex job queue", () => { + it("runs jobs locally even when legacy Cloud Tasks env vars are configured", async () => { + process.env.GOOGLE_CLOUD_PROJECT = "legacy-cloud-project"; + process.env.TEX_TASK_QUEUE = "legacy-queue"; + process.env.TEX_TASK_LOCATION = "asia-northeast3"; + process.env.TEX_JOB_WORKER_URL = "https://worker.example.test"; + + const processLocally = vi.fn().mockResolvedValue(undefined); + + await enqueueTexJob({ + jobId: "job-1", + processLocally, + }); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(processLocally).toHaveBeenCalledOnce(); + }); +}); diff --git a/src/test/texJobStore.test.ts b/src/test/texJobStore.test.ts index 8726ddc..94502f0 100644 --- a/src/test/texJobStore.test.ts +++ b/src/test/texJobStore.test.ts @@ -9,6 +9,17 @@ const ORIGINAL_ENV = { WORKSPACE_REPOSITORY_BACKEND: process.env.WORKSPACE_REPOSITORY_BACKEND, }; +const restoreEnv = (key: keyof typeof ORIGINAL_ENV) => { + const value = ORIGINAL_ENV[key]; + + if (value === undefined) { + delete process.env[key]; + return; + } + + process.env[key] = value; +}; + beforeEach(() => { vi.useFakeTimers(); process.env.GOOGLE_CLOUD_PROJECT = ""; @@ -21,15 +32,31 @@ beforeEach(() => { afterEach(() => { vi.useRealTimers(); - process.env.GOOGLE_CLOUD_PROJECT = ORIGINAL_ENV.GOOGLE_CLOUD_PROJECT; - process.env.K_SERVICE = ORIGINAL_ENV.K_SERVICE; - process.env.K_REVISION = ORIGINAL_ENV.K_REVISION; - process.env.TEX_JOB_STATE_PATH = ORIGINAL_ENV.TEX_JOB_STATE_PATH; - process.env.WORKSPACE_REPOSITORY_BACKEND = ORIGINAL_ENV.WORKSPACE_REPOSITORY_BACKEND; + restoreEnv("GOOGLE_CLOUD_PROJECT"); + restoreEnv("K_SERVICE"); + restoreEnv("K_REVISION"); + restoreEnv("TEX_JOB_STATE_PATH"); + restoreEnv("WORKSPACE_REPOSITORY_BACKEND"); resetTexJobStoreForTests(); }); describe("tex job store", () => { + it("uses the local file store when legacy Firestore settings are present", async () => { + process.env.GOOGLE_CLOUD_PROJECT = "legacy-cloud-project"; + process.env.K_SERVICE = "docsy-tex"; + process.env.WORKSPACE_REPOSITORY_BACKEND = "firestore"; + resetTexJobStoreForTests(); + + const store = getTexJobStore(); + const created = await store.createJob({ + latex: "\\section{Local}", + mode: "preview", + sourceType: "raw-latex", + }); + + expect(await store.getJob(created.jobId)).toEqual(created); + }); + it("creates, claims, and completes preview jobs", async () => { const store = getTexJobStore(); const created = await store.createJob({ diff --git a/src/test/texPreviewStorage.test.ts b/src/test/texPreviewStorage.test.ts index 4670695..8fd436e 100644 --- a/src/test/texPreviewStorage.test.ts +++ b/src/test/texPreviewStorage.test.ts @@ -8,6 +8,17 @@ const ORIGINAL_ENV = { TEX_PREVIEW_URL_TTL_SECONDS: process.env.TEX_PREVIEW_URL_TTL_SECONDS, }; +const restoreEnv = (key: keyof typeof ORIGINAL_ENV) => { + const value = ORIGINAL_ENV[key]; + + if (value === undefined) { + delete process.env[key]; + return; + } + + process.env[key] = value; +}; + const createRequest = (host: string) => ({ headers: { host, @@ -24,9 +35,9 @@ beforeEach(() => { afterEach(() => { vi.useRealTimers(); - process.env.TEX_PREVIEW_BUCKET = ORIGINAL_ENV.TEX_PREVIEW_BUCKET; - process.env.TEX_PREVIEW_PUBLIC_BASE_URL = ORIGINAL_ENV.TEX_PREVIEW_PUBLIC_BASE_URL; - process.env.TEX_PREVIEW_URL_TTL_SECONDS = ORIGINAL_ENV.TEX_PREVIEW_URL_TTL_SECONDS; + restoreEnv("TEX_PREVIEW_BUCKET"); + restoreEnv("TEX_PREVIEW_PUBLIC_BASE_URL"); + restoreEnv("TEX_PREVIEW_URL_TTL_SECONDS"); }); describe("tex preview storage", () => { @@ -48,7 +59,7 @@ describe("tex preview storage", () => { expect(preview.contentDisposition).toContain("inline"); }); - it("does not expose service-local preview URLs for non-loopback hosts without GCS", async () => { + it("does not expose service-local preview URLs for non-loopback hosts without a local public base URL", async () => { const result = await storeTexArtifactPdf({ mode: "preview", pdfBuffer: Buffer.from("%PDF-1.7"), @@ -58,4 +69,17 @@ describe("tex preview storage", () => { expect(result.storageBackend).toBe("unavailable"); expect(result.url).toBeUndefined(); }); + + it("keeps artifact storage local when a legacy GCS bucket env var is present", async () => { + process.env.TEX_PREVIEW_BUCKET = "legacy-preview-bucket"; + + const result = await storeTexArtifactPdf({ + mode: "preview", + pdfBuffer: Buffer.from("%PDF-1.7"), + request: createRequest("localhost:8081"), + }); + + expect(result.storageBackend).toBe("local"); + expect(result.url).toMatch(/^http:\/\/localhost:8081\/artifacts\//); + }); }); diff --git a/src/test/viteConfig.test.ts b/src/test/viteConfig.test.ts new file mode 100644 index 0000000..d551787 --- /dev/null +++ b/src/test/viteConfig.test.ts @@ -0,0 +1,28 @@ +// @vitest-environment node + +import { describe, expect, it } from "vitest"; +import type { UserConfig } from "vite"; +import viteConfig from "../../vite.config"; + +const resolveBuildConfig = (mode: string): UserConfig => { + if (typeof viteConfig !== "function") { + return viteConfig as UserConfig; + } + + return viteConfig({ + command: "build", + isPreview: false, + isSsrBuild: false, + mode, + }) as UserConfig; +}; + +describe("viteConfig", () => { + it("uses relative asset paths for desktop file-protocol builds", () => { + expect(resolveBuildConfig("production").base).toBe("./"); + }); + + it("keeps root-relative asset paths for the hosted web build", () => { + expect(resolveBuildConfig("web").base).toBe("/"); + }); +}); diff --git a/src/test/workspaceRepository.test.ts b/src/test/workspaceRepository.test.ts index aae4410..207ab7b 100644 --- a/src/test/workspaceRepository.test.ts +++ b/src/test/workspaceRepository.test.ts @@ -1,14 +1,13 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { assertSafeWorkspaceRepositoryPath, getWorkspaceRepository, resetWorkspaceRepositoryForTests, resolveWorkspaceRepositoryBackend, resolveWorkspaceRepositoryFilePath, - stripUndefinedDeep, } from "../../server/modules/workspace/repository"; const ORIGINAL_ENV = { @@ -24,15 +23,26 @@ const ORIGINAL_ENV = { let tempDirectoryPath: string | null = null; +const restoreEnv = (key: keyof typeof ORIGINAL_ENV) => { + const value = ORIGINAL_ENV[key]; + + if (value === undefined) { + delete process.env[key]; + return; + } + + process.env[key] = value; +}; + afterEach(async () => { - process.env.CLOUD_RUN_JOB = ORIGINAL_ENV.CLOUD_RUN_JOB; - process.env.K_REVISION = ORIGINAL_ENV.K_REVISION; - process.env.K_SERVICE = ORIGINAL_ENV.K_SERVICE; - process.env.NODE_ENV = ORIGINAL_ENV.NODE_ENV; - process.env.VITEST = ORIGINAL_ENV.VITEST; - process.env.WORKSPACE_DB_PATH = ORIGINAL_ENV.WORKSPACE_DB_PATH; - process.env.WORKSPACE_REPOSITORY_BACKEND = ORIGINAL_ENV.WORKSPACE_REPOSITORY_BACKEND; - process.env.WORKSPACE_STATE_PATH = ORIGINAL_ENV.WORKSPACE_STATE_PATH; + restoreEnv("CLOUD_RUN_JOB"); + restoreEnv("K_REVISION"); + restoreEnv("K_SERVICE"); + restoreEnv("NODE_ENV"); + restoreEnv("VITEST"); + restoreEnv("WORKSPACE_DB_PATH"); + restoreEnv("WORKSPACE_REPOSITORY_BACKEND"); + restoreEnv("WORKSPACE_STATE_PATH"); if (tempDirectoryPath) { await rm(tempDirectoryPath, { force: true, recursive: true }); @@ -60,7 +70,9 @@ describe("workspace repository hardening", () => { expect(cloudRunPath).toBe(path.resolve("F:\\Docsy-document_editor\\markdown-muse", "/tmp/docsy-workspace-state.json")); }); - it("defaults to firestore on Cloud Run and file locally", () => { + it("uses the local file backend even when Cloud Run or legacy Firestore config is present", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined); + expect(resolveWorkspaceRepositoryBackend({ K_SERVICE: "", WORKSPACE_REPOSITORY_BACKEND: "", @@ -69,25 +81,14 @@ describe("workspace repository hardening", () => { expect(resolveWorkspaceRepositoryBackend({ K_SERVICE: "docsy", WORKSPACE_REPOSITORY_BACKEND: "", - } as NodeJS.ProcessEnv)).toBe("firestore"); - }); + } as NodeJS.ProcessEnv)).toBe("file"); - it("removes nested undefined fields before Firestore writes", () => { - expect(stripUndefinedDeep({ - a: 1, - b: undefined, - c: { - d: "value", - e: undefined, - }, - f: [1, undefined, { g: undefined, h: "kept" }], - })).toEqual({ - a: 1, - c: { - d: "value", - }, - f: [1, { h: "kept" }], - }); + expect(resolveWorkspaceRepositoryBackend({ + K_SERVICE: "docsy", + WORKSPACE_REPOSITORY_BACKEND: "firestore", + } as NodeJS.ProcessEnv)).toBe("file"); + + warnSpy.mockRestore(); }); it("rejects repo-local workspace state paths outside tests", () => { diff --git a/vite.config.ts b/vite.config.ts index 7567abf..b78daa7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -73,6 +73,7 @@ export default defineConfig(({ mode }) => { const webGuideContentStub = path.resolve(__dirname, "./src/content/webGuideContentStub.ts"); return ({ + base: mode === "web" ? "/" : "./", server: { host: "::", port: 8080, From aaeacb7eb25a9791bee1e8968fe402bbdf5f6f92 Mon Sep 17 00:00:00 2001 From: ethan Date: Thu, 4 Jun 2026 22:18:07 +0900 Subject: [PATCH 5/7] Guard desktop rendering through local smoke checks Docsy had export conversion decisions embedded in the preview panel while desktop releases only proved build/package success. This adds a render artifact pipeline, a small command boundary for export downloads, and package/first-screen checks so local-first desktop builds fail before upload when file-protocol or blank-window regressions return. Constraint: HWP/HWPX support remains intentionally out of scope. Constraint: Desktop releases must prove packaged file-protocol HTML and an initialized first editor surface, not just a Vite build. Rejected: Rewrite all editor tools into commands now | too broad for this fix and likely to disturb existing editor behavior. Rejected: Replace existing export converters | existing conversion coverage is already snapshot-tested and the safer move is to route them through a render pipeline. Confidence: high Scope-risk: moderate Directive: Keep desktop smoke checks focused on packaged app startup; broaden only with stable selectors and cross-platform CI evidence. Tested: npm run test -- src/test/documentRenderPipeline.test.ts src/test/renderCommands.test.ts src/test/exportPreviewPanel.html.test.tsx src/test/viteConfig.test.ts; npm run typecheck; changed-file ESLint; node --check for new scripts; npm run desktop:dist:dir; npm run verify:desktop-package; npm run verify:desktop-first-screen; git diff --check Not-tested: Full repository-wide npm run test and npm run lint, due known unrelated suite and lint debt recorded in prior closeouts. --- .github/workflows/release-desktop.yml | 13 +- package.json | 4 +- scripts/desktop-package-verifier.mjs | 71 +++++ scripts/smoke-desktop-first-screen.mjs | 154 ++++++++++ scripts/verify-desktop-package.mjs | 68 +++++ src/components/editor/ExportPreviewPanel.tsx | 88 ++---- src/lib/editorCommands/renderCommands.ts | 70 +++++ src/lib/rendering/documentRenderPipeline.ts | 276 ++++++++++++++++++ src/test/documentRenderPipeline.test.ts | 71 +++++ .../fixtures/renderingDocuments.fixture.ts | 26 ++ src/test/renderCommands.test.ts | 62 ++++ 11 files changed, 835 insertions(+), 68 deletions(-) create mode 100644 scripts/desktop-package-verifier.mjs create mode 100644 scripts/smoke-desktop-first-screen.mjs create mode 100644 scripts/verify-desktop-package.mjs create mode 100644 src/lib/editorCommands/renderCommands.ts create mode 100644 src/lib/rendering/documentRenderPipeline.ts create mode 100644 src/test/documentRenderPipeline.test.ts create mode 100644 src/test/fixtures/renderingDocuments.fixture.ts create mode 100644 src/test/renderCommands.test.ts diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index c4bad94..e378cec 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -43,7 +43,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libarchive-tools rpm + sudo apt-get install -y libarchive-tools rpm xvfb - name: Install dependencies run: npm ci @@ -61,6 +61,17 @@ jobs: CSC_IDENTITY_AUTO_DISCOVERY: "false" run: npx electron-builder --publish never + - name: Verify packaged desktop renderer + run: npm run verify:desktop-package + + - name: Smoke packaged desktop first screen + if: runner.os != 'Linux' + run: npm run verify:desktop-first-screen + + - name: Smoke packaged desktop first screen on Linux + if: runner.os == 'Linux' + run: xvfb-run -a npm run verify:desktop-first-screen + - name: Collect release assets shell: bash run: | diff --git a/package.json b/package.json index 956b0ed..bc1b360 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ "typecheck": "npm run typecheck:app && npm run typecheck:server && npm run typecheck:electron", "typecheck:app": "tsc -p tsconfig.app.json --noEmit", "typecheck:electron": "tsc -p tsconfig.electron.json --noEmit", - "typecheck:server": "tsc -p tsconfig.server.json --noEmit" + "typecheck:server": "tsc -p tsconfig.server.json --noEmit", + "verify:desktop-first-screen": "node scripts/smoke-desktop-first-screen.mjs", + "verify:desktop-package": "node scripts/verify-desktop-package.mjs" }, "dependencies": { "@google/genai": "^1.44.0", diff --git a/scripts/desktop-package-verifier.mjs b/scripts/desktop-package-verifier.mjs new file mode 100644 index 0000000..8f2e374 --- /dev/null +++ b/scripts/desktop-package-verifier.mjs @@ -0,0 +1,71 @@ +import fs from "node:fs"; +import path from "node:path"; + +const HTML_REFERENCE_PATTERN = /\b(src|href)=["']([^"']+)["']/gi; + +const isRootRelativeReference = (value) => + value.startsWith("/") && !value.startsWith("//"); + +export const findDesktopUnsafeHtmlReferences = (html) => { + const references = []; + let match; + + while ((match = HTML_REFERENCE_PATTERN.exec(html)) !== null) { + const [, attribute, value] = match; + + if (isRootRelativeReference(value)) { + references.push({ attribute, value }); + } + } + + return references; +}; + +export const validateDesktopHtml = (html, label) => { + const issues = []; + const unsafeReferences = findDesktopUnsafeHtmlReferences(html); + + for (const reference of unsafeReferences) { + issues.push(`${label}: ${reference.attribute}="${reference.value}" is root-relative and will break file:// loads.`); + } + + if (!/ { + const results = []; + + if (!fs.existsSync(releaseDir)) { + return results; + } + + const walk = (currentDir, depth) => { + if (depth > 6) { + return; + } + + for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) { + const entryPath = path.join(currentDir, entry.name); + + if (entry.isFile() && entry.name === "app.asar") { + results.push(entryPath); + continue; + } + + if (entry.isDirectory()) { + walk(entryPath, depth + 1); + } + } + }; + + walk(releaseDir, 0); + return results; +}; diff --git a/scripts/smoke-desktop-first-screen.mjs b/scripts/smoke-desktop-first-screen.mjs new file mode 100644 index 0000000..fc66860 --- /dev/null +++ b/scripts/smoke-desktop-first-screen.mjs @@ -0,0 +1,154 @@ +#!/usr/bin/env node + +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { _electron as electron } from "playwright"; + +const projectRoot = process.cwd(); +const releaseDir = path.join(projectRoot, "release"); +const timeoutMs = Number(process.env.DOCSY_DESKTOP_SMOKE_TIMEOUT_MS || 30000); + +const isExecutable = (filePath) => { + if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) { + return false; + } + + if (process.platform === "win32") { + return filePath.toLowerCase().endsWith(".exe"); + } + + return Boolean(fs.statSync(filePath).mode & 0o111); +}; + +const walkFiles = (directory, maxDepth = 6) => { + const files = []; + + if (!fs.existsSync(directory)) { + return files; + } + + const walk = (currentDir, depth) => { + if (depth > maxDepth) { + return; + } + + for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) { + const entryPath = path.join(currentDir, entry.name); + + if (entry.isDirectory()) { + walk(entryPath, depth + 1); + continue; + } + + files.push(entryPath); + } + }; + + walk(directory, 0); + return files; +}; + +const findPackagedExecutable = () => { + if (process.env.DOCSY_DESKTOP_EXECUTABLE) { + return path.resolve(projectRoot, process.env.DOCSY_DESKTOP_EXECUTABLE); + } + + const preferredCandidates = [ + path.join(releaseDir, "win-unpacked", "Docsy.exe"), + path.join(releaseDir, "mac", "Docsy.app", "Contents", "MacOS", "Docsy"), + path.join(releaseDir, "linux-unpacked", "Docsy"), + path.join(releaseDir, "linux-unpacked", "docsy"), + ]; + const preferred = preferredCandidates.find(isExecutable); + + if (preferred) { + return preferred; + } + + return walkFiles(releaseDir).find((filePath) => { + const normalized = filePath.replace(/\\/g, "/"); + const fileName = path.basename(filePath).toLowerCase(); + return normalized.includes("-unpacked/") && fileName.startsWith("docsy") && isExecutable(filePath); + }); +}; + +const executablePath = findPackagedExecutable(); + +if (!executablePath) { + console.error("[desktop-smoke] No packaged Docsy executable found. Run npm run desktop:dist:dir first."); + process.exit(1); +} + +const app = await electron.launch({ executablePath }); + +try { + const page = await app.firstWindow({ timeout: timeoutMs }); + const runtimeErrors = []; + const failedRequests = []; + + page.on("pageerror", (error) => runtimeErrors.push(error.message)); + page.on("console", (message) => { + if (message.type() === "error") { + runtimeErrors.push(message.text()); + } + }); + page.on("requestfailed", (request) => { + failedRequests.push({ + errorText: request.failure()?.errorText || "unknown", + url: request.url(), + }); + }); + + await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs }); + await page.waitForFunction( + () => { + const root = document.querySelector("#root"); + const visibleText = document.body.innerText.trim(); + return Boolean( + root + && root.children.length > 0 + && visibleText.length > 20 + && !visibleText.includes("Loading editor..."), + ); + }, + undefined, + { timeout: timeoutMs }, + ); + + const diagnostics = await page.evaluate(() => ({ + rootChildren: document.querySelector("#root")?.children.length ?? 0, + title: document.title, + url: window.location.href, + visibleTextSample: document.body.innerText.trim().slice(0, 120), + })); + + const unexpectedRuntimeErrors = runtimeErrors.filter((error) => !error.includes("ERR_BLOCKED_BY_CLIENT")); + const blockedRequests = failedRequests.filter((request) => request.errorText.includes("ERR_BLOCKED_BY_CLIENT")); + const unexpectedFailedRequests = failedRequests.filter((request) => !request.errorText.includes("ERR_BLOCKED_BY_CLIENT")); + + if (unexpectedRuntimeErrors.length > 0) { + console.warn("[desktop-smoke] Runtime console/page errors observed:"); + for (const error of unexpectedRuntimeErrors.slice(0, 10)) { + console.warn(`- ${error}`); + } + } + + if (unexpectedFailedRequests.length > 0) { + console.warn("[desktop-smoke] Unexpected failed requests observed:"); + for (const request of unexpectedFailedRequests.slice(0, 10)) { + console.warn(`- ${request.errorText}: ${request.url}`); + } + } + + console.log( + `[desktop-smoke] First screen rendered: ${diagnostics.title} (${diagnostics.rootChildren} root child nodes).`, + ); + console.log(`[desktop-smoke] ${diagnostics.url}`); + console.log(`[desktop-smoke] Text sample: ${diagnostics.visibleTextSample}`); + if (blockedRequests.length > 0) { + console.log(`[desktop-smoke] Observed ${blockedRequests.length} intentionally blocked local-first request(s).`); + } +} finally { + await app.close().catch(() => undefined); +} diff --git a/scripts/verify-desktop-package.mjs b/scripts/verify-desktop-package.mjs new file mode 100644 index 0000000..b09d7a5 --- /dev/null +++ b/scripts/verify-desktop-package.mjs @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +import fs from "node:fs"; +import path from "node:path"; +import { createRequire } from "node:module"; +import { + collectPackagedAsarPaths, + validateDesktopHtml, +} from "./desktop-package-verifier.mjs"; + +const projectRoot = process.cwd(); +const distIndexPath = path.join(projectRoot, "dist", "index.html"); +const releaseDir = path.join(projectRoot, "release"); +const issues = []; +const checkedLabels = []; + +const readAsarModule = () => { + const require = createRequire(import.meta.url); + return require("@electron/asar"); +}; + +const checkHtml = (html, label) => { + checkedLabels.push(label); + issues.push(...validateDesktopHtml(html, label)); +}; + +if (!fs.existsSync(distIndexPath)) { + issues.push("dist/index.html is missing. Run npm run build before verifying the desktop package."); +} else { + checkHtml(fs.readFileSync(distIndexPath, "utf8"), "dist/index.html"); +} + +const asarPaths = collectPackagedAsarPaths(releaseDir); + +if (asarPaths.length > 0) { + let asar; + + try { + asar = readAsarModule(); + } catch (error) { + issues.push(`Unable to load @electron/asar to inspect packaged app.asar: ${error.message}`); + } + + if (asar) { + for (const asarPath of asarPaths) { + const label = path.relative(projectRoot, asarPath).replace(/\\/g, "/"); + + try { + const htmlBuffer = asar.extractFile(asarPath, "dist/index.html"); + checkHtml(htmlBuffer.toString("utf8"), `${label}:dist/index.html`); + } catch (error) { + issues.push(`${label}: unable to read dist/index.html from app.asar: ${error.message}`); + } + } + } +} else { + console.warn("[desktop-package] No packaged app.asar found; verified dist/index.html only."); +} + +if (issues.length > 0) { + console.error("[desktop-package] Verification failed:"); + for (const issue of issues) { + console.error(`- ${issue}`); + } + process.exit(1); +} + +console.log(`[desktop-package] Verified ${checkedLabels.length} renderer HTML file(s).`); diff --git a/src/components/editor/ExportPreviewPanel.tsx b/src/components/editor/ExportPreviewPanel.tsx index 2cb3272..97ae9d2 100644 --- a/src/components/editor/ExportPreviewPanel.tsx +++ b/src/components/editor/ExportPreviewPanel.tsx @@ -9,17 +9,19 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { htmlToAsciidoc } from "@/components/editor/utils/htmlToAsciidoc"; import { htmlTokenClassMap, tokenizeHtml, type HtmlTokenKind } from "@/components/editor/utils/htmlHighlight"; -import { htmlToRst } from "@/components/editor/utils/htmlToRst"; -import { htmlToTypst } from "@/components/editor/utils/htmlToTypst"; -import { latexToTypst } from "@/components/editor/utils/latexToTypst"; import TexValidationPanel from "@/components/editor/TexValidationPanel"; import type { TexValidationPanelProps } from "@/components/editor/TexValidationPanel"; import { useI18n } from "@/i18n/useI18n"; +import { downloadRenderArtifact, executeEditorCommand, createBuildRenderPreviewCommand } from "@/lib/editorCommands/renderCommands"; +import { + DOCUMENT_RENDER_TARGET_LABELS, + DOCUMENT_RENDER_TARGETS, + type DocumentRenderTarget, +} from "@/lib/rendering/documentRenderPipeline"; import type { EditorMode } from "@/types/document"; -export type PreviewFormat = "asciidoc" | "html" | "latex" | "markdown" | "rst" | "typst"; +export type PreviewFormat = DocumentRenderTarget; interface TexValidationInspectorProps extends TexValidationPanelProps { isExportingPdf: boolean; @@ -47,36 +49,6 @@ interface HighlightLineSegment { text: string; } -const FORMAT_LABELS: Record = { - asciidoc: "AsciiDoc", - html: "HTML", - latex: "LaTeX", - markdown: "Markdown", - rst: "RST", - typst: "Typst", -}; - -const FORMAT_EXTENSIONS: Record = { - asciidoc: ".adoc", - html: ".html", - latex: ".tex", - markdown: ".md", - rst: ".rst", - typst: ".typ", -}; - -const getDefaultFormat = (mode: EditorMode): PreviewFormat => { - if (mode === "markdown") { - return "latex"; - } - - if (mode === "latex") { - return "markdown"; - } - - return "markdown"; -}; - const splitHtmlTokensByLine = (source: string) => { const lines: HighlightLineSegment[][] = [[]]; @@ -112,13 +84,22 @@ const ExportPreviewPanel = ({ texValidationProps, }: ExportPreviewPanelProps) => { const { t } = useI18n(); - const [format, setFormat] = useState(() => getDefaultFormat(editorMode)); const [showLineNumbers, setShowLineNumbers] = useState(true); const [wrapLines, setWrapLines] = useState(false); const [activeTab, setActiveTab] = useState("preview"); const [highlightedLine, setHighlightedLine] = useState(null); const validationAvailable = Boolean(texValidationProps?.validationEnabled); const lineRefs = useRef>({}); + const renderResult = useMemo(() => executeEditorCommand(createBuildRenderPreviewCommand({ + content: rawContent, + html: editorHtml, + latex: editorLatex, + markdown: editorMarkdown, + mode: editorMode, + })), [editorHtml, editorLatex, editorMarkdown, editorMode, rawContent]); + const [format, setFormat] = useState(() => renderResult.defaultTarget); + const artifact = renderResult.artifacts[format]; + const content = artifact.content; useEffect(() => { if (!validationAvailable && (activeTab === "validation" || activeTab === "engine")) { @@ -137,25 +118,6 @@ const ExportPreviewPanel = ({ } }, [activeTab, format, highlightedLine]); - const content = useMemo(() => { - switch (format) { - case "html": - return editorHtml; - case "latex": - return editorLatex; - case "markdown": - return editorMarkdown; - case "typst": - return editorMode === "latex" ? latexToTypst(rawContent) : htmlToTypst(editorHtml); - case "asciidoc": - return htmlToAsciidoc(editorHtml); - case "rst": - return htmlToRst(editorHtml); - default: - return editorMarkdown; - } - }, [editorHtml, editorLatex, editorMarkdown, editorMode, format, rawContent]); - const htmlPreviewLines = useMemo( () => (format === "html" ? splitHtmlTokensByLine(content) : []), [content, format], @@ -167,14 +129,8 @@ const ExportPreviewPanel = ({ }, [content, t]); const handleDownload = useCallback(() => { - const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); - const url = URL.createObjectURL(blob); - const anchor = document.createElement("a"); - anchor.href = url; - anchor.download = `${fileName || "Untitled"}${FORMAT_EXTENSIONS[format]}`; - anchor.click(); - URL.revokeObjectURL(url); - }, [content, fileName, format]); + downloadRenderArtifact(artifact, fileName); + }, [artifact, fileName]); const handleJumpToLine = useCallback((line: number) => { setFormat("latex"); @@ -227,14 +183,14 @@ const ExportPreviewPanel = ({ - {(Object.keys(FORMAT_LABELS) as PreviewFormat[]).map((option) => ( + {DOCUMENT_RENDER_TARGETS.map((option) => ( setFormat(option)}> - {FORMAT_LABELS[option]} + {DOCUMENT_RENDER_TARGET_LABELS[option]} ))} diff --git a/src/lib/editorCommands/renderCommands.ts b/src/lib/editorCommands/renderCommands.ts new file mode 100644 index 0000000..46e52a2 --- /dev/null +++ b/src/lib/editorCommands/renderCommands.ts @@ -0,0 +1,70 @@ +import { + buildDocumentRenderResult, + type DocumentRenderArtifact, + type DocumentRenderInput, + type DocumentRenderResult, +} from "@/lib/rendering/documentRenderPipeline"; + +export interface EditorCommand { + id: string; + execute: () => TResult; +} + +export interface DownloadRenderArtifactResult { + artifactTarget: DocumentRenderArtifact["target"]; + commandId: "render.downloadArtifact"; + fileName: string; +} + +interface DownloadAdapter { + clickDownload: (url: string, fileName: string) => void; + createObjectUrl: (blob: Blob) => string; + revokeObjectUrl: (url: string) => void; +} + +export const createBuildRenderPreviewCommand = ( + input: DocumentRenderInput, +): EditorCommand => ({ + id: "render.buildPreview", + execute: () => buildDocumentRenderResult(input), +}); + +export const executeEditorCommand = (command: EditorCommand) => command.execute(); + +export const getRenderArtifactDownloadName = ( + fileName: string, + artifact: Pick, +) => `${fileName || "Untitled"}${artifact.extension}`; + +const defaultDownloadAdapter: DownloadAdapter = { + clickDownload: (url, fileName) => { + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = fileName; + anchor.click(); + }, + createObjectUrl: (blob) => URL.createObjectURL(blob), + revokeObjectUrl: (url) => URL.revokeObjectURL(url), +}; + +export const downloadRenderArtifact = ( + artifact: DocumentRenderArtifact, + fileName: string, + adapter: DownloadAdapter = defaultDownloadAdapter, +): DownloadRenderArtifactResult => { + const downloadName = getRenderArtifactDownloadName(fileName, artifact); + const blob = new Blob([artifact.content], { type: artifact.mimeType }); + const url = adapter.createObjectUrl(blob); + + try { + adapter.clickDownload(url, downloadName); + } finally { + adapter.revokeObjectUrl(url); + } + + return { + artifactTarget: artifact.target, + commandId: "render.downloadArtifact", + fileName: downloadName, + }; +}; diff --git a/src/lib/rendering/documentRenderPipeline.ts b/src/lib/rendering/documentRenderPipeline.ts new file mode 100644 index 0000000..3b7a5f4 --- /dev/null +++ b/src/lib/rendering/documentRenderPipeline.ts @@ -0,0 +1,276 @@ +import type { JSONContent } from "@tiptap/core"; +import { htmlToAsciidoc } from "@/components/editor/utils/htmlToAsciidoc"; +import { htmlToRst } from "@/components/editor/utils/htmlToRst"; +import { htmlToTypst } from "@/components/editor/utils/htmlToTypst"; +import { latexToTypst } from "@/components/editor/utils/latexToTypst"; +import { getRenderableHtml } from "@/lib/ast/getRenderableHtml"; +import { getRenderableLatex } from "@/lib/ast/getRenderableLatex"; +import { getRenderableMarkdown } from "@/lib/ast/getRenderableMarkdown"; +import type { AstLatexRenderOptions } from "@/lib/ast/renderAstToLatex"; +import { renderAstToHtml } from "@/lib/ast/renderAstToHtml"; +import { renderAstToLatex } from "@/lib/ast/renderAstToLatex"; +import { renderAstToMarkdown } from "@/lib/ast/renderAstToMarkdown"; +import { serializeTiptapToAst } from "@/lib/ast/tiptapAst"; +import { isUsableTiptapDocument } from "@/lib/ast/tiptapUsability"; +import type { DocumentAst } from "@/types/documentAst"; +import type { EditorMode } from "@/types/document"; + +export const DOCUMENT_RENDER_TARGETS = ["asciidoc", "html", "latex", "markdown", "rst", "typst"] as const; + +export type DocumentRenderTarget = (typeof DOCUMENT_RENDER_TARGETS)[number]; + +export const DOCUMENT_RENDER_TARGET_LABELS: Record = { + asciidoc: "AsciiDoc", + html: "HTML", + latex: "LaTeX", + markdown: "Markdown", + rst: "RST", + typst: "Typst", +}; + +export const DOCUMENT_RENDER_TARGET_EXTENSIONS: Record = { + asciidoc: ".adoc", + html: ".html", + latex: ".tex", + markdown: ".md", + rst: ".rst", + typst: ".typ", +}; + +const DOCUMENT_RENDER_TARGET_MIME_TYPES: Record = { + asciidoc: "text/asciidoc;charset=utf-8", + html: "text/html;charset=utf-8", + latex: "text/x-tex;charset=utf-8", + markdown: "text/markdown;charset=utf-8", + rst: "text/x-rst;charset=utf-8", + typst: "text/plain;charset=utf-8", +}; + +export type DocumentRenderDiagnosticSeverity = "info" | "warning" | "error"; +export type DocumentRenderSource = "ast" | "conversion" | "direct" | "fallback" | "tiptap"; + +export interface DocumentRenderDiagnostic { + code: string; + message: string; + severity: DocumentRenderDiagnosticSeverity; + source: DocumentRenderSource; + target?: DocumentRenderTarget; +} + +export interface DocumentRenderInput { + ast?: DocumentAst | null; + content: string; + generatedAt?: number; + html?: string; + latex?: string; + latexOptions?: AstLatexRenderOptions; + markdown?: string; + mode: EditorMode; + tiptapJson?: JSONContent | null; +} + +export interface DocumentRenderArtifact { + cacheKey: string; + content: string; + diagnostics: DocumentRenderDiagnostic[]; + extension: string; + generatedAt: number; + mimeType: string; + source: DocumentRenderSource; + target: DocumentRenderTarget; +} + +export type DocumentRenderArtifacts = Record; + +export interface DocumentRenderResult { + artifacts: DocumentRenderArtifacts; + cacheKey: string; + defaultTarget: DocumentRenderTarget; + diagnostics: DocumentRenderDiagnostic[]; + sourceMode: EditorMode; +} + +interface BaseRenderSources { + html: string; + htmlSource: DocumentRenderSource; + latex: string; + latexSource: DocumentRenderSource; + markdown: string; + markdownSource: DocumentRenderSource; +} + +const createHash = (value: string) => { + let hash = 2166136261; + + for (let index = 0; index < value.length; index += 1) { + hash ^= value.charCodeAt(index); + hash = Math.imul(hash, 16777619); + } + + return (hash >>> 0).toString(16).padStart(8, "0"); +}; + +export const getDefaultRenderTarget = (mode: EditorMode): DocumentRenderTarget => { + if (mode === "markdown") { + return "latex"; + } + + if (mode === "latex") { + return "markdown"; + } + + return "markdown"; +}; + +const directContentForTarget = ( + input: DocumentRenderInput, + target: "html" | "latex" | "markdown", +) => (input.mode === target ? input.content : ""); + +const resolveAstFromInput = ( + input: DocumentRenderInput, + diagnostics: DocumentRenderDiagnostic[], +): { ast: DocumentAst; source: DocumentRenderSource } | null => { + if (input.ast) { + return { ast: input.ast, source: "ast" }; + } + + if (!isUsableTiptapDocument(input.tiptapJson)) { + return null; + } + + try { + return { + ast: serializeTiptapToAst(input.tiptapJson as JSONContent, { throwOnUnsupported: true }), + source: "tiptap", + }; + } catch (error) { + diagnostics.push({ + code: "tiptap_ast_serialize_failed", + message: error instanceof Error ? error.message : "Unable to serialize editor document to AST.", + severity: "warning", + source: "tiptap", + }); + return null; + } +}; + +const resolveBaseSources = ( + input: DocumentRenderInput, + diagnostics: DocumentRenderDiagnostic[], +): BaseRenderSources => { + const astResult = resolveAstFromInput(input, diagnostics); + + if (astResult) { + try { + return { + html: renderAstToHtml(astResult.ast), + htmlSource: astResult.source, + latex: renderAstToLatex(astResult.ast, input.latexOptions), + latexSource: astResult.source, + markdown: renderAstToMarkdown(astResult.ast), + markdownSource: astResult.source, + }; + } catch (error) { + diagnostics.push({ + code: "ast_render_failed", + message: error instanceof Error ? error.message : "Unable to render AST.", + severity: "error", + source: astResult.source, + }); + } + } + + const fallbackHtml = input.html ?? directContentForTarget(input, "html"); + const fallbackLatex = input.latex ?? directContentForTarget(input, "latex"); + const fallbackMarkdown = input.markdown ?? directContentForTarget(input, "markdown"); + + if (isUsableTiptapDocument(input.tiptapJson)) { + return { + html: getRenderableHtml(input.tiptapJson, fallbackHtml), + htmlSource: "fallback", + latex: getRenderableLatex(input.tiptapJson, fallbackLatex, input.latexOptions), + latexSource: "fallback", + markdown: getRenderableMarkdown(input.tiptapJson, fallbackMarkdown), + markdownSource: "fallback", + }; + } + + return { + html: fallbackHtml, + htmlSource: fallbackHtml ? "direct" : "fallback", + latex: fallbackLatex, + latexSource: fallbackLatex ? "direct" : "fallback", + markdown: fallbackMarkdown, + markdownSource: fallbackMarkdown ? "direct" : "fallback", + }; +}; + +const createArtifact = ( + target: DocumentRenderTarget, + content: string, + source: DocumentRenderSource, + generatedAt: number, + cacheKey: string, + diagnostics: DocumentRenderDiagnostic[], +): DocumentRenderArtifact => ({ + cacheKey: `${cacheKey}:${target}`, + content, + diagnostics: diagnostics.filter((diagnostic) => !diagnostic.target || diagnostic.target === target), + extension: DOCUMENT_RENDER_TARGET_EXTENSIONS[target], + generatedAt, + mimeType: DOCUMENT_RENDER_TARGET_MIME_TYPES[target], + source, + target, +}); + +export const buildDocumentRenderCacheKey = (input: DocumentRenderInput) => createHash([ + input.mode, + input.content, + input.html ?? "", + input.latex ?? "", + input.markdown ?? "", + input.ast ? JSON.stringify(input.ast) : "", + input.tiptapJson ? JSON.stringify(input.tiptapJson) : "", +].join("\u001f")); + +export const buildDocumentRenderResult = (input: DocumentRenderInput): DocumentRenderResult => { + const diagnostics: DocumentRenderDiagnostic[] = []; + const cacheKey = buildDocumentRenderCacheKey(input); + const generatedAt = input.generatedAt ?? Date.now(); + const base = resolveBaseSources(input, diagnostics); + const typstSource = input.mode === "latex" ? input.content : base.html; + + if (!base.html && (input.mode === "markdown" || input.mode === "latex")) { + diagnostics.push({ + code: "html_source_missing", + message: "HTML source was not available, so HTML-derived export targets may be empty.", + severity: "warning", + source: "fallback", + }); + } + + const artifacts: DocumentRenderArtifacts = { + asciidoc: createArtifact("asciidoc", htmlToAsciidoc(base.html), "conversion", generatedAt, cacheKey, diagnostics), + html: createArtifact("html", base.html, base.htmlSource, generatedAt, cacheKey, diagnostics), + latex: createArtifact("latex", base.latex, base.latexSource, generatedAt, cacheKey, diagnostics), + markdown: createArtifact("markdown", base.markdown, base.markdownSource, generatedAt, cacheKey, diagnostics), + rst: createArtifact("rst", htmlToRst(base.html), "conversion", generatedAt, cacheKey, diagnostics), + typst: createArtifact( + "typst", + input.mode === "latex" ? latexToTypst(typstSource) : htmlToTypst(typstSource), + "conversion", + generatedAt, + cacheKey, + diagnostics, + ), + }; + + return { + artifacts, + cacheKey, + defaultTarget: getDefaultRenderTarget(input.mode), + diagnostics, + sourceMode: input.mode, + }; +}; diff --git a/src/test/documentRenderPipeline.test.ts b/src/test/documentRenderPipeline.test.ts new file mode 100644 index 0000000..8dcafe6 --- /dev/null +++ b/src/test/documentRenderPipeline.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; +import { buildDocumentRenderResult, getDefaultRenderTarget } from "@/lib/rendering/documentRenderPipeline"; +import { koreanLongHtmlFixture, koreanLongMarkdownFixture } from "@/test/fixtures/renderingDocuments.fixture"; +import { technicalDocumentFixture } from "@/test/fixtures/technicalDocument.fixture"; + +describe("document render pipeline", () => { + it("builds render artifacts from a supported TipTap technical document", () => { + const result = buildDocumentRenderResult({ + content: "# fallback", + generatedAt: 1, + mode: "markdown", + tiptapJson: technicalDocumentFixture, + }); + + expect(result.defaultTarget).toBe("latex"); + expect(result.artifacts.markdown.content).toContain("# System Overview"); + expect(result.artifacts.html.content).toContain('data-type="mermaid"'); + expect(result.artifacts.latex.content).toContain("% begin-mermaid"); + expect(result.artifacts.asciidoc.content).toContain("[source,mermaid]"); + expect(result.artifacts.rst.content).toContain(".. code-block:: mermaid"); + expect(result.artifacts.typst.content).toContain("// Mermaid diagram"); + expect(result.diagnostics.filter((diagnostic) => diagnostic.severity === "error")).toHaveLength(0); + }); + + it("reports diagnostics and keeps direct fallbacks when TipTap serialization fails", () => { + const result = buildDocumentRenderResult({ + content: "# fallback", + generatedAt: 1, + html: "

Fallback

", + markdown: "# Fallback", + mode: "markdown", + tiptapJson: { + type: "doc", + content: [{ type: "unsupportedNode", text: "unsupported" }], + }, + }); + + expect(result.artifacts.markdown.content).toBe("# Fallback"); + expect(result.artifacts.html.content).toBe("

Fallback

"); + expect(result.diagnostics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: "tiptap_ast_serialize_failed", + severity: "warning", + }), + ]), + ); + }); + + it("keeps Korean long-document fixture content available for export conversions", () => { + const result = buildDocumentRenderResult({ + content: koreanLongMarkdownFixture, + generatedAt: 1, + html: koreanLongHtmlFixture, + markdown: koreanLongMarkdownFixture, + mode: "markdown", + }); + + expect(result.cacheKey).toMatch(/^[0-9a-f]{8}$/); + expect(result.artifacts.markdown.content).toContain("한국어 렌더 회귀"); + expect(result.artifacts.html.content).toContain("한글 입력"); + expect(result.artifacts.asciidoc.content).toContain("[source,mermaid]"); + expect(result.artifacts.typst.content).toContain("E=mc^2"); + }); + + it("selects preview target defaults from the source mode", () => { + expect(getDefaultRenderTarget("markdown")).toBe("latex"); + expect(getDefaultRenderTarget("latex")).toBe("markdown"); + expect(getDefaultRenderTarget("html")).toBe("markdown"); + }); +}); diff --git a/src/test/fixtures/renderingDocuments.fixture.ts b/src/test/fixtures/renderingDocuments.fixture.ts new file mode 100644 index 0000000..a3f0060 --- /dev/null +++ b/src/test/fixtures/renderingDocuments.fixture.ts @@ -0,0 +1,26 @@ +export const koreanLongMarkdownFixture = [ + "# 한국어 렌더 회귀", + "", + "한글 입력과 긴 문단이 렌더 중간 계층을 지나도 손상되지 않아야 합니다.", + "수식 $E=mc^2$, 표, Mermaid, 긴 본문이 같은 렌더 작업 안에서 함께 처리되는 케이스입니다.", + "", + "```mermaid", + "graph TD", + " A[초안] --> B[검토]", + " B --> C[내보내기]", + "```", + "", + "| 항목 | 값 |", + "| --- | --- |", + "| IME | 정상 |", + "| 긴 문서 | 안정 |", + "", + "본문 ".repeat(80), +].join("\n"); + +export const koreanLongHtmlFixture = [ + "

한국어 렌더 회귀

", + "

한글 입력과 긴 문단이 렌더 중간 계층을 지나도 손상되지 않아야 합니다.

", + '
', + 'E=mc^2', +].join("\n"); diff --git a/src/test/renderCommands.test.ts b/src/test/renderCommands.test.ts new file mode 100644 index 0000000..1afd78a --- /dev/null +++ b/src/test/renderCommands.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from "vitest"; +import { + createBuildRenderPreviewCommand, + downloadRenderArtifact, + executeEditorCommand, + getRenderArtifactDownloadName, +} from "@/lib/editorCommands/renderCommands"; +import type { DocumentRenderArtifact } from "@/lib/rendering/documentRenderPipeline"; + +const createArtifact = (): DocumentRenderArtifact => ({ + cacheKey: "abc:markdown", + content: "# Draft", + diagnostics: [], + extension: ".md", + generatedAt: 1, + mimeType: "text/markdown;charset=utf-8", + source: "direct", + target: "markdown", +}); + +describe("render commands", () => { + it("wraps render preview building as a command", () => { + const result = executeEditorCommand(createBuildRenderPreviewCommand({ + content: "# Draft", + generatedAt: 1, + markdown: "# Draft", + mode: "markdown", + })); + + expect(result.artifacts.markdown.content).toBe("# Draft"); + expect(result.defaultTarget).toBe("latex"); + }); + + it("downloads a render artifact through an injected adapter", () => { + const calls: string[] = []; + const artifact = createArtifact(); + + const result = downloadRenderArtifact(artifact, "Draft", { + clickDownload: (url, fileName) => calls.push(`click:${url}:${fileName}`), + createObjectUrl: (blob) => { + calls.push(`blob:${blob.type}`); + return "blob:docsy"; + }, + revokeObjectUrl: (url) => calls.push(`revoke:${url}`), + }); + + expect(result).toEqual({ + artifactTarget: "markdown", + commandId: "render.downloadArtifact", + fileName: "Draft.md", + }); + expect(calls).toEqual([ + "blob:text/markdown;charset=utf-8", + "click:blob:docsy:Draft.md", + "revoke:blob:docsy", + ]); + }); + + it("uses Untitled when the document name is empty", () => { + expect(getRenderArtifactDownloadName("", createArtifact())).toBe("Untitled.md"); + }); +}); From f603e71fe22a34fb8c5bf8d5543f88a8c1ae2376 Mon Sep 17 00:00:00 2001 From: ethan Date: Thu, 4 Jun 2026 22:31:30 +0900 Subject: [PATCH 6/7] Make desktop smoke locate Linux app binaries The Linux package exposes the application executable under the unpacked app directory without a stable Docsy-prefixed filename, so the release smoke check must resolve the executable next to app.asar instead of assuming a product-name path. Constraint: CI must keep the first-screen smoke active on Linux because package HTML verification alone cannot catch renderer startup regressions. Rejected: Disable Linux smoke check | would let the exact blank-window regression class slip through on one release target. Confidence: high Scope-risk: narrow Tested: node --check scripts/smoke-desktop-first-screen.mjs; npm run verify:desktop-first-screen; npx eslint scripts/smoke-desktop-first-screen.mjs Not-tested: Linux runner locally; verified through the release workflow rerun after this commit. --- scripts/smoke-desktop-first-screen.mjs | 51 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/scripts/smoke-desktop-first-screen.mjs b/scripts/smoke-desktop-first-screen.mjs index fc66860..3436e14 100644 --- a/scripts/smoke-desktop-first-screen.mjs +++ b/scripts/smoke-desktop-first-screen.mjs @@ -21,6 +21,24 @@ const isExecutable = (filePath) => { return Boolean(fs.statSync(filePath).mode & 0o111); }; +const isLikelyAppExecutable = (filePath) => { + const fileName = path.basename(filePath).toLowerCase(); + + if (!isExecutable(filePath)) { + return false; + } + + if (fileName.endsWith(".so") || fileName.includes(".so.")) { + return false; + } + + return ![ + "chrome-sandbox", + "chrome_crashpad_handler", + "crashpad_handler", + ].includes(fileName); +}; + const walkFiles = (directory, maxDepth = 6) => { const files = []; @@ -49,6 +67,31 @@ const walkFiles = (directory, maxDepth = 6) => { return files; }; +const findExecutableBesideAsar = () => { + const unpackedDirs = [ + path.join(releaseDir, "linux-unpacked"), + path.join(releaseDir, "win-unpacked"), + path.join(releaseDir, "mac", "Docsy.app", "Contents", "MacOS"), + ]; + + for (const unpackedDir of unpackedDirs) { + if (!fs.existsSync(path.join(unpackedDir, "resources", "app.asar"))) { + continue; + } + + const executable = fs + .readdirSync(unpackedDir) + .map((entry) => path.join(unpackedDir, entry)) + .find(isLikelyAppExecutable); + + if (executable) { + return executable; + } + } + + return undefined; +}; + const findPackagedExecutable = () => { if (process.env.DOCSY_DESKTOP_EXECUTABLE) { return path.resolve(projectRoot, process.env.DOCSY_DESKTOP_EXECUTABLE); @@ -66,10 +109,14 @@ const findPackagedExecutable = () => { return preferred; } + const asarSiblingExecutable = findExecutableBesideAsar(); + if (asarSiblingExecutable) { + return asarSiblingExecutable; + } + return walkFiles(releaseDir).find((filePath) => { const normalized = filePath.replace(/\\/g, "/"); - const fileName = path.basename(filePath).toLowerCase(); - return normalized.includes("-unpacked/") && fileName.startsWith("docsy") && isExecutable(filePath); + return normalized.includes("-unpacked/") && isLikelyAppExecutable(filePath); }); }; From d991089c0c76c94985d0ca1409e91b46375937e6 Mon Sep 17 00:00:00 2001 From: ethan Date: Thu, 4 Jun 2026 22:37:51 +0900 Subject: [PATCH 7/7] Run Linux desktop smoke without sandbox The packaged Linux app is valid, but GitHub runner Electron startup fails before the renderer appears unless the CI smoke launches Chromium without the sandbox. The smoke remains a packaged first-screen check and now logs the resolved executable path for future CI diagnosis. Constraint: Keep Linux release smoke active; only adjust launch args needed by CI. Rejected: Skip Linux first-screen smoke | would leave Linux release assets unverified for blank-window regressions. Confidence: medium Scope-risk: narrow Tested: node --check scripts/smoke-desktop-first-screen.mjs; npm run verify:desktop-first-screen; npx eslint scripts/smoke-desktop-first-screen.mjs Not-tested: Linux runner locally; release workflow rerun verifies it. --- scripts/smoke-desktop-first-screen.mjs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/smoke-desktop-first-screen.mjs b/scripts/smoke-desktop-first-screen.mjs index 3436e14..ae53668 100644 --- a/scripts/smoke-desktop-first-screen.mjs +++ b/scripts/smoke-desktop-first-screen.mjs @@ -127,7 +127,17 @@ if (!executablePath) { process.exit(1); } -const app = await electron.launch({ executablePath }); +const launchArgs = process.platform === "linux" ? ["--no-sandbox"] : []; +console.log(`[desktop-smoke] Launching ${executablePath}`); + +const app = await electron.launch({ + args: launchArgs, + env: { + ...process.env, + ELECTRON_ENABLE_LOGGING: "1", + }, + executablePath, +}); try { const page = await app.firstWindow({ timeout: timeoutMs });