@sanity/runtime-cli 13.1.0 → 13.2.1

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.
Files changed (47) hide show
  1. package/README.md +18 -18
  2. package/dist/actions/blueprints/blueprint.d.ts +6 -3
  3. package/dist/actions/blueprints/blueprint.js +16 -7
  4. package/dist/actions/functions/dev.d.ts +1 -1
  5. package/dist/actions/functions/dev.js +2 -2
  6. package/dist/baseCommands.d.ts +4 -0
  7. package/dist/baseCommands.js +8 -0
  8. package/dist/commands/blueprints/add.js +1 -0
  9. package/dist/commands/blueprints/config.js +1 -0
  10. package/dist/commands/blueprints/deploy.js +1 -0
  11. package/dist/commands/blueprints/destroy.js +1 -0
  12. package/dist/commands/blueprints/doctor.js +1 -0
  13. package/dist/commands/blueprints/info.js +1 -0
  14. package/dist/commands/blueprints/init.js +1 -0
  15. package/dist/commands/blueprints/logs.js +1 -0
  16. package/dist/commands/blueprints/plan.js +1 -0
  17. package/dist/commands/blueprints/stacks.js +1 -0
  18. package/dist/commands/functions/add.js +1 -0
  19. package/dist/commands/functions/dev.js +1 -0
  20. package/dist/commands/functions/env/add.js +1 -0
  21. package/dist/commands/functions/env/list.js +1 -0
  22. package/dist/commands/functions/env/remove.js +1 -0
  23. package/dist/commands/functions/logs.js +1 -0
  24. package/dist/commands/functions/test.js +1 -0
  25. package/dist/cores/blueprints/doctor.js +3 -2
  26. package/dist/cores/blueprints/init.js +3 -2
  27. package/dist/cores/functions/dev.js +1 -1
  28. package/dist/cores/functions/logs.js +6 -1
  29. package/dist/cores/index.d.ts +4 -2
  30. package/dist/cores/index.js +4 -2
  31. package/dist/server/app.d.ts +1 -1
  32. package/dist/server/app.js +6 -4
  33. package/dist/server/handlers/invoke.d.ts +1 -1
  34. package/dist/server/handlers/invoke.js +2 -2
  35. package/dist/server/static/api.d.ts +41 -0
  36. package/dist/server/static/api.js +29 -6
  37. package/dist/server/static/components/filters.js +62 -56
  38. package/dist/server/static/components/function-list.js +1 -1
  39. package/dist/server/static/components/payload-panel.js +33 -4
  40. package/dist/server/static/components/run-panel.js +12 -4
  41. package/dist/server/static/vendor/vendor.bundle.js +1 -1
  42. package/dist/utils/functions/resolve-dependencies.js +17 -1
  43. package/dist/utils/functions/resource-to-arc.js +11 -2
  44. package/dist/utils/validate/index.d.ts +3 -0
  45. package/dist/utils/validate/index.js +35 -0
  46. package/oclif.manifest.json +120 -1
  47. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from '../../utils/logger.js';
2
2
  import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata } from '../../utils/types.js';
3
- export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, logger: ReturnType<typeof Logger>, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
3
+ export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, logger: ReturnType<typeof Logger>, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>): Promise<InvocationResponse & {
4
4
  timings: Record<string, number>;
5
5
  }>;
@@ -1,9 +1,9 @@
1
1
  import { readLocalBlueprint } from '../../actions/blueprints/blueprint.js';
2
2
  import { findFunctionInBlueprint } from '../../utils/find-function.js';
3
3
  import invoke from '../../utils/invoke-local.js';
