mirror of
https://github.com/penpot/penpot.git
synced 2026-09-23 20:36:15 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
117c8db0bb
15
.github/workflows/plugins-deploy-api-doc.yml
vendored
15
.github/workflows/plugins-deploy-api-doc.yml
vendored
@ -43,17 +43,12 @@ jobs:
|
||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Setup pnpm + Node.js
|
||||
uses: pnpm/setup@v2
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Enable PNPM
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
working-directory: plugins
|
||||
runtime: node@24.21.0
|
||||
install: false
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
|
||||
15
.github/workflows/plugins-deploy-package.yml
vendored
15
.github/workflows/plugins-deploy-package.yml
vendored
@ -43,17 +43,12 @@ jobs:
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Setup pnpm + Node.js
|
||||
uses: pnpm/setup@v2
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Enable PNPM
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
working-directory: plugins
|
||||
runtime: node@24.21.0
|
||||
install: false
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
|
||||
15
.github/workflows/plugins-deploy-styles-doc.yml
vendored
15
.github/workflows/plugins-deploy-styles-doc.yml
vendored
@ -41,17 +41,12 @@ jobs:
|
||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Setup pnpm + Node.js
|
||||
uses: pnpm/setup@v2
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Enable PNPM
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
working-directory: plugins
|
||||
runtime: node@24.21.0
|
||||
install: false
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
|
||||
2
.github/workflows/tests-common.yml
vendored
2
.github/workflows/tests-common.yml
vendored
@ -44,8 +44,6 @@ jobs:
|
||||
- name: Lint
|
||||
working-directory: ./common
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm run check-fmt:clj
|
||||
pnpm run check-fmt:js
|
||||
|
||||
69
.github/workflows/tests-composable-suite.yml
vendored
69
.github/workflows/tests-composable-suite.yml
vendored
@ -1,69 +0,0 @@
|
||||
name: "CI: Composable Test Suite"
|
||||
|
||||
# Runs the composable component test suite (it exercises component semantics
|
||||
# through the real Plugin API against the full frontend, so it needs the
|
||||
# frontend bundle + the plugin runtime, but no backend): the driver serves the
|
||||
# prebuilt frontend bundle and intercepts every backend RPC with Playwright
|
||||
# fixtures. See plugins/apps/composable-test-suite/README.md ("Running in CI").
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'frontend/**'
|
||||
- 'common/**'
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- staging
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'frontend/**'
|
||||
- 'common/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
composable-test-suite:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
name: "Run composable test suite (mocked backend)"
|
||||
runs-on: penpot-extended-runner
|
||||
container:
|
||||
image: penpotapp/devenv:latest
|
||||
volumes:
|
||||
- /var/cache/github-runner/m2:/root/.m2
|
||||
- /var/cache/github-runner/gitlib:/root/.gitlibs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# The driver serves the prebuilt bundle from frontend/resources/public.
|
||||
- name: Build frontend bundle
|
||||
working-directory: ./frontend
|
||||
run: ./scripts/build
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter composable-test-suite exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run composable test suite (mocked)
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter composable-test-suite run test:ci
|
||||
@ -1,4 +1,26 @@
|
||||
name: "CI: Integration"
|
||||
name: "CI: E2E"
|
||||
|
||||
# Single entry point for every suite that drives a real frontend bundle.
|
||||
# The bundle is built ONCE by `build-bundle` and restored by every consumer,
|
||||
# so adding a suite no longer adds another `frontend/scripts/build` run.
|
||||
#
|
||||
# Suites and what they need from the bundle:
|
||||
#
|
||||
# - Integration Tests: Playwright specs, backend faked with frontend mocks.
|
||||
# - Composable test suite: Plugin runtime, backend faked with Playwright RPC
|
||||
# fixtures. See plugins/apps/composable-test-suite/README.md ("Running in CI").
|
||||
# - Plugin API Test Suite (mocked): Plugin runtime, backend faked with
|
||||
# Playwright RPC fixtures and MOCK_BACKEND=1.
|
||||
# See plugins/apps/plugin-api-test-suite/README.md.
|
||||
#
|
||||
# Hand-off: `build-bundle` restores `actions/cache` keyed by the checked-out
|
||||
# SHA, builds only on a miss, and saves it; every consumer restores the same
|
||||
# key after `needs: build-bundle` completes. A re-run of the same SHA reuses
|
||||
# the cached bundle instead of rebuilding it.
|
||||
#
|
||||
# NOTE: the job `name:` values are the GitHub check contexts, so they are kept
|
||||
# stable on purpose (branch protection may require them by name). Only the
|
||||
# workflow file/name changed.
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@ -30,12 +52,18 @@ on:
|
||||
required: true
|
||||
default: '2'
|
||||
|
||||
base_url:
|
||||
description: 'Penpot base URL for the (disabled) live Plugin API suite'
|
||||
required: false
|
||||
default: 'https://localhost:3449'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
- 'common/**'
|
||||
- 'render-wasm/**'
|
||||
- '.github/workflows/tests-integration.yml'
|
||||
- 'plugins/**'
|
||||
- '.github/workflows/tests-e2e.yml'
|
||||
|
||||
types:
|
||||
- opened
|
||||
@ -51,18 +79,21 @@ on:
|
||||
- 'frontend/**'
|
||||
- 'common/**'
|
||||
- 'render-wasm/**'
|
||||
- '.github/workflows/tests-integration.yml'
|
||||
- 'plugins/**'
|
||||
- '.github/workflows/tests-e2e.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.gh_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-integration:
|
||||
# ── 1. Build the frontend bundle once ──────────────────────────────────
|
||||
build-bundle:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
name: "Build Integration Bundle"
|
||||
runs-on: penpot-extended-runner
|
||||
timeout-minutes: 30
|
||||
|
||||
container:
|
||||
image: penpotapp/devenv:latest
|
||||
volumes:
|
||||
@ -85,26 +116,36 @@ jobs:
|
||||
- name: Extract cache key
|
||||
id: vars
|
||||
run: |
|
||||
echo "bundle_key=integration-bundle-$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "bundle_key=frontend-bundle-$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore Bundle Cache
|
||||
id: restore
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
key: ${{ steps.vars.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
|
||||
- name: Build Bundle
|
||||
if: steps.restore.outputs.cache-hit != 'true'
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
./scripts/build
|
||||
|
||||
- name: Store Bundle Cache
|
||||
uses: actions/cache@v5
|
||||
if: steps.restore.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
key: ${{ steps.vars.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
|
||||
# ── 2. Consumers: restore the bundle, never rebuild it ─────────────────
|
||||
test-integration:
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
name: "Integration Tests (${{ matrix.shard }})"
|
||||
runs-on: penpot-extended-runner
|
||||
timeout-minutes: 40
|
||||
|
||||
needs: build-integration
|
||||
needs: build-bundle
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -129,14 +170,13 @@ jobs:
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
key: ${{ needs.build-integration.outputs.bundle_key }}
|
||||
key: ${{ needs.build-bundle.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install --frozen-lockfile;
|
||||
|
||||
# No-op once the shared volume is warm; keeps the first run working.
|
||||
@ -177,6 +217,89 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
retention-days: 3
|
||||
|
||||
composable-test-suite:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && !github.event.pull_request.draft }}
|
||||
name: "Run composable test suite (mocked backend)"
|
||||
runs-on: penpot-extended-runner
|
||||
timeout-minutes: 30
|
||||
|
||||
needs: build-bundle
|
||||
|
||||
container:
|
||||
image: penpotapp/devenv:latest
|
||||
volumes:
|
||||
- /var/cache/github-runner/m2:/root/.m2
|
||||
- /var/cache/github-runner/gitlib:/root/.gitlibs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# The driver serves the prebuilt bundle from frontend/resources/public.
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
key: ${{ needs.build-bundle.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
run: |
|
||||
pnpm install;
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter composable-test-suite exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run composable test suite (mocked)
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter composable-test-suite run test:ci
|
||||
|
||||
api-test-suite-mocked:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && !github.event.pull_request.draft }}
|
||||
name: "Run Plugin API Test Suite (mocked)"
|
||||
runs-on: penpot-extended-runner
|
||||
timeout-minutes: 30
|
||||
|
||||
needs: build-bundle
|
||||
|
||||
container:
|
||||
image: penpotapp/devenv:latest
|
||||
volumes:
|
||||
- /var/cache/github-runner/m2:/root/.m2
|
||||
- /var/cache/github-runner/gitlib:/root/.gitlibs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Mocked mode serves the prebuilt bundle from frontend/resources/public.
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
key: ${{ needs.build-bundle.outputs.bundle_key }}
|
||||
path: frontend/resources/public
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
run: |
|
||||
pnpm install;
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium
|
||||
|
||||
- name: Generate API surface
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter plugin-api-test-suite run gen:api
|
||||
|
||||
- name: Run API test suite (mocked)
|
||||
working-directory: ./plugins
|
||||
env:
|
||||
MOCK_BACKEND: "1"
|
||||
run: pnpm --filter plugin-api-test-suite run test:ci
|
||||
|
||||
# ── 3. Merge the per-shard integration reports ─────────────────────────
|
||||
merge-reports:
|
||||
if: ${{ !cancelled() && !github.event.pull_request.draft && needs.test-integration.result != 'skipped' }}
|
||||
name: "Merge Integration Reports"
|
||||
@ -200,8 +323,6 @@ jobs:
|
||||
- name: Install deps
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install --frozen-lockfile;
|
||||
|
||||
- name: Download blob reports
|
||||
@ -245,8 +366,53 @@ jobs:
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v7
|
||||
if: always()
|
||||
with:
|
||||
name: integration-html-report
|
||||
path: frontend/playwright-report/
|
||||
overwrite: true
|
||||
retention-days: 7
|
||||
|
||||
# ── 4. Live Plugin API suite (disabled) ────────────────────────────────
|
||||
#
|
||||
# True end-to-end against a LIVE instance: point PENPOT_BASE_URL at a
|
||||
# reachable instance and provide login credentials via repo secrets. Manual
|
||||
# because the CI runner has no Docker to stand up a full stack.
|
||||
#
|
||||
# api-test-suite-live:
|
||||
# if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
# name: Run Plugin API Test Suite (live)
|
||||
# runs-on: penpot-extended-runner
|
||||
# container:
|
||||
# image: penpotapp/devenv:latest
|
||||
#
|
||||
# env:
|
||||
# PENPOT_BASE_URL: ${{ inputs.base_url }}
|
||||
# E2E_LOGIN_EMAIL: ${{ secrets.E2E_LOGIN_EMAIL }}
|
||||
# E2E_LOGIN_PASSWORD: ${{ secrets.E2E_LOGIN_PASSWORD }}
|
||||
#
|
||||
# steps:
|
||||
# - uses: actions/checkout@v6
|
||||
#
|
||||
# - name: Setup Node
|
||||
# uses: actions/setup-node@v6
|
||||
# with:
|
||||
# node-version-file: .nvmrc
|
||||
#
|
||||
# - name: Install deps
|
||||
# working-directory: ./plugins
|
||||
# run: |
|
||||
# pnpm install;
|
||||
#
|
||||
# - name: Install Playwright Chromium
|
||||
# working-directory: ./plugins
|
||||
# run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium
|
||||
#
|
||||
# - name: Generate API surface
|
||||
# working-directory: ./plugins
|
||||
# run: pnpm --filter plugin-api-test-suite run gen:api
|
||||
#
|
||||
# # Note: requires a running Penpot instance reachable at PENPOT_BASE_URL.
|
||||
# - name: Run API test suite
|
||||
# working-directory: ./plugins
|
||||
# run: pnpm --filter plugin-api-test-suite run test:ci
|
||||
2
.github/workflows/tests-exporter.yml
vendored
2
.github/workflows/tests-exporter.yml
vendored
@ -46,8 +46,6 @@ jobs:
|
||||
- name: Lint
|
||||
working-directory: ./exporter
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm run check-fmt:clj
|
||||
pnpm run lint:clj
|
||||
|
||||
2
.github/workflows/tests-frontend.yml
vendored
2
.github/workflows/tests-frontend.yml
vendored
@ -48,8 +48,6 @@ jobs:
|
||||
- name: Lint
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm run check-fmt:js
|
||||
pnpm run check-fmt:clj
|
||||
|
||||
2
.github/workflows/tests-library.yml
vendored
2
.github/workflows/tests-library.yml
vendored
@ -46,8 +46,6 @@ jobs:
|
||||
- name: Lint
|
||||
working-directory: ./library
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm run check-fmt
|
||||
pnpm run lint
|
||||
|
||||
133
.github/workflows/tests-plugin-api-suite.yml
vendored
133
.github/workflows/tests-plugin-api-suite.yml
vendored
@ -1,133 +0,0 @@
|
||||
name: "CI: Plugin API Test Suite"
|
||||
|
||||
# Runs the Plugin API Test Suite (it exercises the real Penpot Plugin API, so it
|
||||
# needs a running frontend + the plugin runtime). Two jobs:
|
||||
#
|
||||
# - api-test-suite-mocked (pull_request / push): the per-PR gate. Serves the
|
||||
# prebuilt frontend bundle and intercepts every backend RPC with Playwright
|
||||
# (MOCK_BACKEND=1). No backend / no login. Validates the frontend Plugin API
|
||||
# binding + in-memory store; backend-result-dependent tests are skipped via the
|
||||
# `skipIfMocked` tag. See plugins/apps/plugin-api-test-suite/README.md.
|
||||
#
|
||||
# - api-test-suite-live (workflow_dispatch): true end-to-end against a LIVE
|
||||
# instance. Point PENPOT_BASE_URL at a reachable instance and provide login
|
||||
# credentials via repo secrets. Manual because the CI runner has no Docker to
|
||||
# stand up a full stack.
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
base_url:
|
||||
description: "Penpot base URL (e.g. https://localhost:3449)"
|
||||
required: false
|
||||
default: "https://localhost:3449"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'frontend/**'
|
||||
- 'common/**'
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- staging
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'frontend/src/app/plugins/**'
|
||||
- 'common/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
api-test-suite-mocked:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && !github.event.pull_request.draft }}
|
||||
name: "Run Plugin API Test Suite (mocked)"
|
||||
runs-on: penpot-extended-runner
|
||||
container:
|
||||
image: penpotapp/devenv:latest
|
||||
volumes:
|
||||
- /var/cache/github-runner/m2:/root/.m2
|
||||
- /var/cache/github-runner/gitlib:/root/.gitlibs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Mocked mode serves the prebuilt bundle from frontend/resources/public.
|
||||
- name: Build frontend bundle
|
||||
working-directory: ./frontend
|
||||
run: ./scripts/build
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium
|
||||
|
||||
- name: Generate API surface
|
||||
working-directory: ./plugins
|
||||
run: pnpm --filter plugin-api-test-suite run gen:api
|
||||
|
||||
- name: Run API test suite (mocked)
|
||||
working-directory: ./plugins
|
||||
env:
|
||||
MOCK_BACKEND: "1"
|
||||
run: pnpm --filter plugin-api-test-suite run test:ci
|
||||
|
||||
## The following job will launch the whole suite of tests but we need
|
||||
## to have a full environment in the CI for this to work.
|
||||
|
||||
# api-test-suite-live:
|
||||
# if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
# name: Run Plugin API Test Suite (live)
|
||||
# runs-on: penpot-extended-runner
|
||||
# container:
|
||||
# image: penpotapp/devenv:latest
|
||||
#
|
||||
# env:
|
||||
# PENPOT_BASE_URL: ${{ github.event.inputs.base_url }}
|
||||
# E2E_LOGIN_EMAIL: ${{ secrets.E2E_LOGIN_EMAIL }}
|
||||
# E2E_LOGIN_PASSWORD: ${{ secrets.E2E_LOGIN_PASSWORD }}
|
||||
#
|
||||
# steps:
|
||||
# - uses: actions/checkout@v6
|
||||
#
|
||||
# - name: Setup Node
|
||||
# uses: actions/setup-node@v6
|
||||
# with:
|
||||
# node-version-file: .nvmrc
|
||||
#
|
||||
# - name: Install deps
|
||||
# working-directory: ./plugins
|
||||
# run: |
|
||||
# corepack enable;
|
||||
# corepack install;
|
||||
# pnpm install;
|
||||
#
|
||||
# - name: Install Playwright Chromium
|
||||
# working-directory: ./plugins
|
||||
# run: pnpm --filter plugin-api-test-suite exec playwright install --with-deps chromium
|
||||
#
|
||||
# - name: Generate API surface
|
||||
# working-directory: ./plugins
|
||||
# run: pnpm --filter plugin-api-test-suite run gen:api
|
||||
#
|
||||
# # Note: requires a running Penpot instance reachable at PENPOT_BASE_URL.
|
||||
# - name: Run API test suite
|
||||
# working-directory: ./plugins
|
||||
# run: pnpm --filter plugin-api-test-suite run test:ci
|
||||
2
.github/workflows/tests-plugins.yml
vendored
2
.github/workflows/tests-plugins.yml
vendored
@ -44,8 +44,6 @@ jobs:
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install -r;
|
||||
|
||||
- name: Run Lint
|
||||
|
||||
@ -1,7 +1,113 @@
|
||||
// Penpot opencode plugin: custom tools for Penpot development.
|
||||
//
|
||||
// Dual V1 + V2 implementation from a single file:
|
||||
// - OpenCode V1 (>= 1.18.29) calls the default export's `server()` and uses
|
||||
// the returned `tool` map (built with the `tool()` helper from
|
||||
// `@opencode-ai/plugin`).
|
||||
// - OpenCode V2 reads the default export's `id` and `setup()` and ignores
|
||||
// `server()`. Tools are registered via `ctx.tool.transform()` with JSON
|
||||
// Schema inputs, and `execute` returns `{ content }`.
|
||||
// See https://opencode.ai/v2/docs/build/plugins/migrate-v1
|
||||
//
|
||||
// NOTE: the V2 side intentionally does NOT
|
||||
// `import { Plugin } from "@opencode/plugin"`. At runtime `Plugin.define` is
|
||||
// the identity function, so a plain `{ id, setup }` object is equivalent, and
|
||||
// skipping the import keeps this plugin dependency-free
|
||||
// (`.opencode/package.json` is gitignored, so a new dependency declared there
|
||||
// would not travel with this file).
|
||||
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import path from "path"
|
||||
import { spawn } from "child_process"
|
||||
|
||||
function runCommand(command, args, options = {}) {
|
||||
const {
|
||||
cwd,
|
||||
env,
|
||||
stdin,
|
||||
closeStdin = false,
|
||||
successMessage = "Command executed successfully",
|
||||
} = options
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let stdout = ""
|
||||
let stderr = ""
|
||||
|
||||
const proc = spawn(command, args, { cwd, env })
|
||||
|
||||
proc.stdout.on("data", (data) => {
|
||||
stdout += data.toString()
|
||||
})
|
||||
|
||||
proc.stderr.on("data", (data) => {
|
||||
stderr += data.toString()
|
||||
})
|
||||
|
||||
proc.on("error", (error) => {
|
||||
resolve(`Error: ${error.message}`)
|
||||
})
|
||||
|
||||
proc.on("close", (exitCode) => {
|
||||
const output =
|
||||
exitCode === 0
|
||||
? stdout.trim() || successMessage
|
||||
: `Error (exit ${exitCode}): ${
|
||||
(stderr || stdout).trim() || "No error output"
|
||||
}`
|
||||
resolve(output)
|
||||
})
|
||||
|
||||
// Close stdin so the child cannot wait on it indefinitely. `psql -c`
|
||||
// never reads stdin, so only the paren-repair pipe mode needs this, but
|
||||
// closing it unconditionally is harmless there.
|
||||
if (stdin !== undefined) {
|
||||
proc.stdin.end(stdin)
|
||||
} else if (closeStdin) {
|
||||
proc.stdin.end()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function executePsql(sql, useTestDb, cwd) {
|
||||
const host = process.env.PENPOT_DB_HOST || "postgres"
|
||||
const user = process.env.PENPOT_DB_USER || "penpot"
|
||||
const db = useTestDb
|
||||
? "penpot_test"
|
||||
: process.env.PENPOT_DB_NAME || "penpot"
|
||||
const password = process.env.PENPOT_DB_PASSWORD || "penpot"
|
||||
|
||||
const psqlArgs = ["-h", host, "-U", user, "-d", db, "-c", sql]
|
||||
|
||||
return runCommand("psql", psqlArgs, {
|
||||
cwd,
|
||||
env: { ...process.env, PGPASSWORD: password },
|
||||
successMessage: "Query executed successfully",
|
||||
})
|
||||
}
|
||||
|
||||
function executeParenRepair({ files, code }, directory) {
|
||||
const script = path.join(directory, "scripts/paren-repair")
|
||||
|
||||
const fileList = files
|
||||
? files
|
||||
.split(",")
|
||||
.map((file) => file.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
|
||||
const childArgs =
|
||||
fileList.length > 0 ? [script, ...fileList] : [script]
|
||||
|
||||
return runCommand("bb", childArgs, {
|
||||
cwd: directory,
|
||||
stdin: code,
|
||||
closeStdin: true,
|
||||
successMessage: "No changes needed",
|
||||
})
|
||||
}
|
||||
|
||||
// --- V1 tool definitions (OpenCode V1 calls `server()` below) ---
|
||||
|
||||
const penpotPsqlTool = tool({
|
||||
description:
|
||||
"Execute a SQL command against the Penpot database. Uses the defaults from scripts/psql.",
|
||||
@ -18,46 +124,7 @@ const penpotPsqlTool = tool({
|
||||
},
|
||||
|
||||
async execute(args, context) {
|
||||
const host = process.env.PENPOT_DB_HOST || "postgres"
|
||||
const user = process.env.PENPOT_DB_USER || "penpot"
|
||||
const db = args.test
|
||||
? "penpot_test"
|
||||
: process.env.PENPOT_DB_NAME || "penpot"
|
||||
const password = process.env.PENPOT_DB_PASSWORD || "penpot"
|
||||
|
||||
const psqlArgs = ["-h", host, "-U", user, "-d", db, "-c", args.sql]
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let stdout = ""
|
||||
let stderr = ""
|
||||
|
||||
const proc = spawn("psql", psqlArgs, {
|
||||
cwd: context.worktree,
|
||||
env: { ...process.env, PGPASSWORD: password },
|
||||
})
|
||||
|
||||
proc.stdout.on("data", (data) => {
|
||||
stdout += data.toString()
|
||||
})
|
||||
|
||||
proc.stderr.on("data", (data) => {
|
||||
stderr += data.toString()
|
||||
})
|
||||
|
||||
proc.on("error", (error) => {
|
||||
resolve(`Error: ${error.message}`)
|
||||
})
|
||||
|
||||
proc.on("close", (exitCode) => {
|
||||
const output =
|
||||
exitCode === 0
|
||||
? stdout.trim() || "Query executed successfully"
|
||||
: `Error (exit ${exitCode}): ${
|
||||
(stderr || stdout).trim() || "No error output"
|
||||
}`
|
||||
resolve(output)
|
||||
})
|
||||
})
|
||||
return executePsql(args.sql, args.test === true, context.worktree)
|
||||
},
|
||||
})
|
||||
|
||||
@ -82,69 +149,11 @@ const parenRepairTool = tool({
|
||||
},
|
||||
|
||||
async execute(args, context) {
|
||||
const script = path.join(context.worktree, "scripts/paren-repair")
|
||||
|
||||
const files = args.files
|
||||
? args.files
|
||||
.split(",")
|
||||
.map((file) => file.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
|
||||
const paramInfo =
|
||||
files.length > 0
|
||||
? `files=[${files.join(", ")}]`
|
||||
: args.code !== undefined
|
||||
? `code=(${args.code.length} chars)`
|
||||
: "none"
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const childArgs =
|
||||
files.length > 0
|
||||
? [script, ...files]
|
||||
: [script]
|
||||
|
||||
const proc = spawn("bb", childArgs, {
|
||||
cwd: context.worktree,
|
||||
})
|
||||
|
||||
let stdout = ""
|
||||
let stderr = ""
|
||||
|
||||
proc.stdout.on("data", (data) => {
|
||||
stdout += data.toString()
|
||||
})
|
||||
|
||||
proc.stderr.on("data", (data) => {
|
||||
stderr += data.toString()
|
||||
})
|
||||
|
||||
proc.on("error", (error) => {
|
||||
resolve(`Error: ${error.message}`)
|
||||
})
|
||||
|
||||
proc.on("close", (exitCode) => {
|
||||
const output =
|
||||
exitCode === 0
|
||||
? stdout.trim() || "No changes needed"
|
||||
: `Error (exit ${exitCode}): ${
|
||||
(stderr || stdout).trim() || "No error output"
|
||||
}`
|
||||
|
||||
resolve(output)
|
||||
})
|
||||
|
||||
// Close stdin in all cases so the process cannot wait indefinitely.
|
||||
if (args.code !== undefined) {
|
||||
proc.stdin.end(args.code)
|
||||
} else {
|
||||
proc.stdin.end()
|
||||
}
|
||||
})
|
||||
return executeParenRepair(args, context.worktree)
|
||||
},
|
||||
})
|
||||
|
||||
export default async function plugin() {
|
||||
async function server() {
|
||||
return {
|
||||
tool: {
|
||||
"paren-repair": parenRepairTool,
|
||||
@ -153,147 +162,83 @@ export default async function plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- V2 setup (OpenCode V2 calls `setup()` and ignores `server()`) ---
|
||||
|
||||
const penpotPsqlInputSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
sql: {
|
||||
type: "string",
|
||||
description: "SQL command to execute",
|
||||
},
|
||||
test: {
|
||||
type: "boolean",
|
||||
description: "Use the penpot_test database",
|
||||
},
|
||||
},
|
||||
required: ["sql"],
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
const parenRepairInputSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
// A string is used instead of an array so OpenCode displays it
|
||||
// in the generic tool invocation.
|
||||
files: {
|
||||
type: "string",
|
||||
description:
|
||||
"Comma-separated file paths to fix, for example: frontend/src/app/config.cljs, backend/src/core.clj",
|
||||
},
|
||||
code: {
|
||||
type: "string",
|
||||
description: "Code string to fix via stdin",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
async function setup(ctx) {
|
||||
// Plugin instance location. This is not the location of every session the
|
||||
// tools may run for, but it is the closest V2 equivalent of the V1
|
||||
// per-execution `context.worktree` (the repo checkout the plugin loaded
|
||||
// from), which is what both tools need as cwd / script base.
|
||||
const directory =
|
||||
ctx.location.directory ?? ctx.location.project?.canonical
|
||||
|
||||
// Keep this callback synchronous: transforms are replayable state edits.
|
||||
// The async work happens later, inside each tool's `execute`.
|
||||
await ctx.tool.transform((editor) => {
|
||||
editor.add({
|
||||
name: "penpot-psql",
|
||||
description:
|
||||
"Execute a SQL command against the Penpot database. Uses the defaults from scripts/psql.",
|
||||
input: penpotPsqlInputSchema,
|
||||
async execute(input) {
|
||||
const content = await executePsql(
|
||||
input.sql,
|
||||
input.test === true,
|
||||
directory,
|
||||
)
|
||||
return { content }
|
||||
},
|
||||
})
|
||||
|
||||
editor.add({
|
||||
name: "paren-repair",
|
||||
description:
|
||||
"Fix mismatched parentheses/braces in Clojure files (.clj, .cljs, .cljc) then reformat with cljfmt.",
|
||||
input: parenRepairInputSchema,
|
||||
async execute(input) {
|
||||
const content = await executeParenRepair(input, directory)
|
||||
return { content }
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// import { tool } from "@opencode-ai/plugin"
|
||||
// import path from "path"
|
||||
// import { spawn } from "child_process"
|
||||
|
||||
// function formatFiles(files) {
|
||||
// if (files.length === 0) return "stdin"
|
||||
|
||||
// // Keep the visible tool title reasonably short.
|
||||
// if (files.length <= 3) return files.join(", ")
|
||||
|
||||
// return `${files.slice(0, 3).join(", ")} (+${files.length - 3} more)`
|
||||
// }
|
||||
|
||||
// const parenRepairTool = tool({
|
||||
// description:
|
||||
// "Fix mismatched parentheses/braces in Clojure files, then reformat with cljfmt.",
|
||||
|
||||
// args: {
|
||||
// files: tool.schema
|
||||
// .array(tool.schema.string())
|
||||
// .describe("Array of file paths to fix")
|
||||
// .optional(),
|
||||
|
||||
// code: tool.schema
|
||||
// .string()
|
||||
// .describe("Code string to fix via stdin")
|
||||
// .optional(),
|
||||
// },
|
||||
|
||||
// async execute(args, context) {
|
||||
// const script = path.join(context.worktree, "scripts/paren-repair")
|
||||
|
||||
// const files = (args.files ?? []).map((file) => {
|
||||
// const absolute = path.isAbsolute(file)
|
||||
// ? file
|
||||
// : path.resolve(context.worktree, file)
|
||||
|
||||
// return path.relative(context.worktree, absolute)
|
||||
// })
|
||||
|
||||
// const targetSummary =
|
||||
// files.length > 0
|
||||
// ? formatFiles(files)
|
||||
// : args.code !== undefined
|
||||
// ? `stdin (${args.code.length} chars)`
|
||||
// : "no input"
|
||||
|
||||
// // This updates the tool-call title immediately, while it is running.
|
||||
// await context.metadata({
|
||||
// title: `Paren repair: ${targetSummary}`,
|
||||
// metadata: {
|
||||
// files,
|
||||
// codeChars: args.code?.length,
|
||||
// },
|
||||
// })
|
||||
|
||||
// const childArgs =
|
||||
// args.files && args.files.length > 0
|
||||
// ? [script, ...args.files]
|
||||
// : [script]
|
||||
|
||||
// return new Promise((resolve) => {
|
||||
// const proc = spawn("bb", childArgs, {
|
||||
// cwd: context.worktree,
|
||||
// })
|
||||
|
||||
// let stdout = ""
|
||||
// let stderr = ""
|
||||
|
||||
// if (args.code !== undefined) {
|
||||
// proc.stdin.end(args.code)
|
||||
// }
|
||||
|
||||
// proc.stdout.on("data", (data) => {
|
||||
// stdout += data.toString()
|
||||
// })
|
||||
|
||||
// proc.stderr.on("data", (data) => {
|
||||
// stderr += data.toString()
|
||||
// })
|
||||
|
||||
// proc.on("close", (exitCode) => {
|
||||
// const successful = exitCode === 0
|
||||
|
||||
// const commandOutput = successful
|
||||
// ? stdout.trim() || "No changes needed"
|
||||
// : `Error (exit ${exitCode}): ${(stderr || stdout).trim()}`
|
||||
|
||||
// const parameterOutput =
|
||||
// files.length > 0
|
||||
// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}`
|
||||
// : args.code !== undefined
|
||||
// ? `Input passed through stdin: ${args.code.length} characters`
|
||||
// : "No files or stdin input were passed"
|
||||
|
||||
// resolve({
|
||||
// title: `Paren repair: ${targetSummary}`,
|
||||
// output: `${parameterOutput}\n\n${commandOutput}`,
|
||||
// metadata: {
|
||||
// files,
|
||||
// codeChars: args.code?.length,
|
||||
// exitCode,
|
||||
// successful,
|
||||
// },
|
||||
// })
|
||||
// })
|
||||
|
||||
// proc.on("error", (error) => {
|
||||
// resolve({
|
||||
// title: `Paren repair failed: ${targetSummary}`,
|
||||
// output: [
|
||||
// files.length > 0
|
||||
// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}`
|
||||
// : `Input: ${targetSummary}`,
|
||||
// `Failed to start bb: ${error.message}`,
|
||||
// ].join("\n\n"),
|
||||
// metadata: {
|
||||
// files,
|
||||
// codeChars: args.code?.length,
|
||||
// successful: false,
|
||||
// },
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
// })
|
||||
|
||||
// export default async function plugin() {
|
||||
// return {
|
||||
// tool: {
|
||||
// "paren-repair": parenRepairTool,
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
export default {
|
||||
id: "penpot",
|
||||
setup,
|
||||
server,
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
- Team/project commands mix DB changes, email, message bus notifications, media/storage cleanup, feature flags, quotas, and audit metadata. Keep mutations transactional when the existing command does so.
|
||||
- Invitation flows validate muted/bounced emails before sending and use tokenized invitation state. Accepting an invitation is tied to the invited member identity, not just possession of a token.
|
||||
- Logical deletion is used for many product objects; prefer existing logical-deletion helpers over hard deletes unless the command already performs permanent cleanup.
|
||||
- Bounced/spam-complaint emails can mute/block a profile for login/registration and email sending. Devenv MailCatcher is the normal local path for registration/email-flow testing.
|
||||
- Bounced/spam-complaint emails can mute/block a profile for login/registration and email sending. Devenv Mailpit is the normal local path for registration/email-flow testing.
|
||||
|
||||
## Comments, webhooks, and audit
|
||||
|
||||
@ -37,4 +37,4 @@
|
||||
- Enable LDAP login locally with frontend flag `enable-login-with-ldap`; the devenv includes a configured test LDAP service.
|
||||
- OIDC testing requires external provider app credentials plus matching backend/frontend config.
|
||||
- Backend domain tests usually live under `backend/test/backend_tests/rpc/commands/*_test.clj` or nearby backend test namespaces. Use focused `clojure -M:dev:test --focus ...` from `backend/` when possible.
|
||||
- For auth/session or HTTP behavior, combine backend tests with the HTTP/session notes in `mem:backend/http-storage-filedata-subtleties` because RPC-level tests may not exercise cookie/header transforms.
|
||||
- For auth/session or HTTP behavior, combine backend tests with the HTTP/session notes in `mem:backend/http-storage-filedata-subtleties` because RPC-level tests may not exercise cookie/header transforms.
|
||||
|
||||
@ -18,7 +18,7 @@ You are working on the GitHub project `penpot/penpot`, a monorepo.
|
||||
- Before `gh issue create` → `mem:workflow/creating-issues` (title derivation, body template, labels, Issue Type)
|
||||
- Before `gh pr create` / `gh pr edit` → `mem:workflow/creating-prs` (title format, body structure, "Note:" line)
|
||||
- Before a repo-wide pnpm version update → `mem:workflow/updating-pnpm` (workspace
|
||||
layout, `corepack use` sweep order, the stamp-missing-field and
|
||||
layout, `scripts/sync-pnpm-version` flow, the stamp-missing-field and
|
||||
ignored-builds gotchas, verification steps)
|
||||
- **Never `git push`, force-push, or modify `git origin`** (or any other remote). The user pushes from their own shell; if a push is required, say so and wait. Never amend a commit that the user has already pushed unless explicitly asked.
|
||||
- **Never edit `CHANGES.md` by hand.** The changelog is generated from GitHub milestones during the release process; update it only via the `update-changelog` skill flow or on explicit user request.
|
||||
@ -55,7 +55,7 @@ module. You can read it from `mem:<MODULE>/core`
|
||||
|
||||
- `docker/` contains devenv related code, not needed unless specifically instructed.
|
||||
When working on devenv startup, compose layout, instance config (`defaults.env`),
|
||||
tmux session lifecycle, MinIO provisioning, or anything in `manage.sh`'s
|
||||
tmux session lifecycle, RustFS provisioning, or anything in `manage.sh`'s
|
||||
`*-devenv` commands, read `mem:devenv/core`.
|
||||
- `experiments/` contains standalone experimental HTML/JS/scripts; treat it as non-core unless the user explicitly asks about it.
|
||||
- `sample_media/` contains sample image/icon media and config used as fixtures/demo material; do not infer app behavior from it.
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
# Devenv startup and configuration
|
||||
|
||||
Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Parallel instances share infra + Postgres + MinIO; each instance has its own `main` container, Valkey, source checkout, tmux session.
|
||||
Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Parallel instances share infra + Postgres + RustFS; each instance has its own `main` container, Valkey, source checkout, tmux session.
|
||||
|
||||
## Compose project layout
|
||||
|
||||
- `penpotdev-infra`: shared `postgres`, `minio`, `minio-setup`, `mailer`, `ldap`. File: `docker-compose.infra.yml`.
|
||||
- `penpotdev-infra`: shared `postgres`, `rustfs`, `valkey`, `mailer`, `ldap`. File: `docker-compose.infra.yml`.
|
||||
- `penpotdev-wsN` (N=0,1,…): per-instance `main` + `redis` (Valkey). File: `docker-compose.main.yml`. ws0 (a.k.a. `main`) binds `$PWD`; ws1+ bind clones at `${PENPOT_WORKSPACES_DIR}/wsN/` (default `~/.penpot/penpot_workspaces/`), maintained by the developer.
|
||||
- Optional overlay `docker-compose.opencode.yml`: added by `instance-compose` as an extra `-f` only when `PENPOT_OPENCODE_CONFIG_DIR` is set (i.e. `run-devenv --opencode-config-dir DIR` ran in this process). Bind-mounts the host dir at `/home/penpot/.config/opencode` (`:z`). Flag-only, per-call; not read from ambient env. Parser `parse-opencode-config-dir` absolutizes (`~`, realpath) because compose resolves relative bind sources against the compose file's dir. Only instances brought up with the flag get the mount.
|
||||
- All projects join external network `penpot_shared`. Created idempotently by `ensure-devenv-network`, never removed by lifecycle commands.
|
||||
@ -13,22 +13,22 @@ Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Par
|
||||
|
||||
- `docker/devenv/defaults.env`: ws0 baseline — container/volume names, runtime env, published host ports, tmux defaults. `manage.sh` aborts if unreadable.
|
||||
- For ws1+, `instance-env-overrides` computes the per-instance overrides (container/volume names, host ports offset `10000·N`, `PENPOT_PUBLIC_URI`, `PENPOT_REDIS_URI`, `PENPOT_BACKEND_WORKER=false`) and `instance-compose` injects them as env vars at compose time — never written to disk, recomputed each call so they can't drift. ws0 uses `defaults.env` as-is.
|
||||
- `backend/scripts/_env`: backend-internal only — secret keys, `PENPOT_FLAGS` (with `enable-backend-worker` gated on `PENPOT_BACKEND_WORKER`), `JAVA_OPTS`, `setup_minio()`. Never duplicates `defaults.env`.
|
||||
- `backend/scripts/_env`: backend-internal only — secret keys, `PENPOT_FLAGS` (with `enable-backend-worker` gated on `PENPOT_BACKEND_WORKER`), `JAVA_OPTS`, `setup_s3_bucket()`. Never duplicates `defaults.env`.
|
||||
- Compose files use pure `${VAR}` substitution; missing var = compose fails.
|
||||
|
||||
## Invariants
|
||||
|
||||
- `infra-compose` / `instance-compose` wrap `docker compose` with `env -i`, then re-inject what compose needs. Stripping is required because `defaults.env` is sourced into manage.sh's shell at startup (stale values would leak); the ws1+ overrides are deliberately re-injected as shell env vars precisely because Compose gives shell precedence over `--env-file`, so they override the `defaults.env` baseline.
|
||||
- Volume names pinned via `name:` (PENPOT_*_VOLUME), decoupled from the compose project name. ws1+ inject distinct per-instance volume names; ws0 keeps the historical `penpotdev_*` physical names so project renames never require data migration.
|
||||
- Volume names pinned via `name:` (PENPOT_*_VOLUME), decoupled from the compose project name. Replacement services use new volumes and leave old volumes untouched. PostgreSQL 18 mounts `penpotdev_postgres_data_pg18` at `/var/lib/postgresql`; the old PG16 volume is not migrated automatically.
|
||||
- Network aliases (`- main`, `- redis`) are not declared in main.yml. Compose's auto-service-alias still registers `redis` on the shared network, so DNS for `redis` is non-deterministic with multiple instances. Backend uses `PENPOT_REDIS_URI=redis://penpot-devenv-wsN-valkey/0` (container_name) instead.
|
||||
- No cross-project `depends_on`. `manage.sh ensure-infra-up` `docker wait`s on the `minio-setup` one-shot.
|
||||
- No cross-project `depends_on`. `manage.sh ensure-infra-up` uses Compose `--wait`; PostgreSQL, RustFS, and Mailpit expose healthchecks. PostgreSQL checks TCP so its temporary init server cannot report ready.
|
||||
- `JAVA_OPTS` in `manage.sh` is shadowed inside the container by `_env`. The `-e JAVA_OPTS=...` flag only matters for processes that don't source `_env`.
|
||||
|
||||
## Worker policy
|
||||
|
||||
Backend workers run only on ws0. `_env` gates `enable-backend-worker` on `PENPOT_BACKEND_WORKER`; ws1+ inject it as false. Workers are pure fire-and-forget: `wrk/submit!` inserts a row into the shared Postgres `task` table and returns; RPC handlers never wait on completion and workers never publish to msgbus. The reason for "ws0 only" is avoiding multi-instance worker races (cron dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across submitters); details in `mem:prod-infra/core`.
|
||||
|
||||
Each workspace is independent and can be started/stopped in any order. Shared infra (postgres, minio, etc.) is shut down only when no instances remain running.
|
||||
Each workspace is independent and can be started/stopped in any order. Shared infra (Postgres, RustFS, etc.) is shut down only when no instances remain running.
|
||||
|
||||
## Port layout
|
||||
|
||||
@ -43,7 +43,7 @@ Container-internal ports fixed; host side offset `10000·N`.
|
||||
| 14181 | 24181 | … | 14281 | Serena MCP |
|
||||
| 14182 | 24182 | … | 24282 | Serena dashboard |
|
||||
|
||||
Everything else (frontend dev, backend API, exporter, storybook, REPLs, plugin dev, MCP inspector/WebSocket) is in-process or same-origin via Caddy/nginx. Infra publishes: mailer 1080, ldap 10389/10636 (singletons, not offset).
|
||||
Everything else (frontend dev, backend API, exporter, storybook, REPLs, plugin dev, MCP inspector/WebSocket) is in-process or same-origin via Caddy/nginx. Infra publishes RustFS S3 API 9000, RustFS console 9001, and Mailpit UI 1080 on loopback; ldap 10389/10636 remains a singleton without offsets. Mailpit stores its SQLite database in `penpotdev_mailpit_data`.
|
||||
|
||||
## Tmux + MCP routing
|
||||
|
||||
|
||||
@ -138,13 +138,15 @@ Distinguishing abstractions (the OOP articulation of the shared principles):
|
||||
control via Playwright; recipe in the README.
|
||||
|
||||
## CI
|
||||
Headless per-PR gate: `.github/workflows/tests-composable-suite.yml` runs
|
||||
`pnpm --filter composable-test-suite run test:ci` — mocked backend (frontend e2e static server +
|
||||
Headless per-PR gate: the `composable-test-suite` job in
|
||||
`.github/workflows/tests-e2e.yml` runs `pnpm --filter composable-test-suite run test:ci` — mocked
|
||||
backend (frontend e2e static server +
|
||||
Playwright RPC fixtures, no backend/login), the in-sandbox bundle injected via `ɵloadPlugin`,
|
||||
results streamed via console markers, `TEST_FILTER` by identifier substring. The mocked backend is
|
||||
NOT a limitation for this suite (everything asserted is frontend store logic; empirically
|
||||
confirmed against the interactive runs). Architecture mirrors `plugin-api-test-suite`'s CI driver;
|
||||
the mock harness exists in THREE places that must stay in sync (provenance note in `ci/run-ci.ts`).
|
||||
The frontend bundle is built once per SHA and restored by this job (`mem:frontend/e2e-ci-workflow`).
|
||||
Details: README, "Running in CI".
|
||||
|
||||
## Substrate
|
||||
|
||||
@ -53,6 +53,7 @@ Diagnostics and validation:
|
||||
- Source-edit compile/hot-reload diagnostics: `mem:frontend/compile-diagnostics`.
|
||||
- Runtime crash recovery: `mem:frontend/handling-crashes`.
|
||||
- Tests and live verification: `mem:frontend/testing`.
|
||||
- CI end-to-end workflow (build-once frontend bundle, check names): `mem:frontend/e2e-ci-workflow`.
|
||||
- Cross-cutting testing principles and anti-patterns: `mem:testing`.
|
||||
- Real pointer/keyboard gesture reproduction: `mem:frontend/playwright-gestures`.
|
||||
|
||||
|
||||
43
.serena/memories/frontend/e2e-ci-workflow.md
Normal file
43
.serena/memories/frontend/e2e-ci-workflow.md
Normal file
@ -0,0 +1,43 @@
|
||||
# E2E CI workflow (build-once frontend bundle)
|
||||
|
||||
`.github/workflows/tests-e2e.yml` ("CI: E2E") is the single workflow for every
|
||||
suite that drives a real frontend bundle:
|
||||
|
||||
- `Integration Tests` — Playwright specs under `frontend/playwright` (sharded).
|
||||
- `Run composable test suite (mocked backend)` — `mem:frontend/composable-component-tests`.
|
||||
- `Run Plugin API Test Suite (mocked)` — `plugins/apps/plugin-api-test-suite`.
|
||||
|
||||
Triggers: PR/push touching `frontend/**`, `common/**`, `render-wasm/**`,
|
||||
`plugins/**` (or the workflow file), plus `workflow_dispatch` (integration only).
|
||||
A `plugins/**` change runs the whole set on purpose: the bundle embeds the
|
||||
built plugins.
|
||||
|
||||
## Invariants
|
||||
|
||||
- ONE `frontend/scripts/build` per SHA. The `build-bundle` job restores
|
||||
`actions/cache` key `frontend-bundle-<sha>`, builds only on a miss, and saves
|
||||
the key before the job ends. A re-run of the same SHA reuses the cache.
|
||||
- Consumer jobs (`needs: build-bundle`) restore the same key with
|
||||
`fail-on-cache-miss: true` and NEVER run `frontend/scripts/build`.
|
||||
- The bundle is `frontend/resources/public`. The integration specs serve it
|
||||
with `frontend/scripts/e2e-server.js`; each mocked plugin driver serves it
|
||||
with its own zero-dependency `ci/static-server.ts` (duplicated in both
|
||||
suites — keep the copies in sync).
|
||||
- Mocked plugin jobs install only `plugins/` deps, so their drivers must not
|
||||
import anything from `frontend/node_modules` at runtime (e.g. no
|
||||
`frontend/scripts/e2e-server.js`, which needs `express`).
|
||||
- Cache key comes from `git rev-parse HEAD` (the checked-out ref), not
|
||||
`github.sha`, because `workflow_dispatch` can target a different ref.
|
||||
- Job `name:` values are the GitHub check contexts. Keep them stable: branch
|
||||
protection may match them by name. Renaming the workflow file/name is safe.
|
||||
|
||||
## Adding a bundle-consuming suite
|
||||
|
||||
Add a job with `needs: build-bundle`, a `Restore Cache` step
|
||||
(`actions/cache/restore@v5`, key `needs.build-bundle.outputs.bundle_key`,
|
||||
`fail-on-cache-miss: true`), then that suite's own deps. Never add a build step.
|
||||
|
||||
## Scope
|
||||
|
||||
Distinct from `Bundles Builder` (`.github/workflows/build-bundle.yml`), the
|
||||
release path that zips the bundle (`manage.sh build-bundle`) and uploads it to S3.
|
||||
@ -31,6 +31,10 @@ Integration tests fake backend behavior by intercepting network/websocket traffi
|
||||
|
||||
Locator priority should follow user-facing semantics: `getByRole`, `getByLabel`, `getByPlaceholder`, `getByText`, then semantic alternatives such as alt/title, with `getByTestId` as the last resort. Name tests from the user's perspective and prefer positive, single-purpose assertions.
|
||||
|
||||
## CI (E2E)
|
||||
|
||||
`.github/workflows/tests-e2e.yml` runs the integration specs, the composable component suite, and the mocked Plugin API suite from one workflow that builds the frontend bundle once per SHA. Before adding a job that needs the bundle, read `mem:frontend/e2e-ci-workflow` (build-once contract, cache key, stable check names).
|
||||
|
||||
## Live browser verification
|
||||
|
||||
Because CLJC compiles to both JVM and CLJS, JVM/common tests can miss frontend-only state caused by browser runtime, WASM modifier math, or real pointer events. Use `mem:frontend/cljs-repl` to inspect live app state and `mem:frontend/playwright-gestures` when real input is needed.
|
||||
|
||||
@ -6,7 +6,7 @@ Backend (`app.config`, `PENPOT_*` env vars) is parameterized; deployments choose
|
||||
|
||||
- **PostgreSQL**: durable store. Profiles, teams, files, sessions, audit, `storage_object` metadata, the `task` queue, `scheduled_task` cron registry, migrations. File-data also lives here when the file-data backend is `legacy-db`/`db`. One shared DB across all backends.
|
||||
- **Redis (Valkey-compatible)**: per-backend message bus and cache. Concrete uses: msgbus Pub/Sub for collaborative-editing broadcasts and team/profile-org notifications fired by RPC handlers (`app.rpc.notifications`, `files_update`, `teams`, `websocket`); file-summary cache gated by `enable-redis-cache`; rate-limit counters; and the dispatcher→runner work hand-off list `penpot.worker.queue:<tenant>:<queue>`. `PENPOT_REDIS_URI`.
|
||||
- **Object storage**: backends `:s3` and `:fs`. S3 in prod; devenv uses MinIO. Holds uploaded media, file-data when the file-data backend is `storage`, exports. Backend-side details (resolve, dedup, bucket set, object lifecycle, and file-data backends): `mem:backend/storage`.
|
||||
- **Object storage**: backends `:s3` and `:fs`. S3 in prod; devenv uses RustFS. Holds uploaded media, file-data when the file-data backend is `storage`, exports. Backend-side details (resolve, dedup, bucket set, object lifecycle, and file-data backends): `mem:backend/storage`.
|
||||
- **SMTP mailer**: invitations, password resets, email verification (sent via the `:sendmail` worker task).
|
||||
- **LDAP** (optional auth provider): helpers in `app.auth.*`, gated by `enable-login-with-ldap`.
|
||||
|
||||
|
||||
@ -29,9 +29,10 @@ bb scripts/paren-repair --help
|
||||
|
||||
## Native Tool Available (opencode)
|
||||
|
||||
A native opencode tool `paren-repair` is available at `.opencode/scripts/paren-repair.ts`.
|
||||
The LLM can call it directly with:
|
||||
- `files`: Array of file paths to fix
|
||||
A native opencode tool `paren-repair` is available (defined in
|
||||
`.opencode/plugins/penpot.js`, which registers it for both opencode V1 via
|
||||
`server()` and opencode V2 via `setup()`). The LLM can call it directly with:
|
||||
- `files`: comma-separated file paths to fix (a string, not an array)
|
||||
- `code`: Code string to fix via stdin
|
||||
|
||||
Example usage by the LLM:
|
||||
|
||||
@ -27,7 +27,9 @@ scripts/psql --host myhost --user myuser --db mydb
|
||||
|
||||
## Native Tool Available (opencode)
|
||||
|
||||
A native opencode tool `penpot-psql` is available. The LLM can call it directly
|
||||
A native opencode tool `penpot-psql` is available (defined in
|
||||
`.opencode/plugins/penpot.js`, which registers it for both opencode V1 via
|
||||
`server()` and opencode V2 via `setup()`). The LLM can call it directly
|
||||
with:
|
||||
- `sql`: SQL command string to execute
|
||||
- `test`: Boolean flag to use the `penpot_test` database
|
||||
|
||||
@ -23,33 +23,46 @@ file (never pipe tool output through filters).
|
||||
- The store survives `node_modules` cleans. It is content-addressed and
|
||||
integrity-verified, so it cannot go stale; staleness lives in
|
||||
node_modules. Only `scripts/clean-node-modules --store` removes it.
|
||||
- Every `package.json` (about 35 of them) must carry a `packageManager` field
|
||||
with the identical `pnpm@<version>+sha512.<hash>` value. Do not let them drift.
|
||||
- CI pins no pnpm version; workflows rely on corepack reading
|
||||
`packageManager`. Fixing the fields fixes CI.
|
||||
- Every first-party `package.json` (35 files: workspace roots plus
|
||||
members) must carry a `packageManager` field with the identical
|
||||
`pnpm@<version>+sha512.<hash>` value. Do not let them drift.
|
||||
`external/` (vendored trees with their own lifecycles), `.opencode/`,
|
||||
`.pnpm-store/`, `bundles/` and `docker/images/bundle-*` copies are
|
||||
never stamped.
|
||||
- pnpm is a system binary everywhere (devenv image, CI runners, Docker
|
||||
images). Nothing may call corepack: it is gone from Node 25+. pnpm
|
||||
auto-downloads the `packageManager` version on mismatch
|
||||
(`pmOnFail: download`, the default), so drift self-heals; aligned pins
|
||||
just skip the download.
|
||||
- The pnpm version pin lives in three places that move together:
|
||||
`PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in
|
||||
`docker/images/Dockerfile.{media-processor,exporter,mcp}`, and the
|
||||
`packageManager` fields (stamped by the script below). The
|
||||
`plugins-deploy-*` workflows need no pnpm pin: a single `pnpm/setup`
|
||||
step (`working-directory: plugins`, `install: false`) reads it from
|
||||
the manifest and installs the pinned Node via `runtime: node@<exact>`;
|
||||
bump that pin together with `.nvmrc` on Node updates.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Resolve the target tag first and note the version. Example:
|
||||
`npm view pnpm dist-tags --json` for `next-12` (latest 12.x). The tag
|
||||
moves over time; always re-check.
|
||||
2. List every directory with a `package.json`, excluding `node_modules`
|
||||
(`fd -H -t f package.json -E node_modules`). This list is the work set;
|
||||
do not maintain a hand-written list.
|
||||
3. Run `corepack use pnpm@<tag>` in workspace roots first, then members.
|
||||
`corepack use` stamps `packageManager` in the nearest package.json and
|
||||
runs an install. Member runs repeat the workspace install; after the root
|
||||
run they are quick no-ops.
|
||||
4. If a run fails, fix the cause (see gotchas) and re-run that directory.
|
||||
1. Resolve the target version first and note it. Example:
|
||||
`pnpm view pnpm dist-tags --json` for `latest-12` (latest 12.x). The
|
||||
tag moves over time; always re-check.
|
||||
2. Bump `PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in
|
||||
`docker/images/Dockerfile.{media-processor,exporter,mcp}`.
|
||||
3. From the repo root, on a host whose system pnpm is the target version
|
||||
(rebuilt devenv), run `scripts/sync-pnpm-version`. It resolves the
|
||||
integrity hash via `pnpm view` + node (no npm, no corepack, no
|
||||
python3) and stamps the identical field into all 35 files, replacing
|
||||
only the value line (each file keeps its own indent) and inserting
|
||||
the key after `"name"`/`"version"` where missing. Explicit version
|
||||
instead: `scripts/sync-pnpm-version <version>`.
|
||||
4. Run `scripts/sync-pnpm-version --install` (plain `pnpm install` in
|
||||
each of the 11 workspace roots) to refresh lockfile metadata, then
|
||||
fix any failing workspace (see gotchas) and re-run that directory.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `corepack use` only updates an existing `packageManager` field. If a
|
||||
package.json lacks the field, corepack walks up to the nearest ancestor
|
||||
that has one and stamps that file instead; the member stays unstamped.
|
||||
After the sweep, assert every package.json carries the field. For a
|
||||
missing one, insert the identical `pnpm@<version>+sha512.<hash>` string,
|
||||
then re-run `corepack use pnpm@<tag>` in that directory.
|
||||
- A workspace may fail with `ERR_PNPM_IGNORED_BUILDS`, and pnpm then writes
|
||||
a placeholder scaffold into its `pnpm-workspace.yaml`:
|
||||
`allowBuilds: esbuild: set this to true or false`. Current pnpm writes
|
||||
@ -70,10 +83,12 @@ file (never pipe tool output through filters).
|
||||
|
||||
## Verification
|
||||
|
||||
- Every `packageManager` field is byte-identical (same version and hash).
|
||||
- `pnpm --version` in each workspace prints the target version.
|
||||
- `scripts/sync-pnpm-version --check` passes: every `packageManager`
|
||||
field is byte-identical (same version and hash).
|
||||
- `pnpm install --frozen-lockfile` succeeds in each of the 11 workspaces.
|
||||
- `git diff` on lockfiles matches the expectations above.
|
||||
- No `corepack` call remains in scripts, workflows, Dockerfiles or docs
|
||||
(`rg corepack` shows only `CHANGES.md` history).
|
||||
|
||||
## Cleaning stale node_modules
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
131
backend/pnpm-lock.yaml
generated
131
backend/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -97,13 +97,31 @@ export JAVA_OPTS="\
|
||||
--enable-native-access=ALL-UNNAMED \
|
||||
--add-opens=java.base/java.nio=ALL-UNNAMED";
|
||||
|
||||
function setup_minio() {
|
||||
function setup_s3_bucket() {
|
||||
if [ "${PENPOT_OBJECTS_STORAGE_BACKEND}" != "s3" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Shared MinIO user/policy provisioning is handled by docker-compose.infra.yml.
|
||||
# Per process startup only ensures that the configured bucket exists.
|
||||
mc alias set penpot-s3/ "${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}" minioadmin minioadmin -q
|
||||
mc mb "penpot-s3/${PENPOT_OBJECTS_STORAGE_S3_BUCKET}" -p -q
|
||||
local attempts=0
|
||||
local endpoint="${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}"
|
||||
local bucket="${PENPOT_OBJECTS_STORAGE_S3_BUCKET}"
|
||||
local region="${PENPOT_OBJECTS_STORAGE_S3_REGION}"
|
||||
|
||||
until aws s3api head-bucket \
|
||||
--endpoint-url "$endpoint" \
|
||||
--region "$region" \
|
||||
--bucket "$bucket" >/dev/null 2>&1; do
|
||||
if aws s3 mb "s3://${bucket}" \
|
||||
--endpoint-url "$endpoint" \
|
||||
--region "$region" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
attempts=$((attempts + 1))
|
||||
if [ "$attempts" -ge 30 ]; then
|
||||
echo "setup_s3_bucket: unable to create s3://${bucket} after ${attempts} attempts" >&2
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@ if [ -f $SCRIPT_DIR/_env.local ]; then
|
||||
source $SCRIPT_DIR/_env.local;
|
||||
fi
|
||||
|
||||
# Initialize MINIO config
|
||||
setup_minio;
|
||||
# Ensure the object storage bucket exists.
|
||||
setup_s3_bucket;
|
||||
|
||||
export JAVA_OPTS="$JAVA_OPTS -Dlog4j2.configurationFile=log4j2-devenv-repl.xml"
|
||||
export OPTIONS="-A:jmx-remote -A:dev"
|
||||
|
||||
@ -8,6 +8,9 @@ if [ -f $SCRIPT_DIR/_env.local ]; then
|
||||
source $SCRIPT_DIR/_env.local;
|
||||
fi
|
||||
|
||||
# Ensure the object storage bucket exists.
|
||||
setup_s3_bucket;
|
||||
|
||||
export OPTIONS="-A:dev"
|
||||
|
||||
entrypoint=${1:-app.main};
|
||||
|
||||
@ -7,8 +7,8 @@ if [ -f $SCRIPT_DIR/_env.local ]; then
|
||||
source $SCRIPT_DIR/_env.local;
|
||||
fi
|
||||
|
||||
# Initialize MINIO config
|
||||
setup_minio;
|
||||
# Ensure the object storage bucket exists.
|
||||
setup_s3_bucket;
|
||||
|
||||
shift 1;
|
||||
set -ex
|
||||
|
||||
@ -75,7 +75,7 @@
|
||||
[:map {:title "create-file"}
|
||||
[:name [:string {:max 250}]]
|
||||
[:project-id ::sm/uuid]
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:id {:optional true} ::sm/user-provided-uuid]
|
||||
[:is-shared {:optional true} ::sm/boolean]
|
||||
[:features {:optional true} ::cfeat/features]])
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
(def ^:private schema:upload-file-media-object
|
||||
[:map {:title "upload-file-media-object"}
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:id {:optional true} ::sm/user-provided-uuid]
|
||||
[:file-id ::sm/uuid]
|
||||
[:is-local ::sm/boolean]
|
||||
[:name [:string {:max 250}]]
|
||||
@ -225,7 +225,7 @@
|
||||
[:file-id ::sm/uuid]
|
||||
[:is-local ::sm/boolean]
|
||||
[:url ::sm/uri]
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:id {:optional true} ::sm/user-provided-uuid]
|
||||
[:name {:optional true} [:string {:max 250}]]])
|
||||
|
||||
(sv/defmethod ::create-file-media-object-from-url
|
||||
@ -556,7 +556,7 @@
|
||||
[:is-local ::sm/boolean]
|
||||
[:name [:string {:max 250}]]
|
||||
[:mtype :string]
|
||||
[:id {:optional true} ::sm/uuid]])
|
||||
[:id {:optional true} ::sm/user-provided-uuid]])
|
||||
|
||||
(sv/defmethod ::assemble-file-media-object
|
||||
{::doc/added "2.17"
|
||||
|
||||
@ -199,7 +199,7 @@
|
||||
[:map {:title "create-project"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:name [:string {:max 250 :min 1}]]
|
||||
[:id {:optional true} ::sm/uuid]])
|
||||
[:id {:optional true} ::sm/user-provided-uuid]])
|
||||
|
||||
(sv/defmethod ::create-project
|
||||
{::doc/added "1.18"
|
||||
|
||||
@ -523,7 +523,7 @@
|
||||
[:map {:title "create-team"}
|
||||
[:name types.team/schema:team-name]
|
||||
[:features {:optional true} ::cfeat/features]
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:id {:optional true} ::sm/user-provided-uuid]
|
||||
[:organization-id {:optional true} ::sm/uuid]
|
||||
[:is-default {:optional true} :boolean]])
|
||||
|
||||
|
||||
@ -538,7 +538,7 @@
|
||||
[:map {:title "create-team-with-invitations"}
|
||||
[:name [:string {:max 250}]]
|
||||
[:features {:optional true} ::cfeat/features]
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:id {:optional true} ::sm/user-provided-uuid]
|
||||
[:emails [::sm/set ::sm/email]]
|
||||
[:role types.team/schema:role]])
|
||||
|
||||
|
||||
@ -166,6 +166,33 @@
|
||||
(t/is (th/ex-info? err))
|
||||
(t/is (th/ex-of-type? err :not-found))))))
|
||||
|
||||
(t/deftest create-file-id-version
|
||||
(let [prof (th/create-profile* 1 {:is-active true})
|
||||
proj-id (:default-project-id prof)
|
||||
v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
v4-id "550e8400-e29b-41d4-a716-446655440000"]
|
||||
|
||||
;; reserved version (v3) must be rejected at the RPC boundary
|
||||
(let [data {::th/type :create-file
|
||||
::rpc/profile-id (:id prof)
|
||||
:project-id proj-id
|
||||
:id v3-id
|
||||
:name "file with v3 id"}
|
||||
out (th/command! data)]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation)))
|
||||
|
||||
;; v4 id is accepted
|
||||
(let [data {::th/type :create-file
|
||||
::rpc/profile-id (:id prof)
|
||||
:project-id proj-id
|
||||
:id v4-id
|
||||
:name "file with v4 id"}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= v4-id (str (:id (:result out))))))))
|
||||
|
||||
(t/deftest file-gc-with-fragments
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
|
||||
@ -138,6 +138,66 @@
|
||||
(t/is (uuid? (:thumbnail-id result))))))
|
||||
|
||||
|
||||
(t/deftest upload-file-media-object-id-version
|
||||
(let [prof (th/create-profile* 1)
|
||||
_ (th/create-project* 1 {:profile-id (:id prof)
|
||||
:team-id (:default-team-id prof)})
|
||||
file (th/create-file* 1 {:profile-id (:id prof)
|
||||
:project-id (:default-project-id prof)
|
||||
:is-shared false})
|
||||
mfile {:filename "sample.jpg"
|
||||
:path (th/tempfile "backend_tests/test_files/sample.jpg")
|
||||
:mtype "image/jpeg"
|
||||
:size 312043}
|
||||
v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
v4-id "550e8400-e29b-41d4-a716-446655440000"]
|
||||
|
||||
;; reserved version (v3) must be rejected at the RPC boundary
|
||||
(let [params {::th/type :upload-file-media-object
|
||||
::rpc/profile-id (:id prof)
|
||||
:file-id (:id file)
|
||||
:is-local true
|
||||
:name "testfile"
|
||||
:content mfile
|
||||
:id v3-id}
|
||||
out (th/command! params)]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation)))
|
||||
|
||||
;; v4 id is accepted
|
||||
(let [params {::th/type :upload-file-media-object
|
||||
::rpc/profile-id (:id prof)
|
||||
:file-id (:id file)
|
||||
:is-local true
|
||||
:name "testfile"
|
||||
:content mfile
|
||||
:id v4-id}
|
||||
out (th/command! params)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= v4-id (str (:id (:result out))))))))
|
||||
|
||||
(t/deftest create-file-media-object-from-url-id-version
|
||||
(let [prof (th/create-profile* 1)
|
||||
_ (th/create-project* 1 {:profile-id (:id prof)
|
||||
:team-id (:default-team-id prof)})
|
||||
file (th/create-file* 1 {:profile-id (:id prof)
|
||||
:project-id (:default-project-id prof)
|
||||
:is-shared false})
|
||||
v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"]
|
||||
|
||||
;; reserved version (v3) must be rejected before any download happens
|
||||
(let [params {::th/type :create-file-media-object-from-url
|
||||
::rpc/profile-id (:id prof)
|
||||
:file-id (:id file)
|
||||
:is-local true
|
||||
:url "https://example.com/sample.jpg"
|
||||
:id v3-id}
|
||||
out (th/command! params)]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation)))))
|
||||
|
||||
(t/deftest media-object-from-url-command
|
||||
(let [prof (th/create-profile* 1)
|
||||
proj (th/create-project* 1 {:profile-id (:id prof)
|
||||
@ -1262,3 +1322,48 @@
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (= :not-found (:type error-data)))
|
||||
(t/is (= :object-not-found (:code error-data))))))
|
||||
|
||||
(t/deftest assemble-file-media-object-id-version
|
||||
(let [prof (th/create-profile* 1)
|
||||
_ (th/create-project* 1 {:profile-id (:id prof)
|
||||
:team-id (:default-team-id prof)})
|
||||
file (th/create-file* 1 {:profile-id (:id prof)
|
||||
:project-id (:default-project-id prof)
|
||||
:is-shared false})
|
||||
v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
v4-id "550e8400-e29b-41d4-a716-446655440000"
|
||||
mtype "image/jpeg"]
|
||||
|
||||
;; reserved version (v3) must be rejected without touching any session
|
||||
(let [out (th/command! {::th/type :assemble-file-media-object
|
||||
::rpc/profile-id (:id prof)
|
||||
:session-id (uuid/next)
|
||||
:file-id (:id file)
|
||||
:is-local true
|
||||
:name "assembled-image"
|
||||
:mtype mtype
|
||||
:id v3-id})]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation)))
|
||||
|
||||
;; v4 id is accepted through the full chunked flow
|
||||
(let [source-path (th/tempfile "backend_tests/test_files/sample.jpg")
|
||||
chunks (split-file-into-chunks source-path 312043)
|
||||
session-id (create-session! prof 1)
|
||||
mfile (make-chunk-mfile (first chunks) mtype)
|
||||
_ (th/command! {::th/type :upload-chunk
|
||||
::rpc/profile-id (:id prof)
|
||||
:session-id session-id
|
||||
:index 0
|
||||
:content mfile})
|
||||
out (th/command! {::th/type :assemble-file-media-object
|
||||
::rpc/profile-id (:id prof)
|
||||
:session-id session-id
|
||||
:file-id (:id file)
|
||||
:is-local true
|
||||
:name "assembled-image"
|
||||
:mtype mtype
|
||||
:id v4-id})]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= v4-id (str (:id (:result out))))))))
|
||||
|
||||
@ -123,6 +123,33 @@
|
||||
(t/is (th/ex-info? error))
|
||||
(t/is (th/ex-of-type? error :not-found))))
|
||||
|
||||
(t/deftest create-project-id-version
|
||||
(let [profile (th/create-profile* 1)
|
||||
team (th/create-team* 1 {:profile-id (:id profile)})
|
||||
v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
v4-id "550e8400-e29b-41d4-a716-446655440000"]
|
||||
|
||||
;; reserved version (v3) must be rejected at the RPC boundary
|
||||
(let [data {::th/type :create-project
|
||||
::rpc/profile-id (:id profile)
|
||||
:team-id (:id team)
|
||||
:id v3-id
|
||||
:name "project with v3 id"}
|
||||
out (th/command! data)]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation)))
|
||||
|
||||
;; v4 id is accepted
|
||||
(let [data {::th/type :create-project
|
||||
::rpc/profile-id (:id profile)
|
||||
:team-id (:id team)
|
||||
:id v4-id
|
||||
:name "project with v4 id"}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= v4-id (str (:id (:result out))))))))
|
||||
|
||||
(t/deftest permissions-checks-rename-project
|
||||
(let [profile1 (th/create-profile* 1)
|
||||
profile2 (th/create-profile* 2)
|
||||
|
||||
@ -1116,6 +1116,62 @@
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out)))))
|
||||
|
||||
(t/deftest create-team-id-version
|
||||
(let [profile (th/create-profile* 1 {:is-active true})
|
||||
v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
v4-id "550e8400-e29b-41d4-a716-446655440000"
|
||||
v7-id "0191062e-3f50-7a5e-9f5a-1a2b3c4d5e6f"]
|
||||
|
||||
;; reserved version (v3) must be rejected at the RPC boundary
|
||||
(let [data {::th/type :create-team
|
||||
::rpc/profile-id (:id profile)
|
||||
:name "team with v3 id"
|
||||
:id v3-id}
|
||||
out (th/command! data)]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation)))
|
||||
|
||||
;; v4, v7 and v8 ids are accepted
|
||||
(doseq [id [v4-id v7-id (str (uuid/next))]]
|
||||
(let [data {::th/type :create-team
|
||||
::rpc/profile-id (:id profile)
|
||||
:name (str "team with id " id)
|
||||
:id id}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= id (str (:id (:result out)))))))))
|
||||
|
||||
(t/deftest create-team-with-invitations-id-version
|
||||
(with-mocks [mock {:target 'app.email/send! :return nil}]
|
||||
(let [profile (th/create-profile* 1 {:is-active true})
|
||||
v3-id "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
v4-id "550e8400-e29b-41d4-a716-446655440000"]
|
||||
|
||||
;; reserved version (v3) must be rejected before any invitation is sent
|
||||
(let [data {::th/type :create-team-with-invitations
|
||||
::rpc/profile-id (:id profile)
|
||||
:name "team with v3 id"
|
||||
:id v3-id
|
||||
:emails #{"invitee@example.com"}
|
||||
:role :editor}
|
||||
out (th/command! data)]
|
||||
(t/is (not (th/success? out)))
|
||||
(t/is (th/ex-of-type? (:error out) :validation))
|
||||
(t/is (th/ex-of-code? (:error out) :params-validation))
|
||||
(t/is (= 0 (:call-count @mock))))
|
||||
|
||||
;; v4 id is accepted
|
||||
(let [data {::th/type :create-team-with-invitations
|
||||
::rpc/profile-id (:id profile)
|
||||
:name "team with v4 id"
|
||||
:id v4-id
|
||||
:emails #{"invitee@example.com"}
|
||||
:role :editor}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= v4-id (str (:id (:result out)))))))))
|
||||
|
||||
(t/deftest create-team-invitations-email-cooldown
|
||||
(with-mocks [mock {:target 'app.email/send! :return nil}]
|
||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
131
common/pnpm-lock.yaml
generated
131
common/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm run test;
|
||||
clojure -M:dev:test;
|
||||
|
||||
@ -448,6 +448,21 @@
|
||||
::oapi/type "string"
|
||||
::oapi/format "uuid"}})
|
||||
|
||||
(register!
|
||||
{:type ::user-provided-uuid
|
||||
:pred uuid/user-provided?
|
||||
:type-properties
|
||||
{:title "user-provided-uuid"
|
||||
:description "UUID provided by the user (v4, v7 or v8)"
|
||||
:error/message "should be a user provided uuid (v4, v7 or v8)"
|
||||
:gen/gen (sg/uuid)
|
||||
:decode/string parse-uuid
|
||||
:decode/json parse-uuid
|
||||
:encode/string encode-uuid
|
||||
:encode/json encode-uuid
|
||||
::oapi/type "string"
|
||||
::oapi/format "uuid"}})
|
||||
|
||||
;; Strict email regex aligned with app.common.spec/email-re.
|
||||
;; Local part: valid RFC chars, no leading/trailing dot, no consecutive dots.
|
||||
;; Domain: labels can't start/end with hyphen, no empty labels.
|
||||
|
||||
@ -70,6 +70,20 @@
|
||||
[v]
|
||||
(= zero v))
|
||||
|
||||
(def ^:private user-provided-regex
|
||||
#"(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[478][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
|
||||
|
||||
(defn user-provided?
|
||||
"Returns true when `v` is a UUID instance penpot can accept when
|
||||
it is provided by the user (v4 random, v7 time-ordered or v8 penpot
|
||||
time-ordered). Anything else, including UUID strings, returns false;
|
||||
strings are meant to be decoded into UUID instances first, mirroring
|
||||
how ::sm/uuid behaves."
|
||||
[v]
|
||||
(boolean
|
||||
(and (uuid? v)
|
||||
(re-matches user-provided-regex (str v)))))
|
||||
|
||||
(defn get-word-high
|
||||
[id]
|
||||
#?(:clj (.getMostSignificantBits ^UUID id)
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest test-set-of-email
|
||||
@ -245,3 +246,40 @@
|
||||
(t/is (false? (sm/validate ::sm/email "user@")))
|
||||
(t/is (false? (sm/validate ::sm/email "userdomain.com")))
|
||||
(t/is (false? (sm/validate ::sm/email "user@@domain.com")))))
|
||||
|
||||
(t/deftest test-user-provided-uuid
|
||||
(let [v4 (uuid/uuid "550e8400-e29b-41d4-a716-446655440000")
|
||||
v7 (uuid/uuid "0191062e-3f50-7a5e-9f5a-1a2b3c4d5e6f")
|
||||
v8 (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011")
|
||||
v1 (uuid/uuid "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||
v3 (uuid/uuid "6fa459ea-ee8a-3ca4-894e-db77e160355e")
|
||||
v5 (uuid/uuid "886313e1-3b8a-5372-9b90-0c9aee199e5d")]
|
||||
|
||||
(t/testing "accepts v4, v7 and v8 instances"
|
||||
(t/is (true? (sm/validate ::sm/user-provided-uuid v4)))
|
||||
(t/is (true? (sm/validate ::sm/user-provided-uuid v7)))
|
||||
(t/is (true? (sm/validate ::sm/user-provided-uuid v8)))
|
||||
(t/is (true? (sm/validate ::sm/user-provided-uuid (uuid/random))))
|
||||
(t/is (true? (sm/validate ::sm/user-provided-uuid (uuid/next)))))
|
||||
|
||||
(t/testing "rejects reserved and other versions"
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid v1)))
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid v3)))
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid v5)))
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid uuid/zero)))
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid nil)))
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid "not-an-uuid"))))
|
||||
|
||||
(t/testing "decodes strings like the RPC layer does"
|
||||
(let [decode (sm/decoder ::sm/user-provided-uuid sm/json-transformer)]
|
||||
(t/is (= v4 (decode (str v4))))
|
||||
(t/is (= v3 (decode (str v3))))
|
||||
(t/is (true? (sm/validate ::sm/user-provided-uuid (decode (str v4)))))
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid (decode (str v3)))))))
|
||||
|
||||
(t/testing "rejects raw strings without decoding, like ::sm/uuid does"
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid (str v4))))
|
||||
(t/is (false? (sm/validate ::sm/user-provided-uuid (str v3)))))
|
||||
|
||||
(t/testing "generate"
|
||||
(t/is (true? (sm/validate ::sm/user-provided-uuid (sg/generate ::sm/user-provided-uuid)))))))
|
||||
|
||||
@ -95,3 +95,46 @@
|
||||
(t/is (= (nth expected 1) (aget parts 1)))
|
||||
(t/is (= (nth expected 2) (aget parts 2)))
|
||||
(t/is (= (nth expected 3) (aget parts 3))))))))
|
||||
|
||||
(t/deftest user-provided-test
|
||||
;; The version is the first nibble of the 3rd group, the variant is
|
||||
;; the first nibble of the 4th group (8, 9, a or b means RFC 4122).
|
||||
(let [v4 (uuid/uuid "550e8400-e29b-41d4-a716-446655440000") ; v4, variant a: valid
|
||||
v7 (uuid/uuid "0191062e-3f50-7a5e-9f5a-1a2b3c4d5e6f") ; v7, variant 9: valid
|
||||
v8 (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011") ; v8, variant 8: valid
|
||||
v4-upper (uuid/uuid "550E8400-E29B-41D4-A716-446655440000") ; v4 uppercase, variant A: valid
|
||||
v1 (uuid/uuid "6ba7b810-9dad-11d1-80b4-00c04fd430c8") ; v1 time-based: rejected
|
||||
v3 (uuid/uuid "6fa459ea-ee8a-3ca4-894e-db77e160355e") ; v3 MD5 name-based (reserved): rejected
|
||||
v5 (uuid/uuid "886313e1-3b8a-5372-9b90-0c9aee199e5d") ; v5 SHA-1 name-based: rejected
|
||||
v6 (uuid/uuid "1e29effc-7a68-6d2c-9b6f-7f6a5f9b5b5b") ; v6 time-ordered: rejected
|
||||
v4-bad-variant (uuid/uuid "550e8400-e29b-41d4-0716-446655440000")] ; v4 but variant 0 (NCS reserved): rejected
|
||||
|
||||
(t/testing "accepts v4, v7 and v8 instances"
|
||||
(t/is (true? (uuid/user-provided? v4)))
|
||||
(t/is (true? (uuid/user-provided? v7)))
|
||||
(t/is (true? (uuid/user-provided? v8)))
|
||||
(t/is (true? (uuid/user-provided? v4-upper)))
|
||||
(t/is (true? (uuid/user-provided? (uuid/random)))) ; generates v4
|
||||
(t/is (true? (uuid/user-provided? (uuid/next))))) ; generates v8
|
||||
|
||||
(t/testing "rejects strings, they must be decoded first"
|
||||
(t/is (false? (uuid/user-provided? (str v4))))
|
||||
(t/is (false? (uuid/user-provided? (str v7))))
|
||||
(t/is (false? (uuid/user-provided? (str v8))))
|
||||
(t/is (false? (uuid/user-provided? (str v3)))))
|
||||
|
||||
(t/testing "rejects nil uuid and reserved and other versions"
|
||||
(t/is (false? (uuid/user-provided? uuid/zero))) ; version 0, variant 0
|
||||
(t/is (false? (uuid/user-provided? v1)))
|
||||
(t/is (false? (uuid/user-provided? v3)))
|
||||
(t/is (false? (uuid/user-provided? v5)))
|
||||
(t/is (false? (uuid/user-provided? v6)))
|
||||
(t/is (false? (uuid/user-provided? (uuid/custom 1))))) ; version 0, variant 0
|
||||
|
||||
(t/testing "rejects right version with wrong variant"
|
||||
(t/is (false? (uuid/user-provided? v4-bad-variant))))
|
||||
|
||||
(t/testing "rejects non-uuid values"
|
||||
(t/is (false? (uuid/user-provided? nil)))
|
||||
(t/is (false? (uuid/user-provided? 42)))
|
||||
(t/is (false? (uuid/user-provided? "not-an-uuid"))))))
|
||||
|
||||
@ -66,7 +66,8 @@ RUN set -ex; \
|
||||
|
||||
FROM base AS setup-node
|
||||
|
||||
ENV NODE_VERSION=v24.20.0 \
|
||||
ENV NODE_VERSION=v24.21.0 \
|
||||
PNPM_VERSION=12.5.1 \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
|
||||
RUN set -eux; \
|
||||
@ -75,10 +76,14 @@ RUN set -eux; \
|
||||
aarch64|arm64) \
|
||||
OPENSSL_ARCH='linux-aarch64'; \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
OPENSSL_ARCH='linux-x86_64'; \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@ -89,10 +94,14 @@ RUN set -eux; \
|
||||
mkdir -p /opt/node; \
|
||||
cd /opt/node; \
|
||||
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
||||
PNPM_URL="https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \
|
||||
curl -LfsSo /tmp/pnpm.tar.gz "${PNPM_URL}"; \
|
||||
echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \
|
||||
tar -xzf /tmp/pnpm.tar.gz -C /opt/node/bin pnpm; \
|
||||
chmod 755 /opt/node/bin/pnpm; \
|
||||
chown -R root /opt/node; \
|
||||
find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \
|
||||
corepack enable; \
|
||||
rm -rf /tmp/nodejs.tar.gz;
|
||||
rm -f /tmp/nodejs.tar.gz /tmp/pnpm.tar.gz;
|
||||
|
||||
################################################################################
|
||||
## OPENCODE SETUP
|
||||
@ -100,7 +109,7 @@ RUN set -eux; \
|
||||
|
||||
FROM base AS setup-opencode
|
||||
|
||||
ENV OPENCODE_VERSION=1.18.25
|
||||
ENV OPENCODE_VERSION=1.18.31
|
||||
|
||||
RUN set -ex; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
@ -121,6 +130,37 @@ RUN set -ex; \
|
||||
chmod +x /tmp/opencode/opencode; \
|
||||
rm -f /tmp/opencode.tar.gz;
|
||||
|
||||
################################################################################
|
||||
## OPENCODE2 SETUP (V2, side-by-side with V1 `opencode`)
|
||||
################################################################################
|
||||
|
||||
FROM base AS setup-opencode2
|
||||
|
||||
ENV OPENCODE2_VERSION=2.0.12
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-${OPENCODE2_VERSION}.tgz"; \
|
||||
ESUM='33f0dde9f0f055b6a3665d290371bcfc8c63daceade7a535c4b94d386f2bd917'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${OPENCODE2_VERSION}.tgz"; \
|
||||
ESUM='2a79beb2e24382cb2bdbb709237dbc22f498cf52a106d029a6a3a3243760c78b'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/opencode2.tgz "${BINARY_URL}"; \
|
||||
echo "${ESUM} */tmp/opencode2.tgz" | sha256sum -c -; \
|
||||
mkdir -p /tmp/opencode2; \
|
||||
tar -xzf /tmp/opencode2.tgz -C /tmp/opencode2 --strip-components=2 package/bin; \
|
||||
mv /tmp/opencode2/opencode /tmp/opencode2/opencode2; \
|
||||
chmod +x /tmp/opencode2/opencode2; \
|
||||
rm -f /tmp/opencode2.tgz;
|
||||
|
||||
|
||||
################################################################################
|
||||
## CADDYSERVER SETUP
|
||||
@ -166,12 +206,12 @@ RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
ESUM='5b222fce0b7076a10ac7ae3b1009a6c2caf4f35bc4e81de72010af6750c5e146'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_aarch64.tar.gz'; \
|
||||
ESUM='9fa5bf865783c43840101fcdc3a5222b60daf4fe11f26806247bf807c08aba38'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu27.28.101-ca-jdk27.0.0-linux_aarch64.tar.gz'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
ESUM='4b7c114917aebd0fc6284fc7111245d7747a4d9603bd12d86b384b1abc9d575d'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_x64.tar.gz'; \
|
||||
ESUM='ccbc15c4edbedfdcc03c2d29a2aa2c6daf9e6ffb4cda7dd5ece0fcbb37350267'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu27.28.101-ca-jdk27.0.0-linux_x64.tar.gz'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@ -242,16 +282,17 @@ RUN set -eux; \
|
||||
|
||||
FROM base AS setup-utils
|
||||
|
||||
ENV CLJKONDO_VERSION=2026.07.24 \
|
||||
BABASHKA_VERSION=1.13.219 \
|
||||
ENV CLJKONDO_VERSION=2026.08.04 \
|
||||
BABASHKA_VERSION=1.13.223 \
|
||||
CLJFMT_VERSION=0.16.5 \
|
||||
PIXI_VERSION=0.75.0 \
|
||||
GITHUB_CLI_VERSION=2.97.0 \
|
||||
UV_VERSION=0.12.1 \
|
||||
PIXI_VERSION=0.81.0 \
|
||||
GITHUB_CLI_VERSION=2.101.0 \
|
||||
AWS_CLI_VERSION=2.36.49 \
|
||||
UV_VERSION=0.12.17 \
|
||||
UV_TOOL_DIR=/opt/uv/tools \
|
||||
UV_TOOL_BIN_DIR=/opt/utils/bin \
|
||||
UV_PYTHON_INSTALL_DIR=/opt/uv/python \
|
||||
SERENA_VERSION=1.6.1
|
||||
SERENA_VERSION=1.7.0
|
||||
|
||||
RUN set -ex; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
@ -356,24 +397,30 @@ RUN set -ex; \
|
||||
tar -xv --strip-components=1 -f /tmp/gh.tar.gz; \
|
||||
rm -rf /tmp/gh.tar.gz;
|
||||
|
||||
# Install minio client
|
||||
# Install AWS CLI
|
||||
RUN set -ex; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://dl.min.io/client/mc/release/linux-arm64/mc"; \
|
||||
BINARY_ARCH="aarch64"; \
|
||||
BINARY_SHA256="2dec26fdaff2f5e34961ea98f456297104f73d8409fc3599c8f2909c6e27a07e"; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://dl.min.io/client/mc/release/linux-amd64/mc"; \
|
||||
BINARY_ARCH="x86_64"; \
|
||||
BINARY_SHA256="6b51b62d232217350e72d8f2493f47d3461239d011d1d5eef960dfcd8cb07069"; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
wget -O /tmp/mc ${BINARY_URL}; \
|
||||
mv /tmp/mc /opt/utils/bin/; \
|
||||
chmod +x /opt/utils/bin/mc;
|
||||
BINARY_URL="https://awscli.amazonaws.com/awscli-exe-linux-${BINARY_ARCH}-${AWS_CLI_VERSION}.zip"; \
|
||||
curl -LfsSo /tmp/awscliv2.zip ${BINARY_URL}; \
|
||||
echo "${BINARY_SHA256} */tmp/awscliv2.zip" | sha256sum -c -; \
|
||||
cd /tmp; \
|
||||
unzip -q awscliv2.zip; \
|
||||
./aws/install --install-dir /opt/utils/aws-cli --bin-dir /opt/utils/bin; \
|
||||
rm -rf /tmp/aws /tmp/awscliv2.zip;
|
||||
|
||||
# Install uv
|
||||
RUN set -ex; \
|
||||
@ -503,7 +550,7 @@ RUN set -ex; \
|
||||
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc; \
|
||||
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt trixie-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy install postgresql-client-16; \
|
||||
apt-get -qqy install postgresql-client-18; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
@ -540,6 +587,7 @@ COPY --from=setup-rust /opt/rustup /opt/rustup
|
||||
COPY --from=setup-rust /opt/emsdk /opt/emsdk
|
||||
COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy
|
||||
COPY --from=setup-opencode /tmp/opencode/opencode /opt/utils/bin/opencode
|
||||
COPY --from=setup-opencode2 /tmp/opencode2/opencode2 /opt/utils/bin/opencode2
|
||||
|
||||
COPY files/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY files/nginx-mime.types /etc/nginx/mime.types
|
||||
@ -560,4 +608,3 @@ COPY files/init.sh /home/init.sh
|
||||
|
||||
ENTRYPOINT ["/home/entrypoint.sh"]
|
||||
CMD ["/home/init.sh"]
|
||||
|
||||
|
||||
@ -10,11 +10,12 @@
|
||||
|
||||
# Container names and volume names. Volumes are pinned by explicit name
|
||||
# (rather than relying on COMPOSE_PROJECT_NAME prefixing) so the physical
|
||||
# volumes survive project renames without a data migration. ws0 reuses the
|
||||
# pre-Stage-2 physical volume names (penpotdev_*).
|
||||
# volumes survive project renames. Replacement services use new volume names
|
||||
# so data from the previous service remains available for rollback.
|
||||
PENPOT_MAIN_CONTAINER_NAME=penpot-devenv-ws0-main
|
||||
PENPOT_POSTGRES_DATA_VOLUME=penpotdev_postgres_data_pg16
|
||||
PENPOT_MINIO_DATA_VOLUME=penpotdev_minio_data
|
||||
PENPOT_POSTGRES_DATA_VOLUME=penpotdev_postgres_data_pg18
|
||||
PENPOT_RUSTFS_DATA_VOLUME=penpotdev_rustfs_data
|
||||
PENPOT_MAILPIT_DATA_VOLUME=penpotdev_mailpit_data
|
||||
PENPOT_USER_DATA_VOLUME=penpotdev_user_data
|
||||
|
||||
# Backend runtime config (passed to the container env block). Valkey is a
|
||||
@ -28,12 +29,14 @@ PENPOT_DATABASE_PASSWORD=penpot
|
||||
PENPOT_DATABASE_MAX_POOL_SIZE=20
|
||||
PENPOT_REDIS_URI=redis://valkey/0
|
||||
|
||||
# Object storage (MinIO user/policy are provisioned by the infra compose file).
|
||||
# Object storage (RustFS uses these credentials for its root S3 user).
|
||||
PENPOT_OBJECTS_STORAGE_BACKEND=s3
|
||||
PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
|
||||
PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://rustfs:9000
|
||||
PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
|
||||
PENPOT_OBJECTS_STORAGE_S3_REGION=us-east-1
|
||||
AWS_ACCESS_KEY_ID=penpot-devenv
|
||||
AWS_SECRET_ACCESS_KEY=penpot-devenv
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
|
||||
# Published host ports. Only ports that need to be reachable from outside the
|
||||
# container are exposed; everything else (frontend dev server, backend API,
|
||||
|
||||
@ -4,53 +4,47 @@ networks:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
postgres_data_pg16:
|
||||
postgres_data_pg18:
|
||||
name: ${PENPOT_POSTGRES_DATA_VOLUME}
|
||||
minio_data:
|
||||
name: ${PENPOT_MINIO_DATA_VOLUME}
|
||||
rustfs_data:
|
||||
name: ${PENPOT_RUSTFS_DATA_VOLUME}
|
||||
mailpit_data:
|
||||
name: ${PENPOT_MAILPIT_DATA_VOLUME}
|
||||
valkey_data:
|
||||
name: penpotdev_valkey_data
|
||||
|
||||
services:
|
||||
minio:
|
||||
image: "minio/minio:RELEASE.2025-04-03T14-56-28Z"
|
||||
command: minio server /mnt/data --console-address ":9001"
|
||||
rustfs:
|
||||
image: "rustfs/rustfs:1.0.0"
|
||||
command: /data
|
||||
|
||||
volumes:
|
||||
- "minio_data:/mnt/data"
|
||||
- "rustfs_data:/data"
|
||||
|
||||
environment:
|
||||
- MINIO_ROOT_USER=minioadmin
|
||||
- MINIO_ROOT_PASSWORD=minioadmin
|
||||
- RUSTFS_ACCESS_KEY=${AWS_ACCESS_KEY_ID}
|
||||
- RUSTFS_SECRET_KEY=${AWS_SECRET_ACCESS_KEY}
|
||||
- RUSTFS_ADDRESS=:9000
|
||||
- RUSTFS_CONSOLE_ADDRESS=:9001
|
||||
- RUSTFS_CONSOLE_ENABLE=true
|
||||
|
||||
ports:
|
||||
- "127.0.0.1:9000:9000"
|
||||
- "127.0.0.1:9001:9001"
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "http://localhost:9000/health"]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
- minio
|
||||
|
||||
minio-setup:
|
||||
image: "minio/mc:latest"
|
||||
depends_on:
|
||||
- minio
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
attempts=0
|
||||
until mc alias set penpot-s3 http://minio:9000 minioadmin minioadmin -q; do
|
||||
attempts=$$((attempts + 1))
|
||||
if [ "$$attempts" -ge 30 ]; then
|
||||
echo "minio-setup: gave up waiting for MinIO after $$attempts attempts" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
mc admin user info penpot-s3 penpot-devenv >/dev/null 2>&1 || mc admin user add penpot-s3 penpot-devenv penpot-devenv -q
|
||||
mc admin policy attach penpot-s3 readwrite --user=penpot-devenv -q
|
||||
networks:
|
||||
default:
|
||||
- rustfs
|
||||
|
||||
postgres:
|
||||
image: postgres:16.8
|
||||
image: postgres:18.6
|
||||
command: postgres -c config_file=/etc/postgresql.conf
|
||||
restart: always
|
||||
stop_signal: SIGINT
|
||||
@ -62,7 +56,12 @@ services:
|
||||
volumes:
|
||||
- ./files/postgresql.conf:/etc/postgresql.conf:z
|
||||
- ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql:z
|
||||
- postgres_data_pg16:/var/lib/postgresql/data
|
||||
- postgres_data_pg18:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 2s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
@ -80,12 +79,24 @@ services:
|
||||
- valkey
|
||||
|
||||
mailer:
|
||||
image: sj26/mailcatcher:latest
|
||||
image: axllent/mailpit:v1.31.1
|
||||
restart: always
|
||||
volumes:
|
||||
- "mailpit_data:/data"
|
||||
environment:
|
||||
- MP_DATABASE=/data/mailpit.db
|
||||
- MP_MAX_MESSAGES=5000
|
||||
- MP_DISABLE_VERSION_CHECK=true
|
||||
expose:
|
||||
- '1025'
|
||||
ports:
|
||||
- "1080:1080"
|
||||
- "127.0.0.1:1080:8025"
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8025/readyz"]
|
||||
interval: 2s
|
||||
timeout: 5s
|
||||
retries: 15
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
||||
@ -84,8 +84,10 @@ services:
|
||||
- PENPOT_OBJECTS_STORAGE_BACKEND=${PENPOT_OBJECTS_STORAGE_BACKEND}
|
||||
- PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}
|
||||
- PENPOT_OBJECTS_STORAGE_S3_BUCKET=${PENPOT_OBJECTS_STORAGE_S3_BUCKET}
|
||||
- PENPOT_OBJECTS_STORAGE_S3_REGION=${PENPOT_OBJECTS_STORAGE_S3_REGION}
|
||||
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
|
||||
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
|
||||
- AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
|
||||
- PENPOT_TENANT=${PENPOT_TENANT}
|
||||
- PENPOT_TMUX_ATTACH=${PENPOT_TMUX_ATTACH}
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
CREATE DATABASE penpot_test;
|
||||
CREATE DATABASE penpot_telemetry;
|
||||
CREATE DATABASE penpot_nexus;
|
||||
|
||||
@ -91,9 +91,35 @@ COPY --from=penpotapp/imagemagick:7.1.2-27 /opt/imagick /opt/imagick
|
||||
|
||||
WORKDIR /opt/penpot/exporter
|
||||
|
||||
# DHI Node image installs Node at the system level (symlinked into
|
||||
# /usr/bin), so `./setup`'s internal `corepack enable` needs root to write
|
||||
# there. Ownership is fixed right after.
|
||||
# pnpm ships as a system binary (same tarball + SHA pin as
|
||||
# docker/devenv/Dockerfile); the generated ./setup expects it on PATH.
|
||||
# Corepack is gone from Node 25+, so nothing here may use it.
|
||||
ARG PNPM_VERSION=12.5.1
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/pnpm.tar.gz "https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \
|
||||
echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \
|
||||
tar -xzf /tmp/pnpm.tar.gz -C /usr/local/bin pnpm; \
|
||||
chmod 755 /usr/local/bin/pnpm; \
|
||||
rm -f /tmp/pnpm.tar.gz; \
|
||||
pnpm --version;
|
||||
|
||||
# `./setup` only needs pnpm on PATH (installed above as root).
|
||||
# Ownership is fixed right after.
|
||||
RUN ./setup && chown -R penpot:penpot /opt/penpot/exporter
|
||||
|
||||
USER penpot:penpot
|
||||
|
||||
@ -8,6 +8,36 @@ COPY $BUNDLE_PATH /opt/penpot/mcp/
|
||||
|
||||
WORKDIR /opt/penpot/mcp
|
||||
|
||||
# pnpm ships as a system binary (same tarball + SHA pin as
|
||||
# docker/devenv/Dockerfile); ./setup expects it on PATH.
|
||||
# Corepack is gone from Node 25+, so nothing here may use it.
|
||||
ARG PNPM_VERSION=12.5.1
|
||||
RUN set -eux; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy --no-install-recommends install curl ca-certificates; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/pnpm.tar.gz "https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \
|
||||
echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \
|
||||
tar -xzf /tmp/pnpm.tar.gz -C /usr/local/bin pnpm; \
|
||||
chmod 755 /usr/local/bin/pnpm; \
|
||||
rm -f /tmp/pnpm.tar.gz; \
|
||||
pnpm --version;
|
||||
|
||||
RUN ./setup
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
NODE_VERSION=v24.20.0 \
|
||||
PNPM_VERSION=12.5.1 \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
|
||||
@ -56,9 +57,13 @@ RUN set -eux; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||
PNPM_ARCH='arm64'; \
|
||||
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
||||
PNPM_ARCH='x64'; \
|
||||
PNPM_SHA256='5a397dfb6b3d4b07d3d7769586aeb471048faf04299a492e2808b95a9a1c701f'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@ -71,7 +76,13 @@ RUN set -eux; \
|
||||
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
||||
chown -R root /opt/node; \
|
||||
rm -rf /tmp/nodejs.tar.gz; \
|
||||
corepack enable; \
|
||||
PNPM_URL="https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}.tar.gz"; \
|
||||
curl -LfsSo /tmp/pnpm.tar.gz "${PNPM_URL}"; \
|
||||
echo "${PNPM_SHA256} */tmp/pnpm.tar.gz" | sha256sum -c -; \
|
||||
tar -xzf /tmp/pnpm.tar.gz -C /opt/node/bin pnpm; \
|
||||
chmod 755 /opt/node/bin/pnpm; \
|
||||
rm -f /tmp/pnpm.tar.gz; \
|
||||
pnpm --version; \
|
||||
mkdir -p /opt/penpot; \
|
||||
chown -R penpot:penpot /opt/penpot;
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ To view this site locally, first set up the environment:
|
||||
# only if necessary
|
||||
nvm install
|
||||
nvm use
|
||||
corepack enable
|
||||
|
||||
pnpm install
|
||||
```
|
||||
|
||||
@ -39,5 +39,5 @@
|
||||
"markdown-it-anchor": "^9.2.1",
|
||||
"markdown-it-plantuml": "^1.4.1"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f"
|
||||
}
|
||||
|
||||
131
docs/pnpm-lock.yaml
generated
131
docs/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -2,9 +2,6 @@
|
||||
|
||||
set -ex
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
|
||||
rm -rf ./_dist
|
||||
pnpm install
|
||||
pnpm run build
|
||||
|
||||
@ -62,7 +62,7 @@ See the dedicated section [Agentic Dev Environment](../agentic-devenv/) for deta
|
||||
### Parallel workspaces
|
||||
|
||||
The devenv runs as separate compose projects:
|
||||
* shared infra (`penpotdev-infra`: Postgres, MinIO, mailer, LDAP)
|
||||
* shared infra (`penpotdev-infra`: Postgres, RustFS, Valkey, mailer, LDAP)
|
||||
* `penpotdev-wsN` project per runtime instance.
|
||||
- `ws0` (a.k.a. `main`) is the current state of your repo;
|
||||
- `ws1` and up are clones that you maintain explicitly under `${PENPOT_WORKSPACES_DIR}/wsN/`
|
||||
@ -168,7 +168,7 @@ the container's data volume regardless of this flag.
|
||||
|
||||
### Shared state and workers
|
||||
|
||||
All instances share one Penpot database and one MinIO bucket; users, teams,
|
||||
All instances share one Penpot database and one RustFS bucket; users, teams,
|
||||
files, and MCP tokens are visible from every instance. Per-instance Valkey
|
||||
keeps msgbus Pub/Sub channels (collab broadcasts, team-org notifications,
|
||||
file-summary cache, rate-limit counters) isolated.
|
||||
@ -176,7 +176,7 @@ file-summary cache, rate-limit counters) isolated.
|
||||
Background workers (`enable-backend-worker`) run only on ws0 — ws1+ overlays
|
||||
disable it. ws1+ RPC handlers still enqueue tasks into the shared Postgres
|
||||
`task` table; ws0's dispatcher claims them via `FOR UPDATE SKIP LOCKED` and
|
||||
runs them against the shared DB and MinIO. Workers are fire-and-forget:
|
||||
runs them against the shared DB and RustFS. Workers are fire-and-forget:
|
||||
`wrk/submit!` inserts a row and returns; RPC handlers never wait on
|
||||
completion. The "ws0 only" policy avoids multi-instance worker races (cron
|
||||
dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across
|
||||
@ -190,7 +190,8 @@ Shared infrastructure shuts down only when no instances remain running.
|
||||
The devenv compose configuration has been split into two files and reorganized
|
||||
into separate compose projects per runtime instance:
|
||||
|
||||
- `docker/devenv/docker-compose.infra.yml` (Postgres, MinIO, mailer, LDAP)
|
||||
- `docker/devenv/docker-compose.infra.yml` (Postgres, RustFS, Valkey, mailer,
|
||||
LDAP)
|
||||
runs under the compose project `penpotdev-infra`.
|
||||
- `docker/devenv/docker-compose.main.yml` (one main container + its Valkey)
|
||||
runs once per runtime instance under `penpotdev-ws0`, `penpotdev-ws1`, ….
|
||||
@ -202,10 +203,17 @@ into separate compose projects per runtime instance:
|
||||
If you had the devenv running on the previous single-project (`penpotdev`)
|
||||
layout, leftover containers and the auto-generated `penpotdev_default`
|
||||
network must be removed before bringing the new ws0 instance up. The named
|
||||
data volumes (`penpotdev_postgres_data_pg16`, `penpotdev_minio_data`,
|
||||
`penpotdev_user_data`, `penpotdev_valkey_data`) are pinned by explicit
|
||||
`name:` entries in the new compose files and are preserved through the
|
||||
transition — your Postgres DB, MinIO objects, and home cache survive.
|
||||
data volumes (`penpotdev_postgres_data_pg18`, `penpotdev_rustfs_data`,
|
||||
`penpotdev_mailpit_data`, `penpotdev_user_data`, `penpotdev_valkey_data`) are
|
||||
pinned by explicit `name:` entries in the new compose files. The legacy
|
||||
`penpotdev_postgres_data_pg16` and `penpotdev_minio_data` volumes remain
|
||||
untouched. PostgreSQL 16 data and MinIO objects are not migrated automatically.
|
||||
|
||||
PostgreSQL 18 stores its versioned data directory under
|
||||
`/var/lib/postgresql/18/docker`, so the devenv mounts its volume at
|
||||
`/var/lib/postgresql`. To retain data from PostgreSQL 16, export and restore it
|
||||
with `pg_dump` and `pg_restore`; do not mount the PostgreSQL 16 volume directly
|
||||
in the PostgreSQL 18 container.
|
||||
|
||||
One-time cleanup, then bring up ws0:
|
||||
|
||||
@ -388,15 +396,29 @@ An example of your cursor configuration can be:
|
||||
}
|
||||
```
|
||||
|
||||
## Object storage
|
||||
|
||||
The devenv uses RustFS for S3-compatible object storage. Its API is available
|
||||
at [http://localhost:9000](http://localhost:9000), and its management console
|
||||
is available at [http://localhost:9001](http://localhost:9001). Log in to the
|
||||
console with `penpot-devenv` as both the access key and secret key.
|
||||
|
||||
Both ports bind only to the host loopback interface and are not exposed to the
|
||||
local network.
|
||||
|
||||
## Email
|
||||
|
||||
To test email sending, the devenv includes [MailCatcher](https://mailcatcher.me/),
|
||||
a SMTP server that is used for develop. It does not send any mail outbounds.
|
||||
Instead, it stores them in memory and allows to browse them via a web interface
|
||||
similar to a webmail client. Simply navigate to:
|
||||
To test email sending, the devenv includes
|
||||
[Mailpit](https://mailpit.axllent.org/), an SMTP server for development. It does
|
||||
not send mail externally. Instead, it stores messages in a persistent Docker
|
||||
volume and provides a webmail-like interface. Simply navigate to:
|
||||
|
||||
[http://localhost:1080](http://localhost:1080)
|
||||
|
||||
The inbox persists when the container is recreated. `drop-devenv` preserves
|
||||
the `penpotdev_mailpit_data` volume, together with the other devenv data
|
||||
volumes.
|
||||
|
||||
## Create user
|
||||
|
||||
You can register a new user manually, or create new users automatically with this script. From your tmux instance, run:
|
||||
|
||||
@ -356,7 +356,7 @@ npx playwright test --ui
|
||||
|
||||
> ❗️ **IMPORTANT**: You might need to [install Playwright's browsers and dependencies](https://playwright.dev/docs/intro) in your host machine with: <code class="language-bash">npx playwright install --with-deps</code>. In case you are using a Linux distribution other than Ubuntu, [you might need to install the dependencies manually](https://github.com/microsoft/playwright/issues/11122).
|
||||
|
||||
> You will also need pnpm in your host nodejs. For this, do <code class="language-bash">corepack enable</code> and then just <code class="language-bash">pnpm</code>.
|
||||
> You will also need pnpm in your host nodejs (the devenv already ships it; outside it, run <code class="language-bash">npm install -g pnpm</code>) and then just <code class="language-bash">pnpm</code>.
|
||||
|
||||
### How to write a test
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
@ -30,7 +30,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
|
||||
"watch:app": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main",
|
||||
"build:wasm": "exit 0",
|
||||
"watch:app": "pnpm run build:wasm && pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main",
|
||||
"watch": "pnpm run watch:app",
|
||||
"build:app": "clojure -M:dev:shadow-cljs release main",
|
||||
"build:wasm": "../render-wasm/build export",
|
||||
|
||||
131
exporter/pnpm-lock.yaml
generated
131
exporter/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -5,8 +5,6 @@ set -ex
|
||||
export CURRENT_VERSION=$1;
|
||||
export NODE_ENV=production;
|
||||
|
||||
corepack enable;
|
||||
corepack install || exit 1;
|
||||
pnpm install || exit 1;
|
||||
pnpm run build:wasm;
|
||||
|
||||
@ -35,8 +33,7 @@ cp "$WASM_SRC/render-wasm.js" "$WASM_SRC/render-wasm.wasm" target/$WASM_SRC/;
|
||||
cat <<EOF | tee target/setup
|
||||
#/usr/bin/env bash
|
||||
set -e;
|
||||
corepack enable;
|
||||
corepack install;
|
||||
command -v pnpm >/dev/null || { echo "error: pnpm not found in PATH" >&2; exit 1; };
|
||||
pnpm install
|
||||
pnpm exec playwright install chromium;
|
||||
EOF
|
||||
|
||||
@ -2,7 +2,5 @@
|
||||
|
||||
set -e;
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm exec playwright install chromium
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm run test;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "Penpot Draft-JS Wrapper",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "Simple library for handling keyboard shortcuts",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"author": "Craig Campbell",
|
||||
"license": "Apache-2.0 WITH LLVM-exception"
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js"
|
||||
|
||||
131
frontend/pnpm-lock.yaml
generated
131
frontend/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -18,8 +18,6 @@ export NODE_ENV=production;
|
||||
|
||||
rm -rf node_modules;
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
|
||||
rm -rf target/dist;
|
||||
@ -43,7 +41,6 @@ popd;
|
||||
pushd ../plugins
|
||||
rm -rf node_modules;
|
||||
rm -rf dist/apps/;
|
||||
corepack install;
|
||||
pnpm -r install;
|
||||
pnpm run build:plugins;
|
||||
popd
|
||||
|
||||
@ -12,8 +12,6 @@ export VERSION_TAG="${VERSION}-${BUILD_TS}";
|
||||
|
||||
export NODE_ENV=production;
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
|
||||
pnpm run build:storybook;
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm exec playwright install chromium;
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
|
||||
# Build render wasm binary
|
||||
|
||||
@ -4,7 +4,5 @@ TARGET=${1:-app};
|
||||
|
||||
set -ex
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
pnpm run watch:$TARGET
|
||||
|
||||
@ -28,5 +28,5 @@
|
||||
"vite": "^8.2.0",
|
||||
"vitest": "^4.1.10"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f"
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"version": "1.2.0-RC1",
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
131
library/pnpm-lock.yaml
generated
131
library/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm install;
|
||||
|
||||
pnpm run build:bundle;
|
||||
|
||||
17
manage.sh
17
manage.sh
@ -209,9 +209,7 @@ function ensure-devenv-network {
|
||||
|
||||
# Compose-project plumbing for the parallel-workspaces layout.
|
||||
#
|
||||
# - Shared infrastructure (postgres, minio, mailer, ldap, minio-setup) runs
|
||||
# under project `penpotdev-infra`.
|
||||
# - Shared infrastructure (postgres, minio, mailer, ldap, valkey, minio-setup)
|
||||
# - Shared infrastructure (postgres, RustFS, mailer, LDAP, Valkey)
|
||||
# runs under project `penpotdev-infra`.
|
||||
# - Each runtime instance (ws0, ws1, ...) runs only its own main container
|
||||
# under project `penpotdev-wsN`. All workspaces uniformly overlay their
|
||||
@ -296,16 +294,11 @@ function devenv-main-running {
|
||||
[[ -n "$container" ]] && [[ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" = "true" ]]
|
||||
}
|
||||
|
||||
# Bring shared infra up and block until minio-setup has provisioned the
|
||||
# shared MinIO user/policy. Idempotent: a second call when everything is
|
||||
# already up returns immediately.
|
||||
# Bring shared infra up and block until services with healthchecks are healthy.
|
||||
# Removing orphaned containers retires old infra services without deleting
|
||||
# their named volumes.
|
||||
function ensure-infra-up {
|
||||
infra-compose up -d
|
||||
local setup_container
|
||||
setup_container=$(infra-compose ps -aq minio-setup 2>/dev/null)
|
||||
if [[ -n "$setup_container" ]]; then
|
||||
docker wait "$setup_container" >/dev/null 2>&1 || true
|
||||
fi
|
||||
infra-compose up -d --wait --wait-timeout 60 --remove-orphans
|
||||
}
|
||||
|
||||
# Refuse to sync workspaces if the live repo is in a fragile Git state.
|
||||
|
||||
@ -68,7 +68,7 @@ Once the servers are running, continue with step 2.
|
||||
|
||||
#### Running the Source Version from the Repository
|
||||
|
||||
The tools `corepack` and `npx` should be available in your terminal.
|
||||
The tools `pnpm` and `npx` should be available in your terminal.
|
||||
|
||||
On Windows, use the Git Bash terminal to ensure compatibility with the provided scripts.
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot.git"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"devDependencies": {
|
||||
"concurrently": "^10.0.5",
|
||||
"prettier": "^3.9.6"
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "Shared type definitions and interfaces for Penpot MCP",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"scripts": {
|
||||
"build": "tsc --build --clean && tsc --build",
|
||||
"watch": "tsc --watch",
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"scripts": {
|
||||
"start": "vite build --watch --config vite.config.ts",
|
||||
"start:multi-user": "pnpm run start",
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
|
||||
131
mcp/pnpm-lock.yaml
generated
131
mcp/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -9,9 +9,6 @@ echo "Preparing bundle for types from $URL"
|
||||
|
||||
set -ex
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
|
||||
# Ensure clean working directory
|
||||
rm -rf dist;
|
||||
rm -rf node_modules;
|
||||
@ -37,8 +34,7 @@ touch ./dist/pnpm-workspace.yaml;
|
||||
cat <<EOF | tee ./dist/setup
|
||||
#/usr/bin/env bash
|
||||
set -e;
|
||||
corepack enable;
|
||||
corepack install;
|
||||
command -v pnpm >/dev/null || { echo "error: pnpm not found in PATH" >&2; exit 1; };
|
||||
pnpm install -P
|
||||
EOF
|
||||
|
||||
|
||||
@ -2,6 +2,4 @@
|
||||
|
||||
set -e;
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
pnpm -r install;
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
"fmt:check": "prettier --check src/ test/",
|
||||
"clean": "rm -rf dist/"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"dependencies": {
|
||||
"express": "^5.2.1",
|
||||
"multer": "^2.2.0",
|
||||
|
||||
131
media-processor/pnpm-lock.yaml
generated
131
media-processor/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.3.4
|
||||
version: 12.3.4
|
||||
specifier: 12.5.1
|
||||
version: 12.5.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6LGBmQuDzUmgpHSuy+haG0jrmkwFNa4EuShDZqK3cIBv4Wfb75CzjJmowkzr+iJg40vnBKMQ62t4Ko6mwsId7A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
resolution: {integrity: sha512-091oCltP3Rx89yxL1Wh5rXkUXEyFzyxIzSHKRFDyDFlQkb8Ox8FaDcm+M8PNkMyqnLA+qeGOLzY7Rjj9izl1Ww==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-r1vEMQPVjRexZMyL2HonxJoo3+nuB/+G+tg+f2PK6faRdXX1TDwevKXOxJAfqlPInTy6W/pm912+s+m0NcR4BQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
resolution: {integrity: sha512-sFUMHiYFs6q5syFwgaVppzhQSmqnXoJIz/YwTXIOXTDE76ndIpTRpDGAnBCBKjsaEBnqjJA/2l6hpJcNDNoMiA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-zupuxYZBuBEGjUz805/4/Asq01UNziFADANt8yp+LdUjU9bbMa3xY78Bx1PsGDqBy7aiad4sdUMK8ZxH4n4gmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
resolution: {integrity: sha512-Gg3maDzaE/ZFMV9HIx/BwMN0Y6QbEs13URfxmiYHtKjjLAs5wEQZRMUP87YNLYO7cNaEfbM8GRbS9vPosyD9Gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
resolution: {integrity: sha512-6fgWxXYMBMsWQ8DAiRevscX4QZQjwh+FsyCj8ZOk8pMXvqeq84MqBLCmS6KckvnbdqJ+HlSVQ7fs/kd0ldwfhA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
resolution: {integrity: sha512-nzMZruRvxDhEfYUhzu+7EmM39+82Dgagc/6hPFPqGqzXNGcIG0anuxbUxfeNtI64SHt91KuuYUmCB/inlM+Pag==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
resolution: {integrity: sha512-kHCbegdTNHpzmPtUD4S5fqkVkqgdQR47spF9p2VPP3dIIkZl6Ytk8w0vhlAsAGgge8FmucfwX+vvPKgm0NtOCQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.3.4:
|
||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
||||
pnpm@12.5.1:
|
||||
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
||||
'@pnpm/exe.android-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.3.4':
|
||||
'@pnpm/exe.android-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
||||
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.3.4':
|
||||
'@pnpm/exe.darwin-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
||||
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.3.4':
|
||||
'@pnpm/exe.linux-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.3.4':
|
||||
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.3.4:
|
||||
'@pnpm/exe.linux-riscv64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.5.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.5.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.5.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
||||
'@pnpm/exe.darwin-x64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64': 12.3.4
|
||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
||||
'@pnpm/exe.linux-x64': 12.3.4
|
||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
||||
'@pnpm/exe.win32-arm64': 12.3.4
|
||||
'@pnpm/exe.win32-x64': 12.3.4
|
||||
'@pnpm/exe.android-arm64': 12.5.1
|
||||
'@pnpm/exe.android-x64': 12.5.1
|
||||
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||
'@pnpm/exe.darwin-x64': 12.5.1
|
||||
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64': 12.5.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||
'@pnpm/exe.linux-ppc64': 12.5.1
|
||||
'@pnpm/exe.linux-riscv64': 12.5.1
|
||||
'@pnpm/exe.linux-s390x': 12.5.1
|
||||
'@pnpm/exe.linux-x64': 12.5.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.5.1
|
||||
'@pnpm/exe.win32-arm64': 12.5.1
|
||||
'@pnpm/exe.win32-x64': 12.5.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
corepack enable
|
||||
corepack install
|
||||
pnpm install
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC Sucursal en España SL",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
|
||||
@ -13,5 +13,5 @@
|
||||
"lint": "eslint .",
|
||||
"test": "vitest"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f"
|
||||
}
|
||||
|
||||
@ -172,7 +172,9 @@ pnpm --filter composable-test-suite run test:ci
|
||||
|
||||
This builds the in-sandbox entry (`src/ci/headless.ts`) as a single
|
||||
self-executing bundle and hands it to the driver (`ci/run-ci.ts`), which
|
||||
serves the prebuilt frontend bundle via the frontend e2e static server,
|
||||
serves the prebuilt frontend bundle with a zero-dependency static server
|
||||
built into the driver (`ci/static-server.ts`, same bundle on the same port —
|
||||
no `frontend/` install needed),
|
||||
intercepts every backend RPC with Playwright fixtures (no backend, no login),
|
||||
opens the mocked workspace file, injects the bundle directly into the plugin
|
||||
sandbox, and streams each test's result from the page console — failing the
|
||||
@ -182,9 +184,10 @@ backend's only role is persistence, which the mock answers with a canned
|
||||
response.
|
||||
|
||||
Prerequisites: the frontend bundle must exist at `frontend/resources/public`
|
||||
(the devenv watch build suffices; CI builds it via `frontend/scripts/build`),
|
||||
and the Playwright browser must be installed
|
||||
(the devenv watch build suffices), and the Playwright browser must be installed
|
||||
(`pnpm --filter composable-test-suite exec playwright install chromium`).
|
||||
In CI the shared E2E workflow (`.github/workflows/tests-e2e.yml`) builds that
|
||||
bundle once per commit and this job restores it; do not add a build step.
|
||||
|
||||
Options via environment variables:
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { chromium, type Page } from "playwright";
|
||||
import { startStaticServer, type StaticServer } from "./static-server.ts";
|
||||
|
||||
// Out-of-sandbox CI driver (Node + Playwright) for the composable test suite,
|
||||
// following the plugin-api-test-suite's CI driver. NOTE on provenance: the mock
|
||||
@ -11,7 +11,8 @@ import { chromium, type Page } from "playwright";
|
||||
// (frontend/playwright, the origin), the plugin-api-test-suite driver, and this
|
||||
// file. If workspace loading changes and this driver times out waiting for the
|
||||
// viewport, diff against those two first. It serves the prebuilt
|
||||
// frontend bundle via the frontend e2e static server, intercepts every backend
|
||||
// frontend bundle with the zero-dependency static server in
|
||||
// `ci/static-server.ts`, intercepts every backend
|
||||
// RPC with Playwright `page.route` (reusing the frontend e2e mock fixtures),
|
||||
// injects the prebuilt `headless.js` bundle into the plugin sandbox via
|
||||
// `globalThis.ɵloadPlugin`, and captures the results from the page console.
|
||||
@ -30,6 +31,7 @@ const here = dirname(fileURLToPath(import.meta.url));
|
||||
// here = <root>/plugins/apps/composable-test-suite/ci
|
||||
const repoRoot = resolve(here, "../../../../");
|
||||
const frontendDir = resolve(repoRoot, "frontend");
|
||||
const staticRoot = resolve(frontendDir, "resources/public");
|
||||
const e2eDataDir = resolve(frontendDir, "playwright/data");
|
||||
|
||||
const BASE_URL = "http://localhost:3000";
|
||||
@ -96,14 +98,15 @@ async function waitForServer(url: string, timeoutMs = 30000): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function startE2eServer(): ChildProcess {
|
||||
// Reuse the frontend e2e static server: it serves frontend/resources/public
|
||||
// on port 3000, which is also the host the app opens its notifications
|
||||
function startE2eServer(): Promise<StaticServer> {
|
||||
// Serve the prebuilt frontend bundle from `frontend/resources/public` on
|
||||
// port 3000, which is also the host the app opens its notifications
|
||||
// WebSocket against — so the WS mock below matches without extra config.
|
||||
return spawn("node", ["scripts/e2e-server.js"], {
|
||||
cwd: frontendDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
// This used to shell out to the express-based
|
||||
// `frontend/scripts/e2e-server.js`, but that resolves `express` from
|
||||
// `frontend/node_modules`, which the CI jobs never install (only
|
||||
// `plugins/` deps), so the driver crashed before serving anything.
|
||||
return startStaticServer(staticRoot, 3000);
|
||||
}
|
||||
|
||||
// Install the frontend e2e WebSocket mock so the workspace's notifications
|
||||
@ -204,7 +207,7 @@ function printReport(results: ReportedResult[]) {
|
||||
async function main() {
|
||||
const bundle = readFileSync(headlessBundlePath, "utf-8");
|
||||
|
||||
const server = startE2eServer();
|
||||
const server = await startE2eServer();
|
||||
await waitForServer(BASE_URL);
|
||||
|
||||
const browser = await chromium.launch();
|
||||
@ -280,7 +283,7 @@ async function main() {
|
||||
]);
|
||||
|
||||
await browser.close();
|
||||
server.kill();
|
||||
await server.close();
|
||||
|
||||
printReport(results);
|
||||
|
||||
|
||||
79
plugins/apps/composable-test-suite/ci/static-server.test.ts
Normal file
79
plugins/apps/composable-test-suite/ci/static-server.test.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { strict as assert } from "node:assert";
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { after, before, describe, it } from "node:test";
|
||||
import { startStaticServer, type StaticServer } from "./static-server.ts";
|
||||
|
||||
describe("static-server", () => {
|
||||
let dir: string = "";
|
||||
let server: StaticServer | undefined;
|
||||
const baseUrl = (): string => {
|
||||
if (!server) throw new Error("static server not started");
|
||||
return server.url;
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
dir = await mkdtemp(join(tmpdir(), "penpot-static-server-"));
|
||||
await mkdir(join(dir, "js"), { recursive: true });
|
||||
await writeFile(join(dir, "index.html"), "<!doctype html><html></html>");
|
||||
await writeFile(join(dir, "js", "app.js"), 'console.log("hi");');
|
||||
await writeFile(join(dir, "data.bin"), Buffer.from([0, 1, 2]));
|
||||
server = await startStaticServer(dir, 0);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server?.close();
|
||||
// Closing twice must be safe (the driver closes unconditionally).
|
||||
await server?.close();
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("serves / as index.html", async () => {
|
||||
const res = await fetch(`${baseUrl()}/`);
|
||||
assert.equal(res.status, 200);
|
||||
assert.match(res.headers.get("content-type") ?? "", /text\/html/);
|
||||
assert.match(await res.text(), /<!doctype html>/);
|
||||
});
|
||||
|
||||
it("serves nested files with a javascript content type", async () => {
|
||||
const res = await fetch(`${baseUrl()}/js/app.js`);
|
||||
assert.equal(res.status, 200);
|
||||
assert.match(res.headers.get("content-type") ?? "", /javascript/);
|
||||
assert.equal(await res.text(), 'console.log("hi");');
|
||||
});
|
||||
|
||||
it("ignores query strings", async () => {
|
||||
const res = await fetch(`${baseUrl()}/index.html?rev=123`);
|
||||
assert.equal(res.status, 200);
|
||||
assert.match(await res.text(), /<!doctype html>/);
|
||||
});
|
||||
|
||||
it("falls back to octet-stream for unknown extensions", async () => {
|
||||
const res = await fetch(`${baseUrl()}/data.bin`);
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.headers.get("content-type"), "application/octet-stream");
|
||||
});
|
||||
|
||||
it("answers HEAD without a body", async () => {
|
||||
const res = await fetch(`${baseUrl()}/index.html`, { method: "HEAD" });
|
||||
assert.equal(res.status, 200);
|
||||
assert.match(res.headers.get("content-type") ?? "", /text\/html/);
|
||||
assert.equal(await res.text(), "");
|
||||
});
|
||||
|
||||
it("rejects other methods", async () => {
|
||||
const res = await fetch(`${baseUrl()}/index.html`, { method: "POST" });
|
||||
assert.equal(res.status, 405);
|
||||
});
|
||||
|
||||
it("returns 404 for missing files", async () => {
|
||||
const res = await fetch(`${baseUrl()}/nope/missing.js`);
|
||||
assert.equal(res.status, 404);
|
||||
});
|
||||
|
||||
it("blocks path traversal outside the root", async () => {
|
||||
const res = await fetch(`${baseUrl()}/..%2f..%2fsecret`);
|
||||
assert.equal(res.status, 403);
|
||||
});
|
||||
});
|
||||
141
plugins/apps/composable-test-suite/ci/static-server.ts
Normal file
141
plugins/apps/composable-test-suite/ci/static-server.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { createServer, type Server } from "node:http";
|
||||
import { readFile, stat } from "node:fs/promises";
|
||||
import { extname, join, resolve, sep } from "node:path";
|
||||
|
||||
// Zero-dependency static file server for the mocked-backend CI driver.
|
||||
//
|
||||
// It replaces `frontend/scripts/e2e-server.js` (express-based) on purpose:
|
||||
// that script resolves `express`/`compression` from `frontend/node_modules`,
|
||||
// which the CI jobs never install (they only run `pnpm install` inside
|
||||
// `plugins/` and restore the prebuilt bundle), so the driver crashed with
|
||||
// ERR_MODULE_NOT_FOUND and timed out waiting for localhost:3000. Serving the
|
||||
// bundle from here keeps the suite runnable with only `plugins/`
|
||||
// dependencies — the documented local workflow — and identical in CI.
|
||||
//
|
||||
// NOTE on provenance: this file is duplicated in
|
||||
// `plugins/apps/plugin-api-test-suite/ci/static-server.ts`. Keep the two in
|
||||
// sync (same as the mock harness in `run-ci.ts`).
|
||||
|
||||
const MIME_TYPES: Record<string, string> = {
|
||||
".css": "text/css; charset=utf-8",
|
||||
".gif": "image/gif",
|
||||
".html": "text/html; charset=utf-8",
|
||||
".ico": "image/x-icon",
|
||||
".jpeg": "image/jpeg",
|
||||
".jpg": "image/jpeg",
|
||||
".js": "application/javascript; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".map": "application/json; charset=utf-8",
|
||||
".mjs": "application/javascript; charset=utf-8",
|
||||
".otf": "font/otf",
|
||||
".png": "image/png",
|
||||
".svg": "image/svg+xml",
|
||||
".ttf": "font/ttf",
|
||||
".txt": "text/plain; charset=utf-8",
|
||||
".wasm": "application/wasm",
|
||||
".webmanifest": "application/manifest+json",
|
||||
".webp": "image/webp",
|
||||
".woff": "font/woff",
|
||||
".woff2": "font/woff2",
|
||||
".xml": "application/xml; charset=utf-8",
|
||||
};
|
||||
|
||||
const INDEX = "index.html";
|
||||
|
||||
export interface StaticServer {
|
||||
/** Base URL the server listens on (e.g. `http://localhost:3000`). */
|
||||
url: string;
|
||||
/** Stop accepting connections; safe to call more than once. */
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve `root` over HTTP on `port` (`0` picks a free port, reported in
|
||||
* `url`). Directory requests fall back to `index.html`; the app uses hash
|
||||
* routing, so no other fallback is needed.
|
||||
*/
|
||||
export function startStaticServer(root: string, port: number): Promise<StaticServer> {
|
||||
const docRoot = resolve(root);
|
||||
const server: Server = createServer(async (req, res) => {
|
||||
try {
|
||||
if (req.method !== "GET" && req.method !== "HEAD") {
|
||||
res.writeHead(405, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
res.end("Method Not Allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
const rawPath = (req.url ?? "/").split(/[?#]/, 1)[0] ?? "/";
|
||||
let pathname: string;
|
||||
try {
|
||||
pathname = decodeURIComponent(rawPath);
|
||||
} catch {
|
||||
res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
res.end("Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
const resolved = resolve(docRoot, `.${sep}${pathname}`);
|
||||
if (resolved !== docRoot && !resolved.startsWith(docRoot + sep)) {
|
||||
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
res.end("Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
let filePath = resolved;
|
||||
const info = await stat(filePath).catch((error: unknown) => {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
||||
throw error;
|
||||
});
|
||||
if (info === null) {
|
||||
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
res.end("Not Found");
|
||||
return;
|
||||
}
|
||||
if (info.isDirectory()) {
|
||||
filePath = join(filePath, INDEX);
|
||||
}
|
||||
|
||||
const body = await readFile(filePath).catch((error: unknown) => {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
||||
throw error;
|
||||
});
|
||||
if (body === null) {
|
||||
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
res.end("Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
||||
res.writeHead(200, {
|
||||
"Content-Type": contentType,
|
||||
"Content-Length": body.length,
|
||||
});
|
||||
res.end(req.method === "GET" ? body : undefined);
|
||||
} catch {
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
}
|
||||
res.end("Internal Server Error");
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((fulfill, reject) => {
|
||||
server.once("error", reject);
|
||||
server.listen(port, "0.0.0.0", () => {
|
||||
server.off("error", reject);
|
||||
const address = server.address();
|
||||
const actualPort = typeof address === "object" && address !== null ? address.port : port;
|
||||
fulfill({
|
||||
url: `http://localhost:${actualPort}`,
|
||||
close: () =>
|
||||
new Promise<void>((done, fail) => {
|
||||
if (!server.listening) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
server.close((error) => (error ? fail(error) : done()));
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"build": "tsc && vite build",
|
||||
"build:headless": "vite build --config vite.config.headless.ts",
|
||||
"test:ci": "pnpm run build:headless && tsx ci/run-ci.ts",
|
||||
"test:unit": "tsx --test ci/static-server.test.ts",
|
||||
"preview": "vite preview",
|
||||
"bootstrap": "pnpm install --ignore-workspace && pnpm run build && pnpm run start",
|
||||
"types:check": "tsc --noEmit",
|
||||
@ -26,5 +27,5 @@
|
||||
"vite": "^8.2.2",
|
||||
"vite-live-preview": "^0.4.0"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f"
|
||||
}
|
||||
|
||||
@ -13,5 +13,5 @@
|
||||
"lint": "eslint .",
|
||||
"test": "vitest"
|
||||
},
|
||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457"
|
||||
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user