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

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 (35) hide show
  1. package/README.md +19 -19
  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/config.d.ts +1 -0
  10. package/dist/config.js +6 -0
  11. package/dist/constants.d.ts +1 -0
  12. package/dist/constants.js +1 -0
  13. package/dist/cores/blueprints/config.js +1 -1
  14. package/dist/cores/blueprints/deploy.d.ts +2 -0
  15. package/dist/cores/blueprints/deploy.js +10 -1
  16. package/dist/cores/blueprints/doctor.js +1 -1
  17. package/dist/cores/blueprints/mint-deploy-token.js +2 -1
  18. package/dist/server/static/vendor/vendor.bundle.js +21791 -25197
  19. package/dist/utils/blueprints/deploy-metadata.d.ts +10 -0
  20. package/dist/utils/blueprints/deploy-metadata.js +17 -0
  21. package/dist/utils/display/blueprints-formatting.d.ts +1 -0
  22. package/dist/utils/display/blueprints-formatting.js +17 -3
  23. package/dist/utils/display/dates.d.ts +0 -2
  24. package/dist/utils/display/dates.js +0 -25
  25. package/dist/utils/display/functions-formatting.js +1 -1
  26. package/dist/utils/display/prompt.js +5 -2
  27. package/dist/utils/display/resources-formatting.d.ts +6 -0
  28. package/dist/utils/display/resources-formatting.js +17 -0
  29. package/dist/utils/functions/dependency-info.d.ts +30 -0
  30. package/dist/utils/functions/dependency-info.js +57 -0
  31. package/dist/utils/functions/prepare-asset.js +17 -18
  32. package/dist/utils/studios/prepare-asset.js +31 -6
  33. package/dist/utils/types.d.ts +17 -0
  34. package/oclif.manifest.json +1002 -1002
  35. package/package.json +16 -18
@@ -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>;