@sanity/runtime-cli 14.3.0 → 14.5.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.
Files changed (47) hide show
  1. package/README.md +31 -27
  2. package/dist/actions/blueprints/assets.d.ts +3 -2
  3. package/dist/actions/blueprints/assets.js +14 -3
  4. package/dist/actions/blueprints/blueprint.js +2 -2
  5. package/dist/actions/blueprints/resources.js +2 -1
  6. package/dist/actions/functions/logs.d.ts +0 -1
  7. package/dist/actions/functions/logs.js +0 -1
  8. package/dist/commands/blueprints/add.js +2 -1
  9. package/dist/commands/blueprints/deploy.d.ts +1 -0
  10. package/dist/commands/blueprints/deploy.js +10 -0
  11. package/dist/commands/functions/add.js +2 -1
  12. package/dist/commands/functions/build.d.ts +1 -0
  13. package/dist/commands/functions/build.js +7 -0
  14. package/dist/constants.d.ts +18 -1
  15. package/dist/constants.js +26 -9
  16. package/dist/cores/blueprints/deploy.d.ts +1 -0
  17. package/dist/cores/blueprints/deploy.js +2 -1
  18. package/dist/cores/functions/add.js +13 -13
  19. package/dist/cores/functions/build.d.ts +1 -0
  20. package/dist/cores/functions/build.js +2 -1
  21. package/dist/cores/functions/logs.js +3 -5
  22. package/dist/cores/functions/test.js +1 -1
  23. package/dist/server/app.js +6 -4
  24. package/dist/server/static/components/api-base.js +1 -1
  25. package/dist/server/static/components/filters.js +1 -1
  26. package/dist/server/static/components/function-list.js +2 -2
  27. package/dist/server/static/components/payload-panel.js +1 -1
  28. package/dist/server/static/components/run-panel.js +2 -2
  29. package/dist/utils/display/blueprints-formatting.js +12 -5
  30. package/dist/utils/find-function.js +4 -3
  31. package/dist/utils/functions/detect-native-modules.d.ts +7 -1
  32. package/dist/utils/functions/detect-native-modules.js +69 -1
  33. package/dist/utils/functions/prepare-asset.d.ts +3 -1
  34. package/dist/utils/functions/prepare-asset.js +28 -3
  35. package/dist/utils/functions/resolve-dependencies.d.ts +5 -2
  36. package/dist/utils/functions/resolve-dependencies.js +19 -12
  37. package/dist/utils/invoke-local.js +1 -1
  38. package/dist/utils/pnpm.d.ts +1 -0
  39. package/dist/utils/pnpm.js +9 -0
  40. package/dist/utils/transpile/transpile-function.d.ts +4 -2
  41. package/dist/utils/transpile/transpile-function.js +5 -8
  42. package/dist/utils/types.d.ts +8 -5
  43. package/dist/utils/types.js +8 -4
  44. package/dist/utils/validate/index.js +7 -4
  45. package/dist/utils/validate/resource.js +3 -2
  46. package/oclif.manifest.json +42 -7
  47. package/package.json +5 -4
@@ -74,7 +74,7 @@ async function streamLogs({ name, externalId, auth, log, }) {
74
74
  }
75
75
  async function getLogs({ name, externalId, auth, limit, json, utc, log, }) {
76
76
  const spinner = log.ora(`Finding logs for function "${name}"`).start();
77
- const { ok, error, logs, total } = await getLogsAction(externalId, { limit }, auth, log);
77
+ const { ok, error, logs } = await getLogsAction(externalId, { limit }, auth, log);
78
78
  if (!ok) {
79
79
  spinner.fail(`${styleText('red', 'Failed')} to retrieve logs`);
80
80
  return { success: false, error: error || 'Unknown error' };
@@ -86,10 +86,8 @@ async function getLogs({ name, externalId, auth, limit, json, utc, log, }) {
86
86
  }
87
87
  spinner.succeed(`${formatTitle('Function', name)} Logs`);
88
88
  if (!json) {
89
- log(`Found ${styleText('bold', String(total))} log entries for function ${styleText('yellow', name)}`);
90
- if (logs.length < total) {
91
- log(`Here are the last ${styleText('bold', filteredLogs.length.toString())} entries`);
92
- }
89
+ const logLength = filteredLogs.length;
90
+ log(`Found ${styleText('bold', logLength.toString())} log ${logLength === 1 ? 'entry' : 'entries'} for function ${styleText('yellow', name)}`);
93
91
  log('\n');
94
92
  for (const { time, level, message } of filteredLogs) {
95
93
  log(formatLog(time, level, message, utc));
@@ -135,7 +135,7 @@ export async function functionTestCore(options) {
135
135
  after,
136
136
  }
137
137
  : {
138
- event: 'schedule',
138
+ event: 'scheduled',
139
139
  };
140
140
  const spinner = log.ora('Executing function...').start();
141
141
  const { json, logs, error } = await testAction(resource, invokeOptions, contextOptions, {
@@ -329,13 +329,13 @@ function parseInvokeRequest(body) {
329
329
  }
330
330
  const metadataEvent = metadata.event;
331
331
  if (typeof metadataEvent !== 'string' ||
332
- (!isEventType(metadataEvent) && metadataEvent !== 'schedule')) {
333
- throw new Error('Request body is not valid, `metadata.event` field is not one of `create`, `update`, `delete`, or `schedule`');
332
+ (!isEventType(metadataEvent) && metadataEvent !== 'scheduled')) {
333
+ throw new Error('Request body is not valid, `metadata.event` field is not one of `create`, `update`, `delete`, or `scheduled`');
334
334
  }
335
335
  let before = null;
336
336
  let after = null;
337
337
  // Only GROQ-based events (create, update, delete) have before and after fields
338
- if (metadataEvent !== 'schedule') {
338
+ if (metadataEvent !== 'scheduled') {
339
339
  if (!('before' in metadata)) {
340
340
  throw new Error('Request body is not valid, `metadata.before` field is missing');
341
341
  }
@@ -374,7 +374,9 @@ function parseInvokeRequest(body) {
374
374
  },
375
375
  event,
376
376
  },
377
- metadata: metadataEvent === 'schedule' ? { event: metadataEvent } : { event: metadataEvent, before, after },
377
+ metadata: metadataEvent === 'scheduled'
378
+ ? { event: metadataEvent }
379
+ : { event: metadataEvent, before, after },
378
380
  };
379
381
  }
380
382
  export { app, buildApiUrl, parseDocumentUrl };
@@ -9,6 +9,6 @@ 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_SCHEDULE = 'sanity.function.cron'
12
+ this.SANITY_FUNCTION_SCHEDULED = 'sanity.function.cron'
13
13
  }
14
14
  }
@@ -79,7 +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 scheduleFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SCHEDULE
82
+ const scheduleFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SCHEDULED
83
83
 
84
84
  const container = this.shadowRoot.querySelector('fieldset')
85
85
  container.innerHTML = this.buildFilters(docFunction, mediaFunction, scheduleFunction)
@@ -58,8 +58,8 @@ class FunctionList extends ApiBaseElement {
58
58
  return 'Document'
59
59
  case this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET:
60
60
  return 'Media Library'
61
- case this.SANITY_FUNCTION_SCHEDULE:
62
- return 'Schedule'
61
+ case this.SANITY_FUNCTION_SCHEDULED:
62
+ return 'Scheduled'
63
63
  default:
64
64
  return type.split('.').pop().replaceAll('-', ' ')
65
65
  }
@@ -79,7 +79,7 @@ class PayloadPanel extends ApiBaseElement {
79
79
  }
80
80
 
81
81
  updateCodeMirror = ({selectedFunctionType}) => {
82
- if (selectedFunctionType === this.SANITY_FUNCTION_SCHEDULE) {
82
+ if (selectedFunctionType === this.SANITY_FUNCTION_SCHEDULED) {
83
83
  this.api.store.beforePayload.dispatch({
84
84
  effects: editableCompartment.reconfigure(EditorView.editable.of(false)),
85
85
  })
@@ -25,7 +25,7 @@ class RunPanel extends ApiBaseElement {
25
25
  const docFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_DOCUMENT
26
26
  const mediaFunction =
27
27
  this.api.store.selectedFunctionType === this.SANITY_FUNCTION_MEDIA_LIBRARY_ASSET
28
- const scheduleFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SCHEDULE
28
+ const scheduleFunction = this.api.store.selectedFunctionType === this.SANITY_FUNCTION_SCHEDULED
29
29
  const docOrScheduleFunction = docFunction || scheduleFunction
30
30
 
31
31
  this.api.store.result = {logs: '', time: 0}
@@ -58,7 +58,7 @@ class RunPanel extends ApiBaseElement {
58
58
  token: this.api.store.withToken,
59
59
  },
60
60
  eventResourceType: scheduleFunction
61
- ? 'schedule'
61
+ ? 'scheduled'
62
62
  : mediaFunction
63
63
  ? 'media-library'
64
64
  : 'dataset',
@@ -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_SCHEDULE, 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_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, 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 { formatDate, formatDuration } from './dates.js';
@@ -19,7 +19,7 @@ const RESOURCE_CATEGORIES = {
19
19
  },
20
20
  },
21
21
  [SANITY_ACCESS_ROBOT]: {
22
- label: 'Robots',
22
+ label: 'Robot Tokens',
23
23
  displayNameAttribute: 'label',
24
24
  formatDetails(res) {
25
25
  return isRobotResource(res) ? arrayifyRobot(res) : [];
@@ -27,7 +27,14 @@ const RESOURCE_CATEGORIES = {
27
27
  },
28
28
  [SANITY_FUNCTION_DOCUMENT]: functionCategory,
29
29
  [SANITY_FUNCTION_MEDIA_LIBRARY_ASSET]: functionCategory,
30
- [SANITY_FUNCTION_SCHEDULE]: functionCategory,
30
+ [SANITY_FUNCTION_SCHEDULED]: functionCategory,
31
+ [SANITY_PROJECT]: {
32
+ label: 'Projects',
33
+ displayNameAttribute: 'displayName',
34
+ formatDetails(_res) {
35
+ return [];
36
+ },
37
+ },
31
38
  [SANITY_PROJECT_CORS]: {
32
39
  label: 'CORS Origins',
33
40
  formatDetails(res) {
@@ -212,12 +219,12 @@ export function formatStacksListing(stacks, currentStackId) {
212
219
  const IGNORED_PARAMS = {
213
220
  [SANITY_FUNCTION_DOCUMENT]: new Set(['src']),
214
221
  [SANITY_FUNCTION_MEDIA_LIBRARY_ASSET]: new Set(['src']),
215
- [SANITY_FUNCTION_SCHEDULE]: new Set(['src']),
222
+ [SANITY_FUNCTION_SCHEDULED]: new Set(['src']),
216
223
  };
217
224
  const ASSET_RESOURCE_TYPES = new Set([
218
225
  SANITY_FUNCTION_DOCUMENT,
219
226
  SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
220
- SANITY_FUNCTION_SCHEDULE,
227
+ SANITY_FUNCTION_SCHEDULED,
221
228
  ]);
222
229
  function stringifyUnknown(val) {
223
230
  if (val === null || val === undefined)
@@ -1,17 +1,18 @@
1
+ import { SANITY_FUNCTION_PREFIX } from '../constants.js';
1
2
  export function getFunctionNames(resources) {
2
3
  return (resources
3
- ?.filter((r) => r?.type?.startsWith('sanity.function.'))
4
+ ?.filter((r) => r?.type?.startsWith(SANITY_FUNCTION_PREFIX))
4
5
  .map((r) => r.name)
5
6
  .filter((name) => typeof name === 'string') ?? []);
6
7
  }
7
8
  export function findFunctionInBlueprint(blueprint, name) {
8
- const func = blueprint?.resources?.find((r) => r?.type?.startsWith('sanity.function.') && r.name === name);
9
+ const func = blueprint?.resources?.find((r) => r?.type?.startsWith(SANITY_FUNCTION_PREFIX) && r.name === name);
9
10
  if (!func)
10
11
  throw Error(`Unable to find function ${name}`);
11
12
  return func;
12
13
  }
13
14
  export function findFunctionInStack(stack, name) {
14
- const func = stack?.resources?.find((r) => r?.type?.startsWith('sanity.function.') && r.name === name);
15
+ const func = stack?.resources?.find((r) => r?.type?.startsWith(SANITY_FUNCTION_PREFIX) && r.name === name);
15
16
  if (!func)
16
17
  throw Error(`Unable to find function: "${name}"`);
17
18
  if (!isDeployedResource(func))
@@ -1,6 +1,12 @@
1
1
  import type AdmZip from 'adm-zip';
2
+ import type { FunctionResource } from '../types.js';
2
3
  /**
3
- * Scan zip entries of dependencies for native modules.
4
+ * Recurse node_modules for native modules.
4
5
  * Native modules built on one platform may not work on another (e.g., macOS vs Linux)
5
6
  */
6
7
  export declare const detectNativeModules: (zip: AdmZip) => string[];
8
+ /**
9
+ * Finds the dependencies of the function and compares to the workspace root to see if any native modules are included.
10
+ * Due to how PNPM with workspaces symlinks dependencies, we have to check the workspace root for native modules prior to the bundle step.
11
+ */
12
+ export declare const detectNativeModulesForBundle: (resource: FunctionResource) => string[];
@@ -1,9 +1,13 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { cwd } from 'node:process';
4
+ import { createPnpmRequire } from '../pnpm.js';
1
5
  /**
2
6
  * Patterns to identify native modules based on common file types and build configurations.
3
7
  */
4
8
  const KNOWN_MODULE_PATTERNS = [/binding\.gyp$/, /\.node$/];
5
9
  /**
6
- * Scan zip entries of dependencies for native modules.
10
+ * Recurse node_modules for native modules.
7
11
  * Native modules built on one platform may not work on another (e.g., macOS vs Linux)
8
12
  */
9
13
  export const detectNativeModules = (zip) => {
@@ -24,3 +28,67 @@ export const detectNativeModules = (zip) => {
24
28
  throw new Error(`Failed to scan zip for native modules: ${message}`);
25
29
  }
26
30
  };
31
+ /**
32
+ * Recursively checks a directory for files that match known native module patterns.
33
+ */
34
+ const hasNativeFiles = (directory) => {
35
+ for (const name of readdirSync(directory)) {
36
+ // don't recurse back into node_modules.
37
+ if (name === 'node_modules')
38
+ continue;
39
+ const fullPath = path.join(directory, name);
40
+ if (statSync(fullPath).isDirectory()) {
41
+ if (hasNativeFiles(fullPath))
42
+ return true;
43
+ }
44
+ else if (KNOWN_MODULE_PATTERNS.some((pattern) => pattern.test(name))) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ };
50
+ /**
51
+ * Finds the dependencies of the function and compares to the workspace root to see if any native modules are included.
52
+ * Due to how PNPM with workspaces symlinks dependencies, we have to check the workspace root for native modules prior to the bundle step.
53
+ */
54
+ export const detectNativeModulesForBundle = (resource) => {
55
+ if (!resource.src)
56
+ throw new Error('Resource src is required');
57
+ const hasWorkspace = existsSync(path.join(cwd(), 'pnpm-workspace.yaml'));
58
+ if (!hasWorkspace) {
59
+ return [];
60
+ }
61
+ const sourcePath = path.resolve(cwd(), resource.src);
62
+ const stats = statSync(sourcePath);
63
+ const entryDir = stats.isFile() ? path.dirname(sourcePath) : sourcePath;
64
+ const hasPackageJson = existsSync(path.join(entryDir, 'package.json'));
65
+ if (!hasPackageJson) {
66
+ throw new Error('pnpm workspace detected but no package.json found in function src');
67
+ }
68
+ // gets package.json dependencies of the function. We use these function deps to compare against the workspace root modules.
69
+ const pkgJsonPath = path.join(entryDir, 'package.json');
70
+ // a function should always have deps, but this will just be a little safer.
71
+ const functionDeps = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')).dependencies ?? {};
72
+ const workspaceModules = createPnpmRequire(cwd());
73
+ const nativeModules = [];
74
+ for (const depName of Object.keys(functionDeps)) {
75
+ try {
76
+ const resolved = workspaceModules.resolve(depName);
77
+ let currentDir = path.dirname(resolved);
78
+ while (currentDir !== cwd() && currentDir !== path.parse(currentDir).root) {
79
+ // look for node_module package.json to locate top level
80
+ if (existsSync(path.join(currentDir, 'package.json'))) {
81
+ if (hasNativeFiles(currentDir)) {
82
+ nativeModules.push(depName);
83
+ break;
84
+ }
85
+ }
86
+ currentDir = path.dirname(currentDir);
87
+ }
88
+ }
89
+ catch (_e) {
90
+ // If we fail to resolve, just skip
91
+ }
92
+ }
93
+ return nativeModules;
94
+ };
@@ -1,6 +1,8 @@
1
- import type { CollectionFunction, FunctionResource } from '../types.js';
1
+ import type { CollectionFunction, FunctionResource, InstallerType } from '../types.js';
2
2
  export declare function prepareAsset({ resource, }: {
3
3
  resource: FunctionResource | CollectionFunction;
4
+ }, { installer }?: {
5
+ installer?: InstallerType;
4
6
  }): Promise<{
5
7
  success: boolean;
6
8
  outputPath?: string;
@@ -1,12 +1,14 @@
1
+ import { rm, stat } from 'node:fs/promises';
1
2
  import path from 'node:path';
2
3
  import { cwd } from 'node:process';
3
4
  import { MAX_ASSET_SIZE } from '../../constants.js';
4
5
  import { transpileFunction } from '../transpile/transpile-function.js';
6
+ import { detectNativeModulesForBundle } from './detect-native-modules.js';
5
7
  import { getFolderSize } from './getFolderSize.js';
6
8
  import { resolveResourceDependencies } from './resolve-dependencies.js';
7
9
  import { shouldAutoResolveDependencies } from './should-auto-resolve-deps.js';
8
10
  import { shouldTranspileFunction } from './should-transpile.js';
9
- export async function prepareAsset({ resource, }) {
11
+ export async function prepareAsset({ resource, }, { installer } = {}) {
10
12
  if (!resource.src)
11
13
  throw new Error('Resource src is required');
12
14
  let functionPath = path.join(cwd(), resource.src);
@@ -15,7 +17,13 @@ export async function prepareAsset({ resource, }) {
15
17
  const shouldTranspile = await shouldTranspileFunction(resource);
16
18
  if (shouldTranspile) {
17
19
  try {
18
- const result = await transpileFunction(resource);
20
+ const hasNativeModules = detectNativeModulesForBundle(resource);
21
+ if (hasNativeModules.length) {
22
+ const errorMsg = `Native modules detected:\n${hasNativeModules.join('\n')}\n\n` +
23
+ `Please replace with JavaScript alternatives.`;
24
+ throw new Error(errorMsg);
25
+ }
26
+ const result = await transpileFunction(resource, { installer });
19
27
  functionPath = result.outputDir;
20
28
  cleanup = result.cleanup;
21
29
  wasBundled = result.bundled;
@@ -26,7 +34,8 @@ export async function prepareAsset({ resource, }) {
26
34
  }
27
35
  const shouldResolveDependencies = await shouldAutoResolveDependencies(resource);
28
36
  if (shouldResolveDependencies && !wasBundled) {
29
- await resolveResourceDependencies(resource, shouldTranspile);
37
+ await resolveResourceDependencies(resource, { transpiled: shouldTranspile, installer });
38
+ await removeArcAutoInstallPackageJson(functionPath);
30
39
  }
31
40
  try {
32
41
  const size = getFolderSize(functionPath);
@@ -39,3 +48,19 @@ export async function prepareAsset({ resource, }) {
39
48
  return { success: false, error: err instanceof Error ? err.message : `${err}` };
40
49
  }
41
50
  }
51
+ /**
52
+ * Remove file that contains the current date to prevent the hash from changing in the final asset.
53
+ * @param functionPath The path to the built function
54
+ */
55
+ async function removeArcAutoInstallPackageJson(functionPath) {
56
+ const arcAutoInstallPackageJson = path.join(functionPath, 'node_modules', '_arc-autoinstall', 'package.json');
57
+ try {
58
+ const fileStat = await stat(arcAutoInstallPackageJson);
59
+ if (fileStat.isFile()) {
60
+ await rm(arcAutoInstallPackageJson);
61
+ }
62
+ }
63
+ catch {
64
+ // ignore errors
65
+ }
66
+ }
@@ -1,2 +1,5 @@
1
- import type { FunctionResource } from '../types.js';
2
- export declare function resolveResourceDependencies(resource: FunctionResource, transpiled: boolean): Promise<void>;
1
+ import type { FunctionResource, InstallerType } from '../types.js';
2
+ export declare function resolveResourceDependencies(resource: FunctionResource, { transpiled, installer, }: {
3
+ transpiled: boolean;
4
+ installer?: InstallerType;
5
+ }): Promise<void>;
@@ -3,25 +3,22 @@ import { join } from 'node:path';
3
3
  import hydrate from '@architect/hydrate';
4
4
  import inventory from '@architect/inventory';
5
5
  import { convertResourceToArcFormat } from './resource-to-arc.js';
6
- export async function resolveResourceDependencies(resource, transpiled) {
6
+ export async function resolveResourceDependencies(resource, { transpiled, installer, }) {
7
7
  const rawArc = await convertResourceToArcFormat(resource, transpiled);
8
8
  const inv = await inventory({ rawArc });
9
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;
10
+ let installType = 'npm';
11
+ if (installer) {
12
+ installType = installer;
13
+ }
14
+ else if (existsSync(join(cwd, 'pnpm-lock.yaml'))) {
15
+ installType = 'pnpm';
19
16
  }
20
17
  else if (existsSync(join(cwd, 'yarn.lock'))) {
21
- installOptions.yarn = true;
18
+ installType = 'yarn';
22
19
  }
23
20
  try {
24
- await hydrate.install(installOptions);
21
+ await hydrate.install(toInstallerOptions(inv, installType));
25
22
  }
26
23
  catch (err) {
27
24
  // This is a temporary fix.
@@ -32,3 +29,13 @@ export async function resolveResourceDependencies(resource, transpiled) {
32
29
  }
33
30
  }
34
31
  }
32
+ function toInstallerOptions(inv, type) {
33
+ return {
34
+ inventory: inv,
35
+ hydrateShared: false,
36
+ quiet: true,
37
+ npm: type === 'npm',
38
+ pnpm: type === 'pnpm',
39
+ yarn: type === 'yarn',
40
+ };
41
+ }
@@ -84,7 +84,7 @@ export default async function invoke(resource, payload, context, options) {
84
84
  }
85
85
  const shouldResolveDependencies = await shouldAutoResolveDependencies(resource);
86
86
  if (shouldResolveDependencies && existingPackageJson) {
87
- await resolveResourceDependencies(resource, shouldTranspile);
87
+ await resolveResourceDependencies(resource, { transpiled: shouldTranspile });
88
88
  }
89
89
  if (!existingPackageJson) {
90
90
  createTempPackageJson(functionPath);
@@ -0,0 +1 @@
1
+ export declare const createPnpmRequire: (workspaceRoot: string) => NodeJS.Require;
@@ -0,0 +1,9 @@
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+ export const createPnpmRequire = (workspaceRoot) => {
4
+ const pnpmModulesPath = path.join(workspaceRoot, 'node_modules', '.pnpm', 'node_modules');
5
+ // createRequire gives us Node's full module resolution algorithm rooted at
6
+ // the pnpm virtual store — it reads package.json exports/main and returns
7
+ // the absolute path to the actual entry file, not just the directory.
8
+ return createRequire(path.join(pnpmModulesPath, '_virtual.js'));
9
+ };
@@ -1,5 +1,7 @@
1
- import type { FunctionResource } from '../types.js';
2
- export declare function transpileFunction(resource: FunctionResource): Promise<{
1
+ import type { FunctionResource, InstallerType } from '../types.js';
2
+ export declare function transpileFunction(resource: FunctionResource, { installer }?: {
3
+ installer?: InstallerType;
4
+ }): Promise<{
3
5
  type: string;
4
6
  outputDir: string;
5
7
  warnings: string[];
@@ -1,6 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
- import { createRequire } from 'node:module';
4
3
  import path from 'node:path';
5
4
  import { performance } from 'node:perf_hooks';
6
5
  import { cwd } from 'node:process';
@@ -8,9 +7,10 @@ import * as find from 'empathic/find';
8
7
  import { build as viteBuild } from 'vite';
9
8
  import tsConfigPaths from 'vite-tsconfig-paths';
10
9
  import { findFunctionEntryPoint } from '../functions/find-entry-point.js';
10
+ import { createPnpmRequire } from '../pnpm.js';
11
11
  import { cleanupSourceMaps } from './cleanup-source-maps.js';
12
12
  import { verifyHandler } from './verify-handler.js';
13
- export async function transpileFunction(resource) {
13
+ export async function transpileFunction(resource, { installer } = {}) {
14
14
  if (!resource.src)
15
15
  throw new Error('Resource src is required');
16
16
  if (!resource.name)
@@ -35,7 +35,8 @@ export async function transpileFunction(resource) {
35
35
  }
36
36
  try {
37
37
  const viteStart = performance.now();
38
- const bundle = existsSync(path.join(cwd(), 'pnpm-workspace.yaml'));
38
+ // Always bundle the code if pnpm usage is detected or specified by the user
39
+ const bundle = existsSync(path.join(cwd(), 'pnpm-workspace.yaml')) || installer === 'pnpm';
39
40
  const result = await viteBuild({
40
41
  root: fnRootDir,
41
42
  logLevel: 'silent',
@@ -140,11 +141,7 @@ function logCleanupFailure(err) {
140
141
  * which isn't reachable via standard Node resolution.
141
142
  */
142
143
  function pnpmResolvePlugin(workspaceRoot) {
143
- const pnpmModulesPath = path.join(workspaceRoot, 'node_modules', '.pnpm', 'node_modules');
144
- // createRequire gives us Node's full module resolution algorithm rooted at
145
- // the pnpm virtual store — it reads package.json exports/main and returns
146
- // the absolute path to the actual entry file, not just the directory.
147
- const pnpmRequire = createRequire(path.join(pnpmModulesPath, '_virtual.js'));
144
+ const pnpmRequire = createPnpmRequire(workspaceRoot);
148
145
  return {
149
146
  name: 'pnpm-resolve',
150
147
  async resolveId(source, importer, options) {
@@ -1,6 +1,6 @@
1
- import { type BlueprintCorsOriginResource, type BlueprintDatasetResource, type BlueprintDocumentWebhookResource, type BlueprintResource, type BlueprintRobotResource, type BlueprintRoleResource } from '@sanity/blueprints';
1
+ import { type BlueprintCorsOriginResource, type BlueprintDatasetResource, type BlueprintDocumentWebhookResource, type BlueprintProjectResource, type BlueprintResource, type BlueprintRobotResource, type BlueprintRoleResource } from '@sanity/blueprints';
2
2
  import type { Blueprint } from '@sanity/blueprints-parser';
3
- import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULE } from '../constants.js';
3
+ import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED } from '../constants.js';
4
4
  export type ScopeType = 'organization' | 'project';
5
5
  /** Result utility type */
6
6
  export type Result<T, E = string> = {
@@ -65,6 +65,7 @@ export declare function isScheduleFunctionResource<T extends BlueprintResource>(
65
65
  export declare function isLocalFunctionCollection<T extends BlueprintResource>(r: T): r is T & FunctionsCollection;
66
66
  export declare function isScheduleEvent(e: unknown): e is FunctionResourceScheduleEvent;
67
67
  export declare function isCorsOriginResource(r: unknown): r is BlueprintCorsOriginResource;
68
+ export declare function isProjectResource(r: unknown): r is BlueprintProjectResource;
68
69
  export declare function isRobotResource(r: unknown): r is BlueprintRobotResource;
69
70
  export declare function isRoleResource(r: unknown): r is BlueprintRoleResource;
70
71
  export declare function isDatasetResource(r: unknown): r is BlueprintDatasetResource;
@@ -91,11 +92,11 @@ interface FunctionResourceMediaLibraryAsset extends FunctionResourceBase {
91
92
  event: GroqRuleMediaLibraryFunction;
92
93
  }
93
94
  interface FunctionResourceSchedule extends FunctionResourceBase {
94
- type: typeof SANITY_FUNCTION_SCHEDULE;
95
+ type: typeof SANITY_FUNCTION_SCHEDULED;
95
96
  event: FunctionResourceScheduleEvent;
96
97
  }
97
98
  export interface CollectionFunction {
98
- type: typeof SANITY_FUNCTION_DOCUMENT | typeof SANITY_FUNCTION_SCHEDULE | typeof SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
99
+ type: typeof SANITY_FUNCTION_DOCUMENT | typeof SANITY_FUNCTION_SCHEDULED | typeof SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
99
100
  src: string;
100
101
  name: string;
101
102
  displayName?: string;
@@ -159,7 +160,7 @@ export interface InvokeGroqPayloadOptions {
159
160
  }
160
161
  export interface InvokeSchedulePayloadOptions {
161
162
  payload?: Record<string, unknown>;
162
- event: 'schedule';
163
+ event: 'scheduled';
163
164
  }
164
165
  export type InvokePayloadOptions = InvokeGroqPayloadOptions | InvokeSchedulePayloadOptions;
165
166
  export type InvokePayloadMetadata = Pick<InvokeGroqPayloadOptions, 'event' | 'before' | 'after'> | Pick<InvokeSchedulePayloadOptions, 'event'>;
@@ -254,4 +255,6 @@ export declare interface FetchConfig {
254
255
  apiHost?: string;
255
256
  apiVersion?: string;
256
257
  }
258
+ export declare const INSTALLER_OPTIONS: readonly ["npm", "pnpm", "yarn"];
259
+ export type InstallerType = 'npm' | 'pnpm' | 'yarn';
257
260
  export {};
@@ -1,8 +1,8 @@
1
- import { validateCorsOrigin, validateDataset, validateDocumentWebhook, validateRobot, validateRole, } from '@sanity/blueprints';
2
- import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULE, } from '../constants.js';
1
+ import { validateCorsOrigin, validateDataset, validateDocumentWebhook, validateProject, validateRobot, validateRole, } from '@sanity/blueprints';
2
+ import { SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_PREFIX, SANITY_FUNCTION_SCHEDULED, } from '../constants.js';
3
3
  // type narrowing with predicate functions
4
4
  export function isLocalFunctionResource(r) {
5
- return r.type.startsWith('sanity.function.');
5
+ return r.type.startsWith(SANITY_FUNCTION_PREFIX);
6
6
  }
7
7
  export function isDocumentFunctionResource(r) {
8
8
  return r.type === SANITY_FUNCTION_DOCUMENT;
@@ -11,7 +11,7 @@ export function isMediaLibraryAssetFunctionResource(r) {
11
11
  return r.type === SANITY_FUNCTION_MEDIA_LIBRARY_ASSET;
12
12
  }
13
13
  export function isScheduleFunctionResource(r) {
14
- return r.type === SANITY_FUNCTION_SCHEDULE;
14
+ return r.type === SANITY_FUNCTION_SCHEDULED;
15
15
  }
16
16
  export function isLocalFunctionCollection(r) {
17
17
  return r.type === 'sanity.experimental.functions-collection';
@@ -22,6 +22,9 @@ export function isScheduleEvent(e) {
22
22
  export function isCorsOriginResource(r) {
23
23
  return validateCorsOrigin(r).length === 0;
24
24
  }
25
+ export function isProjectResource(r) {
26
+ return validateProject(r).length === 0;
27
+ }
25
28
  export function isRobotResource(r) {
26
29
  return validateRobot(r).length === 0;
27
30
  }
@@ -54,3 +57,4 @@ export var BlueprintParserErrorType;
54
57
  BlueprintParserErrorType["InvalidInput"] = "invalid_input";
55
58
  BlueprintParserErrorType["MissingParameter"] = "missing_parameter";
56
59
  })(BlueprintParserErrorType || (BlueprintParserErrorType = {}));
60
+ export const INSTALLER_OPTIONS = ['npm', 'pnpm', 'yarn'];
@@ -1,5 +1,5 @@
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
+ 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';
3
3
  export * as validate from './resource.js';
4
4
  const RESOURCE_VALIDATORS = {
5
5
  [SANITY_ACCESS_ROLE]: {
@@ -11,8 +11,11 @@ const RESOURCE_VALIDATORS = {
11
11
  [SANITY_FUNCTION_MEDIA_LIBRARY_ASSET]: {
12
12
  validate: validateMediaLibraryAssetFunction,
13
13
  },
14
- [SANITY_FUNCTION_SCHEDULE]: {
15
- validate: validateScheduleFunction,
14
+ [SANITY_FUNCTION_SCHEDULED]: {
15
+ validate: validateScheduledFunction,
16
+ },
17
+ [SANITY_PROJECT]: {
18
+ validate: validateProject,
16
19
  },
17
20
  [SANITY_PROJECT_CORS]: {
18
21
  validate: validateCorsOrigin,
@@ -1,3 +1,4 @@
1
+ import { SANITY_FUNCTION_PREFIX } from '../../constants.js';
1
2
  import { BlueprintParserErrorType, isDocumentFunctionResource, isMediaLibraryAssetFunctionResource, isScheduleEvent, } from '../types.js';
2
3
  export function validateFunctionName(name) {
3
4
  // must be 3+ characters, no special characters, no spaces, allow _ and -
@@ -14,9 +15,9 @@ export function validateFunctionResource(resource) {
14
15
  type: BlueprintParserErrorType.InvalidProperty,
15
16
  });
16
17
  }
17
- if (!resource.type.startsWith('sanity.function.')) {
18
+ if (!resource.type.startsWith(SANITY_FUNCTION_PREFIX)) {
18
19
  errors.push({
19
- message: `${msgPrefix} Resource type must start with "sanity.function."`,
20
+ message: `${msgPrefix} Resource type must start with "${SANITY_FUNCTION_PREFIX}"`,
20
21
  type: BlueprintParserErrorType.InvalidType,
21
22
  });
22
23
  }