0
Fork 0
mirror of https://github.com/penpot/penpot-plugins.git synced 2025-01-06 14:50:21 -05:00

feat: add type checking to plugin api (t#7606)

This commit is contained in:
Juanfran 2024-05-17 12:52:42 +02:00
parent 2f2b5fcdf4
commit 7c2dee65fc
12 changed files with 262 additions and 51 deletions

View file

@ -172,17 +172,17 @@ export interface PenpotGridLayout {
bottomPadding: number;
leftPadding: number;
addRow(type: PenpotTrackType, value?: number);
addRowAtIndex(index: number, type: PenpotTrackType, value?: number);
addColumn(type: PenpotTrackType, value?: number);
addColumnAtIndex(index: number, type: PenpotTrackType, value: number);
removeRow(index: number);
removeColumn(index: number);
setColumn(index: number, type: PenpotTrackType, value?: number);
setRow(index: number, type: PenpotTrackType, value?: number);
addRow(type: PenpotTrackType, value?: number): void;
addRowAtIndex(index: number, type: PenpotTrackType, value?: number): void;
addColumn(type: PenpotTrackType, value?: number): void;
addColumnAtIndex(index: number, type: PenpotTrackType, value: number): void;
removeRow(index: number): void;
removeColumn(index: number): void;
setColumn(index: number, type: PenpotTrackType, value?: number): void;
setRow(index: number, type: PenpotTrackType, value?: number): void;
appendChild(child: PenpotShape, row: number, column: number);
remove();
appendChild(child: PenpotShape, row: number, column: number): void;
remove(): void;
}
export interface PenpotShapeBase {
@ -239,9 +239,9 @@ export interface PenpotShapeBase {
fills: PenpotFill[];
strokes: PenpotStroke[];
resize(width: number, height: number);
resize(width: number, height: number): void;
clone(): PenpotShape;
remove();
remove(): void;
}
export interface PenpotFrame extends PenpotShapeBase {
@ -342,16 +342,21 @@ export interface PenpotContext {
uploadMediaUrl(name: string, url: string): Promise<PenpotImageData>;
group(first: PenpotShape, ...rest: PenpotShape): PenpotGroup;
ungroup(first: PenpotShape, ...rest: PenpotShape);
group(first: PenpotShape, ...rest: PenpotShape[]): PenpotGroup;
ungroup(first: PenpotShape, ...rest: PenpotShape[]): void;
createRectangle(): PenpotRectangle;
createFrame(): PenpotFrame;
createShapeFromSvg(svgString): PenpotGroup;
createShapeFromSvg(svgString: string): PenpotGroup;
createText(text: string): PenpotText;
addListener<T extends keyof EventsMap>(
type: T,
callback: (event: EventsMap[T]) => void
): void;
}
export interface Penpot extends PenpotContext {
export interface Penpot
extends Omit<PenpotContext, 'addListener' | 'group' | 'ungroup'> {
ui: {
open: (
name: string,

View file

@ -4,6 +4,9 @@
"sourceRoot": "libs/plugin-types",
"projectType": "library",
"targets": {
"lint": {
"command": "tsc --noEmit"
},
"build": {
"command": "node tools/scripts/build-types.mjs",
"options": {

View file

@ -14,12 +14,13 @@ repairIntrinsics({
consoleTaming: 'unsafe',
});
globalThis.initPluginsRuntime = (context: PenpotContext) => {
const globalThisAny$ = globalThis as any;
globalThisAny$.initPluginsRuntime = (context: PenpotContext) => {
if (context) {
console.log('%c[PLUGINS] Initialize context', 'color: #008d7c');
/* eslint-disable */
globalThis.ɵcontext = context;
globalThisAny$.ɵcontext = context;
globalThis.ɵloadPlugin = ɵloadPlugin;
setContext(context);
@ -27,7 +28,5 @@ globalThis.initPluginsRuntime = (context: PenpotContext) => {
for (const event of api.validEvents) {
context.addListener(event, api.triggerEvent.bind(null, event));
}
/* eslint-enable */
}
};

View file

@ -16,7 +16,7 @@ import type {
import { Manifest, Permissions } from '../models/manifest.model.js';
import { OpenUIOptions } from '../models/open-ui-options.model.js';
import openUIApi from './openUI.api.js';
import z from 'zod';
import { z } from 'zod';
import type { PluginModalElement } from '../plugin-modal.js';
type Callback<T> = (message: T) => void;
@ -45,7 +45,7 @@ export function triggerEvent(
message: EventsMap[keyof EventsMap]
) {
if (type === 'themechange' && modal) {
modal.setTheme(message);
modal.setTheme(message as PenpotTheme);
}
const listeners = eventListeners.get(type) || [];
listeners.forEach((listener) => listener(message));
@ -110,7 +110,7 @@ export function createApi(context: PenpotContext, manifest: Manifest): Penpot {
setTimeout: z
.function()
.args(z.function(), z.number())
.implement((callback, time) => {
.implement((callback: Callback<unknown>, time: number) => {
setTimeout(callback, time);
}),
@ -169,17 +169,17 @@ export function createApi(context: PenpotContext, manifest: Manifest): Penpot {
return context.selection;
},
get viewport(): PenpotViewport[] {
get viewport(): PenpotViewport {
checkPermission('selection:read');
return context.viewport;
},
getFile(): PenpotFile {
getFile(): PenpotFile | null {
checkPermission('file:read');
return context.getFile();
},
getPage(): PenpotPage {
getPage(): PenpotPage | null {
checkPermission('page:read');
return context.getPage();
},

View file

@ -1,4 +1,4 @@
import z from 'zod';
import { z } from 'zod';
import { openUISchema } from '../models/open-ui-options.schema.js';
import { createModal } from '../create-modal.js';

View file

@ -1,7 +1,7 @@
import { expect, describe, vi } from 'vitest';
import { createApi, triggerEvent, uiMessagesCallbacks } from './index.js';
import openUIApi from './openUI.api.js';
import { FileState } from '@penpot/plugin-types';
import type { PenpotFile } from '@penpot/plugin-types';
vi.mock('./openUI.api', () => {
return {
@ -23,7 +23,6 @@ vi.hoisted(() => {
describe('Plugin api', () => {
const mockContext = {
addListener: vi.fn(),
getFile: vi.fn(),
getPage: vi.fn(),
getSelected: vi.fn(),
@ -31,7 +30,7 @@ describe('Plugin api', () => {
getTheme: vi.fn(() => 'dark'),
};
const api = createApi(mockContext, {
const api = createApi(mockContext as any, {
name: 'test',
code: '',
permissions: ['page:read', 'file:read', 'selection:read'],
@ -168,11 +167,14 @@ describe('Plugin api', () => {
});
describe.concurrent('permissions', () => {
const api = createApi({
name: 'test',
code: '',
permissions: [],
});
const api = createApi(
{} as any,
{
name: 'test',
code: '',
permissions: [],
} as any
);
it('on', () => {
const callback = vi.fn();
@ -192,15 +194,15 @@ describe('Plugin api', () => {
it('get states', () => {
expect(() => {
api.getFileState();
api.getFile();
}).toThrow();
expect(() => {
api.getPageState();
api.getPage();
}).toThrow();
expect(() => {
api.getSelection();
api.getSelected();
}).toThrow();
});
});
@ -223,7 +225,7 @@ describe('Plugin api', () => {
name: 'test',
id: '123',
revn: 0,
} as FileState;
} as PenpotFile;
mockContext.getFile.mockImplementation(() => exampleFile);

View file

@ -1,5 +1,5 @@
import z from 'zod';
import { manifestSchema } from './manifest.schema';
import { z } from 'zod';
import { manifestSchema } from './manifest.schema.js';
export type Manifest = z.infer<typeof manifestSchema>;
export type Permissions = Manifest['permissions'][number];

View file

@ -1,4 +1,4 @@
import z from 'zod';
import { openUISchema } from './open-ui-options.schema';
import { z } from 'zod';
import { openUISchema } from './open-ui-options.schema.js';
export type OpenUIOptions = z.infer<typeof openUISchema>;

View file

@ -1,6 +1,6 @@
import { Manifest } from './models/manifest.model';
import { manifestSchema } from './models/manifest.schema';
import { PluginConfig } from './models/plugin-config.model';
import { Manifest } from './models/manifest.model.js';
import { manifestSchema } from './models/manifest.schema.js';
import { PluginConfig } from './models/plugin-config.model.js';
export function loadManifest(url: string): Promise<Manifest> {
return fetch(url)

View file

@ -3,6 +3,7 @@ import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import checker from 'vite-plugin-checker';
export default defineConfig({
root: __dirname,
@ -15,6 +16,11 @@ export default defineConfig({
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
skipDiagnostics: true,
}),
checker({
typescript: {
buildMode: true,
},
}),
],
// Uncomment this if you are using workers.

203
package-lock.json generated
View file

@ -83,6 +83,7 @@
"typescript": "~5.2.2",
"verdaccio": "^5.0.4",
"vite": "^5.0.0",
"vite-plugin-checker": "^0.6.4",
"vite-plugin-dts": "~2.3.0",
"vitest": "1.2.2"
}
@ -10773,6 +10774,18 @@
"node": ">=16"
}
},
"node_modules/conventional-commits-parser/node_modules/meow": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
"integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
"dev": true,
"engines": {
"node": ">=16.10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -16608,17 +16621,58 @@
}
},
"node_modules/meow": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
"integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
"integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@types/minimist": "^1.2.0",
"camelcase-keys": "^6.2.2",
"decamelize": "^1.2.0",
"decamelize-keys": "^1.1.0",
"hard-rejection": "^2.1.0",
"minimist-options": "4.1.0",
"normalize-package-data": "^3.0.0",
"read-pkg-up": "^7.0.1",
"redent": "^3.0.0",
"trim-newlines": "^3.0.0",
"type-fest": "^0.18.0",
"yargs-parser": "^20.2.3"
},
"engines": {
"node": ">=16.10"
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/meow/node_modules/type-fest": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
"integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/meow/node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=10"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@ -21248,6 +21302,12 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"dev": true
},
"node_modules/tiny-lru": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-10.4.1.tgz",
@ -22168,6 +22228,78 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/vite-plugin-checker": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.4.tgz",
"integrity": "sha512-2zKHH5oxr+ye43nReRbC2fny1nyARwhxdm0uNYp/ERy4YvU9iZpNOsueoi/luXw5gnpqRSvjcEPxXbS153O2wA==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.12.13",
"ansi-escapes": "^4.3.0",
"chalk": "^4.1.1",
"chokidar": "^3.5.1",
"commander": "^8.0.0",
"fast-glob": "^3.2.7",
"fs-extra": "^11.1.0",
"npm-run-path": "^4.0.1",
"semver": "^7.5.0",
"strip-ansi": "^6.0.0",
"tiny-invariant": "^1.1.0",
"vscode-languageclient": "^7.0.0",
"vscode-languageserver": "^7.0.0",
"vscode-languageserver-textdocument": "^1.0.1",
"vscode-uri": "^3.0.2"
},
"engines": {
"node": ">=14.16"
},
"peerDependencies": {
"eslint": ">=7",
"meow": "^9.0.0",
"optionator": "^0.9.1",
"stylelint": ">=13",
"typescript": "*",
"vite": ">=2.0.0",
"vls": "*",
"vti": "*",
"vue-tsc": ">=1.3.9"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
},
"meow": {
"optional": true
},
"optionator": {
"optional": true
},
"stylelint": {
"optional": true
},
"typescript": {
"optional": true
},
"vls": {
"optional": true
},
"vti": {
"optional": true
},
"vue-tsc": {
"optional": true
}
}
},
"node_modules/vite-plugin-checker/node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true,
"engines": {
"node": ">= 12"
}
},
"node_modules/vite-plugin-dts": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-2.3.0.tgz",
@ -22434,6 +22566,69 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/vscode-jsonrpc": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
"integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==",
"dev": true,
"engines": {
"node": ">=8.0.0 || >=10.0.0"
}
},
"node_modules/vscode-languageclient": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz",
"integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==",
"dev": true,
"dependencies": {
"minimatch": "^3.0.4",
"semver": "^7.3.4",
"vscode-languageserver-protocol": "3.16.0"
},
"engines": {
"vscode": "^1.52.0"
}
},
"node_modules/vscode-languageserver": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz",
"integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==",
"dev": true,
"dependencies": {
"vscode-languageserver-protocol": "3.16.0"
},
"bin": {
"installServerIntoExtension": "bin/installServerIntoExtension"
}
},
"node_modules/vscode-languageserver-protocol": {
"version": "3.16.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz",
"integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==",
"dev": true,
"dependencies": {
"vscode-jsonrpc": "6.0.0",
"vscode-languageserver-types": "3.16.0"
}
},
"node_modules/vscode-languageserver-textdocument": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz",
"integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==",
"dev": true
},
"node_modules/vscode-languageserver-types": {
"version": "3.16.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
"integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==",
"dev": true
},
"node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"dev": true
},
"node_modules/w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",

View file

@ -68,6 +68,7 @@
"typescript": "~5.2.2",
"verdaccio": "^5.0.4",
"vite": "^5.0.0",
"vite-plugin-checker": "^0.6.4",
"vite-plugin-dts": "~2.3.0",
"vitest": "1.2.2"
},