@sanity/runtime-cli 17.5.0 → 17.6.0-blueprinit.g77b7a64
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 +25 -25
- package/dist/actions/blueprinit/manifest.d.ts +15 -0
- package/dist/actions/blueprinit/manifest.js +82 -0
- package/dist/actions/blueprinit/studio.d.ts +6 -0
- package/dist/actions/blueprinit/studio.js +43 -0
- package/dist/actions/blueprinit/templates.d.ts +34 -0
- package/dist/actions/blueprinit/templates.js +83 -0
- package/dist/actions/blueprinit/workspace.d.ts +16 -0
- package/dist/actions/blueprinit/workspace.js +143 -0
- package/dist/actions/blueprints/blueprint.js +2 -1
- package/dist/actions/blueprints/resources.js +17 -1
- package/dist/actions/sanity/datasets.d.ts +15 -0
- package/dist/actions/sanity/datasets.js +26 -0
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.js +59 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +6 -0
- package/dist/cores/blueprinit/adopt.d.ts +6 -0
- package/dist/cores/blueprinit/adopt.js +119 -0
- package/dist/cores/blueprinit/finish.d.ts +14 -0
- package/dist/cores/blueprinit/finish.js +64 -0
- package/dist/cores/blueprinit/fresh.d.ts +3 -0
- package/dist/cores/blueprinit/fresh.js +120 -0
- package/dist/cores/blueprinit/preview.d.ts +15 -0
- package/dist/cores/blueprinit/preview.js +14 -0
- package/dist/cores/blueprinit/prompts.d.ts +5 -0
- package/dist/cores/blueprinit/prompts.js +22 -0
- package/dist/cores/blueprinit/selection.d.ts +15 -0
- package/dist/cores/blueprinit/selection.js +90 -0
- package/dist/cores/blueprinit/types.d.ts +16 -0
- package/dist/cores/blueprinit/types.js +1 -0
- package/dist/cores/blueprinit.d.ts +7 -0
- package/dist/cores/blueprinit.js +48 -0
- package/dist/cores/blueprints/deploy.js +2 -0
- package/dist/cores/blueprints/plan.js +2 -0
- package/dist/cores/functions/add.js +10 -7
- package/dist/server/static/components/api-base.js +1 -0
- package/dist/server/static/components/filters.js +4 -2
- package/dist/server/static/components/payload-panel.js +3 -0
- package/dist/utils/blueprints/extract-durable-config.d.ts +8 -0
- package/dist/utils/blueprints/extract-durable-config.js +60 -0
- package/dist/utils/blueprints/validate-resources.js +5 -2
- package/dist/utils/display/blueprints-formatting.js +5 -1
- package/dist/utils/display/resources-formatting.js +2 -2
- package/dist/utils/types.d.ts +8 -2
- package/dist/utils/types.js +8 -2
- package/oclif.manifest.json +132 -1
- package/package.json +2 -2
- package/skills/init.md +155 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { select } from '@inquirer/prompts';
|
|
2
|
+
export async function chooseOrganization(groups, organizationId, interactive) {
|
|
3
|
+
if (groups.length === 0) {
|
|
4
|
+
return failure('No organizations found for this account.');
|
|
5
|
+
}
|
|
6
|
+
if (organizationId) {
|
|
7
|
+
const organization = groups.find((group) => group.organization.id === organizationId);
|
|
8
|
+
return organization
|
|
9
|
+
? { ok: true, value: organization }
|
|
10
|
+
: failure(`No organization "${organizationId}" found for this account.`);
|
|
11
|
+
}
|
|
12
|
+
if (groups.length === 1)
|
|
13
|
+
return { ok: true, value: groups[0] };
|
|
14
|
+
if (!interactive) {
|
|
15
|
+
return failure('Multiple organizations found.', ['Pass --organization-id <id> to choose one.']);
|
|
16
|
+
}
|
|
17
|
+
const selectedId = await select({
|
|
18
|
+
message: 'Choose a Sanity organization:',
|
|
19
|
+
choices: groups.map(({ organization, projects }) => ({
|
|
20
|
+
name: organization.name,
|
|
21
|
+
value: organization.id,
|
|
22
|
+
description: `${projects?.length ?? 0} projects`,
|
|
23
|
+
})),
|
|
24
|
+
});
|
|
25
|
+
const selected = groups.find(({ organization }) => organization.id === selectedId);
|
|
26
|
+
return selected ? { ok: true, value: selected } : failure('Organization not found.');
|
|
27
|
+
}
|
|
28
|
+
export async function chooseProject(projects, projectId, organizationName, interactive) {
|
|
29
|
+
if (projects.length === 0) {
|
|
30
|
+
return failure(`No projects found in ${organizationName}.`);
|
|
31
|
+
}
|
|
32
|
+
if (projectId) {
|
|
33
|
+
const project = projects.find(({ id }) => id === projectId);
|
|
34
|
+
return project
|
|
35
|
+
? { ok: true, value: project }
|
|
36
|
+
: failure(`No project "${projectId}" found in ${organizationName}.`);
|
|
37
|
+
}
|
|
38
|
+
if (projects.length === 1)
|
|
39
|
+
return { ok: true, value: projects[0] };
|
|
40
|
+
if (!interactive) {
|
|
41
|
+
return failure('Multiple projects found.', ['Pass --project-id <id> to choose one.']);
|
|
42
|
+
}
|
|
43
|
+
const selectedId = await select({
|
|
44
|
+
message: 'Choose a Sanity project:',
|
|
45
|
+
choices: projects.map((project) => ({ name: project.displayName, value: project.id })),
|
|
46
|
+
});
|
|
47
|
+
const selected = projects.find(({ id }) => id === selectedId);
|
|
48
|
+
return selected ? { ok: true, value: selected } : failure('Project not found.');
|
|
49
|
+
}
|
|
50
|
+
export async function chooseDataset(datasets, datasetName, projectName, interactive) {
|
|
51
|
+
if (datasets.length === 0) {
|
|
52
|
+
return failure(`No datasets found in ${projectName}.`);
|
|
53
|
+
}
|
|
54
|
+
if (datasetName) {
|
|
55
|
+
const dataset = datasets.find(({ name }) => name === datasetName);
|
|
56
|
+
return dataset
|
|
57
|
+
? { ok: true, value: dataset }
|
|
58
|
+
: failure(`No dataset "${datasetName}" found in ${projectName}.`);
|
|
59
|
+
}
|
|
60
|
+
if (datasets.length === 1)
|
|
61
|
+
return { ok: true, value: datasets[0] };
|
|
62
|
+
if (!interactive) {
|
|
63
|
+
return failure('Multiple datasets found.', ['Pass --dataset <name> to choose one.']);
|
|
64
|
+
}
|
|
65
|
+
const production = datasets.find(({ name }) => name === 'production');
|
|
66
|
+
const orderedDatasets = production
|
|
67
|
+
? [production, ...datasets.filter(({ name }) => name !== 'production')]
|
|
68
|
+
: datasets;
|
|
69
|
+
const selectedName = await select({
|
|
70
|
+
message: 'Choose a Sanity dataset:',
|
|
71
|
+
choices: orderedDatasets.map((dataset) => ({
|
|
72
|
+
name: dataset.name,
|
|
73
|
+
value: dataset.name,
|
|
74
|
+
description: dataset.aclMode,
|
|
75
|
+
})),
|
|
76
|
+
});
|
|
77
|
+
const selected = datasets.find(({ name }) => name === selectedName);
|
|
78
|
+
return selected ? { ok: true, value: selected } : failure('Dataset not found.');
|
|
79
|
+
}
|
|
80
|
+
export function findProject(groups, projectId) {
|
|
81
|
+
for (const { organization, projects = [] } of groups) {
|
|
82
|
+
const project = projects.find(({ id }) => id === projectId);
|
|
83
|
+
if (project)
|
|
84
|
+
return { organization, project };
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
function failure(message, suggestions) {
|
|
89
|
+
return { ok: false, error: { message, suggestions } };
|
|
90
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Logger } from '../../utils/logger.js';
|
|
2
|
+
export interface BlueprinitFlags {
|
|
3
|
+
yes?: boolean;
|
|
4
|
+
organizationId?: string;
|
|
5
|
+
projectId?: string;
|
|
6
|
+
datasetName?: string;
|
|
7
|
+
stackName?: string;
|
|
8
|
+
studioName?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface BlueprinitFlowOptions {
|
|
11
|
+
bin: string;
|
|
12
|
+
dir: string;
|
|
13
|
+
flags: BlueprinitFlags;
|
|
14
|
+
log: Logger;
|
|
15
|
+
token: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BlueprinitFlags } from './blueprinit/types.js';
|
|
2
|
+
import type { CoreConfig, CoreResult } from './index.js';
|
|
3
|
+
export interface BlueprinitOptions extends CoreConfig {
|
|
4
|
+
dir?: string;
|
|
5
|
+
flags?: BlueprinitFlags;
|
|
6
|
+
}
|
|
7
|
+
export declare function blueprinitCore(options: BlueprinitOptions): Promise<CoreResult>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cwd } from 'node:process';
|
|
2
|
+
import { detectStudio } from '../actions/blueprinit/studio.js';
|
|
3
|
+
import { findBlueprintFile } from '../actions/blueprints/blueprint.js';
|
|
4
|
+
import { readConfigFile } from '../actions/blueprints/config.js';
|
|
5
|
+
import { validTokenOrErrorMessage } from '../utils/validated-token.js';
|
|
6
|
+
import { adoptFlow } from './blueprinit/adopt.js';
|
|
7
|
+
import { freshFlow } from './blueprinit/fresh.js';
|
|
8
|
+
export async function blueprinitCore(options) {
|
|
9
|
+
const { bin, log, flags = {}, dir = cwd() } = options;
|
|
10
|
+
try {
|
|
11
|
+
const existingBlueprint = findBlueprintFile(dir);
|
|
12
|
+
if (existingBlueprint) {
|
|
13
|
+
const blueprintConfig = readConfigFile(existingBlueprint.blueprintFilePath);
|
|
14
|
+
if (!blueprintConfig?.stackId) {
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
error: `A Blueprint exists here (${existingBlueprint.fileName}), but it is not connected to a Stack.`,
|
|
18
|
+
suggestions: [`Finish setup with: npx ${bin} blueprints init`],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
log(`A blueprint already exists here (${existingBlueprint.fileName}).`);
|
|
22
|
+
log('');
|
|
23
|
+
log('Use these instead:');
|
|
24
|
+
log(` npx ${bin} blueprints config --edit`);
|
|
25
|
+
log(` npx ${bin} blueprints doctor`);
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
json: { alreadyInitialized: true, blueprintFile: existingBlueprint.fileName },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const tokenCheck = await validTokenOrErrorMessage(log);
|
|
32
|
+
if (!tokenCheck.ok) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
error: 'No Sanity session found.',
|
|
36
|
+
suggestions: ['Sign in first: npx sanity login', 'Then run init again.'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
log('Setting up your Sanity workspace.');
|
|
40
|
+
log('');
|
|
41
|
+
const flowOptions = { bin, dir, flags, log, token: tokenCheck.value };
|
|
42
|
+
const studio = detectStudio(dir);
|
|
43
|
+
return studio ? await adoptFlow({ ...flowOptions, studio }) : await freshFlow(flowOptions);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -3,6 +3,7 @@ import { preDeploy } from '../../actions/blueprints/pre-deploy.js';
|
|
|
3
3
|
import { getStack, updateStack } from '../../actions/blueprints/stacks.js';
|
|
4
4
|
import { waitForOperation } from '../../actions/blueprints/wait-for-operation.js';
|
|
5
5
|
import { checkUserPermission } from '../../actions/sanity/access.js';
|
|
6
|
+
import { extractDurableConfig } from '../../utils/blueprints/extract-durable-config.js';
|
|
6
7
|
import { createHintCollector } from '../../utils/blueprints/hints.js';
|
|
7
8
|
import { check, niceId, severe } from '../../utils/display/presenters.js';
|
|
8
9
|
import { CODE_OPERATION_UNCONFIRMED, EXIT_OPERATION_UNCONFIRMED } from '../../utils/exit-codes.js';
|
|
@@ -34,6 +35,7 @@ export async function blueprintDeployCore(options) {
|
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
const resources = [...blueprint.resources];
|
|
38
|
+
await extractDurableConfig(resources, blueprintFileDir);
|
|
37
39
|
const preDeployResult = await preDeploy({
|
|
38
40
|
scopeType,
|
|
39
41
|
scopeId,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
2
|
import { planStack, resolveStackIdByNameOrId } from '../../actions/blueprints/stacks.js';
|
|
3
|
+
import { extractDurableConfig } from '../../utils/blueprints/extract-durable-config.js';
|
|
3
4
|
import { createHintCollector } from '../../utils/blueprints/hints.js';
|
|
4
5
|
import { formatDeploymentPlan, formatResourceTree, hasActionableChanges, } from '../../utils/display/blueprints-formatting.js';
|
|
5
6
|
import { styleText } from '../../utils/style-text.js';
|
|
@@ -9,6 +10,7 @@ export async function blueprintPlanCore(options) {
|
|
|
9
10
|
const { verbose: _verbose = false } = flags;
|
|
10
11
|
const { scopeType, scopeId, stackId: blueprintStackId, parsedBlueprint, resources, fileInfo, } = blueprint;
|
|
11
12
|
log(`${styleText(['bold', 'blueBright'], 'Local Blueprint')} ${styleText('dim', `(${fileInfo.fileName})`)}`);
|
|
13
|
+
await extractDurableConfig(resources, dirname(fileInfo.blueprintFilePath));
|
|
12
14
|
log(formatResourceTree(resources));
|
|
13
15
|
if (!token || !scopeType || !scopeId) {
|
|
14
16
|
log(styleText('dim', 'Unable to retrieve live Stack deployment for comparison'));
|
|
@@ -5,7 +5,7 @@ import { checkbox, confirm, input, select } from '@inquirer/prompts';
|
|
|
5
5
|
import { highlight } from 'cardinal';
|
|
6
6
|
import { createFunctionResource } from '../../actions/blueprints/resources.js';
|
|
7
7
|
import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
|
|
8
|
-
import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EVENT_MEDIA_LIBRARY_ASSET_CREATE, EVENT_MEDIA_LIBRARY_ASSET_DELETE, EVENT_MEDIA_LIBRARY_ASSET_UPDATE, EVENT_SCHEDULED, EVENT_SYNC_TAG_INVALIDATE, FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPDATE, LABEL_MEDIA_LIBRARY_ASSET_CREATE, LABEL_MEDIA_LIBRARY_ASSET_DELETE, LABEL_MEDIA_LIBRARY_ASSET_UPDATE, LABEL_SCHEDULED, LABEL_SYNC_TAG_INVALIDATE, MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
8
|
+
import { EVENT_DOCUMENT_CREATE, EVENT_DOCUMENT_DELETE, EVENT_DOCUMENT_UPDATE, EVENT_MEDIA_LIBRARY_ASSET_CREATE, EVENT_MEDIA_LIBRARY_ASSET_DELETE, EVENT_MEDIA_LIBRARY_ASSET_UPDATE, EVENT_PUBSUB, EVENT_SCHEDULED, EVENT_SYNC_TAG_INVALIDATE, FUNCTION_TYPES, LABEL_DOCUMENT_CREATE, LABEL_DOCUMENT_DELETE, LABEL_DOCUMENT_UPDATE, LABEL_MEDIA_LIBRARY_ASSET_CREATE, LABEL_MEDIA_LIBRARY_ASSET_DELETE, LABEL_MEDIA_LIBRARY_ASSET_UPDATE, LABEL_PUBSUB, LABEL_SCHEDULED, LABEL_SYNC_TAG_INVALIDATE, MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PUBSUB, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
9
9
|
import { check, indent, warn } from '../../utils/display/presenters.js';
|
|
10
10
|
import { validateFunctionName } from '../../utils/functions/validate-name.js';
|
|
11
11
|
import { styleText } from '../../utils/style-text.js';
|
|
@@ -21,6 +21,9 @@ export const generateFunctionBlueprintResourceTemplate = (fnName, eventNames) =>
|
|
|
21
21
|
case SANITY_FUNCTION_MEDIA_LIBRARY_ASSET:
|
|
22
22
|
definer = `defineMediaLibraryAssetFunction({name: '${fnName}', event: {on: [${eventOns.join(', ')}], resource: {type: 'media-library', id: 'my-media-library-id'}}}), // ← add this line`;
|
|
23
23
|
break;
|
|
24
|
+
case SANITY_FUNCTION_PUBSUB:
|
|
25
|
+
definer = `definePubSubFunction({name: '${fnName}'}), // ← add this line`;
|
|
26
|
+
break;
|
|
24
27
|
case SANITY_FUNCTION_SCHEDULED:
|
|
25
28
|
definer = `defineScheduledFunction({name: '${fnName}', event: {expression: '0 0 * * *'}}), // ← add this line`;
|
|
26
29
|
break;
|
|
@@ -158,7 +161,7 @@ export async function functionAddCore(options) {
|
|
|
158
161
|
}
|
|
159
162
|
const eventSources = new Set(fnTypes.map((t) => t.substring(0, t.lastIndexOf('-'))));
|
|
160
163
|
if (eventSources.size > 1) {
|
|
161
|
-
throw new Error('Invalid function type. Cannot mix document-*, media-library-asset-*, sync-tag-* and/or scheduled-* types.');
|
|
164
|
+
throw new Error('Invalid function type. Cannot mix document-*, media-library-asset-*, pub-sub, sync-tag-* and/or scheduled-* types.');
|
|
162
165
|
}
|
|
163
166
|
let addHelpers;
|
|
164
167
|
let installCommand;
|
|
@@ -242,6 +245,7 @@ async function promptForFunctionType() {
|
|
|
242
245
|
{ name: LABEL_MEDIA_LIBRARY_ASSET_CREATE, value: EVENT_MEDIA_LIBRARY_ASSET_CREATE },
|
|
243
246
|
{ name: LABEL_MEDIA_LIBRARY_ASSET_UPDATE, value: EVENT_MEDIA_LIBRARY_ASSET_UPDATE },
|
|
244
247
|
{ name: LABEL_MEDIA_LIBRARY_ASSET_DELETE, value: EVENT_MEDIA_LIBRARY_ASSET_DELETE },
|
|
248
|
+
{ name: LABEL_PUBSUB, value: EVENT_PUBSUB },
|
|
245
249
|
{ name: LABEL_SCHEDULED, value: EVENT_SCHEDULED },
|
|
246
250
|
{ name: LABEL_SYNC_TAG_INVALIDATE, value: EVENT_SYNC_TAG_INVALIDATE },
|
|
247
251
|
],
|
|
@@ -249,11 +253,10 @@ async function promptForFunctionType() {
|
|
|
249
253
|
if (choices.length === 0) {
|
|
250
254
|
return 'You must choose at least one function type / document change event';
|
|
251
255
|
}
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return 'You cannot mix Document, Media Library Asset, and Scheduled events together in one Function';
|
|
256
|
+
// same grouping as the check in functionAddCore, so the prompt cannot drift from it
|
|
257
|
+
const eventSources = new Set(choices.map((c) => String(c.value).substring(0, String(c.value).lastIndexOf('-'))));
|
|
258
|
+
if (eventSources.size > 1) {
|
|
259
|
+
return 'You cannot mix Document, Media Library Asset, PubSub, Scheduled, and Sync Tag Invalidate events together in one Function';
|
|
257
260
|
}
|
|
258
261
|
return true;
|
|
259
262
|
},
|
|
@@ -9,6 +9,7 @@ export class ApiBaseElement extends HTMLElement {
|
|
|
9
9
|
this.api = api
|
|
10
10
|
this.SANITY_FUNCTION_DOCUMENT = 'sanity.function.document'
|
|
11
11
|
this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET = 'sanity.function.media-library.asset'
|
|
12
|
+
this.SANITY_FUNCTION_PUBSUB = 'sanity.function.pubsub'
|
|
12
13
|
this.SANITY_FUNCTION_SCHEDULED = 'sanity.function.cron'
|
|
13
14
|
this.SANITY_FUNCTION_SYNC_TAG_INVALIDATE = 'sanity.function.sync-tag-invalidate'
|
|
14
15
|
}
|
|
@@ -79,6 +79,7 @@ class FiltersComponent extends ApiBaseElement {
|
|
|
79
79
|
const mediaFunction = this.api.store.selectedFunctionType?.startsWith(
|
|
80
80
|
this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
|
|
81
81
|
)
|
|
82
|
+
const pubsubFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_PUBSUB
|
|
82
83
|
const scheduleFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SCHEDULED
|
|
83
84
|
const stiFunction =
|
|
84
85
|
this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SYNC_TAG_INVALIDATE
|
|
@@ -89,15 +90,16 @@ class FiltersComponent extends ApiBaseElement {
|
|
|
89
90
|
mediaFunction,
|
|
90
91
|
scheduleFunction,
|
|
91
92
|
stiFunction,
|
|
93
|
+
pubsubFunction,
|
|
92
94
|
)
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
buildFilters = (docFunction, mediaFunction, scheduleFunction, stiFunction) => {
|
|
97
|
+
buildFilters = (docFunction, mediaFunction, scheduleFunction, stiFunction, pubsubFunction) => {
|
|
96
98
|
return `
|
|
97
99
|
<legend class="config-label">Client Options</legend>
|
|
98
100
|
<div id="dynamic-dropdowns" class="flex gap-2">
|
|
99
101
|
${
|
|
100
|
-
docFunction || scheduleFunction || stiFunction
|
|
102
|
+
docFunction || scheduleFunction || stiFunction || pubsubFunction
|
|
101
103
|
? `<select-dropdown
|
|
102
104
|
label="Project"
|
|
103
105
|
store-key="projects"
|
|
@@ -164,6 +164,9 @@ class PayloadPanel extends ApiBaseElement {
|
|
|
164
164
|
const payloadTitle = this.shadowRoot.querySelector('#payloadTitle')
|
|
165
165
|
let title = 'Document'
|
|
166
166
|
switch (selectedFunctionType) {
|
|
167
|
+
case this.SANITY_FUNCTION_PUBSUB:
|
|
168
|
+
title = 'PubSub Payload'
|
|
169
|
+
break
|
|
167
170
|
case this.SANITY_FUNCTION_SYNC_TAG_INVALIDATE:
|
|
168
171
|
title = 'Sync Tag Payload'
|
|
169
172
|
break
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type BlueprintResource } from '@sanity/blueprints';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the blueprint config from the function source code and mutates it to the resource object.
|
|
4
|
+
* This is used for durable functions that may not have a config defined in the blueprint.
|
|
5
|
+
* @param resources
|
|
6
|
+
* @param blueprintFileDir
|
|
7
|
+
*/
|
|
8
|
+
export declare const extractDurableConfig: (resources: BlueprintResource[], blueprintFileDir: string) => Promise<void>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { validateDurableFunction, } from '@sanity/blueprints';
|
|
4
|
+
import { SANITY_FUNCTION_DURABLE } from '../../constants.js';
|
|
5
|
+
import { findFunctionEntryPoint } from '../functions/find-entry-point.js';
|
|
6
|
+
import { transpileFunction } from '../functions/transpile/function.js';
|
|
7
|
+
import { isLocalFunctionResource } from '../types.js';
|
|
8
|
+
const DURABLE_CONFIG_KEYS = ['event', 'concurrency', 'debounce', 'debounceKey'];
|
|
9
|
+
/**
|
|
10
|
+
* Extracts the blueprint config from the function source code and mutates it to the resource object.
|
|
11
|
+
* This is used for durable functions that may not have a config defined in the blueprint.
|
|
12
|
+
* @param resources
|
|
13
|
+
* @param blueprintFileDir
|
|
14
|
+
*/
|
|
15
|
+
export const extractDurableConfig = async (resources, blueprintFileDir) => {
|
|
16
|
+
for (const resource of resources) {
|
|
17
|
+
if (!isLocalFunctionResource(resource))
|
|
18
|
+
continue;
|
|
19
|
+
// skip if not a durable function
|
|
20
|
+
if (resource.type !== SANITY_FUNCTION_DURABLE)
|
|
21
|
+
continue;
|
|
22
|
+
// Check if source needs to be transpiled before attempting to get config
|
|
23
|
+
const resourceSrc = resolve(blueprintFileDir, resource.src);
|
|
24
|
+
const entryPoint = await findFunctionEntryPoint(resourceSrc, resource.name);
|
|
25
|
+
let importPath = entryPoint;
|
|
26
|
+
let cleanup = async () => { };
|
|
27
|
+
// need to transpile and grab the config from the built output if the entry point is a TypeScript file
|
|
28
|
+
if (entryPoint.endsWith('.ts')) {
|
|
29
|
+
const built = await transpileFunction({ resource, functionPath: resourceSrc, entryPoint });
|
|
30
|
+
importPath = await findFunctionEntryPoint(built.outputDir, resource.name);
|
|
31
|
+
cleanup = built.cleanup;
|
|
32
|
+
}
|
|
33
|
+
let config = {};
|
|
34
|
+
try {
|
|
35
|
+
const mod = await import(pathToFileURL(importPath).href);
|
|
36
|
+
config = (mod.handler || mod.default)?.config;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await cleanup();
|
|
40
|
+
}
|
|
41
|
+
// Update values not included in the blueprint.
|
|
42
|
+
if (resource.event === undefined && config?.event !== undefined) {
|
|
43
|
+
resource.event = config.event;
|
|
44
|
+
}
|
|
45
|
+
if (resource.concurrency === undefined && config?.concurrency !== undefined) {
|
|
46
|
+
resource.concurrency = config.concurrency;
|
|
47
|
+
}
|
|
48
|
+
if (resource.debounce === undefined && config?.debounce !== undefined) {
|
|
49
|
+
resource.debounce = config.debounce;
|
|
50
|
+
}
|
|
51
|
+
if (resource.debounceKey === undefined && config?.debounceKey !== undefined) {
|
|
52
|
+
resource.debounceKey = config.debounceKey;
|
|
53
|
+
}
|
|
54
|
+
// Validate the resource after merging the config values
|
|
55
|
+
const errors = validateDurableFunction(resource);
|
|
56
|
+
if (errors.length > 0) {
|
|
57
|
+
throw new Error(`Invalid durable resource "${resource.name}": ${errors.map((error) => error.message).join('; ')}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { validateCorsOrigin, validateDataset, validateDocumentFunction, validateDocumentWebhook, validateMediaLibraryAssetFunction, validateProject, validateRole, validateScheduledFunction, } from '@sanity/blueprints';
|
|
2
|
-
import { SANITY_ACCESS_ROLE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_PROJECT, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, } from '../../constants.js';
|
|
1
|
+
import { validateCorsOrigin, validateDataset, validateDocumentFunction, validateDocumentWebhook, validateDurableFunction, validateMediaLibraryAssetFunction, validateProject, validateRole, validateScheduledFunction, } from '@sanity/blueprints';
|
|
2
|
+
import { SANITY_ACCESS_ROLE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_DURABLE, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_PROJECT, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, } from '../../constants.js';
|
|
3
3
|
const RESOURCE_VALIDATORS = {
|
|
4
4
|
[SANITY_ACCESS_ROLE]: {
|
|
5
5
|
validate: validateRole,
|
|
@@ -25,6 +25,9 @@ const RESOURCE_VALIDATORS = {
|
|
|
25
25
|
[SANITY_PROJECT_WEBHOOK]: {
|
|
26
26
|
validate: validateDocumentWebhook,
|
|
27
27
|
},
|
|
28
|
+
[SANITY_FUNCTION_DURABLE]: {
|
|
29
|
+
validate: validateDurableFunction,
|
|
30
|
+
},
|
|
28
31
|
};
|
|
29
32
|
export function validateResources(resources) {
|
|
30
33
|
const errors = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { treeify } from 'array-treeify';
|
|
2
|
-
import { SANITY_ACCESS_ROBOT, SANITY_ACCESS_ROLE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, SANITY_PROJECT, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, } from '../../constants.js';
|
|
2
|
+
import { SANITY_ACCESS_ROBOT, SANITY_ACCESS_ROLE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_DURABLE, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PUBSUB, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, SANITY_PROJECT, SANITY_PROJECT_CORS, SANITY_PROJECT_DATASET, SANITY_PROJECT_WEBHOOK, } from '../../constants.js';
|
|
3
3
|
import { styleText } from '../style-text.js';
|
|
4
4
|
import { isCorsOriginResource, isDatasetResource, isRobotResource, isRoleResource, isWebhookResource, } from '../types.js';
|
|
5
5
|
import { formatDateTime, formatDuration } from './dates.js';
|
|
@@ -27,8 +27,10 @@ const RESOURCE_CATEGORIES = {
|
|
|
27
27
|
},
|
|
28
28
|
[SANITY_FUNCTION_DOCUMENT]: functionCategory,
|
|
29
29
|
[SANITY_FUNCTION_MEDIA_LIBRARY_ASSET]: functionCategory,
|
|
30
|
+
[SANITY_FUNCTION_PUBSUB]: functionCategory,
|
|
30
31
|
[SANITY_FUNCTION_SCHEDULED]: functionCategory,
|
|
31
32
|
[SANITY_FUNCTION_SYNC_TAG_INVALIDATE]: functionCategory,
|
|
33
|
+
[SANITY_FUNCTION_DURABLE]: functionCategory,
|
|
32
34
|
[SANITY_PROJECT]: {
|
|
33
35
|
label: 'Projects',
|
|
34
36
|
displayNameAttribute: 'displayName',
|
|
@@ -215,11 +217,13 @@ export function formatStacksListing(stacks, currentStackId) {
|
|
|
215
217
|
const IGNORED_PARAMS = {
|
|
216
218
|
[SANITY_FUNCTION_DOCUMENT]: new Set(['src']),
|
|
217
219
|
[SANITY_FUNCTION_MEDIA_LIBRARY_ASSET]: new Set(['src']),
|
|
220
|
+
[SANITY_FUNCTION_PUBSUB]: new Set(['src']),
|
|
218
221
|
[SANITY_FUNCTION_SCHEDULED]: new Set(['src']),
|
|
219
222
|
};
|
|
220
223
|
const ASSET_RESOURCE_TYPES = new Set([
|
|
221
224
|
SANITY_FUNCTION_DOCUMENT,
|
|
222
225
|
SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
|
|
226
|
+
SANITY_FUNCTION_PUBSUB,
|
|
223
227
|
SANITY_FUNCTION_SCHEDULED,
|
|
224
228
|
]);
|
|
225
229
|
function stringifyUnknown(val) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isLocalFunctionResource, isScheduleEvent } from '../../utils/types.js';
|
|
1
|
+
import { hasEventSource, isLocalFunctionResource, isScheduleEvent, } from '../../utils/types.js';
|
|
2
2
|
import { styleText } from '../style-text.js';
|
|
3
3
|
function formatLabel(label) {
|
|
4
4
|
return styleText('dim', `${label}:`);
|
|
@@ -46,7 +46,7 @@ export function arrayifyFunction(fn) {
|
|
|
46
46
|
details.push(formatLabeledValue('memory', fn.memory));
|
|
47
47
|
if (fn.timeout)
|
|
48
48
|
details.push(formatLabeledValue('timeout', fn.timeout));
|
|
49
|
-
if (fn.event) {
|
|
49
|
+
if (hasEventSource(fn) && fn.event) {
|
|
50
50
|
const eventDetails = arrayifyEvent(fn.event);
|
|
51
51
|
if (eventDetails) {
|
|
52
52
|
details.push(formatLabel('event'));
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BlueprintCorsOriginResource, type BlueprintDatasetResource, type BlueprintDocumentFunctionResource, type BlueprintDocumentWebhookResource, type BlueprintMediaLibraryAssetFunctionResource, type BlueprintProjectResource, type BlueprintResource, type BlueprintRobotResource, type BlueprintRoleResource, type BlueprintScheduledFunctionResource, type BlueprintStudioResource, type BlueprintSyncTagInvalidateFunctionResource } from '@sanity/blueprints';
|
|
1
|
+
import { type BlueprintCorsOriginResource, type BlueprintDatasetResource, type BlueprintDocumentFunctionResource, type BlueprintDocumentWebhookResource, type BlueprintDurableFunctionResource, type BlueprintMediaLibraryAssetFunctionResource, type BlueprintProjectResource, type BlueprintPubSubFunctionResource, type BlueprintResource, type BlueprintRobotResource, type BlueprintRoleResource, type BlueprintScheduledFunctionResource, type BlueprintStudioResource, type BlueprintSyncTagInvalidateFunctionResource } from '@sanity/blueprints';
|
|
2
2
|
export type ScopeType = 'organization' | 'project';
|
|
3
3
|
/** Result utility type */
|
|
4
4
|
export type Result<T, E = string> = {
|
|
@@ -15,7 +15,11 @@ export interface ActionResponse {
|
|
|
15
15
|
}
|
|
16
16
|
/** @internal */
|
|
17
17
|
export type FunctionGroqResource = BlueprintDocumentFunctionResource | BlueprintMediaLibraryAssetFunctionResource;
|
|
18
|
-
export type FunctionResource = FunctionGroqResource | BlueprintScheduledFunctionResource | BlueprintSyncTagInvalidateFunctionResource;
|
|
18
|
+
export type FunctionResource = FunctionGroqResource | BlueprintPubSubFunctionResource | BlueprintScheduledFunctionResource | BlueprintSyncTagInvalidateFunctionResource | BlueprintDurableFunctionResource;
|
|
19
|
+
/** Function resources that declare an `event`; PubSub functions are triggered by other functions */
|
|
20
|
+
export type EventFunctionResource = Exclude<FunctionResource, BlueprintPubSubFunctionResource>;
|
|
21
|
+
/** The `event` of any function resource that declares one */
|
|
22
|
+
export type FunctionResourceEvent = EventFunctionResource['event'];
|
|
19
23
|
/** Resource types that carry a deployable asset (have src: string) */
|
|
20
24
|
export type AssetResource = FunctionResource | BlueprintStudioResource;
|
|
21
25
|
/** @internal */
|
|
@@ -36,6 +40,7 @@ export interface DeployedResource extends BlueprintResourceRecord {
|
|
|
36
40
|
externalId: string;
|
|
37
41
|
}
|
|
38
42
|
export declare function isLocalFunctionResource(r: BlueprintResource): r is FunctionResource;
|
|
43
|
+
export declare function hasEventSource(r: FunctionResource): r is EventFunctionResource;
|
|
39
44
|
export declare function isScheduleEvent(e: unknown): e is BlueprintScheduledFunctionResource['event'];
|
|
40
45
|
export declare function isCorsOriginResource(r: unknown): r is BlueprintCorsOriginResource;
|
|
41
46
|
export declare function isProjectResource(r: unknown): r is BlueprintProjectResource;
|
|
@@ -43,6 +48,7 @@ export declare function isRobotResource(r: unknown): r is BlueprintRobotResource
|
|
|
43
48
|
export declare function isRoleResource(r: unknown): r is BlueprintRoleResource;
|
|
44
49
|
export declare function isDatasetResource(r: unknown): r is BlueprintDatasetResource;
|
|
45
50
|
export declare function isStudioResource(r: BlueprintResource): r is BlueprintStudioResource;
|
|
51
|
+
export declare function isDurableFunctionResource(r: unknown): r is BlueprintDurableFunctionResource;
|
|
46
52
|
export declare function isAssetResource(r: BlueprintResource): r is AssetResource;
|
|
47
53
|
export declare function isWebhookResource(r: unknown): r is BlueprintDocumentWebhookResource;
|
|
48
54
|
interface StackBase {
|
package/dist/utils/types.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { validateCorsOrigin, validateDataset, validateDocumentWebhook, validateProject, validateRobot, validateRole, } from '@sanity/blueprints';
|
|
2
|
-
import { SANITY_FUNCTION_PREFIX, SANITY_STUDIO } from '../constants.js';
|
|
1
|
+
import { validateCorsOrigin, validateDataset, validateDocumentWebhook, validateDurableFunction, validateProject, validateRobot, validateRole, } from '@sanity/blueprints';
|
|
2
|
+
import { SANITY_FUNCTION_PREFIX, SANITY_FUNCTION_PUBSUB, SANITY_STUDIO } from '../constants.js';
|
|
3
3
|
export function isLocalFunctionResource(r) {
|
|
4
4
|
return r.type.startsWith(SANITY_FUNCTION_PREFIX);
|
|
5
5
|
}
|
|
6
|
+
export function hasEventSource(r) {
|
|
7
|
+
return r.type !== SANITY_FUNCTION_PUBSUB;
|
|
8
|
+
}
|
|
6
9
|
// type narrowing with predicate functions
|
|
7
10
|
export function isScheduleEvent(e) {
|
|
8
11
|
return e !== null && typeof e === 'object' && ('hour' in e || 'expression' in e);
|
|
@@ -25,6 +28,9 @@ export function isDatasetResource(r) {
|
|
|
25
28
|
export function isStudioResource(r) {
|
|
26
29
|
return r.type === SANITY_STUDIO;
|
|
27
30
|
}
|
|
31
|
+
export function isDurableFunctionResource(r) {
|
|
32
|
+
return validateDurableFunction(r).length === 0;
|
|
33
|
+
}
|
|
28
34
|
export function isAssetResource(r) {
|
|
29
35
|
return isLocalFunctionResource(r) || isStudioResource(r);
|
|
30
36
|
}
|