diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eb4ef0..27d3d5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,3 +74,16 @@ jobs: - name: Run test run: yarn test + + check-dependencies: + name: Check dependencies + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Setup node + uses: ./.github/actions/setup-node + + - name: Run syncpack + run: yarn syncpack lint --dependency-types prod,dev,peer diff --git a/.gitignore b/.gitignore index 757b52b..a6a5e86 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ dist/ tsc_output tsconfig*.tsbuildinfo +# turbo +.turbo + # yarn .yarn/* diff --git a/package.json b/package.json index 2e46a28..2e21296 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@sigmacomputing/plugin", + "name": "@sigmacomputing/plugin-sdk-root", "version": "1.1.1", "description": "Sigma Computing Plugin Client SDK", "license": "MIT", @@ -15,23 +15,14 @@ "url": "https://github.com/sigmacomputing/plugin/issues" }, "scripts": { - "build": "tsdown", - "build:watch": "tsdown --watch", - "format": "yarn oxfmt", - "lint": "yarn oxlint", + "build": "yarn turbo build", + "format": "yarn g:format", + "g:format": "cd $INIT_CWD && yarn oxfmt", + "g:lint": "cd $INIT_CWD && yarn oxlint --type-aware", + "lint": "yarn g:lint", "precommit": "lint-staged", - "publish": "yarn build && yarn npm publish", - "test": "vitest run", - "test:watch": "vitest", - "types": "tsc --noEmit" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } + "test": "yarn turbo test", + "types": "yarn turbo types && tsc --noEmit" }, "devDependencies": { "@arethetypeswrong/core": "^0.18.2", @@ -44,35 +35,16 @@ "oxlint-tsgolint": "^0.16.0", "playwright": "^1.49.0", "publint": "^0.3.18", + "syncpack": "^14.3.1", "tsdown": "^0.21.10", + "turbo": "^2.9.7", "typescript": "^6.0.2", "unplugin-unused": "^0.5.7", "vitest": "^4.1.5" }, - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.js", - "types": "./dist/cjs/index.d.cts", - "unpkg": "./dist/umd/sigmacomputing-plugin.umd.js", - "jsdelivr": "./dist/umd/sigmacomputing-plugin.umd.js", - "exports": { - ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.cts", - "default": "./dist/cjs/index.cjs" - } - }, - "./package.json": "./package.json" - }, - "files": [ - "dist/**/*", - "src/**/*", - "!src/**/__tests__/**/*" - ], - "publishConfig": { - "access": "public" + "workspaces": { + "packages": [ + "packages/*" + ] } } diff --git a/packages/plugin-sdk/README.md b/packages/plugin-sdk/README.md new file mode 100644 index 0000000..efd2182 --- /dev/null +++ b/packages/plugin-sdk/README.md @@ -0,0 +1,988 @@ +

+ + + + Sigma Logo + + +

