Compare commits

...

6 Commits

Author SHA1 Message Date
Andrey Antukh
e2f355ce07 Make constrast plugin be able to run on subpath 2026-02-10 18:58:17 +01:00
Andrey Antukh
483ead59fe Make penpot depend on local plugins runtime
This removes the need to publish versions to pnpn
2026-02-10 18:53:52 +01:00
Andrey Antukh
d8d532ed4f Make the colors-to-tokens plugin work on subpath 2026-02-10 18:53:52 +01:00
Andrey Antukh
a7cec4573d Make the table-plugin work correctly in subpath 2026-02-10 18:53:52 +01:00
Andrey Antukh
6049fa1c96 Add better approach for handling plugin iframe url
Ensure params are passed correctly to plugins declared to be version
2 and are prepared to run in a subpath.
2026-02-10 18:53:52 +01:00
Andrey Antukh
a0fef67c16 Update devenv nginx to serve locally builded plugins 2026-02-10 18:38:51 +01:00
25 changed files with 107 additions and 93 deletions

View File

@@ -126,6 +126,12 @@ http {
proxy_http_version 1.1; proxy_http_version 1.1;
} }
location /plugins {
autoindex on;
alias /home/penpot/penpot/plugins/dist/apps;
proxy_http_version 1.1;
}
location /mcp/ws { location /mcp/ws {
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade'; proxy_set_header Connection 'upgrade';

View File

@@ -42,17 +42,18 @@
"clear:shadow-cache": "rm -rf .shadow-cljs", "clear:shadow-cache": "rm -rf .shadow-cljs",
"watch": "exit 0", "watch": "exit 0",
"watch:app": "pnpm run clear:shadow-cache && pnpm run build:wasm && concurrently --kill-others-on-fail \"pnpm run watch:app:assets\" \"pnpm run watch:app:main\" \"pnpm run watch:app:libs\"", "watch:app": "pnpm run clear:shadow-cache && pnpm run build:wasm && concurrently --kill-others-on-fail \"pnpm run watch:app:assets\" \"pnpm run watch:app:main\" \"pnpm run watch:app:libs\"",
"watch:storybook": "pnpm run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"" "watch:storybook": "pnpm run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"",
"postinstall": "(cd ../plugins/libs/plugins-runtime; pnpm run build)"
}, },
"devDependencies": { "devDependencies": {
"@penpot/draft-js": "workspace:./packages/draft-js", "@penpot/draft-js": "workspace:./packages/draft-js",
"@penpot/mousetrap": "workspace:./packages/mousetrap", "@penpot/mousetrap": "workspace:./packages/mousetrap",
"@penpot/tokenscript": "workspace:./packages/tokenscript", "@penpot/plugins-runtime": "link:../plugins/libs/plugins-runtime",
"@penpot/plugins-runtime": "1.4.2",
"@penpot/svgo": "penpot/svgo#v3.2", "@penpot/svgo": "penpot/svgo#v3.2",
"@penpot/text-editor": "workspace:./text-editor", "@penpot/text-editor": "workspace:./text-editor",
"@playwright/test": "1.58.0", "@penpot/tokenscript": "workspace:./packages/tokenscript",
"@penpot/ui": "workspace:./packages/ui", "@penpot/ui": "workspace:./packages/ui",
"@playwright/test": "1.58.0",
"@storybook/addon-docs": "10.1.11", "@storybook/addon-docs": "10.1.11",
"@storybook/addon-themes": "10.1.11", "@storybook/addon-themes": "10.1.11",
"@storybook/addon-vitest": "10.1.11", "@storybook/addon-vitest": "10.1.11",
@@ -103,6 +104,7 @@
"sass": "^1.89.0", "sass": "^1.89.0",
"sass-embedded": "^1.89.0", "sass-embedded": "^1.89.0",
"sax": "^1.4.1", "sax": "^1.4.1",
"scheduler": "^0.27.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"storybook": "10.1.11", "storybook": "10.1.11",
"style-dictionary": "5.0.0-rc.1", "style-dictionary": "5.0.0-rc.1",

View File

@@ -20,8 +20,8 @@ importers:
specifier: workspace:./packages/mousetrap specifier: workspace:./packages/mousetrap
version: link:packages/mousetrap version: link:packages/mousetrap
'@penpot/plugins-runtime': '@penpot/plugins-runtime':
specifier: 1.4.2 specifier: link:../plugins/libs/plugins-runtime
version: 1.4.2 version: link:../plugins/libs/plugins-runtime
'@penpot/svgo': '@penpot/svgo':
specifier: penpot/svgo#v3.2 specifier: penpot/svgo#v3.2
version: svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b version: svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b
@@ -187,6 +187,9 @@ importers:
sax: sax:
specifier: ^1.4.1 specifier: ^1.4.1
version: 1.4.4 version: 1.4.4
scheduler:
specifier: ^0.27.0
version: 0.27.0
source-map-support: source-map-support:
specifier: ^0.5.21 specifier: ^0.5.21
version: 0.5.21 version: 0.5.21
@@ -578,15 +581,6 @@ packages:
'@dabh/diagnostics@2.0.8': '@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
'@endo/cache-map@1.1.0':
resolution: {integrity: sha512-owFGshs/97PDw9oguZqU/px8Lv1d0KjAUtDUiPwKHNXRVUE/jyettEbRoTbNJR1OaI8biMn6bHr9kVJsOh6dXw==}
'@endo/env-options@1.1.11':
resolution: {integrity: sha512-p9OnAPsdqoX4YJsE98e3NBVhIr2iW9gNZxHhAI2/Ul5TdRfoOViItzHzTqrgUVopw6XxA1u1uS6CykLMDUxarA==}
'@endo/immutable-arraybuffer@1.1.2':
resolution: {integrity: sha512-u+NaYB2aqEugQ3u7w3c5QNkPogf8q/xGgsPaqdY6pUiGWtYiTiFspKFcha6+oeZhWXWQ23rf0KrUq0kfuzqYyQ==}
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -1255,12 +1249,6 @@ packages:
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
'@penpot/plugin-types@1.4.2':
resolution: {integrity: sha512-O8wU6RSYE8bIVU7g8cSTYi32ppxs3R13dq7X3Nn9tmDaJjBOKOBpVLuoRPIp3fJC65fv8/7om0sdrtFoL5v19g==}
'@penpot/plugins-runtime@1.4.2':
resolution: {integrity: sha512-y9TDZOnb96JBW9E33dHKpmTMeAPXLtHDIZruUVjtM8hBJWZK7RCv+vAGDGxeoZJC/OB2YAHrCZG+mukePBzcuQ==}
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -4633,9 +4621,6 @@ packages:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
ses@1.14.0:
resolution: {integrity: sha512-T07hNgOfVRTLZGwSS50RnhqrG3foWP+rM+Q5Du4KUQyMLFI3A8YA4RKl0jjZzhihC1ZvDGrWi/JMn4vqbgr/Jg==}
set-function-length@1.2.2: set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -5496,9 +5481,6 @@ packages:
peerDependencies: peerDependencies:
zod: ^3.25.0 || ^4.0.0 zod: ^3.25.0 || ^4.0.0
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
zod@4.3.6: zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
@@ -5772,12 +5754,6 @@ snapshots:
enabled: 2.0.0 enabled: 2.0.0
kuler: 2.0.0 kuler: 2.0.0
'@endo/cache-map@1.1.0': {}
'@endo/env-options@1.1.11': {}
'@endo/immutable-arraybuffer@1.1.2': {}
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.21.5':
optional: true optional: true
@@ -6294,14 +6270,6 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.6 '@parcel/watcher-win32-x64': 2.5.6
optional: true optional: true
'@penpot/plugin-types@1.4.2': {}
'@penpot/plugins-runtime@1.4.2':
dependencies:
'@penpot/plugin-types': 1.4.2
ses: 1.14.0
zod: 3.25.76
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
@@ -9997,12 +9965,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
ses@1.14.0:
dependencies:
'@endo/cache-map': 1.1.0
'@endo/env-options': 1.1.11
'@endo/immutable-arraybuffer': 1.1.2
set-function-length@1.2.2: set-function-length@1.2.2:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@@ -10971,6 +10933,4 @@ snapshots:
dependencies: dependencies:
zod: 4.3.6 zod: 4.3.6
zod@3.25.76: {}
zod@4.3.6: {} zod@4.3.6: {}

View File

@@ -66,22 +66,22 @@
(update-in state [:workspace-local :open-plugins] (fnil disj #{}) id)))) (update-in state [:workspace-local :open-plugins] (fnil disj #{}) id))))
(defn- load-plugin! (defn- load-plugin!
[{:keys [plugin-id name description host code icon permissions]}] [{:keys [plugin-id name version description host code icon permissions]}]
(try (try
(st/emit! (save-current-plugin plugin-id) (st/emit! (save-current-plugin plugin-id)
(reset-plugin-flags plugin-id)) (reset-plugin-flags plugin-id))
(.ɵloadPlugin (.ɵloadPlugin ^js ug/global
^js ug/global #js {:pluginId plugin-id
#js {:pluginId plugin-id :name name
:name name :description description
:description description :version version
:host host :host host
:code code :code code
:icon icon :icon icon
:permissions (apply array permissions)} :permissions (apply array permissions)}
(fn [] (fn []
(st/emit! (remove-current-plugin plugin-id)))) (st/emit! (remove-current-plugin plugin-id))))
(catch :default e (catch :default e
(st/emit! (remove-current-plugin plugin-id)) (st/emit! (remove-current-plugin plugin-id))

View File

@@ -78,6 +78,7 @@
(d/without-nils (d/without-nils
{:plugin-id plugin-id {:plugin-id plugin-id
:url (str plugin-url) :url (str plugin-url)
:version vers
:name name :name name
:description desc :description desc
:host origin :host origin

View File

@@ -12,7 +12,10 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": "dist/apps/contrast-plugin", "outputPath": {
"base": "dist/apps/contrast-plugin",
"browser": "",
},
"index": "apps/contrast-plugin/src/index.html", "index": "apps/contrast-plugin/src/index.html",
"browser": "apps/contrast-plugin/src/main.ts", "browser": "apps/contrast-plugin/src/main.ts",
"polyfills": ["zone.js"], "polyfills": ["zone.js"],
@@ -20,6 +23,7 @@
"assets": [ "assets": [
"apps/contrast-plugin/src/_headers", "apps/contrast-plugin/src/_headers",
"apps/contrast-plugin/src/favicon.ico", "apps/contrast-plugin/src/favicon.ico",
"apps/contrast-plugin/src/manifest.json",
"apps/contrast-plugin/src/assets" "apps/contrast-plugin/src/assets"
], ],
"styles": [ "styles": [
@@ -218,7 +222,10 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": "dist/apps/table-plugin", "outputPath": {
"base": "dist/apps/table-plugin",
"browser": ""
},
"index": "apps/table-plugin/src/index.html", "index": "apps/table-plugin/src/index.html",
"browser": "apps/table-plugin/src/main.ts", "browser": "apps/table-plugin/src/main.ts",
"polyfills": ["zone.js"], "polyfills": ["zone.js"],
@@ -226,6 +233,7 @@
"assets": [ "assets": [
"apps/table-plugin/src/_headers", "apps/table-plugin/src/_headers",
"apps/table-plugin/src/favicon.ico", "apps/table-plugin/src/favicon.ico",
"apps/table-plugin/src/manifest.json",
"apps/table-plugin/src/assets" "apps/table-plugin/src/assets"
], ],
"styles": [ "styles": [
@@ -356,7 +364,10 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": "dist/apps/colors-to-tokens-plugin", "outputPath": {
"base": "dist/apps/colors-to-tokens-plugin",
"browser": ""
},
"index": "apps/colors-to-tokens-plugin/src/index.html", "index": "apps/colors-to-tokens-plugin/src/index.html",
"browser": "apps/colors-to-tokens-plugin/src/main.ts", "browser": "apps/colors-to-tokens-plugin/src/main.ts",
"polyfills": ["zone.js"], "polyfills": ["zone.js"],
@@ -364,6 +375,7 @@
"assets": [ "assets": [
"apps/colors-to-tokens-plugin/src/_headers", "apps/colors-to-tokens-plugin/src/_headers",
"apps/colors-to-tokens-plugin/src/favicon.ico", "apps/colors-to-tokens-plugin/src/favicon.ico",
"apps/colors-to-tokens-plugin/src/manifest.json",
"apps/colors-to-tokens-plugin/src/assets" "apps/colors-to-tokens-plugin/src/assets"
], ],
"styles": [ "styles": [

View File

@@ -6,6 +6,7 @@
"scripts": { "scripts": {
"build": "ng build colors-to-tokens-plugin", "build": "ng build colors-to-tokens-plugin",
"build:dev": "ng build colors-to-tokens-plugin --configuration development", "build:dev": "ng build colors-to-tokens-plugin --configuration development",
"watch": "ng build colors-to-tokens-plugin --configuration development --watch",
"serve": "ng serve colors-to-tokens-plugin", "serve": "ng serve colors-to-tokens-plugin",
"lint": "eslint .", "lint": "eslint .",
"test": "vitest" "test": "vitest"

View File

@@ -1,6 +1,8 @@
import { ApplicationConfig } from '@angular/core'; import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter, withHashLocation } from '@angular/router';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideRouter([])], providers: [
}; provideRouter([], withHashLocation())
],
};

View File

@@ -3,7 +3,6 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>colors-to-tokens-plugin</title> <title>colors-to-tokens-plugin</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>

View File

@@ -1,7 +1,8 @@
{ {
"name": "Colors to Tokens", "name": "Colors to Tokens",
"description": "Generate a design tokens file from a list of colors", "description": "Generate a design tokens file from a list of colors",
"code": "/assets/plugin.js", "version": 2,
"icon": "/assets/icon.png", "code": "assets/plugin.js",
"icon": "assets/icon.png",
"permissions": ["content:read", "library:read", "allow:downloads"] "permissions": ["content:read", "library:read", "allow:downloads"]
} }

View File

@@ -1,6 +1,6 @@
import { ApplicationConfig } from '@angular/core'; import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter, withHashLocation } from '@angular/router';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideRouter([])], providers: [provideRouter([], withHashLocation())],
}; };

View File

@@ -3,7 +3,6 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>contrast-plugin</title> <title>contrast-plugin</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>

View File

@@ -1,7 +1,8 @@
{ {
"name": "Contrast", "name": "Contrast",
"description": "Measure contrast plugin", "description": "Measure contrast plugin",
"code": "/assets/plugin.js", "version": 2,
"icon": "/assets/icon.png", "code": "assets/plugin.js",
"icon": "assets/icon.png",
"permissions": ["content:read"] "permissions": ["content:read"]
} }

View File

@@ -6,6 +6,7 @@
"scripts": { "scripts": {
"build": "ng build table-plugin", "build": "ng build table-plugin",
"build:dev": "ng build table-plugin --configuration development", "build:dev": "ng build table-plugin --configuration development",
"watch": "ng build table-plugin --configuration development --watch",
"serve": "ng serve table-plugin", "serve": "ng serve table-plugin",
"lint": "eslint .", "lint": "eslint .",
"test": "vitest" "test": "vitest"

View File

@@ -1,7 +1,7 @@
import { ApplicationConfig } from '@angular/core'; import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter, withHashLocation } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes)], providers: [provideRouter(appRoutes, withHashLocation())],
}; };

View File

@@ -3,7 +3,6 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>table-plugin</title> <title>table-plugin</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" /> <link rel="icon" type="image/x-icon" href="favicon.ico" />
</head> </head>

View File

@@ -1,7 +1,8 @@
{ {
"name": "Table plugin", "name": "Table plugin",
"description": "Table plugin to import or create tables", "description": "Table plugin to import or create tables",
"code": "/assets/plugin.js", "version": 2,
"icon": "/assets/icon.png", "code": "assets/plugin.js",
"icon": "assets/icon.png",
"permissions": ["content:read", "content:write"] "permissions": ["content:read", "content:write"]
} }

View File

@@ -0,0 +1,6 @@
/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
export default defineConfig({
root: "./"
});

View File

@@ -6,8 +6,8 @@
"ses": "^1.1.0", "ses": "^1.1.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"module": "./index.mjs", "module": "./dist/index.js",
"typings": "./index.d.ts", "typings": "./dist/index.d.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",

View File

@@ -6,6 +6,7 @@ export const manifestSchema = z.object({
host: z.string().url(), host: z.string().url(),
code: z.string(), code: z.string(),
icon: z.string().optional(), icon: z.string().optional(),
version: z.number().optional(),
description: z.string().max(200).optional(), description: z.string().max(200).optional(),
permissions: z.array( permissions: z.array(
z.enum([ z.enum([

View File

@@ -2,5 +2,5 @@ import { z } from 'zod';
export const openUISchema = z.object({ export const openUISchema = z.object({
width: z.number().positive(), width: z.number().positive(),
height: z.number().positive(), height: z.number().positive()
}); });

View File

@@ -1,8 +1,28 @@
import { Manifest } from './models/manifest.model.js'; import { Manifest } from './models/manifest.model.js';
import { manifestSchema } from './models/manifest.schema.js'; import { manifestSchema } from './models/manifest.schema.js';
export function getValidUrl(host: string, path: string): string { export function getValidUrl(host: string, path: string): URL {
return new URL(path, host).toString(); return new URL(path, host);
}
export function prepareUrl(manifest: Manifest, url: string, params: Object): string {
const result = getValidUrl(manifest.host, url);
for (let [k, v] of Object.entries(params)) {
if (!result.searchParams.has(k)) {
result.searchParams.set(k, v);
}
}
if (manifest.version === undefined || manifest.version === 1) {
return result.toString();
} else if (manifest.version === 2) {
const queryString = result.searchParams.toString();
result.search = "";
result.hash = `/?${queryString}`;
return result.toString();
} else {
throw new Error("invalid manifest version");
}
} }
export function loadManifest(url: string): Promise<Manifest> { export function loadManifest(url: string): Promise<Manifest> {

View File

@@ -1,6 +1,6 @@
import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest'; import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest';
import { createPluginManager } from './plugin-manager'; import { createPluginManager } from './plugin-manager';
import { loadManifestCode, getValidUrl } from './parse-manifest.js'; import { loadManifestCode, getValidUrl, prepareUrl } from './parse-manifest.js';
import { PluginModalElement } from './modal/plugin-modal.js'; import { PluginModalElement } from './modal/plugin-modal.js';
import { openUIApi } from './api/openUI.api.js'; import { openUIApi } from './api/openUI.api.js';
import type { Context, Theme } from '@penpot/plugin-types'; import type { Context, Theme } from '@penpot/plugin-types';
@@ -9,6 +9,7 @@ import type { Manifest } from './models/manifest.model.js';
vi.mock('./parse-manifest.js', () => ({ vi.mock('./parse-manifest.js', () => ({
loadManifestCode: vi.fn(), loadManifestCode: vi.fn(),
getValidUrl: vi.fn(), getValidUrl: vi.fn(),
prepareUrl: vi.fn(),
})); }));
vi.mock('./api/openUI.api.js', () => ({ vi.mock('./api/openUI.api.js', () => ({
@@ -71,7 +72,8 @@ describe('createPluginManager', () => {
vi.mocked(loadManifestCode).mockResolvedValue( vi.mocked(loadManifestCode).mockResolvedValue(
'console.log("Plugin loaded");', 'console.log("Plugin loaded");',
); );
vi.mocked(getValidUrl).mockReturnValue('https://example.com/plugin'); vi.mocked(getValidUrl).mockReturnValue(new URL('https://example.com/plugin'));
vi.mocked(prepareUrl).mockReturnValue('https://example.com/plugin');
}); });
afterEach(() => { afterEach(() => {
@@ -110,7 +112,7 @@ describe('createPluginManager', () => {
height: 300, height: 300,
}); });
expect(getValidUrl).toHaveBeenCalledWith(manifest.host, '/test-url'); expect(prepareUrl).toHaveBeenCalledWith(manifest, '/test-url', {theme: "light"});
expect(openUIApi).toHaveBeenCalledWith( expect(openUIApi).toHaveBeenCalledWith(
'Test Modal', 'Test Modal',
'https://example.com/plugin', 'https://example.com/plugin',

View File

@@ -1,6 +1,6 @@
import type { Context, Theme } from '@penpot/plugin-types'; import type { Context, Theme } from '@penpot/plugin-types';
import { getValidUrl, loadManifestCode } from './parse-manifest.js'; import { prepareUrl, loadManifestCode } from './parse-manifest.js';
import { Manifest } from './models/manifest.model.js'; import { Manifest } from './models/manifest.model.js';
import { PluginModalElement } from './modal/plugin-modal.js'; import { PluginModalElement } from './modal/plugin-modal.js';
import { openUIApi } from './api/openUI.api.js'; import { openUIApi } from './api/openUI.api.js';
@@ -8,6 +8,7 @@ import { OpenUIOptions } from './models/open-ui-options.model.js';
import { RegisterListener } from './models/plugin.model.js'; import { RegisterListener } from './models/plugin.model.js';
import { openUISchema } from './models/open-ui-options.schema.js'; import { openUISchema } from './models/open-ui-options.schema.js';
export async function createPluginManager( export async function createPluginManager(
context: Context, context: Context,
manifest: Manifest, manifest: Manifest,
@@ -80,9 +81,8 @@ export async function createPluginManager(
}; };
const openModal = (name: string, url: string, options?: OpenUIOptions) => { const openModal = (name: string, url: string, options?: OpenUIOptions) => {
const theme = context.theme as 'light' | 'dark'; const theme = context.theme as Theme;
const modalUrl = prepareUrl(manifest, url, {theme});
const modalUrl = getValidUrl(manifest.host, url);
if (modal?.getAttribute('iframe-src') === modalUrl) { if (modal?.getAttribute('iframe-src') === modalUrl) {
return; return;

View File

@@ -35,7 +35,7 @@ export default defineConfig({
// Configuration for building your library. // Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode // See: https://vitejs.dev/guide/build.html#library-mode
build: { build: {
outDir: '../../dist/plugins-runtime', outDir: './dist/',
reportCompressedSize: true, reportCompressedSize: true,
commonjsOptions: { commonjsOptions: {
transformMixedEsModules: true, transformMixedEsModules: true,