sanity-plugin-singleton-management 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 RC Maples
4
+ Copyright (c) 2024 RD Pennell
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ [![CI](https://github.com/rcmaples/sanity-plugin-singleton-management/actions/workflows/ci.yml/badge.svg)](https://github.com/rcmaples/sanity-plugin-singleton-management/actions/workflows/ci.yml) [![codecov](https://codecov.io/github/rcmaples/sanity-plugin-singleton-management/graph/badge.svg?token=WYKSA756IY)](https://codecov.io/github/rcmaples/sanity-plugin-singleton-management)
2
+
3
+ # sanity-plugin-singleton-tools
4
+
5
+ > This is compatible with v4 and v3 of Sanity Studio.
6
+
7
+ ## What does this plugin do?
8
+
9
+ This plugin adds convenience functions to reduce the overhead of creating [singleton](https://www.sanity.io/docs/studio/structure-builder-cheat-sheet#k5cd7ca204386) documents in the [Sanity Studio](https://www.sanity.io).
10
+
11
+ In short, this does the following:
12
+
13
+ - Limits a singleton document's actions to Publish, Unpublish, and Discard Changes.
14
+ - Removes the ability to create new versions of the singleton document in both the global Create menu and Structure.
15
+ - Adds simple methods for customizing the way your singletons are listed in your Studio's Structure.
16
+
17
+ ## Why Use This Plugin vs. Native Sanity Approaches?
18
+
19
+ While Sanity Studio supports singleton documents through the Structure Builder API, this
20
+ plugin eliminates the repetitive boilerplate and provides additional safeguards:
21
+
22
+ ### Native Sanity Approach
23
+ ```js
24
+ // Requires manual Structure Builder configuration for each singleton
25
+ export const structure = (S) =>
26
+ S.list()
27
+ .title('Content')
28
+ .items([
29
+ S.listItem()
30
+ .title('Site Settings')
31
+ .child(
32
+ S.document()
33
+ .schemaType('siteSettings')
34
+ .documentId('siteSettings')
35
+ ),
36
+ // Must manually filter out singletons from main list
37
+ ...S.documentTypeListItems().filter(listItem =>
38
+ !['siteSettings'].includes(listItem.getId()))
39
+ ])
40
+ ```
41
+
42
+ **Issues:**
43
+
44
+ - Repetitive boilerplate for each singleton
45
+ - No prevention of duplicate document creation
46
+ - Users can still create new versions via global Create menu
47
+ - Manual filtering required to prevent duplicates in document lists
48
+
49
+
50
+ **With This Plugin:**
51
+
52
+ ```js
53
+ // Schema configuration
54
+ export const siteSettings = {
55
+ name: 'siteSettings',
56
+ type: 'document',
57
+ options: { singleton: true } // That's it!
58
+ }
59
+
60
+ // Structure configuration
61
+ export const structure = (S, context) =>
62
+ S.list()
63
+ .items([
64
+ ...singletonDocumentListItems({ S, context }), // Auto-generates all singletons
65
+ ...filteredDocumentListItems({ S, context }) // Auto-filters singletons from main list
66
+ ])
67
+ ```
68
+
69
+ Benefits:
70
+ - ✅ Minimal configuration with `singleton: true`
71
+ - ✅ Automatic action restrictions (no duplicate creation)
72
+ - ✅ Global Create menu integration
73
+ - ✅ Helper functions eliminate boilerplate
74
+ - ✅ Consistent singleton behavior across your studio
75
+
76
+ ## Installation
77
+
78
+ ```sh
79
+ npm install sanity-plugin-singleton-management
80
+ ```
81
+
82
+ ## Migration from sanity-plugin-singleton-tools
83
+
84
+ This plugin is a modernized, actively maintained fork of `sanity-plugin-singleton-tools` with identical API compatibility. Migration is straightforward:
85
+
86
+ ### Steps
87
+ 1. **Uninstall the old package**:
88
+ ```sh
89
+ npm uninstall sanity-plugin-singleton-tools
90
+ ```
91
+
92
+ 2. **Install this package**:
93
+ ```sh
94
+ npm install sanity-plugin-singleton-management
95
+ ```
96
+
97
+ 3. **Update your import statements**:
98
+ ```diff
99
+ // sanity.config.js
100
+ - import { singletonTools } from 'sanity-plugin-singleton-tools'
101
+ + import { singletonTools } from 'sanity-plugin-singleton-management'
102
+
103
+ // structure.js
104
+ - import { singletonDocumentListItem } from 'sanity-plugin-singleton-tools'
105
+ + import { singletonDocumentListItem } from 'sanity-plugin-singleton-management'
106
+ ```
107
+
108
+ **That's it!** No other code changes are required. Your existing schema configurations and structure customizations will work exactly the same.
109
+
110
+ ### What's New
111
+ - ✅ **React 18/19 Support**: Compatible with modern React versions
112
+ - ✅ **Sanity v3/v4 Support**: Works with both Sanity Studio versions
113
+ - ✅ **Modern Tooling**: ESM/CommonJS dual package, better TypeScript support
114
+ - ✅ **Comprehensive Tests**: 100% test coverage for reliability
115
+ - ✅ **Active Maintenance**: Regular updates and dependency management
116
+ - ✅ **Node 18+ Support**: Modern Node.js compatibility
117
+
118
+ ## Usage
119
+
120
+ ### 1. Add the plugin to your `sanity.config`
121
+
122
+ ```js
123
+ //sanity.config.js
124
+ import { defineConfig } from "sanity";
125
+ import { singletonTools } from "sanity-plugin-singleton-management";
126
+
127
+ export default defineConfig({
128
+ //...
129
+ plugins: [singletonTools()],
130
+ });
131
+ ```
132
+
133
+ ### 2. Configure your singleton's schema
134
+
135
+ ```js
136
+ //mySingleton.js
137
+ export const mySingleton = {
138
+ name: "mySingleton",
139
+ title: "My Singleton",
140
+ type: "document",
141
+ options: {
142
+ singleton: true, // Identify this document as a singleton
143
+ },
144
+ };
145
+ ```
146
+
147
+ ### 3. Customize how your singleton is shown in your Structure:
148
+
149
+ ```js
150
+ // structure.js
151
+ import {
152
+ singletonDocumentListItem,
153
+ singletonDocumentListItems,
154
+ filteredDocumentListItems,
155
+ } from "sanity-plugin-singleton-management";
156
+ import { PlugIcon } from "@sanity/icons";
157
+
158
+ export const structure = (S, context) =>
159
+ S.list()
160
+ .title("Sanity Love Content")
161
+ .items([
162
+ // Create a list item for each singleton document in your schema that links directly to a document view
163
+ ...singletonDocumentListItems({ S, context }),
164
+ // Create a list item for a specific singleton
165
+ singletonDocumentListItem({
166
+ S,
167
+ context,
168
+ // Schema type
169
+ type: "mySingleton",
170
+ // Required for showing multiple singletons of the same schema type
171
+ title: "My Singleton",
172
+ // Required for showing multiple singletons of the same schema type
173
+ id: "mySingleton",
174
+ // Specify a custom icon
175
+ icon: PlugIcon,
176
+ }),
177
+ S.divider(),
178
+ // Filter singleton documents out of the default S.documentTypeListItems() to prevent them from being rendered as lists or as duplicates
179
+ ...filteredDocumentListItems({ S, context }),
180
+ ]);
181
+ ```
182
+
183
+ ## Appendix
184
+
185
+ Other reading (Sanity docs):
186
+
187
+ - [Singleton documents](https://www.sanity.io/docs/studio/structure-builder-cheat-sheet#k5cd7ca204386)
188
+ - [Filtering out singletons in Structure](https://www.sanity.io/docs/studio/create-a-link-to-a-single-edit-page-in-your-main-document-type-list#fa1e82fd32be)
189
+ - [Creating a link to a single edit page](https://www.sanity.io/docs/studio/create-a-link-to-a-single-edit-page-in-your-main-document-type-list)
190
+
191
+ ## License
192
+
193
+ [MIT](LICENSE) © RD Pennell & RC Maples
package/dist/index.cjs ADDED
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: !0 });
3
+ var sanity = require("sanity"), icons = require("@sanity/icons");
4
+ const getSingletonDocuments = (schema) => {
5
+ if (schema?._original?.types)
6
+ return schema._original.types.filter(({ options }) => options?.singleton).map((s) => s.name);
7
+ }, getIsSingleton = (schema, schemaType) => !schema?._original?.types || !schemaType ? !1 : schema._original.types.find(
8
+ ({ name }) => name === schemaType
9
+ )?.options?.singleton ?? !1, actions = (prev, { schema, schemaType }) => getIsSingleton(schema, schemaType) ? prev.filter(
10
+ ({ action }) => ["publish", "discardChanges", "restore"].includes(action)
11
+ ) : prev, newDocumentOptions = (prev, { schema, creationContext: { type, schemaType } }) => {
12
+ const singletons = getSingletonDocuments(schema), filterSingletons = ({ templateId }) => !singletons?.includes(templateId);
13
+ return type === "global" || singletons?.includes(schemaType ?? "") ? prev.filter(filterSingletons) : prev;
14
+ }, singletonTools = sanity.definePlugin(() => ({
15
+ name: "singleton-tools",
16
+ document: {
17
+ newDocumentOptions,
18
+ actions
19
+ }
20
+ })), singletonDocumentListItem = (config) => {
21
+ if (!config?.S || !config?.type || !config.context)
22
+ throw new Error(
23
+ "S, context, and type must be provided to singletonDocumentListItem. Example: singletonDocumentListItem({ S, context, type: 'product' })"
24
+ );
25
+ const { S, type, title, icon, id, context } = config, { schema } = context;
26
+ if (!schema)
27
+ throw new Error(
28
+ "Schema is required in context for singletonDocumentListItem"
29
+ );
30
+ const schemaType = schema.get(type), listTitle = title ?? schemaType?.title ?? type, listIcon = icon ?? schemaType?.icon ?? icons.DocumentIcon, listId = id ?? type;
31
+ return S.listItem().title(listTitle).icon(listIcon).child(S.document().schemaType(type).title(listTitle).id(listId));
32
+ }, singletonDocumentListItems = (config) => {
33
+ if (!config.S || !config.context)
34
+ throw new Error(
35
+ "S and context must be provided to singletonDocumentListItems. Example: singletonDocumentListItems({ S, context })"
36
+ );
37
+ const { S, context } = config, { schema } = context;
38
+ return getSingletonDocuments(schema)?.map(
39
+ (schemaType) => singletonDocumentListItem({ S, context, type: schemaType })
40
+ ) || [];
41
+ }, filteredDocumentListItems = (config) => {
42
+ if (!config.S || !config.context)
43
+ throw new Error(
44
+ "S and context must be provided to filteredDocumentListItems. Example: filteredDocumentListItems({ S, context })"
45
+ );
46
+ const { S, context } = config, { schema } = context, singletons = getSingletonDocuments(schema);
47
+ return S.documentTypeListItems().filter(
48
+ (type) => !singletons || !singletons.includes(type.getId())
49
+ );
50
+ };
51
+ exports.filteredDocumentListItems = filteredDocumentListItems;
52
+ exports.singletonDocumentListItem = singletonDocumentListItem;
53
+ exports.singletonDocumentListItems = singletonDocumentListItems;
54
+ exports.singletonTools = singletonTools;
55
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/helpers/index.ts","../src/actions.ts","../src/newDocumentOptions.ts","../src/pluginConfig.ts","../src/structure/index.ts"],"sourcesContent":["import { Schema } from \"sanity\";\n\nimport { SingletonPluginOptions } from \"../types\";\n\nexport const getSingletonDocuments = (schema: Schema): string[] | undefined => {\n if (!schema?._original?.types) {\n return undefined;\n }\n\n return schema._original.types\n .filter(({ options }) => (options as SingletonPluginOptions)?.singleton)\n .map((s: { name: string }) => s.name);\n};\n\nexport const getIsSingleton = (schema: Schema, schemaType: string): boolean => {\n if (!schema?._original?.types || !schemaType) {\n return false;\n }\n\n const documentSchema = schema._original.types.find(\n ({ name }) => name === schemaType,\n );\n\n return (\n (documentSchema?.options as SingletonPluginOptions)?.singleton ?? false\n );\n};\n","import { DocumentActionsContext, DocumentActionsResolver } from \"sanity\";\n\nimport { getIsSingleton } from \"./helpers\";\n\nexport const actions: DocumentActionsResolver = (\n prev,\n { schema, schemaType }: DocumentActionsContext,\n) => {\n return getIsSingleton(schema, schemaType)\n ? prev.filter(({ action }) =>\n [\"publish\", \"discardChanges\", \"restore\"].includes(action as string),\n )\n : prev;\n};\n","import { NewDocumentOptionsContext, NewDocumentOptionsResolver } from \"sanity\";\n\nimport { getSingletonDocuments } from \"./helpers\";\n\nexport const newDocumentOptions: NewDocumentOptionsResolver = (\n prev,\n { schema, creationContext: { type, schemaType } }: NewDocumentOptionsContext,\n) => {\n const singletons = getSingletonDocuments(schema);\n\n const filterSingletons = ({ templateId }: { templateId: string }) =>\n !singletons?.includes(templateId);\n\n if (type === \"global\") return prev.filter(filterSingletons);\n\n return singletons?.includes(schemaType ?? \"\")\n ? prev.filter(filterSingletons)\n : prev;\n};\n","import { definePlugin } from \"sanity\";\n\nimport { actions } from \"./actions\";\nimport { newDocumentOptions } from \"./newDocumentOptions\";\n\nexport const singletonTools = definePlugin(() => {\n return {\n name: \"singleton-tools\",\n document: {\n newDocumentOptions,\n actions,\n },\n };\n});\n","import { DocumentIcon } from \"@sanity/icons\";\nimport type { ListItemBuilder } from \"sanity/structure\";\n\nimport { getSingletonDocuments } from \"../helpers\";\nimport {\n SingletonDocumentListItemConfig,\n SingletonPluginListItemsConfig,\n} from \"../types\";\n\nconst singletonDocumentListItem = (\n config: SingletonDocumentListItemConfig,\n): ListItemBuilder => {\n if (!config?.S || !config?.type || !config.context) {\n throw new Error(\n \"S, context, and type must be provided to singletonDocumentListItem. \" +\n \"Example: singletonDocumentListItem({ S, context, type: 'product' })\",\n );\n }\n const { S, type, title, icon, id, context } = config;\n const { schema } = context;\n\n if (!schema) {\n throw new Error(\n \"Schema is required in context for singletonDocumentListItem\",\n );\n }\n\n const schemaType = schema.get(type);\n const listTitle = title ?? schemaType?.title ?? type;\n const listIcon = icon ?? schemaType?.icon ?? DocumentIcon;\n const listId = id ?? type;\n\n return S.listItem()\n .title(listTitle)\n .icon(listIcon)\n .child(S.document().schemaType(type).title(listTitle).id(listId));\n};\n\nconst singletonDocumentListItems = (\n config: SingletonPluginListItemsConfig,\n): ListItemBuilder[] => {\n if (!config.S || !config.context) {\n throw new Error(\n \"S and context must be provided to singletonDocumentListItems. \" +\n \"Example: singletonDocumentListItems({ S, context })\",\n );\n }\n\n const { S, context } = config;\n const { schema } = context;\n\n const singletons = getSingletonDocuments(schema);\n\n return (\n singletons?.map((schemaType) =>\n singletonDocumentListItem({ S, context, type: schemaType }),\n ) || []\n );\n};\n\nconst filteredDocumentListItems = (\n config: SingletonPluginListItemsConfig,\n): ListItemBuilder[] => {\n if (!config.S || !config.context) {\n throw new Error(\n \"S and context must be provided to filteredDocumentListItems. \" +\n \"Example: filteredDocumentListItems({ S, context })\",\n );\n }\n const { S, context } = config;\n const { schema } = context;\n\n const singletons = getSingletonDocuments(schema);\n\n return S.documentTypeListItems().filter(\n (type) => !singletons || !singletons.includes(type.getId() as string),\n );\n};\n\nexport {\n filteredDocumentListItems,\n singletonDocumentListItem,\n singletonDocumentListItems,\n};\n"],"names":["definePlugin","DocumentIcon"],"mappings":";;;AAIO,MAAM,wBAAwB,CAAC,WAAyC;AAC7E,MAAK,QAAQ,WAAW;AAIxB,WAAO,OAAO,UAAU,MACrB,OAAO,CAAC,EAAE,QAAA,MAAe,SAAoC,SAAS,EACtE,IAAI,CAAC,MAAwB,EAAE,IAAI;AACxC,GAEa,iBAAiB,CAAC,QAAgB,eACzC,CAAC,QAAQ,WAAW,SAAS,CAAC,aACzB,KAGc,OAAO,UAAU,MAAM;AAAA,EAC5C,CAAC,EAAE,KAAA,MAAW,SAAS;AACzB,GAGmB,SAAoC,aAAa,ICpBzD,UAAmC,CAC9C,MACA,EAAE,QAAQ,WAAA,MAEH,eAAe,QAAQ,UAAU,IACpC,KAAK;AAAA,EAAO,CAAC,EAAE,OAAA,MACb,CAAC,WAAW,kBAAkB,SAAS,EAAE,SAAS,MAAgB;AACpE,IACA,MCRO,qBAAiD,CAC5D,MACA,EAAE,QAAQ,iBAAiB,EAAE,MAAM,WAAA,QAChC;AACH,QAAM,aAAa,sBAAsB,MAAM,GAEzC,mBAAmB,CAAC,EAAE,iBAC1B,CAAC,YAAY,SAAS,UAAU;AAElC,SAAI,SAAS,YAEN,YAAY,SAAS,cAAc,EAAE,IAFd,KAAK,OAAO,gBAAgB,IAItD;AACN,GCba,iBAAiBA,OAAAA,aAAa,OAClC;AAAA,EACL,MAAM;AAAA,EACN,UAAU;AAAA,IACR;AAAA,IACA;AAAA,EAAA;AAEJ,EACD,GCJK,4BAA4B,CAChC,WACoB;AACpB,MAAI,CAAC,QAAQ,KAAK,CAAC,QAAQ,QAAQ,CAAC,OAAO;AACzC,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,QAAM,EAAE,GAAG,MAAM,OAAO,MAAM,IAAI,YAAY,QACxC,EAAE,OAAA,IAAW;AAEnB,MAAI,CAAC;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,QAAM,aAAa,OAAO,IAAI,IAAI,GAC5B,YAAY,SAAS,YAAY,SAAS,MAC1C,WAAW,QAAQ,YAAY,QAAQC,oBACvC,SAAS,MAAM;AAErB,SAAO,EAAE,WACN,MAAM,SAAS,EACf,KAAK,QAAQ,EACb,MAAM,EAAE,SAAA,EAAW,WAAW,IAAI,EAAE,MAAM,SAAS,EAAE,GAAG,MAAM,CAAC;AACpE,GAEM,6BAA6B,CACjC,WACsB;AACtB,MAAI,CAAC,OAAO,KAAK,CAAC,OAAO;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAKJ,QAAM,EAAE,GAAG,QAAA,IAAY,QACjB,EAAE,WAAW;AAInB,SAFmB,sBAAsB,MAAM,GAGjC;AAAA,IAAI,CAAC,eACf,0BAA0B,EAAE,GAAG,SAAS,MAAM,YAAY;AAAA,EAAA,KACvD,CAAA;AAET,GAEM,4BAA4B,CAChC,WACsB;AACtB,MAAI,CAAC,OAAO,KAAK,CAAC,OAAO;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,QAAM,EAAE,GAAG,QAAA,IAAY,QACjB,EAAE,OAAA,IAAW,SAEb,aAAa,sBAAsB,MAAM;AAE/C,SAAO,EAAE,wBAAwB;AAAA,IAC/B,CAAC,SAAS,CAAC,cAAc,CAAC,WAAW,SAAS,KAAK,MAAA,CAAiB;AAAA,EAAA;AAExE;;;;;"}
@@ -0,0 +1,40 @@
1
+ import { ComponentType } from "react";
2
+ import { ConfigContext } from "sanity";
3
+ import type { ListItemBuilder } from "sanity/structure";
4
+ import { Plugin as Plugin_2 } from "sanity";
5
+ import { ReactNode } from "react";
6
+ import { StructureBuilder } from "sanity/structure";
7
+
8
+ export declare const filteredDocumentListItems: (
9
+ config: SingletonPluginListItemsConfig,
10
+ ) => ListItemBuilder[];
11
+
12
+ export declare const singletonDocumentListItem: (
13
+ config: SingletonDocumentListItemConfig,
14
+ ) => ListItemBuilder;
15
+
16
+ declare interface SingletonDocumentListItemConfig {
17
+ S: StructureBuilder;
18
+ context: ConfigContext;
19
+ type: string;
20
+ title?: string;
21
+ id?: string;
22
+ icon?: ComponentType | ReactNode;
23
+ }
24
+
25
+ export declare const singletonDocumentListItems: (
26
+ config: SingletonPluginListItemsConfig,
27
+ ) => ListItemBuilder[];
28
+
29
+ declare interface SingletonPluginListItemsConfig {
30
+ S: StructureBuilder;
31
+ context: ConfigContext;
32
+ }
33
+
34
+ export declare const singletonTools: Plugin_2<void>;
35
+
36
+ export {};
37
+
38
+ declare module "sanity" {
39
+ interface DocumentOptions extends SingletonPluginOptions {}
40
+ }
@@ -0,0 +1,40 @@
1
+ import { ComponentType } from "react";
2
+ import { ConfigContext } from "sanity";
3
+ import type { ListItemBuilder } from "sanity/structure";
4
+ import { Plugin as Plugin_2 } from "sanity";
5
+ import { ReactNode } from "react";
6
+ import { StructureBuilder } from "sanity/structure";
7
+
8
+ export declare const filteredDocumentListItems: (
9
+ config: SingletonPluginListItemsConfig,
10
+ ) => ListItemBuilder[];
11
+
12
+ export declare const singletonDocumentListItem: (
13
+ config: SingletonDocumentListItemConfig,
14
+ ) => ListItemBuilder;
15
+
16
+ declare interface SingletonDocumentListItemConfig {
17
+ S: StructureBuilder;
18
+ context: ConfigContext;
19
+ type: string;
20
+ title?: string;
21
+ id?: string;
22
+ icon?: ComponentType | ReactNode;
23
+ }
24
+
25
+ export declare const singletonDocumentListItems: (
26
+ config: SingletonPluginListItemsConfig,
27
+ ) => ListItemBuilder[];
28
+
29
+ declare interface SingletonPluginListItemsConfig {
30
+ S: StructureBuilder;
31
+ context: ConfigContext;
32
+ }
33
+
34
+ export declare const singletonTools: Plugin_2<void>;
35
+
36
+ export {};
37
+
38
+ declare module "sanity" {
39
+ interface DocumentOptions extends SingletonPluginOptions {}
40
+ }
@@ -0,0 +1,56 @@
1
+ import { definePlugin } from "sanity";
2
+ import { DocumentIcon } from "@sanity/icons";
3
+ const getSingletonDocuments = (schema) => {
4
+ if (schema?._original?.types)
5
+ return schema._original.types.filter(({ options }) => options?.singleton).map((s) => s.name);
6
+ }, getIsSingleton = (schema, schemaType) => !schema?._original?.types || !schemaType ? !1 : schema._original.types.find(
7
+ ({ name }) => name === schemaType
8
+ )?.options?.singleton ?? !1, actions = (prev, { schema, schemaType }) => getIsSingleton(schema, schemaType) ? prev.filter(
9
+ ({ action }) => ["publish", "discardChanges", "restore"].includes(action)
10
+ ) : prev, newDocumentOptions = (prev, { schema, creationContext: { type, schemaType } }) => {
11
+ const singletons = getSingletonDocuments(schema), filterSingletons = ({ templateId }) => !singletons?.includes(templateId);
12
+ return type === "global" || singletons?.includes(schemaType ?? "") ? prev.filter(filterSingletons) : prev;
13
+ }, singletonTools = definePlugin(() => ({
14
+ name: "singleton-tools",
15
+ document: {
16
+ newDocumentOptions,
17
+ actions
18
+ }
19
+ })), singletonDocumentListItem = (config) => {
20
+ if (!config?.S || !config?.type || !config.context)
21
+ throw new Error(
22
+ "S, context, and type must be provided to singletonDocumentListItem. Example: singletonDocumentListItem({ S, context, type: 'product' })"
23
+ );
24
+ const { S, type, title, icon, id, context } = config, { schema } = context;
25
+ if (!schema)
26
+ throw new Error(
27
+ "Schema is required in context for singletonDocumentListItem"
28
+ );
29
+ const schemaType = schema.get(type), listTitle = title ?? schemaType?.title ?? type, listIcon = icon ?? schemaType?.icon ?? DocumentIcon, listId = id ?? type;
30
+ return S.listItem().title(listTitle).icon(listIcon).child(S.document().schemaType(type).title(listTitle).id(listId));
31
+ }, singletonDocumentListItems = (config) => {
32
+ if (!config.S || !config.context)
33
+ throw new Error(
34
+ "S and context must be provided to singletonDocumentListItems. Example: singletonDocumentListItems({ S, context })"
35
+ );
36
+ const { S, context } = config, { schema } = context;
37
+ return getSingletonDocuments(schema)?.map(
38
+ (schemaType) => singletonDocumentListItem({ S, context, type: schemaType })
39
+ ) || [];
40
+ }, filteredDocumentListItems = (config) => {
41
+ if (!config.S || !config.context)
42
+ throw new Error(
43
+ "S and context must be provided to filteredDocumentListItems. Example: filteredDocumentListItems({ S, context })"
44
+ );
45
+ const { S, context } = config, { schema } = context, singletons = getSingletonDocuments(schema);
46
+ return S.documentTypeListItems().filter(
47
+ (type) => !singletons || !singletons.includes(type.getId())
48
+ );
49
+ };
50
+ export {
51
+ filteredDocumentListItems,
52
+ singletonDocumentListItem,
53
+ singletonDocumentListItems,
54
+ singletonTools
55
+ };
56
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/helpers/index.ts","../src/actions.ts","../src/newDocumentOptions.ts","../src/pluginConfig.ts","../src/structure/index.ts"],"sourcesContent":["import { Schema } from \"sanity\";\n\nimport { SingletonPluginOptions } from \"../types\";\n\nexport const getSingletonDocuments = (schema: Schema): string[] | undefined => {\n if (!schema?._original?.types) {\n return undefined;\n }\n\n return schema._original.types\n .filter(({ options }) => (options as SingletonPluginOptions)?.singleton)\n .map((s: { name: string }) => s.name);\n};\n\nexport const getIsSingleton = (schema: Schema, schemaType: string): boolean => {\n if (!schema?._original?.types || !schemaType) {\n return false;\n }\n\n const documentSchema = schema._original.types.find(\n ({ name }) => name === schemaType,\n );\n\n return (\n (documentSchema?.options as SingletonPluginOptions)?.singleton ?? false\n );\n};\n","import { DocumentActionsContext, DocumentActionsResolver } from \"sanity\";\n\nimport { getIsSingleton } from \"./helpers\";\n\nexport const actions: DocumentActionsResolver = (\n prev,\n { schema, schemaType }: DocumentActionsContext,\n) => {\n return getIsSingleton(schema, schemaType)\n ? prev.filter(({ action }) =>\n [\"publish\", \"discardChanges\", \"restore\"].includes(action as string),\n )\n : prev;\n};\n","import { NewDocumentOptionsContext, NewDocumentOptionsResolver } from \"sanity\";\n\nimport { getSingletonDocuments } from \"./helpers\";\n\nexport const newDocumentOptions: NewDocumentOptionsResolver = (\n prev,\n { schema, creationContext: { type, schemaType } }: NewDocumentOptionsContext,\n) => {\n const singletons = getSingletonDocuments(schema);\n\n const filterSingletons = ({ templateId }: { templateId: string }) =>\n !singletons?.includes(templateId);\n\n if (type === \"global\") return prev.filter(filterSingletons);\n\n return singletons?.includes(schemaType ?? \"\")\n ? prev.filter(filterSingletons)\n : prev;\n};\n","import { definePlugin } from \"sanity\";\n\nimport { actions } from \"./actions\";\nimport { newDocumentOptions } from \"./newDocumentOptions\";\n\nexport const singletonTools = definePlugin(() => {\n return {\n name: \"singleton-tools\",\n document: {\n newDocumentOptions,\n actions,\n },\n };\n});\n","import { DocumentIcon } from \"@sanity/icons\";\nimport type { ListItemBuilder } from \"sanity/structure\";\n\nimport { getSingletonDocuments } from \"../helpers\";\nimport {\n SingletonDocumentListItemConfig,\n SingletonPluginListItemsConfig,\n} from \"../types\";\n\nconst singletonDocumentListItem = (\n config: SingletonDocumentListItemConfig,\n): ListItemBuilder => {\n if (!config?.S || !config?.type || !config.context) {\n throw new Error(\n \"S, context, and type must be provided to singletonDocumentListItem. \" +\n \"Example: singletonDocumentListItem({ S, context, type: 'product' })\",\n );\n }\n const { S, type, title, icon, id, context } = config;\n const { schema } = context;\n\n if (!schema) {\n throw new Error(\n \"Schema is required in context for singletonDocumentListItem\",\n );\n }\n\n const schemaType = schema.get(type);\n const listTitle = title ?? schemaType?.title ?? type;\n const listIcon = icon ?? schemaType?.icon ?? DocumentIcon;\n const listId = id ?? type;\n\n return S.listItem()\n .title(listTitle)\n .icon(listIcon)\n .child(S.document().schemaType(type).title(listTitle).id(listId));\n};\n\nconst singletonDocumentListItems = (\n config: SingletonPluginListItemsConfig,\n): ListItemBuilder[] => {\n if (!config.S || !config.context) {\n throw new Error(\n \"S and context must be provided to singletonDocumentListItems. \" +\n \"Example: singletonDocumentListItems({ S, context })\",\n );\n }\n\n const { S, context } = config;\n const { schema } = context;\n\n const singletons = getSingletonDocuments(schema);\n\n return (\n singletons?.map((schemaType) =>\n singletonDocumentListItem({ S, context, type: schemaType }),\n ) || []\n );\n};\n\nconst filteredDocumentListItems = (\n config: SingletonPluginListItemsConfig,\n): ListItemBuilder[] => {\n if (!config.S || !config.context) {\n throw new Error(\n \"S and context must be provided to filteredDocumentListItems. \" +\n \"Example: filteredDocumentListItems({ S, context })\",\n );\n }\n const { S, context } = config;\n const { schema } = context;\n\n const singletons = getSingletonDocuments(schema);\n\n return S.documentTypeListItems().filter(\n (type) => !singletons || !singletons.includes(type.getId() as string),\n );\n};\n\nexport {\n filteredDocumentListItems,\n singletonDocumentListItem,\n singletonDocumentListItems,\n};\n"],"names":[],"mappings":";;AAIO,MAAM,wBAAwB,CAAC,WAAyC;AAC7E,MAAK,QAAQ,WAAW;AAIxB,WAAO,OAAO,UAAU,MACrB,OAAO,CAAC,EAAE,QAAA,MAAe,SAAoC,SAAS,EACtE,IAAI,CAAC,MAAwB,EAAE,IAAI;AACxC,GAEa,iBAAiB,CAAC,QAAgB,eACzC,CAAC,QAAQ,WAAW,SAAS,CAAC,aACzB,KAGc,OAAO,UAAU,MAAM;AAAA,EAC5C,CAAC,EAAE,KAAA,MAAW,SAAS;AACzB,GAGmB,SAAoC,aAAa,ICpBzD,UAAmC,CAC9C,MACA,EAAE,QAAQ,WAAA,MAEH,eAAe,QAAQ,UAAU,IACpC,KAAK;AAAA,EAAO,CAAC,EAAE,OAAA,MACb,CAAC,WAAW,kBAAkB,SAAS,EAAE,SAAS,MAAgB;AACpE,IACA,MCRO,qBAAiD,CAC5D,MACA,EAAE,QAAQ,iBAAiB,EAAE,MAAM,WAAA,QAChC;AACH,QAAM,aAAa,sBAAsB,MAAM,GAEzC,mBAAmB,CAAC,EAAE,iBAC1B,CAAC,YAAY,SAAS,UAAU;AAElC,SAAI,SAAS,YAEN,YAAY,SAAS,cAAc,EAAE,IAFd,KAAK,OAAO,gBAAgB,IAItD;AACN,GCba,iBAAiB,aAAa,OAClC;AAAA,EACL,MAAM;AAAA,EACN,UAAU;AAAA,IACR;AAAA,IACA;AAAA,EAAA;AAEJ,EACD,GCJK,4BAA4B,CAChC,WACoB;AACpB,MAAI,CAAC,QAAQ,KAAK,CAAC,QAAQ,QAAQ,CAAC,OAAO;AACzC,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,QAAM,EAAE,GAAG,MAAM,OAAO,MAAM,IAAI,YAAY,QACxC,EAAE,OAAA,IAAW;AAEnB,MAAI,CAAC;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,QAAM,aAAa,OAAO,IAAI,IAAI,GAC5B,YAAY,SAAS,YAAY,SAAS,MAC1C,WAAW,QAAQ,YAAY,QAAQ,cACvC,SAAS,MAAM;AAErB,SAAO,EAAE,WACN,MAAM,SAAS,EACf,KAAK,QAAQ,EACb,MAAM,EAAE,SAAA,EAAW,WAAW,IAAI,EAAE,MAAM,SAAS,EAAE,GAAG,MAAM,CAAC;AACpE,GAEM,6BAA6B,CACjC,WACsB;AACtB,MAAI,CAAC,OAAO,KAAK,CAAC,OAAO;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAKJ,QAAM,EAAE,GAAG,QAAA,IAAY,QACjB,EAAE,WAAW;AAInB,SAFmB,sBAAsB,MAAM,GAGjC;AAAA,IAAI,CAAC,eACf,0BAA0B,EAAE,GAAG,SAAS,MAAM,YAAY;AAAA,EAAA,KACvD,CAAA;AAET,GAEM,4BAA4B,CAChC,WACsB;AACtB,MAAI,CAAC,OAAO,KAAK,CAAC,OAAO;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,QAAM,EAAE,GAAG,QAAA,IAAY,QACjB,EAAE,OAAA,IAAW,SAEb,aAAa,sBAAsB,MAAM;AAE/C,SAAO,EAAE,wBAAwB;AAAA,IAC/B,CAAC,SAAS,CAAC,cAAc,CAAC,WAAW,SAAS,KAAK,MAAA,CAAiB;AAAA,EAAA;AAExE;"}
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "sanity-plugin-singleton-management",
3
+ "version": "1.0.0",
4
+ "description": "A plugin to streamline singleton management in your Sanity Studio",
5
+ "keywords": [
6
+ "sanity",
7
+ "sanity-plugin"
8
+ ],
9
+ "license": "MIT",
10
+ "author": "RD Pennell <racheal@sanity.io> & RC Maples <rc@rcmaples.io>",
11
+ "exports": {
12
+ ".": {
13
+ "source": "./src/index.ts",
14
+ "import": "./dist/index.esm.js",
15
+ "require": "./dist/index.cjs",
16
+ "default": "./dist/index.esm.js"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "type": "module",
21
+ "main": "./dist/index.cjs",
22
+ "module": "./dist/index.esm.js",
23
+ "types": "./dist/index.d.ts",
24
+ "browserslist": "extends @sanity/browserslist-config",
25
+ "files": [
26
+ "dist",
27
+ "sanity.json",
28
+ "src",
29
+ "v2-incompatible.js",
30
+ "!src/__tests__",
31
+ "!src/__mocks__"
32
+ ],
33
+ "scripts": {
34
+ "build": "npm run clean && npx @sanity/pkg-utils build --strict",
35
+ "clean": "rimraf dist",
36
+ "format": "prettier --write --cache --ignore-unknown .",
37
+ "link-watch": "plugin-kit link-watch",
38
+ "lint": "eslint .",
39
+ "prepare": "husky",
40
+ "prepublishOnly": "run-s build",
41
+ "watch": "pkg-utils watch --strict",
42
+ "test": "vitest",
43
+ "test:ui": "vitest --ui",
44
+ "test:run": "vitest run",
45
+ "test:coverage": "vitest run --coverage"
46
+ },
47
+ "dependencies": {
48
+ "@sanity/icons": "^3.7.4",
49
+ "@sanity/incompatible-plugin": "^1.0.4"
50
+ },
51
+ "devDependencies": {
52
+ "@commitlint/cli": "^19.8.1",
53
+ "@commitlint/config-conventional": "^19.8.1",
54
+ "@sanity/browserslist-config": "^1.0.5",
55
+ "@sanity/pkg-utils": "^8.1.12",
56
+ "@sanity/plugin-kit": "^4.0.19",
57
+ "@semantic-release/changelog": "^6.0.3",
58
+ "@semantic-release/git": "^10.0.1",
59
+ "@semantic-release/github": "^11.0.6",
60
+ "@semantic-release/npm": "^12.0.2",
61
+ "@testing-library/jest-dom": "^6.4.6",
62
+ "@types/react": "^19.1.13",
63
+ "@typescript-eslint/eslint-plugin": "^8.44.0",
64
+ "@typescript-eslint/parser": "^8.44.0",
65
+ "@vitest/coverage-v8": "^3.2.4",
66
+ "@vitest/ui": "^3.2.4",
67
+ "eslint": "^9.35.0",
68
+ "eslint-config-prettier": "^10.1.8",
69
+ "eslint-config-sanity": "^7.1.1",
70
+ "eslint-plugin-prettier": "^5.1.3",
71
+ "eslint-plugin-react": "^7.34.0",
72
+ "eslint-plugin-react-hooks": "^5.2.0",
73
+ "husky": "^9.1.7",
74
+ "jsdom": "^27.0.0",
75
+ "npm-run-all": "^4.1.5",
76
+ "prettier": "^3.2.5",
77
+ "prettier-plugin-packagejson": "^2.4.12",
78
+ "react": "^19.1.1",
79
+ "react-dom": "^19.1.1",
80
+ "react-is": "^19.1.1",
81
+ "rimraf": "^6.0.1",
82
+ "sanity": "^4.9.0",
83
+ "semantic-release": "^24.2.8",
84
+ "styled-components": "^6.1.15",
85
+ "typescript": "^5.3.3",
86
+ "typescript-eslint": "^8.44.0",
87
+ "vitest": "^3.2.4"
88
+ },
89
+ "overrides": {
90
+ "esbuild": "0.25.0",
91
+ "prismjs": "1.30.0",
92
+ "tmp": "0.2.4"
93
+ },
94
+ "peerDependencies": {
95
+ "react": "^18 || ^19",
96
+ "sanity": "^3 || ^4"
97
+ },
98
+ "engines": {
99
+ "node": ">=20"
100
+ },
101
+ "repository": {
102
+ "type": "git",
103
+ "url": "https://github.com/rcmaples/sanity-plugin-singleton-management"
104
+ },
105
+ "sideEffects": true
106
+ }
package/sanity.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "parts": [
3
+ {
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
6
+ }
7
+ ]
8
+ }
package/src/actions.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { DocumentActionsContext, DocumentActionsResolver } from "sanity";
2
+
3
+ import { getIsSingleton } from "./helpers";
4
+
5
+ export const actions: DocumentActionsResolver = (
6
+ prev,
7
+ { schema, schemaType }: DocumentActionsContext,
8
+ ) => {
9
+ return getIsSingleton(schema, schemaType)
10
+ ? prev.filter(({ action }) =>
11
+ ["publish", "discardChanges", "restore"].includes(action as string),
12
+ )
13
+ : prev;
14
+ };
@@ -0,0 +1,27 @@
1
+ import { Schema } from "sanity";
2
+
3
+ import { SingletonPluginOptions } from "../types";
4
+
5
+ export const getSingletonDocuments = (schema: Schema): string[] | undefined => {
6
+ if (!schema?._original?.types) {
7
+ return undefined;
8
+ }
9
+
10
+ return schema._original.types
11
+ .filter(({ options }) => (options as SingletonPluginOptions)?.singleton)
12
+ .map((s: { name: string }) => s.name);
13
+ };
14
+
15
+ export const getIsSingleton = (schema: Schema, schemaType: string): boolean => {
16
+ if (!schema?._original?.types || !schemaType) {
17
+ return false;
18
+ }
19
+
20
+ const documentSchema = schema._original.types.find(
21
+ ({ name }) => name === schemaType,
22
+ );
23
+
24
+ return (
25
+ (documentSchema?.options as SingletonPluginOptions)?.singleton ?? false
26
+ );
27
+ };
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { singletonTools } from "./pluginConfig";
2
+ import {
3
+ filteredDocumentListItems,
4
+ singletonDocumentListItem,
5
+ singletonDocumentListItems,
6
+ } from "./structure";
7
+
8
+ export {
9
+ filteredDocumentListItems,
10
+ singletonDocumentListItem,
11
+ singletonDocumentListItems,
12
+ singletonTools,
13
+ };
@@ -0,0 +1,19 @@
1
+ import { NewDocumentOptionsContext, NewDocumentOptionsResolver } from "sanity";
2
+
3
+ import { getSingletonDocuments } from "./helpers";
4
+
5
+ export const newDocumentOptions: NewDocumentOptionsResolver = (
6
+ prev,
7
+ { schema, creationContext: { type, schemaType } }: NewDocumentOptionsContext,
8
+ ) => {
9
+ const singletons = getSingletonDocuments(schema);
10
+
11
+ const filterSingletons = ({ templateId }: { templateId: string }) =>
12
+ !singletons?.includes(templateId);
13
+
14
+ if (type === "global") return prev.filter(filterSingletons);
15
+
16
+ return singletons?.includes(schemaType ?? "")
17
+ ? prev.filter(filterSingletons)
18
+ : prev;
19
+ };
@@ -0,0 +1,14 @@
1
+ import { definePlugin } from "sanity";
2
+
3
+ import { actions } from "./actions";
4
+ import { newDocumentOptions } from "./newDocumentOptions";
5
+
6
+ export const singletonTools = definePlugin(() => {
7
+ return {
8
+ name: "singleton-tools",
9
+ document: {
10
+ newDocumentOptions,
11
+ actions,
12
+ },
13
+ };
14
+ });
@@ -0,0 +1,84 @@
1
+ import { DocumentIcon } from "@sanity/icons";
2
+ import type { ListItemBuilder } from "sanity/structure";
3
+
4
+ import { getSingletonDocuments } from "../helpers";
5
+ import {
6
+ SingletonDocumentListItemConfig,
7
+ SingletonPluginListItemsConfig,
8
+ } from "../types";
9
+
10
+ const singletonDocumentListItem = (
11
+ config: SingletonDocumentListItemConfig,
12
+ ): ListItemBuilder => {
13
+ if (!config?.S || !config?.type || !config.context) {
14
+ throw new Error(
15
+ "S, context, and type must be provided to singletonDocumentListItem. " +
16
+ "Example: singletonDocumentListItem({ S, context, type: 'product' })",
17
+ );
18
+ }
19
+ const { S, type, title, icon, id, context } = config;
20
+ const { schema } = context;
21
+
22
+ if (!schema) {
23
+ throw new Error(
24
+ "Schema is required in context for singletonDocumentListItem",
25
+ );
26
+ }
27
+
28
+ const schemaType = schema.get(type);
29
+ const listTitle = title ?? schemaType?.title ?? type;
30
+ const listIcon = icon ?? schemaType?.icon ?? DocumentIcon;
31
+ const listId = id ?? type;
32
+
33
+ return S.listItem()
34
+ .title(listTitle)
35
+ .icon(listIcon)
36
+ .child(S.document().schemaType(type).title(listTitle).id(listId));
37
+ };
38
+
39
+ const singletonDocumentListItems = (
40
+ config: SingletonPluginListItemsConfig,
41
+ ): ListItemBuilder[] => {
42
+ if (!config.S || !config.context) {
43
+ throw new Error(
44
+ "S and context must be provided to singletonDocumentListItems. " +
45
+ "Example: singletonDocumentListItems({ S, context })",
46
+ );
47
+ }
48
+
49
+ const { S, context } = config;
50
+ const { schema } = context;
51
+
52
+ const singletons = getSingletonDocuments(schema);
53
+
54
+ return (
55
+ singletons?.map((schemaType) =>
56
+ singletonDocumentListItem({ S, context, type: schemaType }),
57
+ ) || []
58
+ );
59
+ };
60
+
61
+ const filteredDocumentListItems = (
62
+ config: SingletonPluginListItemsConfig,
63
+ ): ListItemBuilder[] => {
64
+ if (!config.S || !config.context) {
65
+ throw new Error(
66
+ "S and context must be provided to filteredDocumentListItems. " +
67
+ "Example: filteredDocumentListItems({ S, context })",
68
+ );
69
+ }
70
+ const { S, context } = config;
71
+ const { schema } = context;
72
+
73
+ const singletons = getSingletonDocuments(schema);
74
+
75
+ return S.documentTypeListItems().filter(
76
+ (type) => !singletons || !singletons.includes(type.getId() as string),
77
+ );
78
+ };
79
+
80
+ export {
81
+ filteredDocumentListItems,
82
+ singletonDocumentListItem,
83
+ singletonDocumentListItems,
84
+ };
package/src/types.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { ComponentType, ReactNode } from "react";
2
+ import { ConfigContext, SanityDocument } from "sanity";
3
+ import { StructureBuilder } from "sanity/structure";
4
+
5
+ export interface SingletonDocumentListItemConfig {
6
+ S: StructureBuilder;
7
+ context: ConfigContext;
8
+ type: string;
9
+ title?: string;
10
+ id?: string;
11
+ icon?: ComponentType | ReactNode;
12
+ }
13
+
14
+ export interface SingletonPluginListItemsConfig {
15
+ S: StructureBuilder;
16
+ context: ConfigContext;
17
+ }
18
+
19
+ export interface SingletonPluginOptions {
20
+ singleton?: boolean;
21
+ }
22
+
23
+ export interface SanitySingletonDocument extends SanityDocument {
24
+ options?: {
25
+ singleton?: boolean;
26
+ };
27
+ }
28
+
29
+ declare module "sanity" {
30
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
31
+ interface DocumentOptions extends SingletonPluginOptions {}
32
+ }
@@ -0,0 +1,9 @@
1
+ import { showIncompatiblePluginDialog } from "@sanity/incompatible-plugin";
2
+
3
+ export default showIncompatiblePluginDialog({
4
+ name: "sanity-plugin-singleton-management",
5
+ versions: {
6
+ v3: "0.1.0",
7
+ v2: undefined,
8
+ },
9
+ });