4
- export async function handleInvokeRequest(functionName, event, metadata, context, logger, executionOptions) {
4
+ export async function handleInvokeRequest(functionName, event, metadata, context, logger, validateResources, executionOptions) {
5
5
  const start = performance.now();
6
- const { parsedBlueprint } = await readLocalBlueprint(logger);
6
+ const { parsedBlueprint } = await readLocalBlueprint(logger, { resources: validateResources });
7
7
  const resource = findFunctionInBlueprint(parsedBlueprint, functionName);
8
8
  const readBlueprintTime = performance.now() - start;
9
9
  const payload = {
@@ -0,0 +1,41 @@
1
+ export type SubscribeFunc = (fn: () => void) => void
2
+
3
+ export interface ServerAPI {
4
+ blueprint(): Promise<void>
5
+ document({
6
+ projectId,
7
+ dataset,
8
+ docId,
9
+ }: {
10
+ projectId: string
11
+ dataset: string
12
+ docId: string
13
+ }): Promise<void>
14
+ invoke({
15
+ context,
16
+ event,
17
+ metadata,
18
+ }: {
19
+ context: unknown
20
+ event: unknown
21
+ metadata: unknown
22
+ }): Promise<void>
23
+ projects(): Promise<void>
24
+ datasets(selectedProject: string): Promise<void>
25
+ organizations(): Promise<void>
26
+ mediaLibraries(selectedOrganization: string): Promise<void>
27
+ asset({
28
+ organizationId,
29
+ mediaLibraryId,
30
+ docId,
31
+ }: {
32
+ organizationId: string
33
+ mediaLibraryId: string
34
+ docId: string
35
+ }): Promise<void>
36
+ store: Record<string, unknown> & {subscribe: SubscribeFunc; unsubscribe: SubscribeFunc}
37
+ subscribe: SubscribeFunc
38
+ unsubscribe: SubscribeFunc
39
+ }
40
+
41
+ export default function API(): ServerAPI
@@ -4,6 +4,7 @@ import {Store} from './vendor/vendor.bundle.js'
4
4
  // list of events to simulate
5
5
  const events = ['create', 'update', 'delete'].map((e) => ({name: e}))
6
6
 
7
+ /** @type {Record<string, unknown>} */
7
8
  // eslint-disable-next-line new-cap
8
9
  const store = Store({events, selectedEvent: events[0].name})
9
10
 
@@ -23,6 +24,12 @@ export default function API() {
23
24
  }
24
25
  }
25
26
 
27
+ /**
28
+ * @param {object} params
29
+ * @param {unknown} params.context
30
+ * @param {unknown} params.event
31
+ * @param {unknown} params.metadata
32
+ */
26
33
  function invoke({context, event, metadata}) {
27
34
  store.inprogress = true
28
35
  const start = Date.now()
@@ -31,7 +38,7 @@ function invoke({context, event, metadata}) {
31
38
  func: store.selectedIndex,
32
39
  metadata,
33
40
  }
34
- fetch('/invoke', {
41
+ return fetch('/invoke', {
35
42
  body: JSON.stringify(payload),
36
43
  headers: {
37
44
  'Content-Type': 'application/json',
@@ -52,7 +59,7 @@ function invoke({context, event, metadata}) {
52
59
  }
53
60
 
54
61
  function blueprint() {
55
- fetch('/blueprint')
62
+ return fetch('/blueprint')
56
63
  .then((response) => response.json())
57
64
  .then((blueprint) => {
58
65
  const {parsedBlueprint, projectId, organizationId} = blueprint
@@ -72,7 +79,7 @@ function blueprint() {
72
79
  }
73
80
 
74
81
  function projects() {
75
- fetch('/projects')
82
+ return fetch('/projects')
76
83
  .then((response) => response.json())
77
84
  .then(async (projects) => {
78
85
  store.projects = projects
@@ -84,8 +91,11 @@ function projects() {
84
91
  })
85
92
  }
86
93
 
94
+ /**
95
+ * @param {string} selectedProject
96
+ */
87
97
  function datasets(selectedProject) {
88
- fetch(`/datasets?project=${selectedProject}`)
98
+ return fetch(`/datasets?project=${selectedProject}`)
89
99
  .then((response) => response.json())
90
100
  .then((datasets) => {
91
101
  store.datasets = datasets
@@ -96,6 +106,12 @@ function datasets(selectedProject) {
96
106
  })
97
107
  }
98
108
 
109
+ /**
110
+ * @param {object} params
111
+ * @param {string} params.projectId
112
+ * @param {string} params.dataset
113
+ * @param {string} params.docId
114
+ */
99
115
  function document({projectId, dataset, docId}) {
100
116
  return fetch(`/document?project=${projectId}&dataset=${dataset}&doc=${docId}`)
101
117
  .then((response) => response.json())
@@ -108,7 +124,7 @@ function document({projectId, dataset, docId}) {
108
124
  }
109
125
 
110
126
  function organizations() {
111
- fetch('/organizations')
127
+ return fetch('/organizations')
112
128
  .then((response) => response.json())
113
129
  .then(async (organizations) => {
114
130
  store.organizations = organizations
@@ -123,7 +139,7 @@ function organizations() {
123
139
  }
124
140
 
125
141
  function mediaLibraries(selectedOrganization) {
126
- fetch(`/media-libraries?organization=${selectedOrganization}`)
142
+ return fetch(`/media-libraries?organization=${selectedOrganization}`)
127
143
  .then((response) => response.json())
128
144
  .then((mediaLibraries) => {
129
145
  store.mediaLibraries = mediaLibraries
@@ -136,6 +152,13 @@ function mediaLibraries(selectedOrganization) {
136
152
  })
137
153
  }
138
154
 
155
+ /**
156
+ *
157
+ * @param {object} params
158
+ * @param {string} params.organizationId
159
+ * @param {string} params.mediaLibraryId
160
+ * @param {string} params.docId
161
+ */
139
162
  function asset({organizationId, mediaLibraryId, docId}) {
140
163
  return fetch(`/asset?organization=${organizationId}&medialibrary=${mediaLibraryId}&doc=${docId}`)
141
164
  .then((response) => response.json())
@@ -56,18 +56,6 @@ form input {
56
56
  </style>
57
57
  <form class="gap-2 pad-l-3 pad-b-3 border-bottom">
58
58
  <fieldset class="flex gap-2">
59
- <legend class="config-label">Client Options</legend>
60
- <div id="dynamic-dropdowns" class="flex gap-2"></div>
61
- <select-dropdown
62
- label="Event"
63
- store-key="events"
64
- selected-key="selectedEvent"
65
- value-prop="name"
66
- label-prop="name"
67
- ></select-dropdown>
68
- <api-version></api-version>
69
- <with-token></with-token>
70
- <document-id></document-id>
71
59
  </fieldset>
72
60
  </form>
73
61
  `
@@ -87,54 +75,72 @@ class FiltersComponent extends ApiBaseElement {
87
75
  }
88
76
 
89
77
  renderFilters = () => {
90
- const docFunction = this.api.store.selectedFunctionType === 'sanity.function.document'
78
+ const docFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_DOCUMENT
91
79
  const mediaFunction = this.api.store.selectedFunctionType?.startsWith(
92
- 'sanity.function.media-library',
80
+ this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
93
81
  )
82
+ const scheduleFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SCHEDULE
83
+
84
+ const container = this.shadowRoot.querySelector('fieldset')
85
+ container.innerHTML = this.buildFilters(docFunction, mediaFunction, scheduleFunction)
86
+ }
94
87
 
95
- const container = this.shadowRoot.querySelector('#dynamic-dropdowns')
96
-
97
- if (docFunction) {
98
- container.innerHTML = `
99
- <select-dropdown
100
- label="Project"
101
- store-key="projects"
102
- selected-key="selectedProject"
103
- value-prop="id"
104
- label-prop="displayName"
105
- trigger-fetch
106
- ></select-dropdown>
107
- <select-dropdown
108
- label="Dataset"
109
- store-key="datasets"
110
- selected-key="selectedDataset"
111
- value-prop="name"
112
- label-prop="name"
113
- subscribe-to="selectedProject"
114
- ></select-dropdown>
115
- `
116
- } else if (mediaFunction) {
117
- container.innerHTML = `
118
- <select-dropdown
119
- label="Organization"
120
- store-key="organizations"
121
- selected-key="selectedOrganization"
122
- value-prop="id"
123
- label-prop="name"
124
- trigger-fetch
125
- ></select-dropdown>
126
- <select-dropdown
127
- label="Media Library"
128
- store-key="mediaLibraries"
129
- selected-key="selectedMediaLibrary"
130
- value-prop="id"
131
- label-prop="id"
132
- subscribe-to="selectedOrganization"
133
- ></select-dropdown>
134
- `
135
- } else {
136
- container.innerHTML = ''
88
+ buildFilters = (docFunction, mediaFunction, scheduleFunction) => {
89
+ return `
90
+ <legend class="config-label">Client Options</legend>
91
+ <div id="dynamic-dropdowns" class="flex gap-2">
92
+ ${
93
+ docFunction || scheduleFunction
94
+ ? `<select-dropdown
95
+ label="Project"
96
+ store-key="projects"
97
+ selected-key="selectedProject"
98
+ value-prop="id"
99
+ label-prop="displayName"
100
+ trigger-fetch
101
+ ></select-dropdown>
102
+ <select-dropdown
103
+ label="Dataset"
104
+ store-key="datasets"
105
+ selected-key="selectedDataset"
106
+ value-prop="name"
107
+ label-prop="name"
108
+ subscribe-to="selectedProject"
109
+ ></select-dropdown>`
110
+ : `
111
+ <select-dropdown
112
+ label="Organization"
113
+ store-key="organizations"
114
+ selected-key="selectedOrganization"
115
+ value-prop="id"
116
+ label-prop="name"
117
+ trigger-fetch
118
+ ></select-dropdown>
119
+ <select-dropdown
120
+ label="Media Library"
121
+ store-key="mediaLibraries"
122
+ selected-key="selectedMediaLibrary"
123
+ value-prop="id"
124
+ label-prop="id"
125
+ subscribe-to="selectedOrganization"
126
+ ></select-dropdown>`
127
+ }
128
+ </div>
129
+ ${
130
+ docFunction || mediaFunction
131
+ ? `<select-dropdown
132
+ label="Event"
133
+ store-key="events"
134
+ selected-key="selectedEvent"
135
+ value-prop="name"
136
+ label-prop="name"
137
+ ></select-dropdown>`
138
+ : ''
137
139
  }
140
+ <api-version></api-version>
141
+ <with-token></with-token>
142
+ ${docFunction || mediaFunction ? `<document-id></document-id>` : ''}
143
+ `
138
144
  }
139
145
 
140
146
  disconnectedCallback() {
@@ -78,7 +78,7 @@ class FunctionList extends ApiBaseElement {
78
78
  })
79
79
  .join('')
80
80
  } else {
81
- this.list.innerHTML = '<option class="pad-2">No Blueprint found</li>'
81
+ this.list.innerHTML = '<option class="pad-2">No Functions found</li>'
82
82
  this.select.innerHTML = '<option>No blueprint.json file found</option>'
83
83
  }
84
84
  }
@@ -1,10 +1,12 @@
1
1
  /* globals customElements document */
2
2
 
3
- import {basicSetup, EditorView, json} from '../vendor/vendor.bundle.js'
3
+ import {basicSetup, Compartment, EditorView, json} from '../vendor/vendor.bundle.js'
4
4
  import {ApiBaseElement} from './api-base.js'
5
5
  import {sanityCodeMirrorTheme} from './codemirror-theme.js'
6
6
  import {getSharedStyleSheets} from './shared-styles.js'
7
7
 
8
+ const editableCompartment = new Compartment()
9
+
8
10
  const template = document.createElement('template')
9
11
  template.innerHTML = `
10
12
  <style>
@@ -73,8 +75,32 @@ class PayloadPanel extends ApiBaseElement {
73
75
 
74
76
  this.api.subscribe(this.updatePayload, ['document'])
75
77
  this.api.subscribe(this.updateSelectedEvent, ['selectedEvent'])
78
+ this.api.subscribe(this.updateCodeMirror, ['selectedFunctionType'])
76
79
  }
77
80
 
81
+ updateCodeMirror = ({selectedFunctionType}) => {
82
+ if (selectedFunctionType === this.SANITY_FUNCTION_SCHEDULE) {
83
+ this.api.store.beforePayload.dispatch({
84
+ effects: editableCompartment.reconfigure(EditorView.editable.of(false)),
85
+ })
86
+ this.api.store.afterPayload.dispatch({
87
+ effects: editableCompartment.reconfigure(EditorView.editable.of(false)),
88
+ })
89
+ this.api.store.payload.dispatch({
90
+ effects: editableCompartment.reconfigure(EditorView.editable.of(false)),
91
+ })
92
+ } else {
93
+ this.api.store.beforePayload.dispatch({
94
+ effects: editableCompartment.reconfigure(EditorView.editable.of(true)),
95
+ })
96
+ this.api.store.afterPayload.dispatch({
97
+ effects: editableCompartment.reconfigure(EditorView.editable.of(true)),
98
+ })
99
+ this.api.store.payload.dispatch({
100
+ effects: editableCompartment.reconfigure(EditorView.editable.of(true)),
101
+ })
102
+ }
103
+ }
78
104
  updatePayload = ({document}) => {
79
105
  if (!document) return
80
106
 
@@ -93,8 +119,6 @@ class PayloadPanel extends ApiBaseElement {
93
119
  view.dispatch(transaction)
94
120
  }
95
121
  updateSelectedEvent = ({selectedEvent}) => {
96
- console.log('updateSelectedEvent', selectedEvent)
97
-
98
122
  const payloadContainer = this.shadowRoot.querySelector('#payloadContainer')
99
123
  const deltaPayloadContainer = this.shadowRoot.querySelector('#deltaPayloadContainer')
100
124
 
@@ -113,7 +137,12 @@ class PayloadPanel extends ApiBaseElement {
113
137
  function attachEditorView(parent) {
114
138
  return new EditorView({
115
139
  doc: '\n\n\n\n',
116
- extensions: [basicSetup, json(), sanityCodeMirrorTheme],
140
+ extensions: [
141
+ basicSetup,
142
+ json(),
143
+ sanityCodeMirrorTheme,
144
+ editableCompartment.of(EditorView.editable.of(false)),
145
+ ],
117
146
  parent,
118
147
  })
119
148
  }
@@ -23,6 +23,10 @@ class RunPanel extends ApiBaseElement {
23
23
  invoke = () => {
24
24
  const selectedEvent = this.api.store.selectedEvent
25
25
  const docFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_DOCUMENT
26
+ const mediaFunction =
27
+ this.api.store.selectedFunctionType === this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET
28
+ const scheduleFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SCHEDULE
29
+ const docOrScheduleFunction = docFunction || scheduleFunction
26
30
 
27
31
  this.api.store.result = {logs: '', time: 0}
28
32
  let event = {}
@@ -53,12 +57,16 @@ class RunPanel extends ApiBaseElement {
53
57
  projectId: this.api.store.selectedProject,
54
58
  token: this.api.store.withToken,
55
59
  },
56
- eventResourceType: docFunction ? 'dataset' : 'media-library',
57
- eventResourceId: docFunction
60
+ eventResourceType: scheduleFunction
61
+ ? 'schedule'
62
+ : mediaFunction
63
+ ? 'media-library'
64
+ : 'dataset',
65
+ eventResourceId: docOrScheduleFunction
58
66
  ? `${this.api.store.selectedProject}.${this.api.store.selectedDataset}`
59
67
  : this.api.store.selectedMediaLibrary,
60
- functionResourceType: 'project',
61
- functionResourceId: docFunction
68
+ functionResourceType: docOrScheduleFunction ? 'project' : 'organization',
69
+ functionResourceId: docOrScheduleFunction
62
70
  ? this.api.store.selectedProject
63
71
  : this.api.store.selectedOrganization,
64
72
  }
@@ -27259,4 +27259,4 @@ function prettyMilliseconds(milliseconds, options) {
27259
27259
  return sign + result.join(separator);
27260
27260
  }
27261
27261
 
27262
- export { EditorState, EditorView, HighlightStyle, Store, basicSetup, json, prettyBytes, prettyMilliseconds, syntaxHighlighting, tags };
27262
+ export { Compartment, EditorState, EditorView, HighlightStyle, Store, basicSetup, json, prettyBytes, prettyMilliseconds, syntaxHighlighting, tags };
@@ -1,11 +1,27 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
1
3
  import hydrate from '@architect/hydrate';
2
4
  import inventory from '@architect/inventory';
3
5
  import { convertResourceToArcFormat } from './resource-to-arc.js';
4
6
  export async function resolveResourceDependencies(resource, transpiled) {
5
7
  const rawArc = await convertResourceToArcFormat(resource, transpiled);
6
8
  const inv = await inventory({ rawArc });
9
+ const cwd = inv.inv._project.cwd;
10
+ const installOptions = {
11
+ inventory: inv,
12
+ hydrateShared: false,
13
+ quiet: true,
14
+ pnpm: false,
15
+ yarn: false,
16
+ };
17
+ if (existsSync(join(cwd, 'pnpm-lock.yaml'))) {
18
+ installOptions.pnpm = true;
19
+ }
20
+ else if (existsSync(join(cwd, 'yarn.lock'))) {
21
+ installOptions.yarn = true;
22
+ }
7
23
  try {
8
- await hydrate.install({ inventory: inv, hydrateShared: false, quiet: true });
24
+ await hydrate.install(installOptions);
9
25
  }
10
26
  catch (err) {
11
27
  // This is a temporary fix.
@@ -1,7 +1,16 @@
1
- import { sep } from 'node:path';
1
+ import { stat } from 'node:fs/promises';
2
+ import { resolve, sep } from 'node:path';
3
+ import { cwd } from 'node:process';
2
4
  export async function convertResourceToArcFormat(resource, transpiled) {
5
+ if (!resource.src) {
6
+ throw Error('Missing `resource.src` property');
7
+ }
8
+ // Get stats from file
9
+ const stats = await stat(resolve(cwd(), resource.src));
10
+ // Convert resource.src to arc file path format
3
11
  const srcPath = resource.src?.split(sep).join('/');
4
- const functionPath = transpiled ? `${srcPath}/.build/function-${resource.name}` : srcPath;
12
+ const entryDir = stats.isFile() ? srcPath.substring(0, srcPath.lastIndexOf('/')) : srcPath;
13
+ const functionPath = transpiled ? `${entryDir}/.build/function-${resource.name}` : entryDir;
5
14
  return `@app
6
15
  hydrate-function
7
16
 
@@ -1 +1,4 @@
1
+ import type { Resource } from '@sanity/blueprints-parser';
2
+ import type { BlueprintParserError } from '../types.js';
1
3
  export * as validate from './resource.js';
4
+ export declare function validateResources(resources: Resource[]): BlueprintParserError[];
@@ -1 +1,36 @@
1
+ import { validateCorsOrigin, validateDataset, validateDocumentFunction, validateDocumentWebhook, validateMediaLibraryAssetFunction, validateRole, validateScheduleFunction, } from '@sanity/blueprints';
2
+ import { SANITY_ACCESS_ROLE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULE, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, } from '../../constants.js';
1
3
  export * as validate from './resource.js';
4
+ const RESOURCE_VALIDATORS = {
5
+ [SANITY_ACCESS_ROLE]: {
6
+ validate: validateRole,
7
+ },
8
+ [SANITY_FUNCTION_DOCUMENT]: {
9
+ validate: validateDocumentFunction,
10
+ },
11
+ [SANITY_FUNCTION_MEDIA_LIBRARY_ASSET]: {
12
+ validate: validateMediaLibraryAssetFunction,
13
+ },
14
+ [SANITY_FUNCTION_SCHEDULE]: {
15
+ validate: validateScheduleFunction,
16
+ },
17
+ [SANITY_PROJECT_CORS]: {
18
+ validate: validateCorsOrigin,
19
+ },
20
+ [SANITY_PROJECT_DATASET]: {
21
+ validate: validateDataset,
22
+ },
23
+ [SANITY_PROJECT_WEBHOOK]: {
24
+ validate: validateDocumentWebhook,
25
+ },
26
+ };
27
+ export function validateResources(resources) {
28
+ const errors = [];
29
+ for (const resource of resources) {
30
+ const validator = RESOURCE_VALIDATORS[resource.type];
31
+ if (validator) {
32
+ errors.push(...validator.validate(resource));
33
+ }
34
+ }
35
+ return errors;
36
+ }