Refactor presentation export runtime handling
- Update sync-presentation-export script to read version from package.json instead of export-version.json. - Change entrypoint from index.js to index.cjs for CommonJS compatibility. - Implement logic to ensure CommonJS entrypoint is created if missing. - Modify ExportTaskService to resolve entrypoint path dynamically. - Remove inline Puppeteer PDF export logic in favor of bundled export package. - Add runtime availability checks and sync script execution in start.js. - Introduce helper functions for output path normalization and entrypoint resolution.
This commit is contained in:
parent
8c5fd218d1
commit
5e4ee9e039
12 changed files with 778 additions and 236 deletions
10
Dockerfile
10
Dockerfile
|
|
@ -12,6 +12,7 @@ ENV APP_DATA_DIRECTORY=/app_data \
|
|||
UV_LINK_MODE=copy \
|
||||
PATH="/root/.local/bin:${PATH}" \
|
||||
EXPORT_PACKAGE_ROOT=/app/presentation-export \
|
||||
EXPORT_RUNTIME_DIR=/app/presentation-export \
|
||||
BUILT_PYTHON_MODULE_PATH=/app/presentation-export/py/convert-linux-x64 \
|
||||
PRESENTON_APP_ROOT=/app
|
||||
|
||||
|
|
@ -26,17 +27,16 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|||
&& apt-get install -y nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package.json package-lock.json /app/
|
||||
RUN npm --prefix /app install --omit=dev
|
||||
|
||||
RUN mkdir -p /app/document-extraction-liteparse \
|
||||
&& npm --prefix /app/document-extraction-liteparse init -y \
|
||||
&& npm --prefix /app/document-extraction-liteparse install @llamaindex/liteparse@1.4.0 --omit=dev
|
||||
COPY electron/resources/document-extraction/liteparse_runner.mjs /app/document-extraction-liteparse/liteparse_runner.mjs
|
||||
|
||||
# PDF/PPTX export runtime: version pin in presentation-export/export-version.json (or build-arg).
|
||||
COPY presentation-export/export-version.json /app/presentation-export/export-version.json
|
||||
COPY scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs
|
||||
ARG EXPORT_RUNTIME_VERSION
|
||||
RUN export EXPORT_RUNTIME_VERSION="${EXPORT_RUNTIME_VERSION:-}" \
|
||||
&& node /app/scripts/sync-presentation-export.cjs --force \
|
||||
RUN node /app/scripts/sync-presentation-export.cjs --force \
|
||||
&& chmod +x /app/presentation-export/py/convert-linux-x64
|
||||
|
||||
RUN curl -fsSL https://ollama.com/install.sh | sh
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ ENV APP_DATA_DIRECTORY=/app_data \
|
|||
UV_LINK_MODE=copy \
|
||||
PATH="/root/.local/bin:${PATH}" \
|
||||
EXPORT_PACKAGE_ROOT=/app/presentation-export \
|
||||
EXPORT_RUNTIME_DIR=/app/presentation-export \
|
||||
BUILT_PYTHON_MODULE_PATH=/app/presentation-export/py/convert-linux-x64 \
|
||||
PRESENTON_APP_ROOT=/app
|
||||
|
||||
|
|
@ -26,16 +27,16 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|||
&& apt-get install -y nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package.json package-lock.json /app/
|
||||
RUN npm --prefix /app install --omit=dev
|
||||
|
||||
RUN mkdir -p /app/document-extraction-liteparse \
|
||||
&& npm --prefix /app/document-extraction-liteparse init -y \
|
||||
&& npm --prefix /app/document-extraction-liteparse install @llamaindex/liteparse@1.4.0 --omit=dev
|
||||
COPY electron/resources/document-extraction/liteparse_runner.mjs /app/document-extraction-liteparse/liteparse_runner.mjs
|
||||
|
||||
COPY presentation-export/export-version.json /app/presentation-export/export-version.json
|
||||
COPY scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs
|
||||
ARG EXPORT_RUNTIME_VERSION
|
||||
RUN export EXPORT_RUNTIME_VERSION="${EXPORT_RUNTIME_VERSION:-}" \
|
||||
&& node /app/scripts/sync-presentation-export.cjs --force \
|
||||
RUN node /app/scripts/sync-presentation-export.cjs --force \
|
||||
&& chmod +x /app/presentation-export/py/convert-linux-x64
|
||||
|
||||
# Bind mount `.:/app` hides any .venv under servers/fastapi at runtime — install deps into
|
||||
|
|
|
|||
|
|
@ -4,9 +4,6 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# Optional: override presentation-export release (else presentation-export/export-version.json)
|
||||
EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-}
|
||||
ports:
|
||||
# You can replace 5000 with any other port number of your choice to run Presenton on a different port number.
|
||||
- "5000:80"
|
||||
|
|
@ -45,8 +42,6 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-}
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
|
|
@ -91,14 +86,13 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
args:
|
||||
EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-}
|
||||
ports:
|
||||
- "5000:80"
|
||||
# Required for Codex OAuth callback (OpenAI redirects browser directly to localhost:1455)
|
||||
- "1455:1455"
|
||||
volumes:
|
||||
- .:/app
|
||||
- presenton_root_node_modules:/app/node_modules
|
||||
- ./app_data:/app_data
|
||||
environment:
|
||||
# Dockerfile.dev does not install ollama; use a host daemon via OLLAMA_URL or omit.
|
||||
|
|
@ -132,8 +126,6 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
args:
|
||||
EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-}
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
|
|
@ -147,6 +139,7 @@ services:
|
|||
- "1455:1455"
|
||||
volumes:
|
||||
- .:/app
|
||||
- presenton_root_node_modules:/app/node_modules
|
||||
- ./app_data:/app_data
|
||||
environment:
|
||||
- START_EMBEDDED_OLLAMA=false
|
||||
|
|
@ -174,3 +167,6 @@ services:
|
|||
- DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING}
|
||||
- COMFYUI_URL=${COMFYUI_URL}
|
||||
- COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW}
|
||||
|
||||
volumes:
|
||||
presenton_root_node_modules:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import os
|
|||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import uuid
|
||||
from typing import Mapping
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
|
@ -33,7 +32,7 @@ class ExportTaskService:
|
|||
self.timeout_seconds = timeout_seconds
|
||||
self.node_binary = os.getenv("LITEPARSE_NODE_BINARY", "node")
|
||||
self.export_dir = self._resolve_export_dir()
|
||||
self.entrypoint_path = os.path.join(self.export_dir, "index.js")
|
||||
self.entrypoint_path = self._resolve_entrypoint_path(self.export_dir)
|
||||
self.converter_path = self._resolve_converter_path(self.export_dir)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -42,31 +41,40 @@ class ExportTaskService:
|
|||
if configured:
|
||||
return configured
|
||||
|
||||
package_root = (os.getenv("EXPORT_PACKAGE_ROOT") or "").strip()
|
||||
if package_root:
|
||||
return package_root
|
||||
|
||||
cwd = os.path.abspath(".")
|
||||
service_dir = os.path.dirname(__file__)
|
||||
candidates = [
|
||||
os.path.abspath(os.path.join(cwd, "..", "..", "resources", "export")),
|
||||
os.path.abspath(os.path.join(cwd, "..", "export")),
|
||||
os.path.abspath(
|
||||
os.path.join(service_dir, "..", "..", "..", "resources", "export")
|
||||
),
|
||||
os.path.abspath(os.path.join(service_dir, "..", "..", "export")),
|
||||
os.path.abspath(
|
||||
os.path.join(cwd, "..", "..", "electron", "resources", "export")
|
||||
),
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
service_dir, "..", "..", "..", "..", "electron", "resources", "export"
|
||||
)
|
||||
),
|
||||
os.path.abspath(os.path.join(cwd, "..", "..", "presentation-export")),
|
||||
os.path.abspath(os.path.join(cwd, "..", "presentation-export")),
|
||||
os.path.abspath(os.path.join(service_dir, "..", "..", "..", "presentation-export")),
|
||||
os.path.abspath(os.path.join(service_dir, "..", "..", "..", "..", "presentation-export")),
|
||||
]
|
||||
|
||||
for candidate in candidates:
|
||||
if os.path.isfile(os.path.join(candidate, "index.js")):
|
||||
if os.path.isfile(os.path.join(candidate, "index.cjs")) or os.path.isfile(
|
||||
os.path.join(candidate, "index.js")
|
||||
):
|
||||
return candidate
|
||||
|
||||
return candidates[0]
|
||||
|
||||
@staticmethod
|
||||
def _resolve_entrypoint_path(export_dir: str) -> str:
|
||||
index_cjs = os.path.join(export_dir, "index.cjs")
|
||||
if os.path.isfile(index_cjs):
|
||||
return index_cjs
|
||||
|
||||
index_js = os.path.join(export_dir, "index.js")
|
||||
if os.path.isfile(index_js):
|
||||
shutil.copyfile(index_js, index_cjs)
|
||||
return index_cjs
|
||||
|
||||
return index_cjs
|
||||
|
||||
@staticmethod
|
||||
def _resolve_converter_path(export_dir: str) -> str:
|
||||
py_dir = os.path.join(export_dir, "py")
|
||||
|
|
|
|||
577
package-lock.json
generated
577
package-lock.json
generated
|
|
@ -8,48 +8,555 @@
|
|||
"name": "presenton",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"react-colorful": "^5.6.1"
|
||||
"sharp": "^0.34.5"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-colorful": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
|
||||
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.4"
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
||||
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
||||
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
||||
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
||||
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
||||
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
||||
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
||||
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
||||
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
||||
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
||||
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
||||
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
||||
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-riscv64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
||||
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
||||
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
||||
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
||||
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
||||
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
||||
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.0.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
"semver": "^7.7.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.34.5",
|
||||
"@img/sharp-darwin-x64": "0.34.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.2.4",
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-riscv64": "1.2.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.2.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
|
||||
"@img/sharp-linux-arm": "0.34.5",
|
||||
"@img/sharp-linux-arm64": "0.34.5",
|
||||
"@img/sharp-linux-ppc64": "0.34.5",
|
||||
"@img/sharp-linux-riscv64": "0.34.5",
|
||||
"@img/sharp-linux-s390x": "0.34.5",
|
||||
"@img/sharp-linux-x64": "0.34.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.34.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.34.5",
|
||||
"@img/sharp-wasm32": "0.34.5",
|
||||
"@img/sharp-win32-arm64": "0.34.5",
|
||||
"@img/sharp-win32-ia32": "0.34.5",
|
||||
"@img/sharp-win32-x64": "0.34.5"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
{
|
||||
"name": "presenton",
|
||||
"version": "1.0.0",
|
||||
"presentationExportVersion": "v0.2.0",
|
||||
"type": "module",
|
||||
"description": "Open-source AI presentation generator",
|
||||
"scripts": {
|
||||
"sync:presentation-export": "node scripts/sync-presentation-export.cjs",
|
||||
"sync:presentation-export:force": "node scripts/sync-presentation-export.cjs --force",
|
||||
"check:presentation-export": "node scripts/sync-presentation-export.cjs --check-only"
|
||||
},
|
||||
"dependencies": {
|
||||
"sharp": "^0.34.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"exportVersion": "v0.2.0"
|
||||
}
|
||||
|
|
@ -4,10 +4,10 @@
|
|||
*
|
||||
* Version resolution (first match):
|
||||
* 1. EXPORT_RUNTIME_VERSION env
|
||||
* 2. presentation-export/export-version.json → exportVersion
|
||||
* 2. package.json → presentationExportVersion
|
||||
*
|
||||
* CLI: --force re-download even if valid runtime already exists
|
||||
* --check-only verify index.js + converter exist and exit 0/1
|
||||
* --check-only verify index.cjs + converter exist and exit 0/1
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
|
@ -18,8 +18,9 @@ const { execFileSync } = require("child_process");
|
|||
const repoRoot = path.join(__dirname, "..");
|
||||
const targetRoot = path.join(repoRoot, "presentation-export");
|
||||
const targetPyDir = path.join(targetRoot, "py");
|
||||
const targetIndex = path.join(targetRoot, "index.js");
|
||||
const versionFile = path.join(targetRoot, "export-version.json");
|
||||
const targetIndexJs = path.join(targetRoot, "index.js");
|
||||
const targetIndexCjs = path.join(targetRoot, "index.cjs");
|
||||
const packageJsonFile = path.join(repoRoot, "package.json");
|
||||
const cacheDir = path.join(repoRoot, ".cache", "presentation-export");
|
||||
const exportRepoBase =
|
||||
"https://github.com/presenton/presenton-export/releases/download";
|
||||
|
|
@ -34,15 +35,17 @@ function ensureDir(dirPath) {
|
|||
}
|
||||
|
||||
function readPinnedVersion() {
|
||||
if (!fs.existsSync(versionFile)) {
|
||||
if (!fs.existsSync(packageJsonFile)) {
|
||||
throw new Error(
|
||||
`Missing ${path.relative(repoRoot, versionFile)}. Create it with { "exportVersion": "vX.Y.Z" }.`
|
||||
`Missing ${path.relative(repoRoot, packageJsonFile)}. Add \"presentationExportVersion\": \"vX.Y.Z\".`
|
||||
);
|
||||
}
|
||||
const raw = JSON.parse(fs.readFileSync(versionFile, "utf8"));
|
||||
const v = (raw.exportVersion || "").trim();
|
||||
const raw = JSON.parse(fs.readFileSync(packageJsonFile, "utf8"));
|
||||
const v = (raw.presentationExportVersion || "").trim();
|
||||
if (!v) {
|
||||
throw new Error(`${versionFile} must set "exportVersion" (e.g. "v0.2.0").`);
|
||||
throw new Error(
|
||||
`${path.relative(repoRoot, packageJsonFile)} must set \"presentationExportVersion\" (e.g. \"v0.2.0\").`
|
||||
);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
|
@ -124,10 +127,32 @@ function getConverterCandidates() {
|
|||
];
|
||||
}
|
||||
|
||||
function validateExistingRuntime() {
|
||||
if (!fs.existsSync(targetIndex)) {
|
||||
return { ok: false, reason: `Missing runtime bundle: ${targetIndex}` };
|
||||
function ensureCommonJsEntrypoint() {
|
||||
if (!fs.existsSync(targetIndexJs)) {
|
||||
return { ok: false, reason: `Missing runtime bundle: ${targetIndexJs}` };
|
||||
}
|
||||
|
||||
if (fs.existsSync(targetIndexCjs)) {
|
||||
return { ok: true, entrypointPath: targetIndexCjs };
|
||||
}
|
||||
|
||||
try {
|
||||
fs.copyFileSync(targetIndexJs, targetIndexCjs);
|
||||
return { ok: true, entrypointPath: targetIndexCjs };
|
||||
} catch (err) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: `Failed to create CommonJS entrypoint ${targetIndexCjs}: ${err.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function validateExistingRuntime() {
|
||||
const entrypoint = ensureCommonJsEntrypoint();
|
||||
if (!entrypoint.ok) {
|
||||
return { ok: false, reason: entrypoint.reason };
|
||||
}
|
||||
|
||||
const candidates = getConverterCandidates();
|
||||
const converterPath = candidates.find((c) => fs.existsSync(c));
|
||||
if (!converterPath) {
|
||||
|
|
@ -137,7 +162,7 @@ function validateExistingRuntime() {
|
|||
};
|
||||
}
|
||||
chmodIfPossible(converterPath);
|
||||
return { ok: true, converterPath };
|
||||
return { ok: true, entrypointPath: entrypoint.entrypointPath, converterPath };
|
||||
}
|
||||
|
||||
function downloadFile(url, outputPath, redirects = 5) {
|
||||
|
|
@ -207,10 +232,6 @@ async function downloadAndInstallRuntime() {
|
|||
const tag = await getTargetVersion();
|
||||
const downloadUrl = `${exportRepoBase}/${tag}/${linuxAssetName}`;
|
||||
|
||||
const versionPinBackup = fs.existsSync(versionFile)
|
||||
? fs.readFileSync(versionFile, "utf8")
|
||||
: JSON.stringify({ exportVersion: tag }, null, 2) + "\n";
|
||||
|
||||
ensureDir(cacheDir);
|
||||
const zipPath = path.join(cacheDir, linuxAssetName);
|
||||
const extractDir = path.join(cacheDir, `extract-${Date.now()}`);
|
||||
|
|
@ -226,9 +247,6 @@ async function downloadAndInstallRuntime() {
|
|||
ensureDir(targetRoot);
|
||||
fs.cpSync(sourceRoot, targetRoot, { recursive: true, force: true });
|
||||
|
||||
ensureDir(path.dirname(versionFile));
|
||||
fs.writeFileSync(versionFile, versionPinBackup, "utf8");
|
||||
|
||||
fs.rmSync(extractDir, { recursive: true, force: true });
|
||||
|
||||
return { tag, downloadUrl };
|
||||
|
|
@ -242,14 +260,14 @@ async function main() {
|
|||
throw new Error(existing.reason);
|
||||
}
|
||||
console.log("[presentation-export] OK");
|
||||
console.log(` - ${targetIndex}`);
|
||||
console.log(` - ${existing.entrypointPath}`);
|
||||
console.log(` - ${existing.converterPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing.ok && !forceDownload) {
|
||||
console.log("[presentation-export] Using existing runtime:");
|
||||
console.log(` - ${targetIndex}`);
|
||||
console.log(` - ${existing.entrypointPath}`);
|
||||
console.log(` - ${existing.converterPath}`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -263,7 +281,7 @@ async function main() {
|
|||
console.log("[presentation-export] Synced successfully:");
|
||||
console.log(` - release: ${tag}`);
|
||||
console.log(` - url: ${downloadUrl}`);
|
||||
console.log(` - ${targetIndex}`);
|
||||
console.log(` - ${installed.entrypointPath}`);
|
||||
console.log(` - ${installed.converterPath}`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import os
|
|||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import uuid
|
||||
from typing import Mapping
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
|
@ -29,7 +28,7 @@ class ExportTaskService:
|
|||
self.timeout_seconds = timeout_seconds
|
||||
self.node_binary = os.getenv("LITEPARSE_NODE_BINARY", "node")
|
||||
self.export_dir = self._resolve_export_dir()
|
||||
self.entrypoint_path = os.path.join(self.export_dir, "index.js")
|
||||
self.entrypoint_path = self._resolve_entrypoint_path(self.export_dir)
|
||||
self.converter_path = self._resolve_converter_path(self.export_dir)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -38,31 +37,40 @@ class ExportTaskService:
|
|||
if configured:
|
||||
return configured
|
||||
|
||||
package_root = (os.getenv("EXPORT_PACKAGE_ROOT") or "").strip()
|
||||
if package_root:
|
||||
return package_root
|
||||
|
||||
cwd = os.path.abspath(".")
|
||||
service_dir = os.path.dirname(__file__)
|
||||
candidates = [
|
||||
os.path.abspath(os.path.join(cwd, "..", "..", "resources", "export")),
|
||||
os.path.abspath(os.path.join(cwd, "..", "export")),
|
||||
os.path.abspath(
|
||||
os.path.join(service_dir, "..", "..", "..", "resources", "export")
|
||||
),
|
||||
os.path.abspath(os.path.join(service_dir, "..", "..", "export")),
|
||||
os.path.abspath(
|
||||
os.path.join(cwd, "..", "..", "electron", "resources", "export")
|
||||
),
|
||||
os.path.abspath(
|
||||
os.path.join(
|
||||
service_dir, "..", "..", "..", "..", "electron", "resources", "export"
|
||||
)
|
||||
),
|
||||
os.path.abspath(os.path.join(cwd, "..", "..", "presentation-export")),
|
||||
os.path.abspath(os.path.join(cwd, "..", "presentation-export")),
|
||||
os.path.abspath(os.path.join(service_dir, "..", "..", "..", "presentation-export")),
|
||||
os.path.abspath(os.path.join(service_dir, "..", "..", "..", "..", "presentation-export")),
|
||||
]
|
||||
|
||||
for candidate in candidates:
|
||||
if os.path.isfile(os.path.join(candidate, "index.js")):
|
||||
if os.path.isfile(os.path.join(candidate, "index.cjs")) or os.path.isfile(
|
||||
os.path.join(candidate, "index.js")
|
||||
):
|
||||
return candidate
|
||||
|
||||
return candidates[0]
|
||||
|
||||
@staticmethod
|
||||
def _resolve_entrypoint_path(export_dir: str) -> str:
|
||||
index_cjs = os.path.join(export_dir, "index.cjs")
|
||||
if os.path.isfile(index_cjs):
|
||||
return index_cjs
|
||||
|
||||
index_js = os.path.join(export_dir, "index.js")
|
||||
if os.path.isfile(index_js):
|
||||
shutil.copyfile(index_js, index_cjs)
|
||||
return index_cjs
|
||||
|
||||
return index_cjs
|
||||
|
||||
@staticmethod
|
||||
def _resolve_converter_path(export_dir: str) -> str:
|
||||
py_dir = os.path.join(export_dir, "py")
|
||||
|
|
|
|||
|
|
@ -1,106 +1,10 @@
|
|||
import path from "path";
|
||||
import fs from "fs";
|
||||
import puppeteer from "puppeteer";
|
||||
import { NextResponse, NextRequest } from "next/server";
|
||||
|
||||
import { sanitizeFilename } from "@/app/(presentation-generator)/utils/others";
|
||||
import {
|
||||
bundledExportPackageAvailable,
|
||||
runBundledPdfExport,
|
||||
} from "@/lib/run-bundled-pdf-export";
|
||||
|
||||
async function exportPdfWithInlinePuppeteer(
|
||||
id: string,
|
||||
title: string | undefined
|
||||
): Promise<{ path: string }> {
|
||||
let nextjsUrl = process.env.NEXT_PUBLIC_URL;
|
||||
if (!nextjsUrl) {
|
||||
nextjsUrl = "http://127.0.0.1";
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
||||
headless: true,
|
||||
args: [
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
"--disable-gpu",
|
||||
"--disable-web-security",
|
||||
"--disable-background-timer-throttling",
|
||||
"--disable-backgrounding-occluded-windows",
|
||||
"--disable-renderer-backgrounding",
|
||||
"--disable-features=TranslateUI",
|
||||
"--disable-ipc-flooding-protection",
|
||||
],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1280, height: 720 });
|
||||
page.setDefaultNavigationTimeout(300000);
|
||||
page.setDefaultTimeout(300000);
|
||||
|
||||
await page.goto(`${nextjsUrl}/pdf-maker?id=${id}`, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: 300000,
|
||||
});
|
||||
|
||||
await page.waitForFunction('() => document.readyState === "complete"');
|
||||
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
`
|
||||
() => {
|
||||
const allElements = document.querySelectorAll('*');
|
||||
let loadedElements = 0;
|
||||
let totalElements = allElements.length;
|
||||
|
||||
for (let el of allElements) {
|
||||
const style = window.getComputedStyle(el);
|
||||
const isVisible = style.display !== 'none' &&
|
||||
style.visibility !== 'hidden' &&
|
||||
style.opacity !== '0';
|
||||
|
||||
if (isVisible && el.offsetWidth > 0 && el.offsetHeight > 0) {
|
||||
loadedElements++;
|
||||
}
|
||||
}
|
||||
|
||||
return (loadedElements / totalElements) >= 0.99;
|
||||
}
|
||||
`,
|
||||
{ timeout: 300000 }
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} catch (error) {
|
||||
console.log("Warning: Some content may not have loaded completely:", error);
|
||||
}
|
||||
|
||||
const pdfBuffer = await page.pdf({
|
||||
width: "1280px",
|
||||
height: "720px",
|
||||
printBackground: true,
|
||||
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
});
|
||||
|
||||
await browser.close();
|
||||
|
||||
const sanitizedTitle = sanitizeFilename(title ?? "presentation");
|
||||
const appDataDirectory = process.env.APP_DATA_DIRECTORY!;
|
||||
if (!appDataDirectory) {
|
||||
throw new Error("App data directory not found");
|
||||
}
|
||||
const destinationPath = path.join(
|
||||
appDataDirectory,
|
||||
"exports",
|
||||
`${sanitizedTitle}.pdf`
|
||||
);
|
||||
await fs.promises.mkdir(path.dirname(destinationPath), { recursive: true });
|
||||
await fs.promises.writeFile(destinationPath, pdfBuffer);
|
||||
|
||||
return { path: destinationPath };
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const { id, title } = await req.json();
|
||||
if (!id) {
|
||||
|
|
@ -111,18 +15,16 @@ export async function POST(req: NextRequest) {
|
|||
}
|
||||
|
||||
try {
|
||||
if (await bundledExportPackageAvailable()) {
|
||||
const { path: outPath } = await runBundledPdfExport({
|
||||
presentationId: id,
|
||||
title,
|
||||
});
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
path: outPath,
|
||||
});
|
||||
if (!(await bundledExportPackageAvailable())) {
|
||||
throw new Error(
|
||||
"presentation-export runtime is not available. Run scripts/sync-presentation-export.cjs to install it."
|
||||
);
|
||||
}
|
||||
|
||||
const { path: outPath } = await exportPdfWithInlinePuppeteer(id, title);
|
||||
const { path: outPath } = await runBundledPdfExport({
|
||||
presentationId: id,
|
||||
title,
|
||||
});
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
path: outPath,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,20 @@ export function getPresentonAppRoot(): string {
|
|||
);
|
||||
}
|
||||
|
||||
async function resolveExportEntrypoint(exportRoot: string): Promise<string> {
|
||||
const indexCjs = path.join(exportRoot, "index.cjs");
|
||||
const indexJs = path.join(exportRoot, "index.js");
|
||||
|
||||
try {
|
||||
await fs.access(indexCjs);
|
||||
return indexCjs;
|
||||
} catch {
|
||||
await fs.access(indexJs);
|
||||
await fs.copyFile(indexJs, indexCjs);
|
||||
return indexCjs;
|
||||
}
|
||||
}
|
||||
|
||||
function bundledConverterPath(exportRoot: string): string {
|
||||
const fromEnv = process.env.BUILT_PYTHON_MODULE_PATH?.trim();
|
||||
if (fromEnv) {
|
||||
|
|
@ -35,7 +49,7 @@ function bundledConverterPath(exportRoot: string): string {
|
|||
export async function bundledExportPackageAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const root = getExportPackageRoot();
|
||||
await fs.access(path.join(root, "index.js"));
|
||||
await resolveExportEntrypoint(root);
|
||||
await fs.access(bundledConverterPath(root));
|
||||
return true;
|
||||
} catch {
|
||||
|
|
@ -45,6 +59,53 @@ export async function bundledExportPackageAvailable(): Promise<boolean> {
|
|||
|
||||
export type BundledPdfExportResult = { path: string };
|
||||
|
||||
function normalizeExportOutputPath(params: {
|
||||
pathValue?: string;
|
||||
urlValue?: string;
|
||||
}): string {
|
||||
const { pathValue, urlValue } = params;
|
||||
const appData = process.env.APP_DATA_DIRECTORY?.trim();
|
||||
|
||||
const resolveAppDataRelative = (value: string): string => {
|
||||
if (!appData) {
|
||||
throw new Error("APP_DATA_DIRECTORY is required for relative export paths.");
|
||||
}
|
||||
|
||||
const normalized = value.startsWith("/") ? value.slice(1) : value;
|
||||
if (!normalized.startsWith("app_data/")) {
|
||||
return path.join(appData, normalized);
|
||||
}
|
||||
return path.join(appData, normalized.slice("app_data/".length));
|
||||
};
|
||||
|
||||
if (pathValue && typeof pathValue === "string") {
|
||||
if (path.isAbsolute(pathValue)) {
|
||||
return pathValue;
|
||||
}
|
||||
return resolveAppDataRelative(pathValue);
|
||||
}
|
||||
|
||||
if (urlValue && typeof urlValue === "string") {
|
||||
if (urlValue.startsWith("file://")) {
|
||||
const parsed = new URL(urlValue);
|
||||
const fsPath = decodeURIComponent(parsed.pathname || "");
|
||||
if (fsPath.startsWith("/app_data/")) {
|
||||
return resolveAppDataRelative(fsPath);
|
||||
}
|
||||
if (path.isAbsolute(fsPath)) {
|
||||
return fsPath;
|
||||
}
|
||||
return resolveAppDataRelative(fsPath);
|
||||
}
|
||||
|
||||
if (urlValue.startsWith("/app_data/")) {
|
||||
return resolveAppDataRelative(urlValue);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Export finished but response did not include a valid output path.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the bundled export entrypoint (`presentation-export/index.js`) with
|
||||
* `BUILT_PYTHON_MODULE_PATH` pointing at the PyInstaller converter binary.
|
||||
|
|
@ -55,11 +116,10 @@ export async function runBundledPdfExport(params: {
|
|||
}): Promise<BundledPdfExportResult> {
|
||||
const { presentationId, title } = params;
|
||||
const exportRoot = getExportPackageRoot();
|
||||
const indexJs = path.join(exportRoot, "index.js");
|
||||
const entrypoint = await resolveExportEntrypoint(exportRoot);
|
||||
const converter = bundledConverterPath(exportRoot);
|
||||
const appRoot = getPresentonAppRoot();
|
||||
|
||||
await fs.access(indexJs);
|
||||
await fs.access(converter);
|
||||
|
||||
const nextjsUrl =
|
||||
|
|
@ -90,7 +150,7 @@ export async function runBundledPdfExport(params: {
|
|||
const responsePath = exportTaskPath.replace(/\.json$/i, ".response.json");
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const child = spawn(process.execPath, [indexJs, exportTaskPath], {
|
||||
const child = spawn(process.execPath, [entrypoint, exportTaskPath], {
|
||||
cwd: appRoot,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: {
|
||||
|
|
@ -120,20 +180,12 @@ export async function runBundledPdfExport(params: {
|
|||
});
|
||||
|
||||
const responseRaw = await fs.readFile(responsePath, "utf8");
|
||||
const responseData = JSON.parse(responseRaw) as { path?: string };
|
||||
const responseData = JSON.parse(responseRaw) as { path?: string; url?: string };
|
||||
|
||||
if (!responseData?.path || typeof responseData.path !== "string") {
|
||||
throw new Error("Export finished but response did not include a path.");
|
||||
}
|
||||
|
||||
let outPath = responseData.path;
|
||||
if (!path.isAbsolute(outPath)) {
|
||||
const appData = process.env.APP_DATA_DIRECTORY?.trim();
|
||||
if (!appData) {
|
||||
throw new Error("APP_DATA_DIRECTORY is required for relative export paths.");
|
||||
}
|
||||
outPath = path.join(appData, outPath);
|
||||
}
|
||||
const outPath = normalizeExportOutputPath({
|
||||
pathValue: responseData?.path,
|
||||
urlValue: responseData?.url,
|
||||
});
|
||||
|
||||
return { path: outPath };
|
||||
}
|
||||
|
|
|
|||
49
start.js
49
start.js
|
|
@ -10,6 +10,7 @@ const __dirname = dirname(__filename);
|
|||
|
||||
const fastapiDir = join(__dirname, "servers/fastapi");
|
||||
const nextjsDir = join(__dirname, "servers/nextjs");
|
||||
const exportSyncScript = join(__dirname, "scripts/sync-presentation-export.cjs");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const hasDevArg = args.includes("--dev") || args.includes("-d");
|
||||
|
|
@ -55,6 +56,52 @@ const setupNodeModules = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const runNodeScript = (scriptPath, scriptArgs) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const scriptProcess = spawn(process.execPath, [scriptPath, ...scriptArgs], {
|
||||
cwd: __dirname,
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
scriptProcess.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
scriptProcess.on("exit", (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Script failed with exit code: ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const ensurePresentationExportRuntime = async () => {
|
||||
if (process.env.ENSURE_PRESENTATION_EXPORT_RUNTIME === "false") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existsSync(exportSyncScript)) {
|
||||
console.warn("presentation-export sync script not found; skipping runtime check");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await runNodeScript(exportSyncScript, ["--check-only"]);
|
||||
} catch (err) {
|
||||
if (!isDev) {
|
||||
throw new Error(
|
||||
"presentation-export runtime is missing in this container image. Rebuild the image so the runtime package is installed."
|
||||
);
|
||||
}
|
||||
|
||||
console.warn("presentation-export runtime missing in dev mount. Syncing runtime package...");
|
||||
await runNodeScript(exportSyncScript, ["--force"]);
|
||||
}
|
||||
};
|
||||
|
||||
process.env.USER_CONFIG_PATH = userConfigPath;
|
||||
|
||||
//? UserConfig is only setup if API Keys can be changed
|
||||
|
|
@ -223,6 +270,8 @@ const startNginx = () => {
|
|||
};
|
||||
|
||||
const main = async () => {
|
||||
await ensurePresentationExportRuntime();
|
||||
|
||||
if (isDev) {
|
||||
await setupNodeModules();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue