mirror of
https://github.com/penpot/penpot.git
synced 2026-09-24 04:46:14 +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 }}
|
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||||
|
|
||||||
# START: Setup Node and PNPM enabling cache
|
# START: Setup Node and PNPM enabling cache
|
||||||
- name: Setup Node.js
|
- name: Setup pnpm + Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: pnpm/setup@v2
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
working-directory: plugins
|
||||||
|
runtime: node@24.21.0
|
||||||
- name: Enable PNPM
|
install: false
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
- name: Get pnpm store path
|
- name: Get pnpm store path
|
||||||
id: pnpm-store
|
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 }}
|
ref: ${{ inputs.gh_ref }}
|
||||||
|
|
||||||
# START: Setup Node and PNPM enabling cache
|
# START: Setup Node and PNPM enabling cache
|
||||||
- name: Setup Node.js
|
- name: Setup pnpm + Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: pnpm/setup@v2
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
working-directory: plugins
|
||||||
|
runtime: node@24.21.0
|
||||||
- name: Enable PNPM
|
install: false
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
- name: Get pnpm store path
|
- name: Get pnpm store path
|
||||||
id: pnpm-store
|
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 }}
|
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||||
|
|
||||||
# START: Setup Node and PNPM enabling cache
|
# START: Setup Node and PNPM enabling cache
|
||||||
- name: Setup Node.js
|
- name: Setup pnpm + Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: pnpm/setup@v2
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
working-directory: plugins
|
||||||
|
runtime: node@24.21.0
|
||||||
- name: Enable PNPM
|
install: false
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
- name: Get pnpm store path
|
- name: Get pnpm store path
|
||||||
id: pnpm-store
|
id: pnpm-store
|
||||||
|
|||||||
2
.github/workflows/tests-common.yml
vendored
2
.github/workflows/tests-common.yml
vendored
@ -44,8 +44,6 @@ jobs:
|
|||||||
- name: Lint
|
- name: Lint
|
||||||
working-directory: ./common
|
working-directory: ./common
|
||||||
run: |
|
run: |
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run check-fmt:clj
|
pnpm run check-fmt:clj
|
||||||
pnpm run check-fmt:js
|
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:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@ -30,12 +52,18 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
default: '2'
|
default: '2'
|
||||||
|
|
||||||
|
base_url:
|
||||||
|
description: 'Penpot base URL for the (disabled) live Plugin API suite'
|
||||||
|
required: false
|
||||||
|
default: 'https://localhost:3449'
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
- 'common/**'
|
- 'common/**'
|
||||||
- 'render-wasm/**'
|
- 'render-wasm/**'
|
||||||
- '.github/workflows/tests-integration.yml'
|
- 'plugins/**'
|
||||||
|
- '.github/workflows/tests-e2e.yml'
|
||||||
|
|
||||||
types:
|
types:
|
||||||
- opened
|
- opened
|
||||||
@ -51,18 +79,21 @@ on:
|
|||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
- 'common/**'
|
- 'common/**'
|
||||||
- 'render-wasm/**'
|
- 'render-wasm/**'
|
||||||
- '.github/workflows/tests-integration.yml'
|
- 'plugins/**'
|
||||||
|
- '.github/workflows/tests-e2e.yml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.gh_ref || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.gh_ref || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-integration:
|
# ── 1. Build the frontend bundle once ──────────────────────────────────
|
||||||
|
build-bundle:
|
||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Build Integration Bundle"
|
name: "Build Integration Bundle"
|
||||||
runs-on: penpot-extended-runner
|
runs-on: penpot-extended-runner
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: penpotapp/devenv:latest
|
image: penpotapp/devenv:latest
|
||||||
volumes:
|
volumes:
|
||||||
@ -85,26 +116,36 @@ jobs:
|
|||||||
- name: Extract cache key
|
- name: Extract cache key
|
||||||
id: vars
|
id: vars
|
||||||
run: |
|
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
|
- name: Build Bundle
|
||||||
|
if: steps.restore.outputs.cache-hit != 'true'
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
run: |
|
run: |
|
||||||
./scripts/build
|
./scripts/build
|
||||||
|
|
||||||
- name: Store Bundle Cache
|
- name: Store Bundle Cache
|
||||||
uses: actions/cache@v5
|
if: steps.restore.outputs.cache-hit != 'true'
|
||||||
|
uses: actions/cache/save@v5
|
||||||
with:
|
with:
|
||||||
key: ${{ steps.vars.outputs.bundle_key }}
|
key: ${{ steps.vars.outputs.bundle_key }}
|
||||||
path: frontend/resources/public
|
path: frontend/resources/public
|
||||||
|
|
||||||
|
# ── 2. Consumers: restore the bundle, never rebuild it ─────────────────
|
||||||
test-integration:
|
test-integration:
|
||||||
if: ${{ !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
name: "Integration Tests (${{ matrix.shard }})"
|
name: "Integration Tests (${{ matrix.shard }})"
|
||||||
runs-on: penpot-extended-runner
|
runs-on: penpot-extended-runner
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
|
|
||||||
needs: build-integration
|
needs: build-bundle
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -129,14 +170,13 @@ jobs:
|
|||||||
- name: Restore Cache
|
- name: Restore Cache
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
key: ${{ needs.build-integration.outputs.bundle_key }}
|
key: ${{ needs.build-bundle.outputs.bundle_key }}
|
||||||
path: frontend/resources/public
|
path: frontend/resources/public
|
||||||
|
fail-on-cache-miss: true
|
||||||
|
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
run: |
|
run: |
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install --frozen-lockfile;
|
pnpm install --frozen-lockfile;
|
||||||
|
|
||||||
# No-op once the shared volume is warm; keeps the first run working.
|
# No-op once the shared volume is warm; keeps the first run working.
|
||||||
@ -177,6 +217,89 @@ jobs:
|
|||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 3
|
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:
|
merge-reports:
|
||||||
if: ${{ !cancelled() && !github.event.pull_request.draft && needs.test-integration.result != 'skipped' }}
|
if: ${{ !cancelled() && !github.event.pull_request.draft && needs.test-integration.result != 'skipped' }}
|
||||||
name: "Merge Integration Reports"
|
name: "Merge Integration Reports"
|
||||||
@ -200,8 +323,6 @@ jobs:
|
|||||||
- name: Install deps
|
- name: Install deps
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
run: |
|
run: |
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install --frozen-lockfile;
|
pnpm install --frozen-lockfile;
|
||||||
|
|
||||||
- name: Download blob reports
|
- name: Download blob reports
|
||||||
@ -245,8 +366,53 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload HTML report
|
- name: Upload HTML report
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: integration-html-report
|
name: integration-html-report
|
||||||
path: frontend/playwright-report/
|
path: frontend/playwright-report/
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 7
|
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
|
- name: Lint
|
||||||
working-directory: ./exporter
|
working-directory: ./exporter
|
||||||
run: |
|
run: |
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run check-fmt:clj
|
pnpm run check-fmt:clj
|
||||||
pnpm run lint: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
|
- name: Lint
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
run: |
|
run: |
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run check-fmt:js
|
pnpm run check-fmt:js
|
||||||
pnpm run check-fmt:clj
|
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
|
- name: Lint
|
||||||
working-directory: ./library
|
working-directory: ./library
|
||||||
run: |
|
run: |
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run check-fmt
|
pnpm run check-fmt
|
||||||
pnpm run lint
|
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
|
working-directory: ./plugins
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install -r;
|
pnpm install -r;
|
||||||
|
|
||||||
- name: Run Lint
|
- 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 { tool } from "@opencode-ai/plugin"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { spawn } from "child_process"
|
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({
|
const penpotPsqlTool = tool({
|
||||||
description:
|
description:
|
||||||
"Execute a SQL command against the Penpot database. Uses the defaults from scripts/psql.",
|
"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) {
|
async execute(args, context) {
|
||||||
const host = process.env.PENPOT_DB_HOST || "postgres"
|
return executePsql(args.sql, args.test === true, context.worktree)
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -82,69 +149,11 @@ const parenRepairTool = tool({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async execute(args, context) {
|
async execute(args, context) {
|
||||||
const script = path.join(context.worktree, "scripts/paren-repair")
|
return executeParenRepair(args, context.worktree)
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default async function plugin() {
|
async function server() {
|
||||||
return {
|
return {
|
||||||
tool: {
|
tool: {
|
||||||
"paren-repair": parenRepairTool,
|
"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 }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
id: "penpot",
|
||||||
|
setup,
|
||||||
|
server,
|
||||||
// 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,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@ -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.
|
- 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.
|
- 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.
|
- 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
|
## 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.
|
- 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.
|
- 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.
|
- 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 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 `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
|
- 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)
|
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 `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.
|
- **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.
|
- `docker/` contains devenv related code, not needed unless specifically instructed.
|
||||||
When working on devenv startup, compose layout, instance config (`defaults.env`),
|
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`.
|
`*-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.
|
- `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.
|
- `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
|
# 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
|
## 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.
|
- `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.
|
- 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.
|
- 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.
|
- `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.
|
- 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.
|
- Compose files use pure `${VAR}` substitution; missing var = compose fails.
|
||||||
|
|
||||||
## Invariants
|
## 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.
|
- `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.
|
- 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`.
|
- `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
|
## 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`.
|
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
|
## Port layout
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ Container-internal ports fixed; host side offset `10000·N`.
|
|||||||
| 14181 | 24181 | … | 14281 | Serena MCP |
|
| 14181 | 24181 | … | 14281 | Serena MCP |
|
||||||
| 14182 | 24182 | … | 24282 | Serena dashboard |
|
| 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
|
## Tmux + MCP routing
|
||||||
|
|
||||||
|
|||||||
@ -138,13 +138,15 @@ Distinguishing abstractions (the OOP articulation of the shared principles):
|
|||||||
control via Playwright; recipe in the README.
|
control via Playwright; recipe in the README.
|
||||||
|
|
||||||
## CI
|
## CI
|
||||||
Headless per-PR gate: `.github/workflows/tests-composable-suite.yml` runs
|
Headless per-PR gate: the `composable-test-suite` job in
|
||||||
`pnpm --filter composable-test-suite run test:ci` — mocked backend (frontend e2e static server +
|
`.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`,
|
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
|
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
|
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;
|
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 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".
|
Details: README, "Running in CI".
|
||||||
|
|
||||||
## Substrate
|
## Substrate
|
||||||
|
|||||||
@ -53,6 +53,7 @@ Diagnostics and validation:
|
|||||||
- Source-edit compile/hot-reload diagnostics: `mem:frontend/compile-diagnostics`.
|
- Source-edit compile/hot-reload diagnostics: `mem:frontend/compile-diagnostics`.
|
||||||
- Runtime crash recovery: `mem:frontend/handling-crashes`.
|
- Runtime crash recovery: `mem:frontend/handling-crashes`.
|
||||||
- Tests and live verification: `mem:frontend/testing`.
|
- 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`.
|
- Cross-cutting testing principles and anti-patterns: `mem:testing`.
|
||||||
- Real pointer/keyboard gesture reproduction: `mem:frontend/playwright-gestures`.
|
- 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.
|
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
|
## 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.
|
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.
|
- **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`.
|
- **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).
|
- **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`.
|
- **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)
|
## Native Tool Available (opencode)
|
||||||
|
|
||||||
A native opencode tool `paren-repair` is available at `.opencode/scripts/paren-repair.ts`.
|
A native opencode tool `paren-repair` is available (defined in
|
||||||
The LLM can call it directly with:
|
`.opencode/plugins/penpot.js`, which registers it for both opencode V1 via
|
||||||
- `files`: Array of file paths to fix
|
`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
|
- `code`: Code string to fix via stdin
|
||||||
|
|
||||||
Example usage by the LLM:
|
Example usage by the LLM:
|
||||||
|
|||||||
@ -27,7 +27,9 @@ scripts/psql --host myhost --user myuser --db mydb
|
|||||||
|
|
||||||
## Native Tool Available (opencode)
|
## 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:
|
with:
|
||||||
- `sql`: SQL command string to execute
|
- `sql`: SQL command string to execute
|
||||||
- `test`: Boolean flag to use the `penpot_test` database
|
- `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
|
- The store survives `node_modules` cleans. It is content-addressed and
|
||||||
integrity-verified, so it cannot go stale; staleness lives in
|
integrity-verified, so it cannot go stale; staleness lives in
|
||||||
node_modules. Only `scripts/clean-node-modules --store` removes it.
|
node_modules. Only `scripts/clean-node-modules --store` removes it.
|
||||||
- Every `package.json` (about 35 of them) must carry a `packageManager` field
|
- Every first-party `package.json` (35 files: workspace roots plus
|
||||||
with the identical `pnpm@<version>+sha512.<hash>` value. Do not let them drift.
|
members) must carry a `packageManager` field with the identical
|
||||||
- CI pins no pnpm version; workflows rely on corepack reading
|
`pnpm@<version>+sha512.<hash>` value. Do not let them drift.
|
||||||
`packageManager`. Fixing the fields fixes CI.
|
`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
|
## Procedure
|
||||||
|
|
||||||
1. Resolve the target tag first and note the version. Example:
|
1. Resolve the target version first and note it. Example:
|
||||||
`npm view pnpm dist-tags --json` for `next-12` (latest 12.x). The tag
|
`pnpm view pnpm dist-tags --json` for `latest-12` (latest 12.x). The
|
||||||
moves over time; always re-check.
|
tag moves over time; always re-check.
|
||||||
2. List every directory with a `package.json`, excluding `node_modules`
|
2. Bump `PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in
|
||||||
(`fd -H -t f package.json -E node_modules`). This list is the work set;
|
`docker/images/Dockerfile.{media-processor,exporter,mcp}`.
|
||||||
do not maintain a hand-written list.
|
3. From the repo root, on a host whose system pnpm is the target version
|
||||||
3. Run `corepack use pnpm@<tag>` in workspace roots first, then members.
|
(rebuilt devenv), run `scripts/sync-pnpm-version`. It resolves the
|
||||||
`corepack use` stamps `packageManager` in the nearest package.json and
|
integrity hash via `pnpm view` + node (no npm, no corepack, no
|
||||||
runs an install. Member runs repeat the workspace install; after the root
|
python3) and stamps the identical field into all 35 files, replacing
|
||||||
run they are quick no-ops.
|
only the value line (each file keeps its own indent) and inserting
|
||||||
4. If a run fails, fix the cause (see gotchas) and re-run that directory.
|
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
|
## 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 workspace may fail with `ERR_PNPM_IGNORED_BUILDS`, and pnpm then writes
|
||||||
a placeholder scaffold into its `pnpm-workspace.yaml`:
|
a placeholder scaffold into its `pnpm-workspace.yaml`:
|
||||||
`allowBuilds: esbuild: set this to true or false`. Current pnpm writes
|
`allowBuilds: esbuild: set this to true or false`. Current pnpm writes
|
||||||
@ -70,10 +83,12 @@ file (never pipe tool output through filters).
|
|||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
- Every `packageManager` field is byte-identical (same version and hash).
|
- `scripts/sync-pnpm-version --check` passes: every `packageManager`
|
||||||
- `pnpm --version` in each workspace prints the target version.
|
field is byte-identical (same version and hash).
|
||||||
- `pnpm install --frozen-lockfile` succeeds in each of the 11 workspaces.
|
- `pnpm install --frozen-lockfile` succeeds in each of the 11 workspaces.
|
||||||
- `git diff` on lockfiles matches the expectations above.
|
- `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
|
## Cleaning stale node_modules
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC Sucursal en España SL",
|
"author": "Kaleidos INC Sucursal en España SL",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/penpot/penpot"
|
"url": "https://github.com/penpot/penpot"
|
||||||
|
|||||||
131
backend/pnpm-lock.yaml
generated
131
backend/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -97,13 +97,31 @@ export JAVA_OPTS="\
|
|||||||
--enable-native-access=ALL-UNNAMED \
|
--enable-native-access=ALL-UNNAMED \
|
||||||
--add-opens=java.base/java.nio=ALL-UNNAMED";
|
--add-opens=java.base/java.nio=ALL-UNNAMED";
|
||||||
|
|
||||||
function setup_minio() {
|
function setup_s3_bucket() {
|
||||||
if [ "${PENPOT_OBJECTS_STORAGE_BACKEND}" != "s3" ]; then
|
if [ "${PENPOT_OBJECTS_STORAGE_BACKEND}" != "s3" ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Shared MinIO user/policy provisioning is handled by docker-compose.infra.yml.
|
local attempts=0
|
||||||
# Per process startup only ensures that the configured bucket exists.
|
local endpoint="${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}"
|
||||||
mc alias set penpot-s3/ "${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}" minioadmin minioadmin -q
|
local bucket="${PENPOT_OBJECTS_STORAGE_S3_BUCKET}"
|
||||||
mc mb "penpot-s3/${PENPOT_OBJECTS_STORAGE_S3_BUCKET}" -p -q
|
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;
|
source $SCRIPT_DIR/_env.local;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Initialize MINIO config
|
# Ensure the object storage bucket exists.
|
||||||
setup_minio;
|
setup_s3_bucket;
|
||||||
|
|
||||||
export JAVA_OPTS="$JAVA_OPTS -Dlog4j2.configurationFile=log4j2-devenv-repl.xml"
|
export JAVA_OPTS="$JAVA_OPTS -Dlog4j2.configurationFile=log4j2-devenv-repl.xml"
|
||||||
export OPTIONS="-A:jmx-remote -A:dev"
|
export OPTIONS="-A:jmx-remote -A:dev"
|
||||||
|
|||||||
@ -8,6 +8,9 @@ if [ -f $SCRIPT_DIR/_env.local ]; then
|
|||||||
source $SCRIPT_DIR/_env.local;
|
source $SCRIPT_DIR/_env.local;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Ensure the object storage bucket exists.
|
||||||
|
setup_s3_bucket;
|
||||||
|
|
||||||
export OPTIONS="-A:dev"
|
export OPTIONS="-A:dev"
|
||||||
|
|
||||||
entrypoint=${1:-app.main};
|
entrypoint=${1:-app.main};
|
||||||
|
|||||||
@ -7,8 +7,8 @@ if [ -f $SCRIPT_DIR/_env.local ]; then
|
|||||||
source $SCRIPT_DIR/_env.local;
|
source $SCRIPT_DIR/_env.local;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Initialize MINIO config
|
# Ensure the object storage bucket exists.
|
||||||
setup_minio;
|
setup_s3_bucket;
|
||||||
|
|
||||||
shift 1;
|
shift 1;
|
||||||
set -ex
|
set -ex
|
||||||
|
|||||||
@ -75,7 +75,7 @@
|
|||||||
[:map {:title "create-file"}
|
[:map {:title "create-file"}
|
||||||
[:name [:string {:max 250}]]
|
[:name [:string {:max 250}]]
|
||||||
[:project-id ::sm/uuid]
|
[:project-id ::sm/uuid]
|
||||||
[:id {:optional true} ::sm/uuid]
|
[:id {:optional true} ::sm/user-provided-uuid]
|
||||||
[:is-shared {:optional true} ::sm/boolean]
|
[:is-shared {:optional true} ::sm/boolean]
|
||||||
[:features {:optional true} ::cfeat/features]])
|
[:features {:optional true} ::cfeat/features]])
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
(def ^:private schema:upload-file-media-object
|
(def ^:private schema:upload-file-media-object
|
||||||
[:map {:title "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]
|
[:file-id ::sm/uuid]
|
||||||
[:is-local ::sm/boolean]
|
[:is-local ::sm/boolean]
|
||||||
[:name [:string {:max 250}]]
|
[:name [:string {:max 250}]]
|
||||||
@ -225,7 +225,7 @@
|
|||||||
[:file-id ::sm/uuid]
|
[:file-id ::sm/uuid]
|
||||||
[:is-local ::sm/boolean]
|
[:is-local ::sm/boolean]
|
||||||
[:url ::sm/uri]
|
[:url ::sm/uri]
|
||||||
[:id {:optional true} ::sm/uuid]
|
[:id {:optional true} ::sm/user-provided-uuid]
|
||||||
[:name {:optional true} [:string {:max 250}]]])
|
[:name {:optional true} [:string {:max 250}]]])
|
||||||
|
|
||||||
(sv/defmethod ::create-file-media-object-from-url
|
(sv/defmethod ::create-file-media-object-from-url
|
||||||
@ -556,7 +556,7 @@
|
|||||||
[:is-local ::sm/boolean]
|
[:is-local ::sm/boolean]
|
||||||
[:name [:string {:max 250}]]
|
[:name [:string {:max 250}]]
|
||||||
[:mtype :string]
|
[:mtype :string]
|
||||||
[:id {:optional true} ::sm/uuid]])
|
[:id {:optional true} ::sm/user-provided-uuid]])
|
||||||
|
|
||||||
(sv/defmethod ::assemble-file-media-object
|
(sv/defmethod ::assemble-file-media-object
|
||||||
{::doc/added "2.17"
|
{::doc/added "2.17"
|
||||||
|
|||||||
@ -199,7 +199,7 @@
|
|||||||
[:map {:title "create-project"}
|
[:map {:title "create-project"}
|
||||||
[:team-id ::sm/uuid]
|
[:team-id ::sm/uuid]
|
||||||
[:name [:string {:max 250 :min 1}]]
|
[:name [:string {:max 250 :min 1}]]
|
||||||
[:id {:optional true} ::sm/uuid]])
|
[:id {:optional true} ::sm/user-provided-uuid]])
|
||||||
|
|
||||||
(sv/defmethod ::create-project
|
(sv/defmethod ::create-project
|
||||||
{::doc/added "1.18"
|
{::doc/added "1.18"
|
||||||
|
|||||||
@ -523,7 +523,7 @@
|
|||||||
[:map {:title "create-team"}
|
[:map {:title "create-team"}
|
||||||
[:name types.team/schema:team-name]
|
[:name types.team/schema:team-name]
|
||||||
[:features {:optional true} ::cfeat/features]
|
[:features {:optional true} ::cfeat/features]
|
||||||
[:id {:optional true} ::sm/uuid]
|
[:id {:optional true} ::sm/user-provided-uuid]
|
||||||
[:organization-id {:optional true} ::sm/uuid]
|
[:organization-id {:optional true} ::sm/uuid]
|
||||||
[:is-default {:optional true} :boolean]])
|
[:is-default {:optional true} :boolean]])
|
||||||
|
|
||||||
|
|||||||
@ -538,7 +538,7 @@
|
|||||||
[:map {:title "create-team-with-invitations"}
|
[:map {:title "create-team-with-invitations"}
|
||||||
[:name [:string {:max 250}]]
|
[:name [:string {:max 250}]]
|
||||||
[:features {:optional true} ::cfeat/features]
|
[:features {:optional true} ::cfeat/features]
|
||||||
[:id {:optional true} ::sm/uuid]
|
[:id {:optional true} ::sm/user-provided-uuid]
|
||||||
[:emails [::sm/set ::sm/email]]
|
[:emails [::sm/set ::sm/email]]
|
||||||
[:role types.team/schema:role]])
|
[:role types.team/schema:role]])
|
||||||
|
|
||||||
|
|||||||
@ -166,6 +166,33 @@
|
|||||||
(t/is (th/ex-info? err))
|
(t/is (th/ex-info? err))
|
||||||
(t/is (th/ex-of-type? err :not-found))))))
|
(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
|
(t/deftest file-gc-with-fragments
|
||||||
(let [profile (th/create-profile* 1)
|
(let [profile (th/create-profile* 1)
|
||||||
file (th/create-file* 1 {:profile-id (:id profile)
|
file (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
|||||||
@ -138,6 +138,66 @@
|
|||||||
(t/is (uuid? (:thumbnail-id result))))))
|
(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
|
(t/deftest media-object-from-url-command
|
||||||
(let [prof (th/create-profile* 1)
|
(let [prof (th/create-profile* 1)
|
||||||
proj (th/create-project* 1 {:profile-id (:id prof)
|
proj (th/create-project* 1 {:profile-id (:id prof)
|
||||||
@ -1262,3 +1322,48 @@
|
|||||||
(t/is (th/ex-info? error))
|
(t/is (th/ex-info? error))
|
||||||
(t/is (= :not-found (:type error-data)))
|
(t/is (= :not-found (:type error-data)))
|
||||||
(t/is (= :object-not-found (:code 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-info? error))
|
||||||
(t/is (th/ex-of-type? error :not-found))))
|
(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
|
(t/deftest permissions-checks-rename-project
|
||||||
(let [profile1 (th/create-profile* 1)
|
(let [profile1 (th/create-profile* 1)
|
||||||
profile2 (th/create-profile* 2)
|
profile2 (th/create-profile* 2)
|
||||||
|
|||||||
@ -1116,6 +1116,62 @@
|
|||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
(t/is (th/success? out)))))
|
(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
|
(t/deftest create-team-invitations-email-cooldown
|
||||||
(with-mocks [mock {:target 'app.email/send! :return nil}]
|
(with-mocks [mock {:target 'app.email/send! :return nil}]
|
||||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC Sucursal en España SL",
|
"author": "Kaleidos INC Sucursal en España SL",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
131
common/pnpm-lock.yaml
generated
131
common/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run test;
|
pnpm run test;
|
||||||
clojure -M:dev:test;
|
clojure -M:dev:test;
|
||||||
|
|||||||
@ -448,6 +448,21 @@
|
|||||||
::oapi/type "string"
|
::oapi/type "string"
|
||||||
::oapi/format "uuid"}})
|
::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.
|
;; Strict email regex aligned with app.common.spec/email-re.
|
||||||
;; Local part: valid RFC chars, no leading/trailing dot, no consecutive dots.
|
;; Local part: valid RFC chars, no leading/trailing dot, no consecutive dots.
|
||||||
;; Domain: labels can't start/end with hyphen, no empty labels.
|
;; Domain: labels can't start/end with hyphen, no empty labels.
|
||||||
|
|||||||
@ -70,6 +70,20 @@
|
|||||||
[v]
|
[v]
|
||||||
(= zero 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
|
(defn get-word-high
|
||||||
[id]
|
[id]
|
||||||
#?(:clj (.getMostSignificantBits ^UUID id)
|
#?(:clj (.getMostSignificantBits ^UUID id)
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.schema.generators :as sg]
|
[app.common.schema.generators :as sg]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[clojure.test :as t]))
|
[clojure.test :as t]))
|
||||||
|
|
||||||
(t/deftest test-set-of-email
|
(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 "user@")))
|
||||||
(t/is (false? (sm/validate ::sm/email "userdomain.com")))
|
(t/is (false? (sm/validate ::sm/email "userdomain.com")))
|
||||||
(t/is (false? (sm/validate ::sm/email "user@@domain.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 1) (aget parts 1)))
|
||||||
(t/is (= (nth expected 2) (aget parts 2)))
|
(t/is (= (nth expected 2) (aget parts 2)))
|
||||||
(t/is (= (nth expected 3) (aget parts 3))))))))
|
(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
|
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
|
PATH=/opt/node/bin:$PATH
|
||||||
|
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
@ -75,10 +76,14 @@ RUN set -eux; \
|
|||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
OPENSSL_ARCH='linux-aarch64'; \
|
OPENSSL_ARCH='linux-aarch64'; \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||||
|
PNPM_ARCH='arm64'; \
|
||||||
|
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
amd64|x86_64) \
|
||||||
OPENSSL_ARCH='linux-x86_64'; \
|
OPENSSL_ARCH='linux-x86_64'; \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
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}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
@ -89,10 +94,14 @@ RUN set -eux; \
|
|||||||
mkdir -p /opt/node; \
|
mkdir -p /opt/node; \
|
||||||
cd /opt/node; \
|
cd /opt/node; \
|
||||||
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
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; \
|
chown -R root /opt/node; \
|
||||||
find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \
|
find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \
|
||||||
corepack enable; \
|
rm -f /tmp/nodejs.tar.gz /tmp/pnpm.tar.gz;
|
||||||
rm -rf /tmp/nodejs.tar.gz;
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
## OPENCODE SETUP
|
## OPENCODE SETUP
|
||||||
@ -100,7 +109,7 @@ RUN set -eux; \
|
|||||||
|
|
||||||
FROM base AS setup-opencode
|
FROM base AS setup-opencode
|
||||||
|
|
||||||
ENV OPENCODE_VERSION=1.18.25
|
ENV OPENCODE_VERSION=1.18.31
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
@ -121,6 +130,37 @@ RUN set -ex; \
|
|||||||
chmod +x /tmp/opencode/opencode; \
|
chmod +x /tmp/opencode/opencode; \
|
||||||
rm -f /tmp/opencode.tar.gz;
|
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
|
## CADDYSERVER SETUP
|
||||||
@ -166,12 +206,12 @@ RUN set -eux; \
|
|||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
case "${ARCH}" in \
|
case "${ARCH}" in \
|
||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
ESUM='5b222fce0b7076a10ac7ae3b1009a6c2caf4f35bc4e81de72010af6750c5e146'; \
|
ESUM='9fa5bf865783c43840101fcdc3a5222b60daf4fe11f26806247bf807c08aba38'; \
|
||||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_aarch64.tar.gz'; \
|
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu27.28.101-ca-jdk27.0.0-linux_aarch64.tar.gz'; \
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
amd64|x86_64) \
|
||||||
ESUM='4b7c114917aebd0fc6284fc7111245d7747a4d9603bd12d86b384b1abc9d575d'; \
|
ESUM='ccbc15c4edbedfdcc03c2d29a2aa2c6daf9e6ffb4cda7dd5ece0fcbb37350267'; \
|
||||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu26.32.13-ca-jdk26.0.2-linux_x64.tar.gz'; \
|
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu27.28.101-ca-jdk27.0.0-linux_x64.tar.gz'; \
|
||||||
;; \
|
;; \
|
||||||
*) \
|
*) \
|
||||||
echo "Unsupported arch: ${ARCH}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
@ -242,16 +282,17 @@ RUN set -eux; \
|
|||||||
|
|
||||||
FROM base AS setup-utils
|
FROM base AS setup-utils
|
||||||
|
|
||||||
ENV CLJKONDO_VERSION=2026.07.24 \
|
ENV CLJKONDO_VERSION=2026.08.04 \
|
||||||
BABASHKA_VERSION=1.13.219 \
|
BABASHKA_VERSION=1.13.223 \
|
||||||
CLJFMT_VERSION=0.16.5 \
|
CLJFMT_VERSION=0.16.5 \
|
||||||
PIXI_VERSION=0.75.0 \
|
PIXI_VERSION=0.81.0 \
|
||||||
GITHUB_CLI_VERSION=2.97.0 \
|
GITHUB_CLI_VERSION=2.101.0 \
|
||||||
UV_VERSION=0.12.1 \
|
AWS_CLI_VERSION=2.36.49 \
|
||||||
|
UV_VERSION=0.12.17 \
|
||||||
UV_TOOL_DIR=/opt/uv/tools \
|
UV_TOOL_DIR=/opt/uv/tools \
|
||||||
UV_TOOL_BIN_DIR=/opt/utils/bin \
|
UV_TOOL_BIN_DIR=/opt/utils/bin \
|
||||||
UV_PYTHON_INSTALL_DIR=/opt/uv/python \
|
UV_PYTHON_INSTALL_DIR=/opt/uv/python \
|
||||||
SERENA_VERSION=1.6.1
|
SERENA_VERSION=1.7.0
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
@ -356,24 +397,30 @@ RUN set -ex; \
|
|||||||
tar -xv --strip-components=1 -f /tmp/gh.tar.gz; \
|
tar -xv --strip-components=1 -f /tmp/gh.tar.gz; \
|
||||||
rm -rf /tmp/gh.tar.gz;
|
rm -rf /tmp/gh.tar.gz;
|
||||||
|
|
||||||
# Install minio client
|
# Install AWS CLI
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
ARCH="$(dpkg --print-architecture)"; \
|
ARCH="$(dpkg --print-architecture)"; \
|
||||||
case "${ARCH}" in \
|
case "${ARCH}" in \
|
||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
BINARY_URL="https://dl.min.io/client/mc/release/linux-arm64/mc"; \
|
BINARY_ARCH="aarch64"; \
|
||||||
|
BINARY_SHA256="2dec26fdaff2f5e34961ea98f456297104f73d8409fc3599c8f2909c6e27a07e"; \
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
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}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
;; \
|
;; \
|
||||||
esac; \
|
esac; \
|
||||||
wget -O /tmp/mc ${BINARY_URL}; \
|
BINARY_URL="https://awscli.amazonaws.com/awscli-exe-linux-${BINARY_ARCH}-${AWS_CLI_VERSION}.zip"; \
|
||||||
mv /tmp/mc /opt/utils/bin/; \
|
curl -LfsSo /tmp/awscliv2.zip ${BINARY_URL}; \
|
||||||
chmod +x /opt/utils/bin/mc;
|
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
|
# Install uv
|
||||||
RUN set -ex; \
|
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; \
|
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; \
|
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 -qq update; \
|
||||||
apt-get -qqy install postgresql-client-16; \
|
apt-get -qqy install postgresql-client-18; \
|
||||||
apt-get clean; \
|
apt-get clean; \
|
||||||
rm -rf /var/lib/apt/lists/*;
|
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-rust /opt/emsdk /opt/emsdk
|
||||||
COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy
|
COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy
|
||||||
COPY --from=setup-opencode /tmp/opencode/opencode /opt/utils/bin/opencode
|
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.conf /etc/nginx/nginx.conf
|
||||||
COPY files/nginx-mime.types /etc/nginx/mime.types
|
COPY files/nginx-mime.types /etc/nginx/mime.types
|
||||||
@ -560,4 +608,3 @@ COPY files/init.sh /home/init.sh
|
|||||||
|
|
||||||
ENTRYPOINT ["/home/entrypoint.sh"]
|
ENTRYPOINT ["/home/entrypoint.sh"]
|
||||||
CMD ["/home/init.sh"]
|
CMD ["/home/init.sh"]
|
||||||
|
|
||||||
|
|||||||
@ -10,11 +10,12 @@
|
|||||||
|
|
||||||
# Container names and volume names. Volumes are pinned by explicit name
|
# Container names and volume names. Volumes are pinned by explicit name
|
||||||
# (rather than relying on COMPOSE_PROJECT_NAME prefixing) so the physical
|
# (rather than relying on COMPOSE_PROJECT_NAME prefixing) so the physical
|
||||||
# volumes survive project renames without a data migration. ws0 reuses the
|
# volumes survive project renames. Replacement services use new volume names
|
||||||
# pre-Stage-2 physical volume names (penpotdev_*).
|
# so data from the previous service remains available for rollback.
|
||||||
PENPOT_MAIN_CONTAINER_NAME=penpot-devenv-ws0-main
|
PENPOT_MAIN_CONTAINER_NAME=penpot-devenv-ws0-main
|
||||||
PENPOT_POSTGRES_DATA_VOLUME=penpotdev_postgres_data_pg16
|
PENPOT_POSTGRES_DATA_VOLUME=penpotdev_postgres_data_pg18
|
||||||
PENPOT_MINIO_DATA_VOLUME=penpotdev_minio_data
|
PENPOT_RUSTFS_DATA_VOLUME=penpotdev_rustfs_data
|
||||||
|
PENPOT_MAILPIT_DATA_VOLUME=penpotdev_mailpit_data
|
||||||
PENPOT_USER_DATA_VOLUME=penpotdev_user_data
|
PENPOT_USER_DATA_VOLUME=penpotdev_user_data
|
||||||
|
|
||||||
# Backend runtime config (passed to the container env block). Valkey is a
|
# 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_DATABASE_MAX_POOL_SIZE=20
|
||||||
PENPOT_REDIS_URI=redis://valkey/0
|
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_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_BUCKET=penpot
|
||||||
|
PENPOT_OBJECTS_STORAGE_S3_REGION=us-east-1
|
||||||
AWS_ACCESS_KEY_ID=penpot-devenv
|
AWS_ACCESS_KEY_ID=penpot-devenv
|
||||||
AWS_SECRET_ACCESS_KEY=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
|
# Published host ports. Only ports that need to be reachable from outside the
|
||||||
# container are exposed; everything else (frontend dev server, backend API,
|
# container are exposed; everything else (frontend dev server, backend API,
|
||||||
|
|||||||
@ -4,53 +4,47 @@ networks:
|
|||||||
external: true
|
external: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data_pg16:
|
postgres_data_pg18:
|
||||||
name: ${PENPOT_POSTGRES_DATA_VOLUME}
|
name: ${PENPOT_POSTGRES_DATA_VOLUME}
|
||||||
minio_data:
|
rustfs_data:
|
||||||
name: ${PENPOT_MINIO_DATA_VOLUME}
|
name: ${PENPOT_RUSTFS_DATA_VOLUME}
|
||||||
|
mailpit_data:
|
||||||
|
name: ${PENPOT_MAILPIT_DATA_VOLUME}
|
||||||
valkey_data:
|
valkey_data:
|
||||||
name: penpotdev_valkey_data
|
name: penpotdev_valkey_data
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
rustfs:
|
||||||
image: "minio/minio:RELEASE.2025-04-03T14-56-28Z"
|
image: "rustfs/rustfs:1.0.0"
|
||||||
command: minio server /mnt/data --console-address ":9001"
|
command: /data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- "minio_data:/mnt/data"
|
- "rustfs_data:/data"
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
- MINIO_ROOT_USER=minioadmin
|
- RUSTFS_ACCESS_KEY=${AWS_ACCESS_KEY_ID}
|
||||||
- MINIO_ROOT_PASSWORD=minioadmin
|
- 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:
|
networks:
|
||||||
default:
|
default:
|
||||||
aliases:
|
aliases:
|
||||||
- minio
|
- rustfs
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16.8
|
image: postgres:18.6
|
||||||
command: postgres -c config_file=/etc/postgresql.conf
|
command: postgres -c config_file=/etc/postgresql.conf
|
||||||
restart: always
|
restart: always
|
||||||
stop_signal: SIGINT
|
stop_signal: SIGINT
|
||||||
@ -62,7 +56,12 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./files/postgresql.conf:/etc/postgresql.conf:z
|
- ./files/postgresql.conf:/etc/postgresql.conf:z
|
||||||
- ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql: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:
|
networks:
|
||||||
default:
|
default:
|
||||||
aliases:
|
aliases:
|
||||||
@ -80,12 +79,24 @@ services:
|
|||||||
- valkey
|
- valkey
|
||||||
|
|
||||||
mailer:
|
mailer:
|
||||||
image: sj26/mailcatcher:latest
|
image: axllent/mailpit:v1.31.1
|
||||||
restart: always
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- "mailpit_data:/data"
|
||||||
|
environment:
|
||||||
|
- MP_DATABASE=/data/mailpit.db
|
||||||
|
- MP_MAX_MESSAGES=5000
|
||||||
|
- MP_DISABLE_VERSION_CHECK=true
|
||||||
expose:
|
expose:
|
||||||
- '1025'
|
- '1025'
|
||||||
ports:
|
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:
|
networks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -84,8 +84,10 @@ services:
|
|||||||
- PENPOT_OBJECTS_STORAGE_BACKEND=${PENPOT_OBJECTS_STORAGE_BACKEND}
|
- PENPOT_OBJECTS_STORAGE_BACKEND=${PENPOT_OBJECTS_STORAGE_BACKEND}
|
||||||
- PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}
|
- PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=${PENPOT_OBJECTS_STORAGE_S3_ENDPOINT}
|
||||||
- PENPOT_OBJECTS_STORAGE_S3_BUCKET=${PENPOT_OBJECTS_STORAGE_S3_BUCKET}
|
- 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_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
|
||||||
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
|
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
|
||||||
|
- AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
|
||||||
- PENPOT_TENANT=${PENPOT_TENANT}
|
- PENPOT_TENANT=${PENPOT_TENANT}
|
||||||
- PENPOT_TMUX_ATTACH=${PENPOT_TMUX_ATTACH}
|
- PENPOT_TMUX_ATTACH=${PENPOT_TMUX_ATTACH}
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
CREATE DATABASE penpot_test;
|
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
|
WORKDIR /opt/penpot/exporter
|
||||||
|
|
||||||
# DHI Node image installs Node at the system level (symlinked into
|
# pnpm ships as a system binary (same tarball + SHA pin as
|
||||||
# /usr/bin), so `./setup`'s internal `corepack enable` needs root to write
|
# docker/devenv/Dockerfile); the generated ./setup expects it on PATH.
|
||||||
# there. Ownership is fixed right after.
|
# 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
|
RUN ./setup && chown -R penpot:penpot /opt/penpot/exporter
|
||||||
|
|
||||||
USER penpot:penpot
|
USER penpot:penpot
|
||||||
|
|||||||
@ -8,6 +8,36 @@ COPY $BUNDLE_PATH /opt/penpot/mcp/
|
|||||||
|
|
||||||
WORKDIR /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
|
RUN ./setup
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ LABEL maintainer="Penpot <docker@penpot.app>"
|
|||||||
ENV LANG=en_US.UTF-8 \
|
ENV LANG=en_US.UTF-8 \
|
||||||
LC_ALL=en_US.UTF-8 \
|
LC_ALL=en_US.UTF-8 \
|
||||||
NODE_VERSION=v24.20.0 \
|
NODE_VERSION=v24.20.0 \
|
||||||
|
PNPM_VERSION=12.5.1 \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
PATH=/opt/node/bin:$PATH
|
PATH=/opt/node/bin:$PATH
|
||||||
|
|
||||||
@ -56,9 +57,13 @@ RUN set -eux; \
|
|||||||
case "${ARCH}" in \
|
case "${ARCH}" in \
|
||||||
aarch64|arm64) \
|
aarch64|arm64) \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \
|
||||||
|
PNPM_ARCH='arm64'; \
|
||||||
|
PNPM_SHA256='84e1290e82c800acd406b6db27e9650e15db3c2344d12162cc305ea1f942c6ff'; \
|
||||||
;; \
|
;; \
|
||||||
amd64|x86_64) \
|
amd64|x86_64) \
|
||||||
BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \
|
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}"; \
|
echo "Unsupported arch: ${ARCH}"; \
|
||||||
@ -71,7 +76,13 @@ RUN set -eux; \
|
|||||||
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
tar -xf /tmp/nodejs.tar.gz --strip-components=1; \
|
||||||
chown -R root /opt/node; \
|
chown -R root /opt/node; \
|
||||||
rm -rf /tmp/nodejs.tar.gz; \
|
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; \
|
mkdir -p /opt/penpot; \
|
||||||
chown -R penpot:penpot /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
|
# only if necessary
|
||||||
nvm install
|
nvm install
|
||||||
nvm use
|
nvm use
|
||||||
corepack enable
|
|
||||||
|
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|||||||
@ -39,5 +39,5 @@
|
|||||||
"markdown-it-anchor": "^9.2.1",
|
"markdown-it-anchor": "^9.2.1",
|
||||||
"markdown-it-plantuml": "^1.4.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: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
rm -rf ./_dist
|
rm -rf ./_dist
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm run build
|
pnpm run build
|
||||||
|
|||||||
@ -62,7 +62,7 @@ See the dedicated section [Agentic Dev Environment](../agentic-devenv/) for deta
|
|||||||
### Parallel workspaces
|
### Parallel workspaces
|
||||||
|
|
||||||
The devenv runs as separate compose projects:
|
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.
|
* `penpotdev-wsN` project per runtime instance.
|
||||||
- `ws0` (a.k.a. `main`) is the current state of your repo;
|
- `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/`
|
- `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
|
### 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
|
files, and MCP tokens are visible from every instance. Per-instance Valkey
|
||||||
keeps msgbus Pub/Sub channels (collab broadcasts, team-org notifications,
|
keeps msgbus Pub/Sub channels (collab broadcasts, team-org notifications,
|
||||||
file-summary cache, rate-limit counters) isolated.
|
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
|
Background workers (`enable-backend-worker`) run only on ws0 — ws1+ overlays
|
||||||
disable it. ws1+ RPC handlers still enqueue tasks into the shared Postgres
|
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
|
`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
|
`wrk/submit!` inserts a row and returns; RPC handlers never wait on
|
||||||
completion. The "ws0 only" policy avoids multi-instance worker races (cron
|
completion. The "ws0 only" policy avoids multi-instance worker races (cron
|
||||||
dedup is best-effort across instances, `wrk/submit!` `dedupe` is racy across
|
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
|
The devenv compose configuration has been split into two files and reorganized
|
||||||
into separate compose projects per runtime instance:
|
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`.
|
runs under the compose project `penpotdev-infra`.
|
||||||
- `docker/devenv/docker-compose.main.yml` (one main container + its Valkey)
|
- `docker/devenv/docker-compose.main.yml` (one main container + its Valkey)
|
||||||
runs once per runtime instance under `penpotdev-ws0`, `penpotdev-ws1`, ….
|
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`)
|
If you had the devenv running on the previous single-project (`penpotdev`)
|
||||||
layout, leftover containers and the auto-generated `penpotdev_default`
|
layout, leftover containers and the auto-generated `penpotdev_default`
|
||||||
network must be removed before bringing the new ws0 instance up. The named
|
network must be removed before bringing the new ws0 instance up. The named
|
||||||
data volumes (`penpotdev_postgres_data_pg16`, `penpotdev_minio_data`,
|
data volumes (`penpotdev_postgres_data_pg18`, `penpotdev_rustfs_data`,
|
||||||
`penpotdev_user_data`, `penpotdev_valkey_data`) are pinned by explicit
|
`penpotdev_mailpit_data`, `penpotdev_user_data`, `penpotdev_valkey_data`) are
|
||||||
`name:` entries in the new compose files and are preserved through the
|
pinned by explicit `name:` entries in the new compose files. The legacy
|
||||||
transition — your Postgres DB, MinIO objects, and home cache survive.
|
`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:
|
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
|
## Email
|
||||||
|
|
||||||
To test email sending, the devenv includes [MailCatcher](https://mailcatcher.me/),
|
To test email sending, the devenv includes
|
||||||
a SMTP server that is used for develop. It does not send any mail outbounds.
|
[Mailpit](https://mailpit.axllent.org/), an SMTP server for development. It does
|
||||||
Instead, it stores them in memory and allows to browse them via a web interface
|
not send mail externally. Instead, it stores messages in a persistent Docker
|
||||||
similar to a webmail client. Simply navigate to:
|
volume and provides a webmail-like interface. Simply navigate to:
|
||||||
|
|
||||||
[http://localhost:1080](http://localhost:1080)
|
[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
|
## Create user
|
||||||
|
|
||||||
You can register a new user manually, or create new users automatically with this script. From your tmux instance, run:
|
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).
|
> ❗️ **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
|
### How to write a test
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC Sucursal en España SL",
|
"author": "Kaleidos INC Sucursal en España SL",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/penpot/penpot"
|
"url": "https://github.com/penpot/penpot"
|
||||||
@ -30,7 +30,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
|
"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",
|
"watch": "pnpm run watch:app",
|
||||||
"build:app": "clojure -M:dev:shadow-cljs release main",
|
"build:app": "clojure -M:dev:shadow-cljs release main",
|
||||||
"build:wasm": "../render-wasm/build export",
|
"build:wasm": "../render-wasm/build export",
|
||||||
|
|||||||
131
exporter/pnpm-lock.yaml
generated
131
exporter/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -5,8 +5,6 @@ set -ex
|
|||||||
export CURRENT_VERSION=$1;
|
export CURRENT_VERSION=$1;
|
||||||
export NODE_ENV=production;
|
export NODE_ENV=production;
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install || exit 1;
|
|
||||||
pnpm install || exit 1;
|
pnpm install || exit 1;
|
||||||
pnpm run build:wasm;
|
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
|
cat <<EOF | tee target/setup
|
||||||
#/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
set -e;
|
set -e;
|
||||||
corepack enable;
|
command -v pnpm >/dev/null || { echo "error: pnpm not found in PATH" >&2; exit 1; };
|
||||||
corepack install;
|
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm exec playwright install chromium;
|
pnpm exec playwright install chromium;
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@ -2,7 +2,5 @@
|
|||||||
|
|
||||||
set -e;
|
set -e;
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm exec playwright install chromium
|
pnpm exec playwright install chromium
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run test;
|
pnpm run test;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC Sucursal en España SL",
|
"author": "Kaleidos INC Sucursal en España SL",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"defaults"
|
"defaults"
|
||||||
],
|
],
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"description": "Penpot Draft-JS Wrapper",
|
"description": "Penpot Draft-JS Wrapper",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"author": "Andrey Antukh",
|
"author": "Andrey Antukh",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"description": "Simple library for handling keyboard shortcuts",
|
"description": "Simple library for handling keyboard shortcuts",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"author": "Craig Campbell",
|
"author": "Craig Campbell",
|
||||||
"license": "Apache-2.0 WITH LLVM-exception"
|
"license": "Apache-2.0 WITH LLVM-exception"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"author": "Andrey Antukh",
|
"author": "Andrey Antukh",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./dist/index.js"
|
"import": "./dist/index.js"
|
||||||
|
|||||||
131
frontend/pnpm-lock.yaml
generated
131
frontend/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -18,8 +18,6 @@ export NODE_ENV=production;
|
|||||||
|
|
||||||
rm -rf node_modules;
|
rm -rf node_modules;
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
|
|
||||||
rm -rf target/dist;
|
rm -rf target/dist;
|
||||||
@ -43,7 +41,6 @@ popd;
|
|||||||
pushd ../plugins
|
pushd ../plugins
|
||||||
rm -rf node_modules;
|
rm -rf node_modules;
|
||||||
rm -rf dist/apps/;
|
rm -rf dist/apps/;
|
||||||
corepack install;
|
|
||||||
pnpm -r install;
|
pnpm -r install;
|
||||||
pnpm run build:plugins;
|
pnpm run build:plugins;
|
||||||
popd
|
popd
|
||||||
|
|||||||
@ -12,8 +12,6 @@ export VERSION_TAG="${VERSION}-${BUILD_TS}";
|
|||||||
|
|
||||||
export NODE_ENV=production;
|
export NODE_ENV=production;
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
|
|
||||||
pnpm run build:storybook;
|
pnpm run build:storybook;
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm exec playwright install chromium;
|
pnpm exec playwright install chromium;
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
|
|
||||||
# Build render wasm binary
|
# Build render wasm binary
|
||||||
|
|||||||
@ -4,7 +4,5 @@ TARGET=${1:-app};
|
|||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run watch:$TARGET
|
pnpm run watch:$TARGET
|
||||||
|
|||||||
@ -28,5 +28,5 @@
|
|||||||
"vite": "^8.2.0",
|
"vite": "^8.2.0",
|
||||||
"vitest": "^4.1.10"
|
"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",
|
"version": "1.2.0-RC1",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC Sucursal en España SL",
|
"author": "Kaleidos INC Sucursal en España SL",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
131
library/pnpm-lock.yaml
generated
131
library/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
pnpm install;
|
||||||
|
|
||||||
pnpm run build:bundle;
|
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.
|
# Compose-project plumbing for the parallel-workspaces layout.
|
||||||
#
|
#
|
||||||
# - Shared infrastructure (postgres, minio, mailer, ldap, minio-setup) runs
|
# - Shared infrastructure (postgres, RustFS, mailer, LDAP, Valkey)
|
||||||
# under project `penpotdev-infra`.
|
|
||||||
# - Shared infrastructure (postgres, minio, mailer, ldap, valkey, minio-setup)
|
|
||||||
# runs under project `penpotdev-infra`.
|
# runs under project `penpotdev-infra`.
|
||||||
# - Each runtime instance (ws0, ws1, ...) runs only its own main container
|
# - Each runtime instance (ws0, ws1, ...) runs only its own main container
|
||||||
# under project `penpotdev-wsN`. All workspaces uniformly overlay their
|
# 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" ]]
|
[[ -n "$container" ]] && [[ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" = "true" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Bring shared infra up and block until minio-setup has provisioned the
|
# Bring shared infra up and block until services with healthchecks are healthy.
|
||||||
# shared MinIO user/policy. Idempotent: a second call when everything is
|
# Removing orphaned containers retires old infra services without deleting
|
||||||
# already up returns immediately.
|
# their named volumes.
|
||||||
function ensure-infra-up {
|
function ensure-infra-up {
|
||||||
infra-compose up -d
|
infra-compose up -d --wait --wait-timeout 60 --remove-orphans
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Refuse to sync workspaces if the live repo is in a fragile Git state.
|
# 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
|
#### 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.
|
On Windows, use the Git Bash terminal to ensure compatibility with the provided scripts.
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/penpot/penpot.git"
|
"url": "https://github.com/penpot/penpot.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^10.0.5",
|
"concurrently": "^10.0.5",
|
||||||
"prettier": "^3.9.6"
|
"prettier": "^3.9.6"
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"description": "Shared type definitions and interfaces for Penpot MCP",
|
"description": "Shared type definitions and interfaces for Penpot MCP",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --build --clean && tsc --build",
|
"build": "tsc --build --clean && tsc --build",
|
||||||
"watch": "tsc --watch",
|
"watch": "tsc --watch",
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite build --watch --config vite.config.ts",
|
"start": "vite build --watch --config vite.config.ts",
|
||||||
"start:multi-user": "pnpm run start",
|
"start:multi-user": "pnpm run start",
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
],
|
],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
|
|||||||
131
mcp/pnpm-lock.yaml
generated
131
mcp/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -9,9 +9,6 @@ echo "Preparing bundle for types from $URL"
|
|||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
# Ensure clean working directory
|
# Ensure clean working directory
|
||||||
rm -rf dist;
|
rm -rf dist;
|
||||||
rm -rf node_modules;
|
rm -rf node_modules;
|
||||||
@ -37,8 +34,7 @@ touch ./dist/pnpm-workspace.yaml;
|
|||||||
cat <<EOF | tee ./dist/setup
|
cat <<EOF | tee ./dist/setup
|
||||||
#/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
set -e;
|
set -e;
|
||||||
corepack enable;
|
command -v pnpm >/dev/null || { echo "error: pnpm not found in PATH" >&2; exit 1; };
|
||||||
corepack install;
|
|
||||||
pnpm install -P
|
pnpm install -P
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,4 @@
|
|||||||
|
|
||||||
set -e;
|
set -e;
|
||||||
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm -r install;
|
pnpm -r install;
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
"fmt:check": "prettier --check src/ test/",
|
"fmt:check": "prettier --check src/ test/",
|
||||||
"clean": "rm -rf dist/"
|
"clean": "rm -rf dist/"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"multer": "^2.2.0",
|
"multer": "^2.2.0",
|
||||||
|
|||||||
131
media-processor/pnpm-lock.yaml
generated
131
media-processor/pnpm-lock.yaml
generated
@ -7,96 +7,153 @@ importers:
|
|||||||
configDependencies: {}
|
configDependencies: {}
|
||||||
packageManagerDependencies:
|
packageManagerDependencies:
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: 12.3.4
|
specifier: 12.5.1
|
||||||
version: 12.3.4
|
version: 12.5.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==}
|
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]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==}
|
resolution: {integrity: sha512-YHwI1g2B+Y2cAEylOR31r781GWijdJL7Gy7TEn/ZpZ5v/Cuwp5/Pz0EFCgo0M2MKLafAbL/laUMGvOpAdnerAw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==}
|
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]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==}
|
resolution: {integrity: sha512-6hbMWW2/eQo1b5wvQPe/ndLHVTH88Rav6N+ffD4/h6IuFMjWCgasFYYsFv0GrvpqREAx3SwmZSvli+0NFXqbxA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==}
|
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]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==}
|
resolution: {integrity: sha512-3PkUBYo5z4dgtlnTNIFj7QGpcDUAuqXz9WGVigPDCeccEnhGiRkWmA5102TmYJHtwJP3LfmE+ZF9PGeWhn8p9Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.win32-arm64@12.5.1':
|
||||||
resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==}
|
resolution: {integrity: sha512-49NvjZIXHly56/PLoEDD7VphbO4N8dwMXoApElDFCpVGDNGeKlwlib1tXtnpTF6i2/XIsAj7GgatHB5KHXgBnA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.win32-x64@12.5.1':
|
||||||
resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==}
|
resolution: {integrity: sha512-ePUmQFPAPJFNMwg1lelDx2PdjHqYRiF0yfg5e6qeDbPNOu97QNo557Ru/SxmCrmt8ydQWNuZXWpdEp019md5vw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
pnpm@12.3.4:
|
pnpm@12.5.1:
|
||||||
resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==}
|
resolution: {integrity: sha512-4/MFvHhKK8ifWtO2E4iJRw+ujSr182thIW7JHCw9ZAiXdfneOKrDMQROpA8kXLDVZmOS399lgk4ZB+9qLGLeXw==}
|
||||||
engines: {node: '>=18.*'}
|
engines: {node: '>=18.*'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@pnpm/exe.darwin-arm64@12.3.4':
|
'@pnpm/exe.android-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.darwin-x64@12.3.4':
|
'@pnpm/exe.android-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64-musl@12.3.4':
|
'@pnpm/exe.darwin-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-arm64@12.3.4':
|
'@pnpm/exe.darwin-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64-musl@12.3.4':
|
'@pnpm/exe.freebsd-x64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.linux-x64@12.3.4':
|
'@pnpm/exe.linux-arm64-musl@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-arm64@12.3.4':
|
'@pnpm/exe.linux-arm64@12.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@pnpm/exe.win32-x64@12.3.4':
|
'@pnpm/exe.linux-ppc64@12.5.1':
|
||||||
optional: true
|
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:
|
optionalDependencies:
|
||||||
'@pnpm/exe.darwin-arm64': 12.3.4
|
'@pnpm/exe.android-arm64': 12.5.1
|
||||||
'@pnpm/exe.darwin-x64': 12.3.4
|
'@pnpm/exe.android-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64': 12.3.4
|
'@pnpm/exe.darwin-arm64': 12.5.1
|
||||||
'@pnpm/exe.linux-arm64-musl': 12.3.4
|
'@pnpm/exe.darwin-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64': 12.3.4
|
'@pnpm/exe.freebsd-x64': 12.5.1
|
||||||
'@pnpm/exe.linux-x64-musl': 12.3.4
|
'@pnpm/exe.linux-arm64': 12.5.1
|
||||||
'@pnpm/exe.win32-arm64': 12.3.4
|
'@pnpm/exe.linux-arm64-musl': 12.5.1
|
||||||
'@pnpm/exe.win32-x64': 12.3.4
|
'@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'
|
lockfileVersion: '9.0'
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
corepack enable
|
|
||||||
corepack install
|
|
||||||
pnpm install
|
pnpm install
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC Sucursal en España SL",
|
"author": "Kaleidos INC Sucursal en España SL",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457",
|
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/penpot/penpot"
|
"url": "https://github.com/penpot/penpot"
|
||||||
|
|||||||
@ -13,5 +13,5 @@
|
|||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"test": "vitest"
|
"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
|
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
|
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),
|
intercepts every backend RPC with Playwright fixtures (no backend, no login),
|
||||||
opens the mocked workspace file, injects the bundle directly into the plugin
|
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
|
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.
|
response.
|
||||||
|
|
||||||
Prerequisites: the frontend bundle must exist at `frontend/resources/public`
|
Prerequisites: the frontend bundle must exist at `frontend/resources/public`
|
||||||
(the devenv watch build suffices; CI builds it via `frontend/scripts/build`),
|
(the devenv watch build suffices), and the Playwright browser must be installed
|
||||||
and the Playwright browser must be installed
|
|
||||||
(`pnpm --filter composable-test-suite exec playwright install chromium`).
|
(`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:
|
Options via environment variables:
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { spawn, type ChildProcess } from "node:child_process";
|
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { chromium, type Page } from "playwright";
|
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,
|
// 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
|
// 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
|
// (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
|
// file. If workspace loading changes and this driver times out waiting for the
|
||||||
// viewport, diff against those two first. It serves the prebuilt
|
// 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),
|
// RPC with Playwright `page.route` (reusing the frontend e2e mock fixtures),
|
||||||
// injects the prebuilt `headless.js` bundle into the plugin sandbox via
|
// injects the prebuilt `headless.js` bundle into the plugin sandbox via
|
||||||
// `globalThis.ɵloadPlugin`, and captures the results from the page console.
|
// `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
|
// here = <root>/plugins/apps/composable-test-suite/ci
|
||||||
const repoRoot = resolve(here, "../../../../");
|
const repoRoot = resolve(here, "../../../../");
|
||||||
const frontendDir = resolve(repoRoot, "frontend");
|
const frontendDir = resolve(repoRoot, "frontend");
|
||||||
|
const staticRoot = resolve(frontendDir, "resources/public");
|
||||||
const e2eDataDir = resolve(frontendDir, "playwright/data");
|
const e2eDataDir = resolve(frontendDir, "playwright/data");
|
||||||
|
|
||||||
const BASE_URL = "http://localhost:3000";
|
const BASE_URL = "http://localhost:3000";
|
||||||
@ -96,14 +98,15 @@ async function waitForServer(url: string, timeoutMs = 30000): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startE2eServer(): ChildProcess {
|
function startE2eServer(): Promise<StaticServer> {
|
||||||
// Reuse the frontend e2e static server: it serves frontend/resources/public
|
// Serve the prebuilt frontend bundle from `frontend/resources/public` on
|
||||||
// on port 3000, which is also the host the app opens its notifications
|
// port 3000, which is also the host the app opens its notifications
|
||||||
// WebSocket against — so the WS mock below matches without extra config.
|
// WebSocket against — so the WS mock below matches without extra config.
|
||||||
return spawn("node", ["scripts/e2e-server.js"], {
|
// This used to shell out to the express-based
|
||||||
cwd: frontendDir,
|
// `frontend/scripts/e2e-server.js`, but that resolves `express` from
|
||||||
stdio: "inherit",
|
// `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
|
// Install the frontend e2e WebSocket mock so the workspace's notifications
|
||||||
@ -204,7 +207,7 @@ function printReport(results: ReportedResult[]) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
const bundle = readFileSync(headlessBundlePath, "utf-8");
|
const bundle = readFileSync(headlessBundlePath, "utf-8");
|
||||||
|
|
||||||
const server = startE2eServer();
|
const server = await startE2eServer();
|
||||||
await waitForServer(BASE_URL);
|
await waitForServer(BASE_URL);
|
||||||
|
|
||||||
const browser = await chromium.launch();
|
const browser = await chromium.launch();
|
||||||
@ -280,7 +283,7 @@ async function main() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
server.kill();
|
await server.close();
|
||||||
|
|
||||||
printReport(results);
|
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": "tsc && vite build",
|
||||||
"build:headless": "vite build --config vite.config.headless.ts",
|
"build:headless": "vite build --config vite.config.headless.ts",
|
||||||
"test:ci": "pnpm run build:headless && tsx ci/run-ci.ts",
|
"test:ci": "pnpm run build:headless && tsx ci/run-ci.ts",
|
||||||
|
"test:unit": "tsx --test ci/static-server.test.ts",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"bootstrap": "pnpm install --ignore-workspace && pnpm run build && pnpm run start",
|
"bootstrap": "pnpm install --ignore-workspace && pnpm run build && pnpm run start",
|
||||||
"types:check": "tsc --noEmit",
|
"types:check": "tsc --noEmit",
|
||||||
@ -26,5 +27,5 @@
|
|||||||
"vite": "^8.2.2",
|
"vite": "^8.2.2",
|
||||||
"vite-live-preview": "^0.4.0"
|
"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 .",
|
"lint": "eslint .",
|
||||||
"test": "vitest"
|
"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