@sanity/runtime-cli 11.0.4 → 11.1.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.
- package/README.md +30 -26
- package/dist/actions/blueprints/blueprint.d.ts +3 -3
- package/dist/actions/blueprints/resources.d.ts +2 -2
- package/dist/actions/blueprints/resources.js +3 -4
- package/dist/commands/blueprints/add.js +9 -1
- package/dist/commands/functions/add.js +9 -1
- package/dist/commands/functions/test.d.ts +1 -0
- package/dist/commands/functions/test.js +6 -0
- package/dist/cores/functions/add.js +31 -9
- package/dist/cores/functions/test.d.ts +1 -0
- package/dist/cores/functions/test.js +37 -7
- package/dist/server/app.js +99 -3
- package/dist/server/static/api.js +48 -2
- package/dist/server/static/components/app.css +16 -1
- package/dist/server/static/components/fetch-button.js +14 -5
- package/dist/server/static/components/filter-api-version.js +14 -0
- package/dist/server/static/components/filter-document-id.js +26 -0
- package/dist/server/static/components/filter-with-token.js +21 -0
- package/dist/server/static/components/filters.js +47 -42
- package/dist/server/static/components/function-list.js +15 -5
- package/dist/server/static/components/run-panel.js +11 -2
- package/dist/server/static/index.html +3 -0
- package/dist/utils/display/blueprints-formatting.d.ts +1 -1
- package/dist/utils/display/blueprints-formatting.js +4 -3
- package/dist/utils/display/resources-formatting.d.ts +2 -2
- package/dist/utils/functions/fetch-document.d.ts +2 -0
- package/dist/utils/functions/fetch-document.js +15 -0
- package/dist/utils/invoke-local.d.ts +2 -2
- package/dist/utils/invoke-local.js +11 -5
- package/dist/utils/types.d.ts +41 -5
- package/dist/utils/types.js +8 -0
- package/dist/utils/validate/resource.js +55 -30
- package/oclif.manifest.json +24 -3
- package/package.json +2 -2
|
@@ -14,6 +14,9 @@ export default function API() {
|
|
|
14
14
|
invoke,
|
|
15
15
|
projects,
|
|
16
16
|
datasets,
|
|
17
|
+
organizations,
|
|
18
|
+
mediaLibraries,
|
|
19
|
+
asset,
|
|
17
20
|
store,
|
|
18
21
|
subscribe: store.subscribe,
|
|
19
22
|
unsubscribe: store.unsubscribe,
|
|
@@ -52,14 +55,16 @@ function blueprint() {
|
|
|
52
55
|
fetch('/blueprint')
|
|
53
56
|
.then((response) => response.json())
|
|
54
57
|
.then((blueprint) => {
|
|
55
|
-
const {parsedBlueprint, projectId} = blueprint
|
|
58
|
+
const {parsedBlueprint, projectId, organizationId} = blueprint
|
|
56
59
|
const functions = parsedBlueprint?.resources.filter((r) =>
|
|
57
60
|
r.type.startsWith('sanity.function.'),
|
|
58
61
|
)
|
|
59
62
|
|
|
60
63
|
store.functions = functions
|
|
61
64
|
store.selectedIndex = functions[0].name
|
|
65
|
+
store.selectedFunctionType = functions[0].type
|
|
62
66
|
store.defaultProject = projectId || undefined
|
|
67
|
+
store.defaultOrganization = organizationId || undefined
|
|
63
68
|
})
|
|
64
69
|
.catch(() => {
|
|
65
70
|
store.functions = []
|
|
@@ -69,9 +74,10 @@ function blueprint() {
|
|
|
69
74
|
function projects() {
|
|
70
75
|
fetch('/projects')
|
|
71
76
|
.then((response) => response.json())
|
|
72
|
-
.then((projects) => {
|
|
77
|
+
.then(async (projects) => {
|
|
73
78
|
store.projects = projects
|
|
74
79
|
store.selectedProject = store.defaultProject ? store.defaultProject : projects[0].id
|
|
80
|
+
await datasets(store.selectedProject)
|
|
75
81
|
})
|
|
76
82
|
.catch(() => {
|
|
77
83
|
store.projects = []
|
|
@@ -101,6 +107,46 @@ function document({projectId, dataset, docId}) {
|
|
|
101
107
|
})
|
|
102
108
|
}
|
|
103
109
|
|
|
110
|
+
function organizations() {
|
|
111
|
+
fetch('/organizations')
|
|
112
|
+
.then((response) => response.json())
|
|
113
|
+
.then(async (organizations) => {
|
|
114
|
+
store.organizations = organizations
|
|
115
|
+
store.selectedOrganization = store.defaultOrganization
|
|
116
|
+
? store.defaultOrganization
|
|
117
|
+
: organizations[0].id
|
|
118
|
+
await mediaLibraries(store.selectedOrganization)
|
|
119
|
+
})
|
|
120
|
+
.catch(() => {
|
|
121
|
+
store.organizations = []
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function mediaLibraries(selectedOrganization) {
|
|
126
|
+
fetch(`/media-libraries?organization=${selectedOrganization}`)
|
|
127
|
+
.then((response) => response.json())
|
|
128
|
+
.then((mediaLibraries) => {
|
|
129
|
+
store.mediaLibraries = mediaLibraries
|
|
130
|
+
store.selectedMediaLibrary = store.defaultMediaLibrary
|
|
131
|
+
? store.defaultMediaLibrary
|
|
132
|
+
: mediaLibraries[0].id
|
|
133
|
+
})
|
|
134
|
+
.catch(() => {
|
|
135
|
+
store.mediaLibraries = []
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function asset({organizationId, mediaLibraryId, docId}) {
|
|
140
|
+
return fetch(`/asset?organization=${organizationId}&medialibrary=${mediaLibraryId}&doc=${docId}`)
|
|
141
|
+
.then((response) => response.json())
|
|
142
|
+
.then((doc) => {
|
|
143
|
+
store.document = doc
|
|
144
|
+
})
|
|
145
|
+
.catch(() => {
|
|
146
|
+
store.document = {}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
104
150
|
function getServerTimings(response) {
|
|
105
151
|
const timings = {}
|
|
106
152
|
const serverTiming = response.headers.get('Server-Timing')
|
|
@@ -678,9 +678,15 @@ body {
|
|
|
678
678
|
}
|
|
679
679
|
|
|
680
680
|
/* Utilities */
|
|
681
|
+
.capitalize {
|
|
682
|
+
text-transform: capitalize;
|
|
683
|
+
}
|
|
681
684
|
.flex {
|
|
682
685
|
display: flex;
|
|
683
686
|
}
|
|
687
|
+
.flex-column {
|
|
688
|
+
flex-direction: column;
|
|
689
|
+
}
|
|
684
690
|
.items-center {
|
|
685
691
|
align-items: center;
|
|
686
692
|
}
|
|
@@ -986,7 +992,6 @@ footer {
|
|
|
986
992
|
}
|
|
987
993
|
|
|
988
994
|
.function-list-item {
|
|
989
|
-
color: light-dark(var(--gray-800), var(--gray-400));
|
|
990
995
|
padding: 8px;
|
|
991
996
|
font-size: 0.875rem;
|
|
992
997
|
border-radius: 3px;
|
|
@@ -994,6 +999,16 @@ footer {
|
|
|
994
999
|
cursor: pointer;
|
|
995
1000
|
}
|
|
996
1001
|
|
|
1002
|
+
.function-list-item-label {
|
|
1003
|
+
color: light-dark(var(--gray-800), var(--gray-400));
|
|
1004
|
+
font-weight: var(--font-weight-3);
|
|
1005
|
+
padding-bottom: var(--space-1);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
.function-list-item-type {
|
|
1009
|
+
color: light-dark(var(--gray-900), var(--gray-300));
|
|
1010
|
+
}
|
|
1011
|
+
|
|
997
1012
|
.dropdown-select {
|
|
998
1013
|
background: transparent;
|
|
999
1014
|
color: light-dark(var(--gray-950), var(--gray-300));
|
|
@@ -63,11 +63,20 @@ export class FetchButton extends ApiBaseElement {
|
|
|
63
63
|
this.button.innerHTML = '<network-spinner></network-spinner>'
|
|
64
64
|
|
|
65
65
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
// TODO: This assumes only two functions types for the sake of releasing today
|
|
67
|
+
if (this.api.store.selectedFunctionType === 'sanity.function.document') {
|
|
68
|
+
await this.api.document({
|
|
69
|
+
projectId: this.api.store.selectedProject,
|
|
70
|
+
dataset: this.api.store.selectedDataset,
|
|
71
|
+
docId,
|
|
72
|
+
})
|
|
73
|
+
} else {
|
|
74
|
+
await this.api.asset({
|
|
75
|
+
organizationId: this.api.store.selectedOrganization,
|
|
76
|
+
mediaLibraryId: this.api.store.selectedMediaLibrary,
|
|
77
|
+
docId,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
71
80
|
} catch (err) {
|
|
72
81
|
console.error('Error fetching document:', err)
|
|
73
82
|
} finally {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class ApiVersionComponent extends HTMLElement {
|
|
2
|
+
connectedCallback() {
|
|
3
|
+
this.innerHTML = `
|
|
4
|
+
<fieldset class="mar-t-sm">
|
|
5
|
+
<label class="slab-text">
|
|
6
|
+
<span style="display: block; margin-bottom: var(--space-1);">API Version</span>
|
|
7
|
+
<input name="apiVersion" id="apiversion" style="background: transparent; border-color: light-dark(var(--gray-200), var(--gray-700)); height: 2.5rem; color: light-dark(var(--gray-950), var(--gray-300));">
|
|
8
|
+
</label>
|
|
9
|
+
</fieldset>
|
|
10
|
+
`
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
customElements.define('api-version', ApiVersionComponent)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class DocumentIdFilter extends HTMLElement {
|
|
2
|
+
connectedCallback() {
|
|
3
|
+
this.innerHTML = `
|
|
4
|
+
<fieldset class="mar-t-sm" style="margin-top: 12px !important;">
|
|
5
|
+
<label class="slab-text">
|
|
6
|
+
<span style="display: block; margin-bottom: var(--space-1);">
|
|
7
|
+
<span style="display: flex; align-items: center;">
|
|
8
|
+
Document ID
|
|
9
|
+
<help-button>
|
|
10
|
+
<p>Fill out "Document ID" text field and then click the "Fetch Document" button to pre-populate the Document panel.</p>
|
|
11
|
+
<p>The Document panel is an editable text field so you can edit the fetched document or replace it with any JSON data you want.</p>
|
|
12
|
+
<p>When you click the "Run" button the contents of the Document panel will be evaluated against your filter/projection and sent to your function as the event part of the payload.</p>
|
|
13
|
+
</help-button>
|
|
14
|
+
</span>
|
|
15
|
+
</span>
|
|
16
|
+
<div style="display: flex; flex-direction: row; gap: var(--space-2);">
|
|
17
|
+
<input name="docid" id="docid" style="background: transparent; border-color: light-dark(var(--gray-200), var(--gray-700)); height: 2.5rem; color: light-dark(var(--gray-950), var(--gray-300));">
|
|
18
|
+
<fetch-button></fetch-button>
|
|
19
|
+
</div>
|
|
20
|
+
</label>
|
|
21
|
+
</fieldset>
|
|
22
|
+
`
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
customElements.define('document-id', DocumentIdFilter)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class WithTokenComponent extends HTMLElement {
|
|
2
|
+
connectedCallback() {
|
|
3
|
+
this.innerHTML = `
|
|
4
|
+
<fieldset class="mar-t-sm" style="display: flex; flex-direction: column; margin-top: 12px !important;">
|
|
5
|
+
<span class="slab-text" style="display: block; margin-bottom: var(--space-1);">
|
|
6
|
+
<span style="display: flex; align-items: center;">
|
|
7
|
+
With Token
|
|
8
|
+
<help-button>
|
|
9
|
+
<p><strong>With Token</strong> controls whether your function runs with or without authentication.</p>
|
|
10
|
+
<p><strong>Enabled:</strong> Function executes with your API token, providing full, authenticated access to your Sanity project.</p>
|
|
11
|
+
<p><strong>Disabled:</strong> Function executes without authentication, useful for testing public endpoints or functions that don't require user permissions.</p>
|
|
12
|
+
</help-button>
|
|
13
|
+
</span>
|
|
14
|
+
</span>
|
|
15
|
+
<toggle-switch toggle-key="withToken" style="display: flex; height: 2.5rem;"></toggle-switch>
|
|
16
|
+
</fieldset>
|
|
17
|
+
`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
customElements.define('with-token', WithTokenComponent)
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {ApiBaseElement} from './api-base.js'
|
|
2
|
+
|
|
3
|
+
class FiltersComponent extends ApiBaseElement {
|
|
4
|
+
renderFilters = () => {
|
|
5
|
+
const docFunction = this.api.store.selectedFunctionType === 'sanity.function.document'
|
|
6
|
+
const mediaFunction = this.api.store.selectedFunctionType?.startsWith(
|
|
7
|
+
'sanity.function.media-library',
|
|
8
|
+
)
|
|
9
|
+
|
|
3
10
|
this.innerHTML = `
|
|
4
11
|
<form style="gap: 8px; padding-left: var(--space-3); padding-bottom: var(--space-3); border-bottom: 1px solid var(--card-border-color);">
|
|
5
12
|
<fieldset style="display: flex; gap: var(--space-2);">
|
|
6
13
|
<legend class="config-label">Client Options</legend>
|
|
7
|
-
|
|
14
|
+
${
|
|
15
|
+
docFunction
|
|
16
|
+
? `<select-dropdown
|
|
8
17
|
label="Project"
|
|
9
18
|
store-key="projects"
|
|
10
19
|
selected-key="selectedProject"
|
|
@@ -19,7 +28,30 @@ class FiltersComponent extends HTMLElement {
|
|
|
19
28
|
value-prop="name"
|
|
20
29
|
label-prop="name"
|
|
21
30
|
subscribe-to="selectedProject"
|
|
31
|
+
></select-dropdown>`
|
|
32
|
+
: ''
|
|
33
|
+
}
|
|
34
|
+
${
|
|
35
|
+
mediaFunction
|
|
36
|
+
? `<select-dropdown
|
|
37
|
+
label="Organization"
|
|
38
|
+
store-key="organizations"
|
|
39
|
+
selected-key="selectedOrganization"
|
|
40
|
+
value-prop="id"
|
|
41
|
+
label-prop="name"
|
|
42
|
+
trigger-fetch
|
|
22
43
|
></select-dropdown>
|
|
44
|
+
<select-dropdown
|
|
45
|
+
label="Media Library"
|
|
46
|
+
store-key="mediaLibraries"
|
|
47
|
+
selected-key="selectedMediaLibrary"
|
|
48
|
+
value-prop="id"
|
|
49
|
+
label-prop="id"
|
|
50
|
+
subscribe-to="selectedOrganization"
|
|
51
|
+
></select-dropdown>`
|
|
52
|
+
: ''
|
|
53
|
+
}
|
|
54
|
+
|
|
23
55
|
<select-dropdown
|
|
24
56
|
label="Event"
|
|
25
57
|
store-key="events"
|
|
@@ -27,49 +59,22 @@ class FiltersComponent extends HTMLElement {
|
|
|
27
59
|
value-prop="name"
|
|
28
60
|
label-prop="name"
|
|
29
61
|
></select-dropdown>
|
|
30
|
-
<fieldset class="mar-t-sm">
|
|
31
|
-
<label class="slab-text">
|
|
32
|
-
<span style="display: block; margin-bottom: var(--space-1);">API Version</span>
|
|
33
|
-
<input name="apiVersion" id="apiversion" style="background: transparent; border-color: light-dark(var(--gray-200), var(--gray-700)); height: 2.5rem; color: light-dark(var(--gray-950), var(--gray-300));">
|
|
34
|
-
</label>
|
|
35
|
-
</fieldset>
|
|
36
|
-
|
|
37
|
-
<fieldset class="mar-t-sm" style="display: flex; flex-direction: column; margin-top: 12px !important;">
|
|
38
|
-
<span class="slab-text" style="display: block; margin-bottom: var(--space-1);">
|
|
39
|
-
<span style="display: flex; align-items: center;">
|
|
40
|
-
With Token
|
|
41
|
-
<help-button>
|
|
42
|
-
<p><strong>With Token</strong> controls whether your function runs with or without authentication.</p>
|
|
43
|
-
<p><strong>Enabled:</strong> Function executes with your API token, providing full, authenticated access to your Sanity project.</p>
|
|
44
|
-
<p><strong>Disabled:</strong> Function executes without authentication, useful for testing public endpoints or functions that don't require user permissions.</p>
|
|
45
|
-
</help-button>
|
|
46
|
-
</span>
|
|
47
|
-
</span>
|
|
48
|
-
<toggle-switch toggle-key="withToken" style="display: flex; height: 2.5rem;"></toggle-switch>
|
|
49
|
-
</fieldset>
|
|
50
|
-
|
|
51
|
-
<fieldset class="mar-t-sm" style="margin-top: 12px !important;">
|
|
52
|
-
<label class="slab-text">
|
|
53
|
-
<span style="display: block; margin-bottom: var(--space-1);">
|
|
54
|
-
<span style="display: flex; align-items: center;">
|
|
55
|
-
Document ID
|
|
56
|
-
<help-button>
|
|
57
|
-
<p>Fill out "Document ID" text field and then click the "Fetch Document" button to pre-populate the Document panel.</p>
|
|
58
|
-
<p>The Document panel is an editable text field so you can edit the fetched document or replace it with any JSON data you want.</p>
|
|
59
|
-
<p>When you click the "Run" button the contents of the Document panel will be evaluated against your filter/projection and sent to your function as the event part of the payload.</p>
|
|
60
|
-
</help-button>
|
|
61
|
-
</span>
|
|
62
|
-
</span>
|
|
63
|
-
<div style="display: flex; flex-direction: row; gap: var(--space-2);">
|
|
64
|
-
<input name="docid" id="docid" style="background: transparent; border-color: light-dark(var(--gray-200), var(--gray-700)); height: 2.5rem; color: light-dark(var(--gray-950), var(--gray-300));">
|
|
65
|
-
<fetch-button></fetch-button>
|
|
66
|
-
</div>
|
|
67
|
-
</label>
|
|
68
|
-
</fieldset>
|
|
69
62
|
|
|
63
|
+
<api-version></api-version>
|
|
64
|
+
<with-token></with-token>
|
|
65
|
+
<document-id></document-id>
|
|
70
66
|
</form>
|
|
71
67
|
`
|
|
72
68
|
}
|
|
69
|
+
|
|
70
|
+
connectedCallback() {
|
|
71
|
+
this.innerHTML = this.renderFilters()
|
|
72
|
+
this.api.subscribe(this.renderFilters, ['selectedFunctionType'])
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
disconnectedCallback() {
|
|
76
|
+
this.api.unsubscribe(this.renderFilters)
|
|
77
|
+
}
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
customElements.define('filters-component', FiltersComponent)
|
|
@@ -7,19 +7,29 @@ const template = `<ol class="hidden-lg" type="content" style="padding:0 16px;"><
|
|
|
7
7
|
|
|
8
8
|
class FunctionList extends ApiBaseElement {
|
|
9
9
|
functionClicked = (event) => {
|
|
10
|
-
|
|
11
|
-
const target = this.api.store.functions.find((func) => func.name ===
|
|
10
|
+
const name = event.srcElement.closest('li').dataset.name
|
|
11
|
+
const target = this.api.store.functions.find((func) => func.name === name)
|
|
12
12
|
this.api.store.selectedIndex = target.name
|
|
13
|
+
this.api.store.selectedFunctionType = target.type
|
|
13
14
|
}
|
|
14
15
|
functionSelected = (event) => {
|
|
15
16
|
this.api.store.selectedIndex = event.srcElement.value
|
|
16
17
|
}
|
|
18
|
+
renderListItem = (func) => {
|
|
19
|
+
const selected = this.api.store.selectedIndex === func.name ? 'selected' : ''
|
|
20
|
+
return `<li data-name="${func.name}" class="function-list-item ${selected} flex flex-column" style="padding: 16px 24px;">
|
|
21
|
+
<span class="function-list-item-label">${func.name}</span>
|
|
22
|
+
<span class="function-list-item-type capitalize">${this.renderType(func.type)}</span>
|
|
23
|
+
</li>`
|
|
24
|
+
}
|
|
25
|
+
renderType = (type) => {
|
|
26
|
+
return type.split('.').pop().replaceAll('-', ' ')
|
|
27
|
+
}
|
|
17
28
|
renderFunctions = () => {
|
|
18
29
|
if (this.api.store.functions.length > 0) {
|
|
19
30
|
this.list.innerHTML = this.api.store.functions
|
|
20
31
|
.map((func) => {
|
|
21
|
-
|
|
22
|
-
return `<li class="function-list-item ${selected}" style="padding: 16px 24px;">${func.name}</li>`
|
|
32
|
+
return this.renderListItem(func)
|
|
23
33
|
})
|
|
24
34
|
.join('')
|
|
25
35
|
this.select.innerHTML = this.api.store.functions
|
|
@@ -29,7 +39,7 @@ class FunctionList extends ApiBaseElement {
|
|
|
29
39
|
})
|
|
30
40
|
.join('')
|
|
31
41
|
} else {
|
|
32
|
-
this.list.innerHTML = '<option class="pad-sm">No
|
|
42
|
+
this.list.innerHTML = '<option class="pad-sm">No Blueprint found</li>'
|
|
33
43
|
this.select.innerHTML = '<option>No blueprint.json file found</option>'
|
|
34
44
|
}
|
|
35
45
|
}
|
|
@@ -15,6 +15,8 @@ const template = `<div style="padding: var(--space-3);">
|
|
|
15
15
|
class RunPanel extends ApiBaseElement {
|
|
16
16
|
invoke = () => {
|
|
17
17
|
const selectedEvent = this.api.store.selectedEvent
|
|
18
|
+
const docFunction = this.api.store.selectedFunctionType === 'sanity.function.document'
|
|
19
|
+
|
|
18
20
|
this.api.store.result = {logs: '', time: 0}
|
|
19
21
|
let event = {}
|
|
20
22
|
let before = null
|
|
@@ -39,11 +41,19 @@ class RunPanel extends ApiBaseElement {
|
|
|
39
41
|
}
|
|
40
42
|
const context = {
|
|
41
43
|
clientOptions: {
|
|
42
|
-
apiVersion:
|
|
44
|
+
apiVersion: document.querySelector('#apiversion').value,
|
|
43
45
|
dataset: this.api.store.selectedDataset,
|
|
44
46
|
projectId: this.api.store.selectedProject,
|
|
45
47
|
token: this.api.store.withToken,
|
|
46
48
|
},
|
|
49
|
+
eventResourceType: docFunction ? 'dataset' : 'media-library',
|
|
50
|
+
eventResourceId: docFunction
|
|
51
|
+
? `${this.api.store.selectedProject}.${this.api.store.selectedDataset}`
|
|
52
|
+
: this.api.store.selectedMediaLibrary,
|
|
53
|
+
functionResourceType: 'project',
|
|
54
|
+
functionResourceId: docFunction
|
|
55
|
+
? this.api.store.selectedProject
|
|
56
|
+
: this.api.store.selectedOrganization,
|
|
47
57
|
}
|
|
48
58
|
const metadata = {
|
|
49
59
|
event: selectedEvent,
|
|
@@ -78,7 +88,6 @@ class RunPanel extends ApiBaseElement {
|
|
|
78
88
|
|
|
79
89
|
connectedCallback() {
|
|
80
90
|
this.innerHTML = template
|
|
81
|
-
this.apiVersion = document.querySelector('#apiversion')
|
|
82
91
|
this.datasetname = document.querySelector('#datasetname')
|
|
83
92
|
this.productId = document.querySelector('#project')
|
|
84
93
|
this.button = this.querySelector('button')
|
|
@@ -47,6 +47,9 @@
|
|
|
47
47
|
<footer><run-panel></run-panel></footer>
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
<script src="./components/filter-api-version.js" type="module"></script>
|
|
51
|
+
<script src="./components/filter-document-id.js" type="module"></script>
|
|
52
|
+
<script src="./components/filter-with-token.js" type="module"></script>
|
|
50
53
|
<script src="./components/filters.js" type="module"></script>
|
|
51
54
|
<script src="./components/select-dropdown.js" type="module"></script>
|
|
52
55
|
<script src="./components/function-list.js" type="module"></script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Blueprint } from '@sanity/blueprints-parser';
|
|
2
|
-
import type
|
|
2
|
+
import { type Resource, type Stack } from '../types.js';
|
|
3
3
|
export declare function formatTitle(title: string, name: string): string;
|
|
4
4
|
export declare function formatResourceTree(resources: Resource[] | undefined): string;
|
|
5
5
|
export declare function formatStackInfo(stack: Stack | Blueprint, isCurrentStack?: boolean): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { treeify } from 'array-treeify';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import { isLocalFunctionResource } from '../types.js';
|
|
3
4
|
import { formatDate, formatDuration } from './dates.js';
|
|
4
5
|
import { niceId } from './presenters.js';
|
|
5
6
|
import { arraifyCors, arrayifyFunction } from './resources-formatting.js';
|
|
@@ -13,7 +14,7 @@ export function formatResourceTree(resources) {
|
|
|
13
14
|
const RESOURCE_CATEGORIES = [
|
|
14
15
|
{
|
|
15
16
|
label: 'Functions',
|
|
16
|
-
match:
|
|
17
|
+
match: isLocalFunctionResource,
|
|
17
18
|
name: (res) => {
|
|
18
19
|
const name = chalk.bold.green(res.displayName || res.name || 'unnamed');
|
|
19
20
|
const ids = [
|
|
@@ -26,7 +27,7 @@ export function formatResourceTree(resources) {
|
|
|
26
27
|
},
|
|
27
28
|
{
|
|
28
29
|
label: 'CORS Origins',
|
|
29
|
-
match: (
|
|
30
|
+
match: (r) => r.type === 'sanity.project.cors',
|
|
30
31
|
name: (res) => {
|
|
31
32
|
const name = chalk.bold.yellow(res.displayName || res.name || 'unnamed');
|
|
32
33
|
const ids = [
|
|
@@ -42,7 +43,7 @@ export function formatResourceTree(resources) {
|
|
|
42
43
|
const matchedIndices = new Set();
|
|
43
44
|
for (const [i, resource] of resources.entries()) {
|
|
44
45
|
for (const cat of RESOURCE_CATEGORIES) {
|
|
45
|
-
if (cat.match(resource
|
|
46
|
+
if (cat.match(resource)) {
|
|
46
47
|
if (!categorized[cat.label])
|
|
47
48
|
categorized[cat.label] = [];
|
|
48
49
|
categorized[cat.label].push(resource);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { TreeInput } from 'array-treeify';
|
|
2
|
-
import type { CorsResource,
|
|
3
|
-
export declare function arrayifyFunction(fn:
|
|
2
|
+
import type { CorsResource, FunctionResourceBase } from '../../utils/types.js';
|
|
3
|
+
export declare function arrayifyFunction(fn: FunctionResourceBase): TreeInput;
|
|
4
4
|
export declare function arraifyCors(resource: CorsResource): TreeInput;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { type ClientConfig } from '@sanity/client';
|
|
2
|
+
import type { FetchConfig } from '../types.js';
|
|
2
3
|
export declare function fetchDocument(documentId: string, { projectId, dataset, useCdn, apiVersion, apiHost, token }: ClientConfig): Promise<Record<string, unknown>>;
|
|
4
|
+
export declare function fetchAsset(documentId: string, { mediaLibraryId, apiVersion, apiHost, token }: FetchConfig): Promise<Record<string, unknown>>;
|
|
@@ -10,3 +10,18 @@ export async function fetchDocument(documentId, { projectId, dataset, useCdn = t
|
|
|
10
10
|
}
|
|
11
11
|
return data[0];
|
|
12
12
|
}
|
|
13
|
+
export async function fetchAsset(documentId, { mediaLibraryId, apiVersion = '2025-03-24', apiHost, token }) {
|
|
14
|
+
const spinner = ora(`Fetching document ID ${documentId}...`).start();
|
|
15
|
+
const url = `${apiHost}/v${apiVersion}/media-libraries/${mediaLibraryId}/doc/${documentId}`;
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Bearer ${token}`,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
spinner.stop();
|
|
23
|
+
if (!data?.documents[0]) {
|
|
24
|
+
throw Error(`Could not fetch document ID ${documentId}`);
|
|
25
|
+
}
|
|
26
|
+
return data.documents[0];
|
|
27
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { FunctionResource,
|
|
1
|
+
import type { FunctionResource, GroqRuleBase, InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadOptions } from './types.js';
|
|
2
2
|
export declare function sanitizeLogs(logs: string): string;
|
|
3
3
|
export declare const DEFAULT_GROQ_RULE: {
|
|
4
4
|
on: string[];
|
|
5
5
|
filter: string;
|
|
6
6
|
projection: string;
|
|
7
7
|
};
|
|
8
|
-
export declare function isDefaultGROQRule(rule:
|
|
8
|
+
export declare function isDefaultGROQRule(rule: GroqRuleBase | undefined): boolean;
|
|
9
9
|
export declare function applyGroqRule(resource: FunctionResource, data: Record<string, unknown> | null, before: Record<string, unknown> | null, after: Record<string, unknown> | null, projectId: string | undefined, dataset: string | undefined): Promise<any>;
|
|
10
10
|
export default function invoke(resource: FunctionResource, payload: InvokePayloadOptions, context: InvokeContextOptions, options: InvokeExecutionOptions): Promise<InvocationResponse>;
|
|
@@ -45,11 +45,17 @@ export async function applyGroqRule(resource, data, before, after, projectId, da
|
|
|
45
45
|
const hasProjection = event.projection?.length;
|
|
46
46
|
const projection = hasProjection ? `${event?.projection}` : '';
|
|
47
47
|
const query = `*[${event?.filter}]${projection}`;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
try {
|
|
49
|
+
const rule = groq.parse(query, { mode: 'delta' });
|
|
50
|
+
const sanity = projectId && dataset ? { projectId, dataset } : undefined;
|
|
51
|
+
const queryResults = await groq.evaluate(rule, { dataset: [data], before, after, sanity });
|
|
52
|
+
const currentFunctionDocumentSet = await queryResults.get();
|
|
53
|
+
return currentFunctionDocumentSet[0];
|
|
54
|
+
}
|
|
55
|
+
catch (groqErr) {
|
|
56
|
+
const detail = groqErr instanceof Error && `; ${groqErr.message}`;
|
|
57
|
+
throw new Error(`Invalid rule configuration: ${query}${detail}`, { cause: groqErr });
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
// default groq rule so just return the data
|
|
55
61
|
return data;
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -15,17 +15,26 @@ export interface AuthParams {
|
|
|
15
15
|
scopeId: string;
|
|
16
16
|
}
|
|
17
17
|
/** @internal */
|
|
18
|
-
export
|
|
18
|
+
export type GroqRule = GroqRuleDocumentFunction | GroqRuleMediaLibraryFunction;
|
|
19
|
+
export interface GroqRuleBase {
|
|
19
20
|
on: Array<string>;
|
|
20
21
|
filter?: string;
|
|
22
|
+
projection?: string;
|
|
23
|
+
}
|
|
24
|
+
interface GroqRuleDocumentFunction extends GroqRuleBase {
|
|
21
25
|
includeDrafts?: boolean;
|
|
22
26
|
includeAllVersions?: boolean;
|
|
23
|
-
projection?: string;
|
|
24
27
|
resource?: {
|
|
25
28
|
type: 'dataset';
|
|
26
29
|
id: string;
|
|
27
30
|
};
|
|
28
31
|
}
|
|
32
|
+
interface GroqRuleMediaLibraryFunction extends GroqRuleBase {
|
|
33
|
+
resource: {
|
|
34
|
+
type: 'media-library';
|
|
35
|
+
id: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
29
38
|
/** @internal */
|
|
30
39
|
export interface Resource {
|
|
31
40
|
name: string;
|
|
@@ -38,16 +47,27 @@ export interface DeployedResource extends Resource {
|
|
|
38
47
|
id: string;
|
|
39
48
|
externalId: string;
|
|
40
49
|
}
|
|
41
|
-
export declare function isLocalFunctionResource(r:
|
|
50
|
+
export declare function isLocalFunctionResource<T extends Resource>(r: T): r is T & FunctionResourceBase;
|
|
51
|
+
export declare function isDocumentFunctionResource<T extends Resource>(r: T): r is T & FunctionResourceDocument;
|
|
52
|
+
export declare function isMediaLibraryAssetFunctionResource<T extends Resource>(r: T): r is T & FunctionResourceMediaLibraryAsset;
|
|
42
53
|
/** @internal */
|
|
43
|
-
export
|
|
54
|
+
export type FunctionResource = FunctionResourceDocument | FunctionResourceMediaLibraryAsset | FunctionResourceBase;
|
|
55
|
+
export interface FunctionResourceBase extends Resource {
|
|
44
56
|
src?: string;
|
|
45
57
|
autoResolveDeps?: boolean;
|
|
46
58
|
transpile?: boolean;
|
|
47
59
|
memory?: number;
|
|
48
60
|
timeout?: number;
|
|
49
61
|
env?: Record<string, string>;
|
|
50
|
-
event?:
|
|
62
|
+
event?: GroqRuleBase;
|
|
63
|
+
}
|
|
64
|
+
interface FunctionResourceDocument extends FunctionResourceBase {
|
|
65
|
+
type: 'sanity.function.document';
|
|
66
|
+
event?: GroqRuleDocumentFunction;
|
|
67
|
+
}
|
|
68
|
+
interface FunctionResourceMediaLibraryAsset extends FunctionResourceBase {
|
|
69
|
+
type: 'sanity.function.media-library.asset';
|
|
70
|
+
event: GroqRuleMediaLibraryFunction;
|
|
51
71
|
}
|
|
52
72
|
export interface CorsResource extends Resource {
|
|
53
73
|
origin?: string;
|
|
@@ -102,6 +122,14 @@ export interface InvokeContextOptions {
|
|
|
102
122
|
organizationId?: string;
|
|
103
123
|
token?: string;
|
|
104
124
|
};
|
|
125
|
+
/** The resource type of the event source; resource type that invoked the function. */
|
|
126
|
+
eventResourceType: string;
|
|
127
|
+
/** The resource ID of the event source; resource ID that invoked the function. */
|
|
128
|
+
eventResourceId: string;
|
|
129
|
+
/** The resource type of the function container; resource type that houses the function. */
|
|
130
|
+
functionResourceType: string;
|
|
131
|
+
/** The resource ID of the function container; resource ID that houses the function. */
|
|
132
|
+
functionResourceId: string;
|
|
105
133
|
}
|
|
106
134
|
/** @internal */
|
|
107
135
|
export interface InvokeExecutionOptions {
|
|
@@ -152,3 +180,11 @@ export interface BlueprintParserError {
|
|
|
152
180
|
message: string;
|
|
153
181
|
type: string;
|
|
154
182
|
}
|
|
183
|
+
/** @internal */
|
|
184
|
+
export declare interface FetchConfig {
|
|
185
|
+
mediaLibraryId?: string;
|
|
186
|
+
token?: string;
|
|
187
|
+
apiHost?: string;
|
|
188
|
+
apiVersion?: string;
|
|
189
|
+
}
|
|
190
|
+
export {};
|
package/dist/utils/types.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export function isLocalFunctionResource(r) {
|
|
2
2
|
return r.type.startsWith('sanity.function.');
|
|
3
3
|
}
|
|
4
|
+
// annoyed that checking for equality on `type` isnt sufficient to narrow down to a specific type from a union
|
|
5
|
+
// instead need to use these type predicated
|
|
6
|
+
export function isDocumentFunctionResource(r) {
|
|
7
|
+
return r.type === 'sanity.function.document';
|
|
8
|
+
}
|
|
9
|
+
export function isMediaLibraryAssetFunctionResource(r) {
|
|
10
|
+
return r.type === 'sanity.function.media-library.asset';
|
|
11
|
+
}
|
|
4
12
|
export function isEventType(arg) {
|
|
5
13
|
return ['create', 'update', 'delete'].includes(arg);
|
|
6
14
|
}
|