@sanity/runtime-cli 17.12.1 → 17.13.0-next.g06897b0

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 (40) hide show
  1. package/README.md +21 -21
  2. package/dist/actions/blueprints/assets.d.ts +2 -0
  3. package/dist/actions/blueprints/assets.js +4 -3
  4. package/dist/actions/blueprints/pre-deploy.d.ts +6 -3
  5. package/dist/actions/blueprints/pre-deploy.js +12 -1
  6. package/dist/actions/blueprints/process-assets.d.ts +1 -0
  7. package/dist/actions/blueprints/process-assets.js +1 -0
  8. package/dist/commands/blueprints/deploy.js +2 -0
  9. package/dist/commands/functions/dev.js +1 -1
  10. package/dist/config.d.ts +1 -0
  11. package/dist/config.js +6 -0
  12. package/dist/constants.d.ts +1 -0
  13. package/dist/constants.js +1 -0
  14. package/dist/cores/blueprints/config.js +1 -1
  15. package/dist/cores/blueprints/deploy.d.ts +2 -0
  16. package/dist/cores/blueprints/deploy.js +10 -1
  17. package/dist/cores/blueprints/doctor.js +1 -1
  18. package/dist/cores/blueprints/mint-deploy-token.js +2 -1
  19. package/dist/server/app.d.ts +2 -1
  20. package/dist/server/app.js +4 -1
  21. package/dist/server/static/vendor/vendor.bundle.js +21791 -25197
  22. package/dist/server/static/web-socket.js +4 -2
  23. package/dist/utils/blueprints/deploy-metadata.d.ts +10 -0
  24. package/dist/utils/blueprints/deploy-metadata.js +17 -0
  25. package/dist/utils/display/blueprints-formatting.d.ts +1 -0
  26. package/dist/utils/display/blueprints-formatting.js +17 -3
  27. package/dist/utils/display/dates.d.ts +0 -2
  28. package/dist/utils/display/dates.js +0 -25
  29. package/dist/utils/display/functions-formatting.js +1 -1
  30. package/dist/utils/display/prompt.js +5 -2
  31. package/dist/utils/display/resources-formatting.d.ts +6 -0
  32. package/dist/utils/display/resources-formatting.js +17 -0
  33. package/dist/utils/functions/dependency-info.d.ts +30 -0
  34. package/dist/utils/functions/dependency-info.js +57 -0
  35. package/dist/utils/functions/prepare-asset.js +17 -18
  36. package/dist/utils/studios/prepare-asset.js +31 -6
  37. package/dist/utils/types.d.ts +17 -0
  38. package/oclif.manifest.json +2 -2
  39. package/package.json +16 -18
  40. package/skills/functions-dev.md +1 -1
@@ -2,8 +2,10 @@ import apiConstructor from '../api.js'
2
2
 
3
3
  const api = apiConstructor()
4
4
 
5
- // Create WebSocket connection.
6
- const socket = new WebSocket('ws://localhost:8974')
5
+ // Create WebSocket connection on the same origin the emulator is served from,
6
+ // so it follows the --host and --port the server was started with.
7
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
8
+ const socket = new WebSocket(`${protocol}//${window.location.host}`)
7
9
 
8
10
  // Connection opened