+ +Sigma Computing Plugins provides an API for third-party applications add +additional functionality into an existing Sigma workbook. + +Plugins are built using Sigma’s Plugin API. This API communicates data and +interaction events between a Sigma workbook and the plugin. Plugins are hosted +by their developer and rendered in an iframe in Sigma. + +#### Warning: Breaking Changes + +`@sigmacomputing/plugin` has moved to https://github.com/sigmacomputing/plugin and +is now open source. Please read our +[CHANGELOG.md](https://github.com/sigmacomputing/plugin/blob/main/CHANGELOG.md) +to review any breaking changes that have been made. + +## Requirements + +To test your plugin in Sigma Plugin Dev Playground, you must: + +- Have an Admin, Creator or Explorer account type +- Have Can Edit permission on the work +- Be in the workbook’s Edit mode + +To test a development version of a registered plugin, you must: + +- Have either: + - An Admin account type + - A custom account type that supports plugin developer feature permissions + +- Have "can edit" permission on the workbook +- Be in the workbook’s Edit mode + +Your plugin must be a Javascript-based project and run in the browser. + +## Getting Started + +### Installation + +Provided you have already followed the steps to create a plugin and a plugin +development environment, you can install `@sigmacomputing/plugin` using one of +the following commands + +```sh +yarn add @sigmacomputing/plugin +# or +npm install @sigmacomputing/plugin +``` + +If you have yet to set up your development environment, follow one of the setup +guides below + +### Create a Development Project + +At Sigma, we use React for all of our frontend development. This was taken into +consideration when building Sigma’s Plugin feature. + +While you are not required to use React for your plugin, it must be written in +Javascript and React is recommended. We support both a standard Javascript API +and a React Hooks API. + +#### Create a Project with Vite + +1. Open your terminal and navigate to the directory you want to create your + project in. +2. Create your new project. We recommend using + [`create-vite`](https://www.npmjs.com/package/create-vite). + + ```sh + yarn create vite + # or + npm create vite@latest + ``` + +3. Then follow the prompts! You can also directly specify the project name and the template you want to use via additional command line options. For example, to scaffold a Vite + Vue project, run: + + ```sh + yarn create vite my-vue-app --template vue + ``` + +4. Navigate to the project's main directory. + + ```sh + cd + ``` + +5. Use your package manager to install Sigma’s plugin library. We recommend + using `yarn`. + + ```sh + yarn add @sigmacomputing/plugin + ``` + +6. Spin up your local development server. + + ```sh + yarn && yarn dev + ``` + +7. Start developing: + - Get started with Sigma’s Plugin APIs. + - Test your plugin directly in a Sigma workbook using the Sigma Plugin Dev + Playground. + - By default, vite dev servers run on http://localhost:5173. + +NOTE: Facebook's [create-react-app](https://github.com/facebook/create-react-app) is deprecated. You should use [vite](https://github.com/vitejs/vite) to setup your project. + +## Testing your Plugin + +Plugin developers should have access to a special plugin called Sigma Plugin Dev +Playground. This plugin is available from any workbook and points to +http://localhost:3000, by default. + +If you cannot find this plugin, or would like a development playground with an +alternative default host, please contact your Organization Admin with a request +to Register a Plugin with its production URL set to your preferred development +URL. + +### Using the Development Playground + +Before you start: + +- Set your plugin’s development URL to http://localhost:3000. +- Start your plugin locally + + > Note: If you followed our recommendations under + > [#create-a-development-project](#create-a-development-project), enter the + > following command in your terminal: + > + > ```sh + > yarn && yarn dev + > ``` + +1. Create/open a workbook. +2. In the workbook header, click Edit. +3. Click the + button in the sidebar, to open the workbook’s ADD NEW panel. +4. Select the PLUGINS element type, located under UI ELEMENTS. +5. The editor panel will show you a list of available plugins. Select Sigma + Plugin Dev Playground. +6. Your new plugin element will appear on the page. + +> **Note:** +> The editor panel will only display content if you have configured your plugin +> using Sigma’s plugin [Configuration API](#documentation). +> Likewise, the element will only display content if your plugin is configured to display content. +> If you change a plugin's configuration options, input values will need to be +> re-added in the editor panel. + +**Now what?** + +- You can refresh your plugin as you make changes to its code. This option is + available from the element’s menu. +- You are responsible for hosting your plugin. [Learn more](#host-your-plugin). +- When you’re ready to register your plugin, [Add your custom your + Plugin](https://help.sigmacomputing.com/hc/en-us/articles/4410105794963) with + Sigma. + +## Documentation + +#### CustomPluginConfigOptions + +A plugin can be configured with any number of configuration fields. Each field +type has its own configuration options. Each field type is also guaranteed to +have the following options: + +- `name : string` - the name of the field +- `type : string` - the field type +- `label : string (optional)` - a display name for the field + +
+Full CustomPluginConfigOptions Type + +```ts +type CustomPluginConfigOptions = + | { + type: 'group'; + name: string; + label?: string; + } + | { + type: 'element'; + name: string; + label?: string; + } + | { + type: 'column'; + name: string; + label?: string; + allowedTypes?: ValueType[]; + source: string; + allowMultiple: boolean; + } + | { + type: 'text'; + name: string; + label?: string; + source?: string; // can point to a group or element config + secure?: boolean; // if true will omit from prehydrated configs + multiline?: boolean; + placeholder?: string; + defaultValue?: string; + } + | { + type: 'toggle'; + name: string; + label?: string; + source?: string; + defaultValue?: boolean; + } + | { + type: 'checkbox'; + name: string; + label?: string; + source?: string; + defaultValue?: boolean; + } + | { + type: 'radio'; + name: string; + label?: string; + source?: string; + values: string[]; + singleLine?: boolean; + defaultValue?: string; + } + | { + type: 'dropdown'; + name: string; + label?: string; + source?: string; + width?: string; + values: string[]; + defaultValue?: string; + } + | { + type: 'color'; + name: string; + label?: string; + source?: string; + } + | { + type: 'variable'; + name: string; + label?: string; + allowedTypes?: ControlType[]; + } + | { + type: 'interaction'; + name: string; + label?: string; + } + | { + type: 'action-trigger'; + name: string; + label?: string; + } + | { + type: 'action-effect'; + name: string; + label?: string; + } + | { + type: 'url-parameter'; + name: string; + }; +``` + +
+ +**Group** + +Can be used to identify a group of related fields + +**Element** + +A custom element that is added by your plugin + +**Column** + +A custom column configuration that your plugin uses + +Additional Fields + +- `allowedTypes : ValueType[] (optional)` - the allowed data types that this + column can contain where `ValueType` has the following type: + + ```ts + type ValueType = + | 'boolean' + | 'datetime' + | 'number' + | 'integer' + | 'text' + | 'variant' + | 'link' + | 'error'; + ``` + +- `source : string` - the data source that should be used to supply this field +- `allowMultiple : boolean` - whether multiple columns should be allowed as + input for this field + +**Text** + +A configurable text input for your plugin + +Additional Fields + +- `source : string (optional)` - the data source that should be used to supply this field +- `secure : boolean (optional)` - whether to omit input from pre-hydrated configs +- `multiline : boolean (optional)` - whether this text input should allow + multiple lines +- `placeholder : string (optional)` - the placeholder for this input field +- `defaultValue : string (optional)` - the default value for this input field + +**Toggle** + +A configurable toggle for your plugin + +Additional Fields + +- `source : string (optional)` - the data source that should be used to supply this field +- `defaultValue : boolean (optional)` - the default value for this input field + +**Checkbox** + +A configurable checkbox for your plugin + +Additional Fields + +- `source : string (optional)` - the data source that should be used to supply this field +- `defaultValue : boolean (optional)` - the default value for this input field + +**Radio** + +A configurable radio button for your plugin + +Additional Fields + +- `source : string (optional)` - the data source that should be used to supply + this field +- `values : string[]` - the options to show for this input field +- `singleLine : boolean (optional)` - whether to display options on a single + line. Good for (2-3) options +- `defaultValue : boolean (optional)` - the default value for this input field + +**Dropdown** + +A configurable dropdown for your plugin + +Additional Fields + +- `source : string (optional)` - the data source that should be used to supply + this field +- `values : string[]` - the options to show for this input field +- `width : string (optional)` - how wide the dropdown should be in pixels +- `defaultValue : boolean (optional)` - the default value for this input field + +**Color** + +A configurable color picker for your plugin + +Additional Fields + +- `source : string (optional)` - the data source that should be used to supply + this field + +**Variable** + +A configurable workbook variable to control other elements within your workbook + +Additional Fields + +- `allowedTypes : ControlType[] (optional)` - the allowed control types that this + variable can use where `ControlType` has the following type: + + ```ts + type ControlType = + | 'boolean' + | 'date' + | 'number' + | 'text' + | 'text-list' + | 'number-list' + | 'date-list' + | 'number-range' + | 'date-range'; + ``` + +**Interaction** + +A configurable workbook interaction to interact with other charts within your workbook + +**Action Trigger** + +A configurable action trigger to trigger actions in other elements within your workbook + +**Action Effect** + +A configurable action effect that can be triggered by other elements within your workbook + +**URL Parameter** + +A configurable URL parameter that can be read from and written to the browser's URL. This allows plugins to sync state with the URL for bookmarking and sharing. + +Additional Fields + +- `name : string` - The config ID used to access this URL parameter via the API + +#### PluginInstance + +```ts +interface PluginInstance { + sigmaEnv: 'author' | 'viewer' | 'explorer'; + + config: { + /** + * Getter for entire Plugin Config + */ + get(): Partial | undefined; + + /** + * Performs a shallow merge between current config and passed in config + */ + set(config: Partial): void; + + /** + * Getter for key within plugin config + */ + getKey(key: K): Pick; + + /** + * Assigns key value pair within plugin + */ + setKey(key: K, value: Pick): void; + + /** + * Subscriber for Plugin Config + */ + subscribe(listener: (arg0: T) => void): Unsubscriber; + + /** + * Set possible options for plugin config + */ + configureEditorPanel(options: CustomPluginConfigOptions[]): void; + + /** + * Gets a static image of a workbook variable + */ + getVariable(configId: string): WorkbookVariable; + + /** + * Setter for workbook variable passed in + */ + setVariable(configId: string, ...values: unknown[]): void; + + /** + * Getter for interaction selection state + */ + getInteraction(configId: string): WorkbookSelection[]; + + /** + * Setter for interaction selection state + */ + setInteraction( + configId: string, + elementId: string, + selection: WorkbookSelection[], + ): void; + + /** + * Triggers an action based on the provided action trigger ID + */ + triggerAction(configId: string): void; + + /** + * Registers an effect with the provided action effect ID + */ + registerEffect(configId: string, effect: Function): void; + + /** + * Overrider function for Config loading state + */ + setLoadingState(isLoading: boolean): void; + + /** + * Allows users to subscribe to changes in the passed in variable + */ + subscribeToWorkbookVariable( + configId: string, + callback: (input: WorkbookVariable) => void, + ): Unsubscriber; + + /** + * Allows users to subscribe to changes in the url parameter + */ + subscribeToUrlParameter( + configId: string, + callback: (input: UrlParameter) => void, + ): Unsubscriber; + + /** + * Gets the current value of a url parameter + */ + getUrlParameter(configId: string): UrlParameter; + + /** + * Setter for url parameter + */ + setUrlParameter(configId: string, value: string): void; + + /** + * @deprecated Use Action API instead + * Allows users to subscribe to changes in the passed in interaction ID + */ + subscribeToWorkbookInteraction( + configId: string, + callback: (input: WorkbookSelection[]) => void, + ): Unsubscriber; + }; + + elements: { + /** + * Getter for Column Data by parent sheet ID + */ + getElementColumns(configId: string): Promise; + + /** + * Subscriber to changes in column data by ID + */ + subscribeToElementColumns( + configId: string, + callback: (cols: WbElementColumns) => void, + ): Unsubscriber; + + /** + * Subscriber for the data within a given sheet + */ + subscribeToElementData( + configId: string, + callback: (data: WbElementData) => void, + ): Unsubscriber; + }; + + style: { + /** + * Subscribe to workbook style updates + */ + subscribe(callback: (style: PluginStyle) => void): () => void; + + /** + * Request current style from workbook + */ + get(): Promise; + }; + + /** + * Destroys plugin instance and removes all subscribers + */ + destroy(): void; +} +``` + +### Framework Agnostic API + +#### client + +The client is a pre-initialized plugin instance. You can use this instance +directly or create your own instance using `initialize` + +```ts +const client: PluginInstance = initialize(); +``` + +Usage + +```ts +import { client } from '@sigmacomputing/plugin'; + +client.config.configureEditorPanel([ + { name: 'source', type: 'element' }, + { name: 'dimension', type: 'column', source: 'source', allowMultiple: true }, +]); +``` + +#### initialize() + +Instead of using the pre-initialized plugin instance, you can create your own +plugin instance. + +```ts +function initialize(): PluginInstance; +``` + +Usage + +```ts +import { initialize } from '@sigmacomputing/plugin'; + +const myClient: PluginInstance = initialize(); + +myClient.config.configureEditorPanel([ + { name: 'source', type: 'element' }, + { name: 'dimension', type: 'column', source: 'source', allowMultiple: true }, +]); +``` + +### React API + +#### + +A context provider your plugin that enables all of the other React API hooks. +You should wrap your plugin with this provider if your want to use the plugin +hook API. + +```ts +interface SigmaClientProviderProps { + client: PluginInstance; + children?: ReactNode; +} + +function SigmaClientProvider(props: SigmaClientProviderProps): ReactNode; +``` + +#### usePlugin() + +Gets the entire plugin instance + +```ts +function usePlugin(): PluginInstance; +``` + +#### useEditorPanelConfig() + +Provides a setter for the plugin's configuration options + +```ts +function useEditorPanelConfig(nextOptions: CustomPluginConfigOptions[]): void; +``` + +Provides a setter for the Plugin's Config Options + +Arguments + +- `nextOptions : CustomPluginConfigOptions[]` - Updated possible Config Options + +#### useLoadingState() + +Gets the current plugin's loading state and a setter. Call the setter with +`false` when your plugin has finished loading + +```ts +function useLoadingState( + initialState: boolean, +): [boolean, (nextState: boolean) => void]; +``` + +Arguments + +- `initialState : boolean` - Initial loading state (typically `true`; then set to `false` when ready) + +#### useElementColumns() + +Provides the latest column values from corresponding sheet + +```ts +function useElementColumns(elementId: string): WorkbookElementColumns; +``` + +Arguments + +- `elementId : string` - A workbook element’s unique identifier. + +Returns the column information from the specified element. + +```ts +interface WorkbookElementColumn { + id: string; + name: string; + columnType: ValueType; +} + +interface WorkbookElementColumns { + [colId: string]: WbElementColumn; +} +``` + +#### useElementData() + +Provides the latest data values from corresponding sheet, up to 25000 values. + +```ts +function useElementData(configId: string): WorkbookElementData; +``` + +Arguments + +- `configId : string` - A workbook element’s unique identifier from the plugin config. + +Returns the row data from the specified element. + +```ts +interface WorkbookElementData { + [colId: string]: any[]; +} +``` + +#### usePaginatedElementData() + +Provides the latest data values from the corresponding sheet (initially 25000), and provides a +callback for fetching more data in chunks of 25000 values. + +```ts +function useElementData(configId: string): [WorkbookElementData, () => void]; +``` + +Arguments + +- `configId : string` - A workbook element’s unique identifier from the plugin config. + +Returns the row data from the specified element, and a callback for fetching +more data. + +```ts +interface WorkbookElementData { + [colId: string]: any[]; +} +``` + +#### useVariable() + +Returns a given variable's value and a setter to update that variable + +```ts +function useVariable( + configId: string, +): [WorkbookVariable | undefined, (...values: unknown[]) => void]; +``` + +Arguments + +- `configId : string` - The config ID corresponding to the workbook control variable + +The returned setter function accepts 1 or more variable values expressed as an +array or multiple parameters + +```ts +function setVariableCallback(...values: unknown[]): void; +``` + +#### useUrlParameter() + +Returns a given URL parameter's value and a setter to update that URL parameter + +```ts +function useUrlParameter( + configId: string, +): [UrlParameter | undefined, (value: string) => void]; +``` + +Arguments + +- `configId : string` - The config ID corresponding to the URL parameter + +The returned setter function accepts a string value that will be set as the URL parameter value + +```ts +function setUrlParameterCallback(value: string): void; +``` + +The URL parameter value has the following structure: + +```ts +interface UrlParameter { + value: string; +} +``` + +Example + +```ts +const [urlParam, setUrlParam] = useUrlParameter('myParamId'); + +// Read the current value +console.log(urlParam?.value); // e.g., "current-value" + +// Update the URL parameter +setUrlParam('new-value'); +``` + +Framework Agnostic Usage + +You can also use the URL parameter API without React hooks: + +```ts +import { initialize } from '@sigmacomputing/plugin'; + +const client = initialize(); + +// Get current value +const urlParam = client.config.getUrlParameter('myParamId'); +console.log(urlParam?.value); + +// Set a new value +client.config.setUrlParameter('myParamId', 'new-value'); + +// Subscribe to changes +const unsubscribe = client.config.subscribeToUrlParameter( + 'myParamId', + urlParam => { + console.log('URL parameter updated:', urlParam.value); + }, +); +``` + +#### useInteraction() + +Returns a given interaction's selection state and a setter to update that interaction + +```ts +function useInteraction( + configId: string, + elementId: string, +): [WorkbookSelection | undefined, (value: WorkbookSelection[]) => void]; +``` + +Arguments + +- `configId : string` - The config ID corresponding to the workbook interaction +- `elementId : string` - The ID of the element that this interaction is + associated with + +The returned setter function accepts an array of workbook selection elements + +```ts +function setVariableCallback(value: WorkbookSelection[]): void; +``` + +#### useActionTrigger() + +- `configId : string` - The config ID corresponding to the action trigger + +Returns a callback function to trigger one or more action effects for a given action trigger + +```ts +function useActionTrigger(configId: string): () => void; +``` + +#### useActionEffect() + +Registers and unregisters an action effect within the plugin + +```ts +function useActionEffect(configId: string, effect: () => void); +``` + +Arguments + +- `configId : string` - The config ID corresponding to the action effect +- `effect : Function` - The function to be called when the effect is triggered + +#### usePluginStyle() + +Returns style properties from the workbook. + +```ts +function usePluginStyle(): PluginStyle | undefined; +``` + +> **Note:** Currently, the `PluginStyle` interface only supports the `backgroundColor` property, which reflects the background color set in the workbook for the plugin element. + +#### useConfig() + +Returns the workbook element’s current configuration. If a key is provided, only +the associated configuration is returned. + +```ts +function useConfig(key?: string): any; +``` + +Arguments + +- `key : string (optional)` - The name of a key within the associated + `PluginConfigOptions` object + +## Examples + +Sigma’s development team has created a set of example plugins, listed below. + +All of our example plugins are hosted and can be added to your organization. To +view / add an example plugin to your organization, follow the steps to register +a plugin using its Production URL, listed below. + +You can also visit Sigma’s [Sample Plugin +repository](https://github.com/sigmacomputing/sigma-sample-plugins) directly on +Github. + +### Available Plugins + +- **Recharts Bar Chart** - A basic bar chart built with the Recharts library. + - [Source Code](https://github.com/sigmacomputing/sigma-sample-plugins/tree/main/sample-plugin-bar-chart) + - Production URL: https://sigma-sample-bar-chart-54049.netlify.app/ + +- **D3 Candlestick** - A candlestick visualization built with D3. + - [Source Code](https://github.com/sigmacomputing/sigma-sample-plugins/tree/main/sample-plugin-bar-chart) + - Production URL: https://sigma-sample-candlestick-chart-1664e5.netlify.app/ + +- **Narrative Science Quill** - Demonstrates secure text entry. + - [Source Code]() + - Production URL: https://narrativescience-quill-3ee312.netlify.app/ + +- **D3 Graph** - Demonstrates usage of multiple data sources and in-memory + joins. + - [Source Code](https://github.com/sigmacomputing/sigma-sample-plugins/tree/main/d3-graph) + - Production URL: https://d3-graph-3a0d0f.netlify.app/ + +- **D3 Sunburst** - A sunburst visualization built with D3. + - [Source Code](https://github.com/sigmacomputing/sigma-sample-plugins/tree/main/d3-sunburst) + - Production URL: https://d3-sunburst-b97c7c.netlify.app/ + +- **Frappe Heatmap** - A basic Frappe visualization example. + - [Source Code](https://github.com/sigmacomputing/sigma-sample-plugins/tree/main/frappe-heatmap) + - Production URL: https://frappe-heatmap-9a4163.netlify.app/ + +### Use an Example in Your Organization + +To add an example plugin to your organization, follow the steps to [register a +plugin](https://help.sigmacomputing.com/hc/en-us/articles/4410105794963) using +its Production URL, listed in the [examples](#available-plugins) above. + +### Run an Example Locally + +1. Open your terminal, and navigate to the directory you want to save the example’s in. +2. Clone Sigma’s [Sample Plugin + repository](https://github.com/sigmacomputing/sigma-sample-plugins). + + ```sh + git clone https://github.com/sigmacomputing/sigma-sample-plugins.git + ``` + +3. Navigate to the plugin you would like to try. + + ```sh + cd sigma-sample-plugins/ + ``` + +4. Run the plugin. + + ```sh + yarn && yarn start + ``` + +> **Note**: For additional instructions, visit the README file located in the main directory of any given example plugin. + +## Host Your Plugin + +As a plugin developer, you are responsible for hosting your plugin(s). If you’re +new to hosting your own projects, here are a few popular hosting platforms you +can get started with: + +- [Heroku](https://devcenter.heroku.com/) +- [Netlify](https://www.netlify.com/) + +## Contributing + +We welcome contributions to `@sigmacomputing/plugin`! + +🐛 Issues, 📥 Pull requests and 🌟 Stars are always welcome. +Read our [contributing +guide](https://github.com/sigmacomputing/plugin/blob/main/CONTRIBUTING.md) to +get started. + +**yarn format** + +Format your code to match the sigmacomputing style guide + +**yarn test** + +Check if the unit tests all pass + +> You can also run the tests in `--watch` mode with **yarn test:watch** diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json new file mode 100644 index 0000000..a65e23f --- /dev/null +++ b/packages/plugin-sdk/package.json @@ -0,0 +1,56 @@ +{ + "name": "@sigmacomputing/plugin", + "version": "1.1.1", + "description": "Sigma Computing Plugin Client SDK", + "license": "MIT", + "type": "module", + "scripts": { + "build": "tsdown", + "build:watch": "tsdown --watch", + "format": "yarn g:format", + "lint": "yarn g:lint", + "prepublish": "yarn types && yarn build", + "publish": "yarn npm publish", + "test": "vitest run", + "test:watch": "vitest", + "types": "tsc -b --noEmit" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + }, + "devDependencies": { + "tsdown": "^0.21.10", + "vitest": "^4.1.5" + }, + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.js", + "types": "./dist/cjs/index.d.cts", + "unpkg": "./dist/umd/sigmacomputing-plugin.umd.js", + "jsdelivr": "./dist/umd/sigmacomputing-plugin.umd.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.cts", + "default": "./dist/cjs/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/**/*", + "src/**/*", + "!src/**/__tests__/**/*" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/src/client.ts b/packages/plugin-sdk/src/client.ts similarity index 100% rename from src/client.ts rename to packages/plugin-sdk/src/client.ts diff --git a/src/client/__tests__/initialize.test.ts b/packages/plugin-sdk/src/client/__tests__/initialize.test.ts similarity index 100% rename from src/client/__tests__/initialize.test.ts rename to packages/plugin-sdk/src/client/__tests__/initialize.test.ts diff --git a/src/client/initialize.ts b/packages/plugin-sdk/src/client/initialize.ts similarity index 99% rename from src/client/initialize.ts rename to packages/plugin-sdk/src/client/initialize.ts index b84e478..8a93187 100644 --- a/src/client/initialize.ts +++ b/packages/plugin-sdk/src/client/initialize.ts @@ -28,7 +28,7 @@ export function initialize(): PluginInstance { try { pluginConfig[key] = JSON.parse(value); } catch (_err: unknown) { - if (__VITEST_BROWSER__ && (key === 'frameId' || key === 'sessionId')) { + if (__VITEST_BROWSER__ && (key === 'iframeId' || key === 'sessionId')) { // noop: vitest browser injects these into the test iframe URL } else { console.error( diff --git a/src/error.ts b/packages/plugin-sdk/src/error.ts similarity index 67% rename from src/error.ts rename to packages/plugin-sdk/src/error.ts index e737dc6..801a871 100644 --- a/src/error.ts +++ b/packages/plugin-sdk/src/error.ts @@ -1,10 +1,10 @@ import { CustomPluginConfigOptions } from './types'; export function validateConfigId( - configId: string, + configId: string | undefined, expectedConfigType: CustomPluginConfigOptions['type'], ) { if (configId === undefined) { - console.warn(`Invalid config ${expectedConfigType}: ${configId}`); + console.warn(`Invalid config ${expectedConfigType}`); } } diff --git a/src/globals.d.ts b/packages/plugin-sdk/src/globals.d.ts similarity index 100% rename from src/globals.d.ts rename to packages/plugin-sdk/src/globals.d.ts diff --git a/src/index.ts b/packages/plugin-sdk/src/index.ts similarity index 100% rename from src/index.ts rename to packages/plugin-sdk/src/index.ts diff --git a/src/react.ts b/packages/plugin-sdk/src/react.ts similarity index 100% rename from src/react.ts rename to packages/plugin-sdk/src/react.ts diff --git a/src/react/Context.ts b/packages/plugin-sdk/src/react/Context.ts similarity index 100% rename from src/react/Context.ts rename to packages/plugin-sdk/src/react/Context.ts diff --git a/src/react/Provider.tsx b/packages/plugin-sdk/src/react/Provider.tsx similarity index 100% rename from src/react/Provider.tsx rename to packages/plugin-sdk/src/react/Provider.tsx diff --git a/src/react/hooks.ts b/packages/plugin-sdk/src/react/hooks.ts similarity index 100% rename from src/react/hooks.ts rename to packages/plugin-sdk/src/react/hooks.ts diff --git a/src/types.ts b/packages/plugin-sdk/src/types.ts similarity index 100% rename from src/types.ts rename to packages/plugin-sdk/src/types.ts diff --git a/src/utils/deepEqual.ts b/packages/plugin-sdk/src/utils/deepEqual.ts similarity index 100% rename from src/utils/deepEqual.ts rename to packages/plugin-sdk/src/utils/deepEqual.ts diff --git a/src/utils/polyfillRequestAnimationFrame.ts b/packages/plugin-sdk/src/utils/polyfillRequestAnimationFrame.ts similarity index 100% rename from src/utils/polyfillRequestAnimationFrame.ts rename to packages/plugin-sdk/src/utils/polyfillRequestAnimationFrame.ts diff --git a/packages/plugin-sdk/tsconfig.app.json b/packages/plugin-sdk/tsconfig.app.json new file mode 100644 index 0000000..2ed40b8 --- /dev/null +++ b/packages/plugin-sdk/tsconfig.app.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.app.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + }, + "include": ["src"] +} diff --git a/packages/plugin-sdk/tsconfig.json b/packages/plugin-sdk/tsconfig.json new file mode 100644 index 0000000..da3c747 --- /dev/null +++ b/packages/plugin-sdk/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo" + }, + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/packages/plugin-sdk/tsconfig.node.json b/packages/plugin-sdk/tsconfig.node.json new file mode 100644 index 0000000..42f7286 --- /dev/null +++ b/packages/plugin-sdk/tsconfig.node.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.node.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + }, + "include": ["tsdown.config.ts", "vitest.config.ts"] +} diff --git a/packages/plugin-sdk/tsdown.config.ts b/packages/plugin-sdk/tsdown.config.ts new file mode 100644 index 0000000..fdb5930 --- /dev/null +++ b/packages/plugin-sdk/tsdown.config.ts @@ -0,0 +1,15 @@ +import { defineConfig, mergeConfig } from 'tsdown'; + +// @ts-ignore - base config is defined outside of this package +import baseConfig from '../../tsdown.base.ts'; + +import packageJson from './package.json' with { type: 'json' }; + +export default mergeConfig( + baseConfig, + defineConfig({ + define: { + __VERSION__: JSON.stringify(packageJson.version), + }, + }), +); diff --git a/packages/plugin-sdk/vitest.config.ts b/packages/plugin-sdk/vitest.config.ts new file mode 100644 index 0000000..c3e1107 --- /dev/null +++ b/packages/plugin-sdk/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; + +// @ts-ignore - base config is defined outside of this package +import baseConfig from '../../vitest.base.ts'; + +import packageJson from './package.json' with { type: 'json' }; + +export default mergeConfig( + baseConfig, + defineConfig({ + define: { + __VERSION__: JSON.stringify(packageJson.version), + }, + }), +); diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..15eb924 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "lib": ["DOM", "ESNext"], + "jsx": "react", + "types": ["vitest/globals"] + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..0e42b90 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ESNext", + + "noImplicitAny": true, + "strict": true, + + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "skipLibCheck": true, + + "forceConsistentCasingInFileNames": true + } +} diff --git a/tsconfig.json b/tsconfig.json index 250fae3..e485c7a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,13 @@ { + "extends": "./tsconfig.node.json", "compilerOptions": { - "lib": ["DOM", "ESNext"], - - "module": "ESNext", - "moduleResolution": "bundler", - "target": "ESNext", - - "jsx": "react", - "noImplicitAny": true, - "strict": true, - - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "types": ["vitest/globals"] + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo" }, - "include": ["src", "vitest.config.ts", "tsdown.config.ts"] + "include": [ + ".github/**/*.ts", + ".lintstagedrc.ts", + "scripts/**/*.ts", + "tsdown.base.ts", + "vitest.base.ts" + ] } diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..6ee1402 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "types": ["node"] + } +} diff --git a/tsdown.config.ts b/tsdown.base.ts similarity index 89% rename from tsdown.config.ts rename to tsdown.base.ts index 6eea879..d0d941e 100644 --- a/tsdown.config.ts +++ b/tsdown.base.ts @@ -7,7 +7,9 @@ export default defineConfig({ failOnWarn: true, logLevel: 'warn', - dts: true, + dts: { + build: true, + }, entry: ['src/index.ts'], format: { esm: { @@ -32,6 +34,11 @@ export default defineConfig({ }, }, }, + inputOptions: { + transform: { + jsx: 'react', + }, + }, platform: 'browser', sourcemap: true, diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..26cf168 --- /dev/null +++ b/turbo.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://v2-9-7.turborepo.dev/schema.json", + "noUpdateNotifier": true, + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputLogs": "new-only", + "inputs": [ + "src/**", + "tsconfig.json", + "tsdown.config.ts", + "$TURBO_ROOT$/tsdown.base.ts", + "$TURBO_ROOT$/yarn.lock" + ], + "outputs": ["dist/**"] + }, + "test": { + "dependsOn": ["^build", "^test"], + "outputLogs": "new-only", + "inputs": [ + "src/**", + "vitest.config.ts", + "$TURBO_ROOT$/vitest.base.ts", + "$TURBO_ROOT$/yarn.lock" + ] + }, + "types": { + "dependsOn": ["^build", "^types"], + "outputLogs": "new-only", + "inputs": [ + "src/**", + "tsconfig.json", + "tsconfig.app.json", + "$TURBO_ROOT$/tsconfig.base.json", + "$TURBO_ROOT$/tsconfig.app.json", + "$TURBO_ROOT$/yarn.lock" + ] + } + }, + "futureFlags": { + "watchUsingTaskInputs": true + } +} diff --git a/vitest.config.ts b/vitest.base.ts similarity index 77% rename from vitest.config.ts rename to vitest.base.ts index d090783..b211dbc 100644 --- a/vitest.config.ts +++ b/vitest.base.ts @@ -1,11 +1,8 @@ import { playwright } from '@vitest/browser-playwright'; import { defineConfig } from 'vitest/config'; -import packageJson from './package.json' with { type: 'json' }; - export default defineConfig({ define: { - __VERSION__: JSON.stringify(packageJson.version), __VITEST_BROWSER__: true.toString(), }, test: { diff --git a/yarn.lock b/yarn.lock index 4b38e28..62a6350 100644 --- a/yarn.lock +++ b/yarn.lock @@ -709,9 +709,9 @@ __metadata: languageName: node linkType: hard -"@sigmacomputing/plugin@workspace:.": +"@sigmacomputing/plugin-sdk-root@workspace:.": version: 0.0.0-use.local - resolution: "@sigmacomputing/plugin@workspace:." + resolution: "@sigmacomputing/plugin-sdk-root@workspace:." dependencies: "@arethetypeswrong/core": "npm:^0.18.2" "@types/node": "npm:^24.0.0" @@ -723,10 +723,21 @@ __metadata: oxlint-tsgolint: "npm:^0.16.0" playwright: "npm:^1.49.0" publint: "npm:^0.3.18" + syncpack: "npm:^14.3.1" tsdown: "npm:^0.21.10" + turbo: "npm:^2.9.7" typescript: "npm:^6.0.2" unplugin-unused: "npm:^0.5.7" vitest: "npm:^4.1.5" + languageName: unknown + linkType: soft + +"@sigmacomputing/plugin@workspace:packages/plugin-sdk": + version: 0.0.0-use.local + resolution: "@sigmacomputing/plugin@workspace:packages/plugin-sdk" + dependencies: + tsdown: "npm:^0.21.10" + vitest: "npm:^4.1.5" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: @@ -742,6 +753,48 @@ __metadata: languageName: node linkType: hard +"@turbo/darwin-64@npm:2.9.7": + version: 2.9.7 + resolution: "@turbo/darwin-64@npm:2.9.7" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@turbo/darwin-arm64@npm:2.9.7": + version: 2.9.7 + resolution: "@turbo/darwin-arm64@npm:2.9.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@turbo/linux-64@npm:2.9.7": + version: 2.9.7 + resolution: "@turbo/linux-64@npm:2.9.7" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@turbo/linux-arm64@npm:2.9.7": + version: 2.9.7 + resolution: "@turbo/linux-arm64@npm:2.9.7" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@turbo/windows-64@npm:2.9.7": + version: 2.9.7 + resolution: "@turbo/windows-64@npm:2.9.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@turbo/windows-arm64@npm:2.9.7": + version: 2.9.7 + resolution: "@turbo/windows-arm64@npm:2.9.7" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@tybys/wasm-util@npm:^0.10.1": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" @@ -2661,6 +2714,97 @@ __metadata: languageName: node linkType: hard +"syncpack-darwin-arm64@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-darwin-arm64@npm:14.3.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"syncpack-darwin-x64@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-darwin-x64@npm:14.3.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"syncpack-linux-arm64-musl@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-linux-arm64-musl@npm:14.3.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"syncpack-linux-arm64@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-linux-arm64@npm:14.3.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"syncpack-linux-x64-musl@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-linux-x64-musl@npm:14.3.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"syncpack-linux-x64@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-linux-x64@npm:14.3.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"syncpack-windows-arm64@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-windows-arm64@npm:14.3.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"syncpack-windows-x64@npm:14.3.1": + version: 14.3.1 + resolution: "syncpack-windows-x64@npm:14.3.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"syncpack@npm:^14.3.1": + version: 14.3.1 + resolution: "syncpack@npm:14.3.1" + dependencies: + syncpack-darwin-arm64: "npm:14.3.1" + syncpack-darwin-x64: "npm:14.3.1" + syncpack-linux-arm64: "npm:14.3.1" + syncpack-linux-arm64-musl: "npm:14.3.1" + syncpack-linux-x64: "npm:14.3.1" + syncpack-linux-x64-musl: "npm:14.3.1" + syncpack-windows-arm64: "npm:14.3.1" + syncpack-windows-x64: "npm:14.3.1" + dependenciesMeta: + syncpack-darwin-arm64: + optional: true + syncpack-darwin-x64: + optional: true + syncpack-linux-arm64: + optional: true + syncpack-linux-arm64-musl: + optional: true + syncpack-linux-x64: + optional: true + syncpack-linux-x64-musl: + optional: true + syncpack-windows-arm64: + optional: true + syncpack-windows-x64: + optional: true + bin: + syncpack: index.cjs + checksum: 10c0/c3b0d87cc6df19053069b049805e704abc2ac2c344eff27f810a8c36d79eee2603f0aa88c5fc2c2651f2c30d5c879475c73c630a463460e0b75e941d241b482d + languageName: node + linkType: hard + "tar@npm:^6.1.11, tar@npm:^6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1" @@ -2785,6 +2929,35 @@ __metadata: languageName: node linkType: hard +"turbo@npm:^2.9.7": + version: 2.9.7 + resolution: "turbo@npm:2.9.7" + dependencies: + "@turbo/darwin-64": "npm:2.9.7" + "@turbo/darwin-arm64": "npm:2.9.7" + "@turbo/linux-64": "npm:2.9.7" + "@turbo/linux-arm64": "npm:2.9.7" + "@turbo/windows-64": "npm:2.9.7" + "@turbo/windows-arm64": "npm:2.9.7" + dependenciesMeta: + "@turbo/darwin-64": + optional: true + "@turbo/darwin-arm64": + optional: true + "@turbo/linux-64": + optional: true + "@turbo/linux-arm64": + optional: true + "@turbo/windows-64": + optional: true + "@turbo/windows-arm64": + optional: true + bin: + turbo: bin/turbo + checksum: 10c0/5fa010810ce251279dac83db02e60ebd8014cf8e74e69a6c09751ae28aef0fdb95aa09b059ce8c987a1746dd5b04bb3002bd7fdcb6d0016d8690f079fec43921 + languageName: node + linkType: hard + "typescript@npm:5.6.1-rc": version: 5.6.1-rc resolution: "typescript@npm:5.6.1-rc"