9
11
  socket.addEventListener('open', () => {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Facts about the environment that runs a deploy.
3
+ * Sent as the `client` part of deploy metadata. Keys with an `undefined` value drop out of the JSON body.
4
+ */
5
+ export declare function getClientMetadata({ cliVersion, dir, }: {
6
+ /** Version of the CLI that hosts this plugin, when known */
7
+ cliVersion?: string;
8
+ /** Directory of the Blueprint file, used to detect the package manager */
9
+ dir: string;
10
+ }): Record<string, unknown>;
@@ -0,0 +1,17 @@
1
+ import { platform, versions } from 'node:process';
2
+ import config, { RUNTIME_CLI_VERSION } from '../../config.js';
3
+ import { detectPackageManager } from '../package-manager.js';
4
+ /**
5
+ * Facts about the environment that runs a deploy.
6
+ * Sent as the `client` part of deploy metadata. Keys with an `undefined` value drop out of the JSON body.
7
+ */
8
+ export function getClientMetadata({ cliVersion, dir, }) {
9
+ return {
10
+ cliVersion,
11
+ runtimeCliVersion: RUNTIME_CLI_VERSION,
12
+ nodeVersion: versions.node,
13
+ nodePackageManager: detectPackageManager(dir),
14
+ platform,
15
+ ci: config.isCi,
16
+ };
17
+ }
@@ -4,6 +4,7 @@ import type { DeploymentPlan } from '../../actions/blueprints/stacks.js';
4
4
  import { type BlueprintResourceRecord, type Stack, type StackSummary } from '../types.js';
5
5
  export declare function formatTitle(title: string, name: string): string;
6
6
  export declare function formatDeployedResourceTree(resources: BlueprintResourceRecord[] | undefined, verbose?: boolean): string;
7
+ export declare function formatStudioUrls(resources: BlueprintResourceRecord[] | undefined): string[];
7
8
  export declare function formatResourceTree(resources: BlueprintResource[] | undefined, verbose?: boolean): string;
8
9
  export declare function formatStackInfo(stack: Stack | StackSummary | Blueprint, isCurrentStack?: boolean): string;
9
10
  export declare function formatStacksListing(stacks: StackSummary[], currentStackId?: string): string;
@@ -1,10 +1,10 @@
1
+ import { formatDateTime, formatElapsed } from '@sanity/cli-core/dates';
1
2
  import { treeify } from 'array-treeify';
2
3
  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, SANITY_STUDIO, } from '../../constants.js';
3
4
  import { styleText } from '../style-text.js';
4
5
  import { isCorsOriginResource, isDatasetResource, isRobotResource, isRoleResource, isStudioResource, isWebhookResource, } from '../types.js';
5
- import { formatDateTime, formatDuration } from './dates.js';
6
6
  import { capitalize, niceId, renderSection } from './presenters.js';
7
- import { arrayifyCors, arrayifyDataset, arrayifyFunction, arrayifyRobot, arrayifyRole, arrayifyStudio, arrayifyWebhook, } from './resources-formatting.js';
7
+ import { arrayifyCors, arrayifyDataset, arrayifyFunction, arrayifyRobot, arrayifyRole, arrayifyStudio, arrayifyWebhook, studioHost, studioUrl, } from './resources-formatting.js';
8
8
  const functionCategory = {
9
9
  label: 'Functions',
10
10
  displayNameAttribute: 'displayName',
@@ -165,6 +165,19 @@ export function formatDeployedResourceTree(resources, verbose = false) {
165
165
  (res) => ({ name: res.name, type: res.type, ...(res.resolvedParameters ?? res.parameters) }), verbose);
166
166
  return `${treeify(output)}\n`;
167
167
  }
168
+ export function formatStudioUrls(resources) {
169
+ if (!resources)
170
+ return [];
171
+ const lines = [];
172
+ for (const res of resources) {
173
+ if (res.type !== SANITY_STUDIO)
174
+ continue;
175
+ const host = studioHost(res.resolvedParameters ?? res.parameters);
176
+ if (host)
177
+ lines.push(`Studio ${styleText(['bold', 'green'], res.name)} ${studioUrl(host)}`);
178
+ }
179
+ return lines;
180
+ }
168
181
  export function formatResourceTree(resources, verbose = false) {
169
182
  if (!resources || resources.length === 0)
170
183
  return ' Zero resources';
@@ -207,7 +220,8 @@ export function formatStackInfo(stack, isCurrentStack = false) {
207
220
  if (time)
208
221
  parts.push(styleText('dim', formatDateTime(time)));
209
222
  if (op.completedAt && op.createdAt) {
210
- parts.push(styleText('yellow', `(${formatDuration(op.createdAt, op.completedAt)})`));
223
+ const duration = new Date(op.completedAt).getTime() - new Date(op.createdAt).getTime();
224
+ parts.push(styleText('yellow', `(${formatElapsed(duration)})`));
211
225
  }
212
226
  rows.push(['Operation', parts.join(' ')]);
213
227
  if (op.userMessage)
@@ -1,6 +1,4 @@
1
1
  type DateInput = string | number | Date;
2
2
  export declare function formatDate(input: DateInput): string;
3
3
  export declare function formatTime(input: DateInput): string;
4
- export declare function formatDateTime(input: DateInput): string;
5
- export declare function formatDuration(startDate: string, endDate: string): string;
6
4
  export {};
@@ -19,28 +19,3 @@ export function formatTime(input) {
19
19
  const s = String(d.getSeconds()).padStart(2, '0');
20
20
  return `${h}:${m}:${s}`;
21
21
  }
22
- export function formatDateTime(input) {
23
- const d = toDate(input);
24
- if (Number.isNaN(d.getTime()))
25
- return '';
26
- return `${formatDate(d)} ${formatTime(d)}`;
27
- }
28
- export function formatDuration(startDate, endDate) {
29
- const start = new Date(startDate).getTime();
30
- const end = new Date(endDate).getTime();
31
- if (start > end)
32
- return 'Invalid duration';
33
- const durationMs = end - start;
34
- if (durationMs < 1000)
35
- return `${durationMs}ms`;
36
- if (durationMs < 60000)
37
- return `${Math.round(durationMs / 1000)}s`;
38
- if (durationMs < 3600000) {
39
- const minutes = Math.floor(durationMs / 60000);
40
- const seconds = Math.floor((durationMs % 60000) / 1000);
41
- return `${minutes}m ${seconds}s`;
42
- }
43
- const hours = Math.floor(durationMs / 3600000);
44
- const minutes = Math.floor((durationMs % 3600000) / 60000);
45
- return `${hours}h ${minutes}m`;
46
- }
@@ -1,6 +1,6 @@
1
+ import { formatDateTime } from '@sanity/cli-core/dates';
1
2
  import { SANITY_FUNCTION_PREFIX } from '../../constants.js';
2
3
  import { styleText } from '../style-text.js';
3
- import { formatDateTime } from './dates.js';
4
4
  import { niceId, renderSection } from './presenters.js';
5
5
  export function formatFunctionsList(functions, options = {}) {
6
6
  if (!options.verbose)
@@ -1,10 +1,13 @@
1
1
  import { confirm, input, Separator, select } from '@inquirer/prompts';
2
2
  import { createEmptyStack, listStacks } from '../../actions/blueprints/stacks.js';
3
3
  import { groupProjectsByOrganization } from '../../actions/sanity/projects.js';
4
+ import { STACKS_DOCS_URL } from '../../constants.js';
4
5
  import { styleText } from '../style-text.js';
5
6
  import { niceId } from './presenters.js';
6
7
  // --- Wizard infrastructure ---
7
8
  export const BACK = Symbol('back');
9
+ /** Shown under the highlighted Stack choice; a printed line would break the wizard's two-line erase on Back */
10
+ const STACK_DEFINITION = `A Stack is the deployed copy of a Blueprint: the live resources on Sanity's side. ${STACKS_DOCS_URL}`;
8
11
  /** Thrown when the user picks "Cancel" in a prompt. Cores translate it to a clean result. */
9
12
  export class CancelledError extends Error {
10
13
  constructor(message = 'Cancelled by user.') {
@@ -270,7 +273,7 @@ export async function promptForStack({ scopeType, scopeId, token, logger, allowB
270
273
  if (stacks.length > 0) {
271
274
  const stackChoices = [
272
275
  new Separator(styleText('underline', 'Create a new Stack:')),
273
- { name: styleText('bold', 'New Stack'), value: NEW_STACK_ID },
276
+ { name: styleText('bold', 'New Stack'), value: NEW_STACK_ID, description: STACK_DEFINITION },
274
277
  new Separator(styleText('underline', 'Use an existing Stack:')),
275
278
  ...stacks.map((s) => ({
276
279
  name: `"${s.name}" ${niceId(s.id)} ${styleText('dim', `(${s.resourceCount} ${s.resourceCount === 1 ? 'resource' : 'resources'})`)}`,
@@ -301,7 +304,7 @@ export async function promptForStack({ scopeType, scopeId, token, logger, allowB
301
304
  {
302
305
  name: 'Create the first Stack',
303
306
  value: NEW_STACK_ID,
304
- description: 'A Blueprint needs a Stack before it can deploy',
307
+ description: STACK_DEFINITION,
305
308
  },
306
309
  { name: 'Cancel', value: CANCEL, description: 'Stop here and change nothing' },
307
310
  ], allowBack),
@@ -5,5 +5,11 @@ export declare function arrayifyDataset(resource: BlueprintDatasetResource): Tre
5
5
  export declare function arrayifyFunction(fn: BlueprintResource): TreeInput;
6
6
  export declare function arrayifyRobot(resource: BlueprintRobotResource): TreeInput;
7
7
  export declare function arrayifyRole(resource: BlueprintRoleResource): TreeInput;
8
+ /**
9
+ * Hostname label of a Studio: the resolved `appHost` when present, else the authored `slug`.
10
+ * undefined when a `$.resources` reference
11
+ */
12
+ export declare function studioHost(params: Record<string, unknown>): string | undefined;
13
+ export declare function studioUrl(host: string): string;
8
14
  export declare function arrayifyStudio(resource: BlueprintStudioResource): TreeInput;
9
15
  export declare function arrayifyWebhook(resource: BlueprintDocumentWebhookResource): TreeInput;
@@ -1,3 +1,4 @@
1
+ import config from '../../config.js';
1
2
  import { hasEventSource, isLocalFunctionResource, isScheduleEvent, } from '../../utils/types.js';
2
3
  import { styleText } from '../style-text.js';
3
4
  import { niceId } from './presenters.js';
@@ -120,8 +121,24 @@ export function arrayifyRole(resource) {
120
121
  }
121
122
  return details;
122
123
  }
124
+ /**
125
+ * Hostname label of a Studio: the resolved `appHost` when present, else the authored `slug`.
126
+ * undefined when a `$.resources` reference
127
+ */
128
+ export function studioHost(params) {
129
+ const host = params.appHost ?? params.slug;
130
+ if (typeof host !== 'string' || host.length === 0 || host.startsWith('$.'))
131
+ return undefined;
132
+ return host;
133
+ }
134
+ export function studioUrl(host) {
135
+ return `https://${host}.${config.studioDomain}`;
136
+ }
123
137
  export function arrayifyStudio(resource) {
124
138
  const details = [formatLabeledValue('appHost', resource.slug)];
139
+ const host = studioHost(resource);
140
+ if (host)
141
+ details.push(formatLabeledValue('url', studioUrl(host)));
125
142
  if (resource.project)
126
143
  details.push(formatLabeledValue('project', niceId(resource.project)));
127
144
  return details;
@@ -0,0 +1,30 @@
1
+ /** Where a function's dependency list came from. */
2
+ export type DependencySource = 'manifest' | 'autoinstall' | 'bundled' | 'none';
3
+ export type DependencyInfo = {
4
+ dependencySource: DependencySource;
5
+ dependencyCount: number;
6
+ /** Declared versions */
7
+ dependencies?: Record<string, string>;
8
+ /** Source files the Arc treeshaker parsed - autoinstall only */
9
+ parsedFileCount?: number;
10
+ };
11
+ /** The package.json Architect generates during autoinstall. */
12
+ export type AutoInstallManifest = {
13
+ dependencies?: Record<string, string>;
14
+ _parsed?: string[];
15
+ };
16
+ /**
17
+ * Read and delete the manifest Architect generated during autoinstall.
18
+ * Arc moves it to `node_modules/_arc-autoinstall/` after the install.
19
+ * Has a timestamp, so it must be deleted to avoid hash changes on every deploy.
20
+ */
21
+ export declare function takeAutoInstallManifest(installDir: string): Promise<AutoInstallManifest | undefined>;
22
+ /**
23
+ * Describe a function's dependencies from the manifest that declared them.
24
+ * The user's own package.json wins. Otherwise the Architect autoinstall manifest is used.
25
+ */
26
+ export declare function getDependencyInfo({ sourceDir, autoInstall, bundled, }: {
27
+ sourceDir: string;
28
+ autoInstall?: AutoInstallManifest;
29
+ bundled: boolean;
30
+ }): Promise<DependencyInfo>;
@@ -0,0 +1,57 @@
1
+ import { readFile, rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ /**
4
+ * Read and delete the manifest Architect generated during autoinstall.
5
+ * Arc moves it to `node_modules/_arc-autoinstall/` after the install.
6
+ * Has a timestamp, so it must be deleted to avoid hash changes on every deploy.
7
+ */
8
+ export async function takeAutoInstallManifest(installDir) {
9
+ const file = path.join(installDir, 'node_modules', '_arc-autoinstall', 'package.json');
10
+ let manifest;
11
+ try {
12
+ manifest = JSON.parse(await readFile(file, 'utf8'));
13
+ }
14
+ catch {
15
+ // no autoinstall happened, or the file is unreadable
16
+ }
17
+ try {
18
+ await rm(file, { force: true });
19
+ }
20
+ catch {
21
+ // best effort
22
+ }
23
+ return manifest;
24
+ }
25
+ /**
26
+ * Describe a function's dependencies from the manifest that declared them.
27
+ * The user's own package.json wins. Otherwise the Architect autoinstall manifest is used.
28
+ */
29
+ export async function getDependencyInfo({ sourceDir, autoInstall, bundled, }) {
30
+ const declared = await readDeclaredDependencies(sourceDir);
31
+ if (declared && Object.keys(declared).length > 0) {
32
+ return {
33
+ dependencySource: 'manifest',
34
+ dependencyCount: Object.keys(declared).length,
35
+ dependencies: declared,
36
+ };
37
+ }
38
+ if (autoInstall) {
39
+ const dependencies = autoInstall.dependencies ?? {};
40
+ return {
41
+ dependencySource: 'autoinstall',
42
+ dependencyCount: Object.keys(dependencies).length,
43
+ dependencies,
44
+ parsedFileCount: autoInstall._parsed?.length,
45
+ };
46
+ }
47
+ return { dependencySource: bundled ? 'bundled' : 'none', dependencyCount: 0 };
48
+ }
49
+ async function readDeclaredDependencies(dir) {
50
+ try {
51
+ const pkg = JSON.parse(await readFile(path.join(dir, 'package.json'), 'utf8'));
52
+ return pkg.dependencies;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
@@ -1,8 +1,9 @@
1
1
  import fs, { existsSync } from 'node:fs';
2
- import { rm, stat } from 'node:fs/promises';
2
+ import { stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import AdmZip from 'adm-zip';
5
5
  import { CONVERT_BYTES_TO_MB, MAX_ASSET_SIZE } from '../../constants.js';
6
+ import { getDependencyInfo, takeAutoInstallManifest, } from './dependency-info.js';
6
7
  import { detectNativeModules, errorMessage } from './detect-native-modules.js';
7
8
  import { isFolderLargerThan } from './is-folder-larger-than.js';
8
9
  import { resolveResourceDependencies } from './resolve-dependencies.js';
@@ -16,6 +17,8 @@ export async function prepareAsset({ blueprintFileDir, resource, installer, }) {
16
17
  let functionPath = resolvedFunctionPath;
17
18
  let cleanup = async () => { };
18
19
  let wasBundled = false;
20
+ let dependenciesInstalled = false;
21
+ let autoInstall;
19
22
  const shouldTranspile = await shouldTranspileFunction(resource, functionPath);
20
23
  if (shouldTranspile.transpile) {
21
24
  try {
@@ -42,7 +45,8 @@ export async function prepareAsset({ blueprintFileDir, resource, installer, }) {
42
45
  transpiled: shouldTranspile.transpile,
43
46
  installer,
44
47
  });
45
- await removeArcAutoInstallPackageJson(functionPath);
48
+ autoInstall = await takeAutoInstallManifest(functionPath);
49
+ dependenciesInstalled = true;
46
50
  }
47
51
  if (existsSync(path.join(functionPath, 'node_modules'))) {
48
52
  const modulesPath = path.join(functionPath, 'node_modules');
@@ -72,6 +76,17 @@ export async function prepareAsset({ blueprintFileDir, resource, installer, }) {
72
76
  fileExtension: 'zip',
73
77
  assetParameter: 'src',
74
78
  cleanup,
79
+ metadata: {
80
+ transpiled: shouldTranspile.transpile,
81
+ bundled: wasBundled,
82
+ dependenciesInstalled,
83
+ ...(await getDependencyInfo({
84
+ sourceDir: resolvedFunctionPath,
85
+ autoInstall,
86
+ bundled: wasBundled,
87
+ })),
88
+ assetBytes: zipEntrySize,
89
+ },
75
90
  };
76
91
  }
77
92
  catch (err) {
@@ -98,19 +113,3 @@ export async function pathToZip(path) {
98
113
  zip.addLocalFile(path, '', 'index.js');
99
114
  return zip;
100
115
  }
101
- /**
102
- * Remove file that contains the current date to prevent the hash from changing in the final asset.
103
- * @param functionPath The path to the built function
104
- */
105
- async function removeArcAutoInstallPackageJson(functionPath) {
106
- const arcAutoInstallPackageJson = path.join(functionPath, 'node_modules', '_arc-autoinstall', 'package.json');
107
- try {
108
- const fileStat = await stat(arcAutoInstallPackageJson);
109
- if (fileStat.isFile()) {
110
- await rm(arcAutoInstallPackageJson);
111
- }
112
- }
113
- catch {
114
- // ignore errors
115
- }
116
- }
@@ -5,12 +5,12 @@ import path, { basename, dirname, join } from 'node:path';
5
5
  import { pipeline } from 'node:stream/promises';
6
6
  import { createGzip } from 'node:zlib';
7
7
  import { buildStudio, compareDependencyVersions } from '@sanity/cli-build/_internal/build';
8
+ import { getLocalPackageVersion } from '@sanity/cli-core';
8
9
  import { pack } from 'tar-fs';
9
10
  export async function prepareStudioAsset({ blueprintFileDir, resource, }) {
10
- // validate resource has an externalId
11
- if (!('externalId' in resource) ||
12
- typeof resource.externalId !== 'string' ||
13
- !resource.externalId) {
11
+ // validate resource has an appId
12
+ const appId = getAppId(resource);
13
+ if (!appId) {
14
14
  return {
15
15
  success: false,
16
16
  async cleanup() {
@@ -23,6 +23,8 @@ export async function prepareStudioAsset({ blueprintFileDir, resource, }) {
23
23
  // no op
24
24
  };
25
25
  const studioPath = path.resolve(blueprintFileDir, resource.src);
26
+ // set only when auto-updates is on, because the build compares versions only then
27
+ let mismatchedPackages;
26
28
  try {
27
29
  const studioPathStat = await stat(studioPath);
28
30
  if (!studioPathStat.isDirectory()) {
@@ -48,14 +50,15 @@ export async function prepareStudioAsset({ blueprintFileDir, resource, }) {
48
50
  await rm(outDir, { force: true, recursive: true });
49
51
  await rm(outFile, { force: true });
50
52
  };
51
- const appId = resource.externalId;
52
53
  try {
53
54
  await buildStudio({
54
55
  appId,
55
56
  autoUpdatesEnabled: resource.autoUpdates.enabled,
56
57
  checkAppId() { },
57
58
  async compareDependencyVersions(packages) {
58
- return compareDependencyVersions(packages, studioPath, { appId });
59
+ const result = await compareDependencyVersions(packages, studioPath, { appId });
60
+ mismatchedPackages = result.mismatched.map((m) => m.pkg);
61
+ return result;
59
62
  },
60
63
  determineBasePath() {
61
64
  return resource.basePath ?? '/';
@@ -112,6 +115,11 @@ export async function prepareStudioAsset({ blueprintFileDir, resource, }) {
112
115
  outputBuffer: buffer,
113
116
  fileExtension: 'tar.gz',
114
117
  assetParameter: 'src',
118
+ metadata: {
119
+ sanityVersion: await getInstalledSanityVersion(studioPath),
120
+ mismatchedPackages,
121
+ assetBytes: buffer.length,
122
+ },
115
123
  };
116
124
  }
117
125
  catch (error) {
@@ -139,3 +147,20 @@ function createSilentOutput() {
139
147
  },
140
148
  };
141
149
  }
150
+ async function getInstalledSanityVersion(studioPath) {
151
+ try {
152
+ return (await getLocalPackageVersion('sanity', studioPath)) ?? undefined;
153
+ }
154
+ catch {
155
+ return undefined;
156
+ }
157
+ }
158
+ function getAppId(resource) {
159
+ if ('externalId' in resource && typeof resource.externalId === 'string' && resource.externalId) {
160
+ return resource.externalId;
161
+ }
162
+ else if (resource.lifecycle?.ownershipAction?.type === 'attach') {
163
+ return resource.lifecycle.ownershipAction.id;
164
+ }
165
+ return undefined;
166
+ }
@@ -87,7 +87,22 @@ export interface StackMutation {
87
87
  };
88
88
  userMessage?: string;
89
89
  preDeploy?: boolean;
90
+ metadata?: DeployMetadata;
90
91
  }
92
+ /**
93
+ * Facts about one deploy, stored with the Blueprint on the server.
94
+ * The API accepts it on stack update and never returns it.
95
+ */
96
+ export interface DeployMetadata {
97
+ /** The environment that ran the deploy */
98
+ client?: Record<string, unknown>;
99
+ /** One entry per resource that had an asset prepared, plus what was learned while preparing it */
100
+ resources?: ResourceMetadata[];
101
+ }
102
+ export type ResourceMetadata = {
103
+ name: string;
104
+ type: string;
105
+ } & Record<string, unknown>;
91
106
  /** @internal */
92
107
  export interface BuildPayloadOptions {
93
108
  data?: string;
@@ -302,6 +317,8 @@ export type AssetPrepResult = {
302
317
  fileExtension: string;
303
318
  assetParameter: string;
304
319
  cleanup: () => Promise<void>;
320
+ /** Facts learned about the resource while preparing its asset. Sent as deploy metadata. */
321
+ metadata?: Record<string, unknown>;
305
322
  } | {
306
323
  success: false;
307
324
  cleanup: () => Promise<void>;
@@ -2042,7 +2042,7 @@
2042
2042
  "functions:dev": {
2043
2043
  "aliases": [],
2044
2044
  "args": {},
2045
- "description": "Runs a local, web-based development server to test your functions before deploying.\n\nOpen the emulator in your browser to interactively test your functions with the payload editor.\n\nOptionally, set the host and port with the --host and --port flags. Port 8974 is reserved for the emulator's live-reload WebSocket server. Function timeout can be configured with the --timeout flag.\n\nTo invoke a function with the CLI, use 'functions test'.",
2045
+ "description": "Runs a local, web-based development server to test your functions before deploying.\n\nOpen the emulator in your browser to interactively test your functions with the payload editor.\n\nOptionally, set the host and port with the --host and --port flags. The live-reload WebSocket server shares this host and port. Function timeout can be configured with the --timeout flag.\n\nTo invoke a function with the CLI, use 'functions test'.",
2046
2046
  "examples": [
2047
2047
  "<%= config.bin %> <%= command.id %> --host 127.0.0.1 --port 3333",
2048
2048
  "<%= config.bin %> <%= command.id %> --timeout 60"
@@ -3148,5 +3148,5 @@
3148
3148
  ]
3149
3149
  }
3150
3150
  },
3151
- "version": "17.12.1"
3151
+ "version": "17.13.0-next.g06897b0"
3152
3152
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "Sanity's Runtime CLI for Blueprints and Functions",
4
- "version": "17.12.1",
4
+ "version": "17.13.0-next.g06897b0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -34,8 +34,8 @@
34
34
  "sanity-run": "bin/run.js"
35
35
  },
36
36
  "scripts": {
37
- "build": "npm run build:rollup && npm run build:ts && npm run build:static && npm run build:oclif",
38
- "build:rollup": "rollup -c",
37
+ "build": "npm run build:vendor && npm run build:ts && npm run build:static && npm run build:oclif",
38
+ "build:vendor": "vite build --config vite.vendor.config.mjs",
39
39
  "build:oclif": "oclif manifest && oclif readme",
40
40
  "build:ts": "shx rm -rf dist *.tsbuildinfo && tsc -b",
41
41
  "build:static": "npm run copy:wrapper && npm run copy:design-tokens && npm run copy:server",
@@ -52,7 +52,7 @@
52
52
  "postpack": "shx rm -f oclif.manifest.json",
53
53
  "test": "npm run typecheck && vitest run && npm run test:depmgmt && npm run lint",
54
54
  "test:depmgmt": "vitest run --config ./test-depmgmt/vitest.config.ts",
55
- "test:coverage": "vitest run --coverage --coverage.include \"runtime-cli/src/**/*\"",
55
+ "test:coverage": "vitest run --coverage --coverage.include \"src/**/*\" --coverage.exclude \"src/server/static/**\"",
56
56
  "test:scenarios": "vitest run test/scenarios",
57
57
  "test:playwright": "playwright install chromium && playwright install-deps chromium && playwright test",
58
58
  "test:bp-version-smoke": "node ./test/blueprints-version-smoke.js",
@@ -64,16 +64,16 @@
64
64
  "dependencies": {
65
65
  "@architect/hydrate": "~6.0.2",
66
66
  "@architect/inventory": "~6.1.0",
67
- "@inquirer/prompts": "^8.7.0",
67
+ "@inquirer/prompts": "^8.7.2",
68
68
  "@oclif/core": "^4.14.0",
69
69
  "@oclif/plugin-help": "^6.3.0",
70
70
  "@sanity-labs/oclif-plugin-skills-flag": "^0.2.1",
71
71
  "@sanity/blueprints": "^0.24.0",
72
72
  "@sanity/blueprints-parser": "^0.5.0",
73
- "@sanity/cli-build": "^5.2.0",
74
- "@sanity/cli-core": "^3.5.0",
75
- "@sanity/client": "^8.4.0",
76
- "adm-zip": "^0.6.0",
73
+ "@sanity/cli-build": "^6.3.0",
74
+ "@sanity/cli-core": "^3.7.0",
75
+ "@sanity/client": "^8.6.1",
76
+ "adm-zip": "^0.6.1",
77
77
  "array-treeify": "^0.2.0",
78
78
  "cardinal": "^2.1.1",
79
79
  "empathic": "^2.0.1",
@@ -86,20 +86,19 @@
86
86
  "strip-ansi": "^7.2.0",
87
87
  "tar-fs": "^3.1.3",
88
88
  "tar-stream": "^3.2.1",
89
- "vite": "^8.2.2",
89
+ "vite": "^8.3.0",
90
90
  "ws": "^8.21.3",
91
91
  "xdg-basedir": "^5.1.0"
92
92
  },
93
93
  "devDependencies": {
94
- "@biomejs/biome": "2.5.11",
94
+ "@biomejs/biome": "2.5.13",
95
95
  "@codemirror/lang-json": "^6.0.2",
96
96
  "@codemirror/language": "^6.12.4",
97
- "@codemirror/state": "^6.7.1",
97
+ "@codemirror/state": "^6.7.4",
98
98
  "@enhance/store": "^1.0.2",
99
99
  "@lezer/highlight": "^1.2.3",
100
100
  "@oclif/test": "^4.2.0",
101
- "@playwright/test": "^1.62.1",
102
- "@rollup/plugin-node-resolve": "^16.0.3",
101
+ "@playwright/test": "^1.63.0",
103
102
  "@sanity-labs/design-tokens": "^0.0.2-alpha.2",
104
103
  "@sanity/functions": "^1.7.2",
105
104
  "@types/cardinal": "^2.1.1",
@@ -107,16 +106,15 @@
107
106
  "@types/node": "^22",
108
107
  "@types/tar-fs": "^2.0.4",
109
108
  "@types/ws": "^8.18.1",
110
- "@vitest/coverage-v8": "4.1.11",
109
+ "@vitest/coverage-v8": "5.0.0",
111
110
  "codemirror": "^6.0.2",
112
111
  "mentoss": "^0.13.0",
113
112
  "oclif": "^4.24.0",
114
- "pretty-bytes": "^7.1.2",
113
+ "pretty-bytes": "^7.1.3",
115
114
  "pretty-ms": "^9.3.1",
116
- "rollup": "^4.63.1",
117
115
  "shx": "^0.4.0",
118
116
  "typescript": "^7.0.2",
119
- "vitest": "4.1.11"
117
+ "vitest": "5.0.0"
120
118
  },
121
119
  "oclif": {
122
120
  "bin": "sanity-run",
@@ -16,6 +16,6 @@ The emulator is available at `http://localhost:8080`. Choose another loopback po
16
16
  npx @sanity/cli functions dev --host 127.0.0.1 --port 3333 --timeout 60
17
17
  ```
18
18
 
19
- Port 8974 is not available: the emulator's WebSocket server binds it. `--timeout` sets the execution limit in seconds for every Function, taking precedence over each Function's configured `timeout` (default 10 seconds). Bind to a non-loopback interface only when network access is requested and exposing the development server is acceptable.
19
+ The emulator's live-reload WebSocket server shares the same host and port, so no additional port is reserved. `--timeout` sets the execution limit in seconds for every Function, taking precedence over each Function's configured `timeout` (default 10 seconds). Bind to a non-loopback interface only when network access is requested and exposing the development server is acceptable.
20
20
 
21
21
  Run this command in a persistent terminal, report the URL, and stop the process when development is complete. For one bounded invocation without a web server, use `functions test`.