@sanity/runtime-cli 17.12.0 → 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 (69) hide show
  1. package/README.md +19 -19
  2. package/dist/actions/blueprints/assets.d.ts +23 -20
  3. package/dist/actions/blueprints/assets.js +18 -87
  4. package/dist/actions/blueprints/logs-polling.d.ts +3 -5
  5. package/dist/actions/blueprints/logs-polling.js +4 -9
  6. package/dist/actions/blueprints/logs.d.ts +1 -2
  7. package/dist/actions/blueprints/logs.js +1 -3
  8. package/dist/actions/blueprints/operations-state.d.ts +10 -0
  9. package/dist/actions/blueprints/operations-state.js +42 -0
  10. package/dist/actions/blueprints/operations.d.ts +2 -9
  11. package/dist/actions/blueprints/operations.js +1 -15
  12. package/dist/actions/blueprints/pre-deploy.d.ts +19 -11
  13. package/dist/actions/blueprints/pre-deploy.js +95 -27
  14. package/dist/actions/blueprints/process-assets.d.ts +13 -11
  15. package/dist/actions/blueprints/process-assets.js +9 -7
  16. package/dist/actions/blueprints/stacks.d.ts +4 -4
  17. package/dist/actions/blueprints/stacks.js +2 -4
  18. package/dist/actions/blueprints/wait-for-operation.d.ts +1 -10
  19. package/dist/actions/blueprints/wait-for-operation.js +56 -101
  20. package/dist/baseCommands.js +6 -1
  21. package/dist/commands/blueprints/deploy.js +2 -0
  22. package/dist/config.d.ts +1 -0
  23. package/dist/config.js +6 -0
  24. package/dist/constants.d.ts +1 -0
  25. package/dist/constants.js +1 -0
  26. package/dist/cores/blueprints/config.js +1 -1
  27. package/dist/cores/blueprints/deploy.d.ts +2 -0
  28. package/dist/cores/blueprints/deploy.js +38 -7
  29. package/dist/cores/blueprints/destroy.js +4 -2
  30. package/dist/cores/blueprints/doctor.js +2 -2
  31. package/dist/cores/blueprints/init.js +3 -1
  32. package/dist/cores/blueprints/logs.js +11 -5
  33. package/dist/cores/blueprints/mint-deploy-token.js +2 -1
  34. package/dist/cores/functions/build.js +4 -5
  35. package/dist/cores/functions/test.js +3 -1
  36. package/dist/cores/index.js +3 -1
  37. package/dist/server/handlers/invoke.js +1 -1
  38. package/dist/server/static/vendor/vendor.bundle.js +21791 -25197
  39. package/dist/utils/blueprints/deploy-metadata.d.ts +10 -0
  40. package/dist/utils/blueprints/deploy-metadata.js +17 -0
  41. package/dist/utils/blueprints/resolve-deployed-resources.d.ts +2 -3
  42. package/dist/utils/blueprints/resolve-deployed-resources.js +2 -2
  43. package/dist/utils/blueprints/wait-for.d.ts +60 -0
  44. package/dist/utils/blueprints/wait-for.js +115 -0
  45. package/dist/utils/display/blueprints-formatting.d.ts +1 -0
  46. package/dist/utils/display/blueprints-formatting.js +17 -3
  47. package/dist/utils/display/dates.d.ts +0 -2
  48. package/dist/utils/display/dates.js +0 -25
  49. package/dist/utils/display/functions-formatting.js +1 -1
  50. package/dist/utils/display/prompt.js +5 -2
  51. package/dist/utils/display/resources-formatting.d.ts +6 -0
  52. package/dist/utils/display/resources-formatting.js +17 -0
  53. package/dist/utils/functions/child-runtime/durable.js +2 -2
  54. package/dist/utils/functions/dependency-info.d.ts +30 -0
  55. package/dist/utils/functions/dependency-info.js +57 -0
  56. package/dist/utils/functions/invoke/assert-console.d.ts +5 -0
  57. package/dist/utils/functions/invoke/assert-console.js +60 -0
  58. package/dist/utils/functions/invoke/local.js +9 -0
  59. package/dist/utils/functions/is-folder-larger-than.d.ts +5 -0
  60. package/dist/utils/functions/is-folder-larger-than.js +38 -0
  61. package/dist/utils/functions/prepare-asset.d.ts +2 -0
  62. package/dist/utils/functions/prepare-asset.js +59 -23
  63. package/dist/utils/studios/prepare-asset.js +36 -9
  64. package/dist/utils/types.d.ts +35 -4
  65. package/dist/utils/types.js +0 -3
  66. package/oclif.manifest.json +1002 -1002
  67. package/package.json +17 -19
  68. package/dist/utils/functions/get-folder-size.d.ts +0 -5
  69. package/dist/utils/functions/get-folder-size.js +0 -24
@@ -0,0 +1,38 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * Recursively validates the total size of a folder, including all nested files and subdirectories.
5
+ */
6
+ export function isFolderLargerThan(folder, maxBytes) {
7
+ if (!statSync(folder).isDirectory()) {
8
+ throw new Error('Provided path is not a directory');
9
+ }
10
+ function recurse(f, bytesSoFar) {
11
+ let totalBytes = bytesSoFar;
12
+ const fileEntries = readdirSync(f);
13
+ for (const file of fileEntries) {
14
+ const filePath = path.join(f.toString(), file);
15
+ const stats = statSync(filePath);
16
+ if (stats.isFile()) {
17
+ totalBytes += stats.size;
18
+ }
19
+ else if (stats.isDirectory()) {
20
+ const result = recurse(filePath, totalBytes);
21
+ totalBytes = result.totalBytes;
22
+ }
23
+ // return early if we have already exceeded the maximum
24
+ if (totalBytes > maxBytes) {
25
+ return {
26
+ totalBytes,
27
+ exceededMax: true,
28
+ };
29
+ }
30
+ }
31
+ return {
32
+ totalBytes,
33
+ exceededMax: false,
34
+ };
35
+ }
36
+ const result = recurse(folder, 0);
37
+ return result.exceededMax;
38
+ }
@@ -1,6 +1,8 @@
1
+ import AdmZip from 'adm-zip';
1
2
  import type { AssetPrepResult, FunctionResource, InstallerType } from '../types.js';
2
3
  export declare function prepareAsset({ blueprintFileDir, resource, installer, }: {
3
4
  blueprintFileDir: string;
4
5
  resource: FunctionResource;
5
6
  installer?: InstallerType;
6
7
  }): Promise<AssetPrepResult>;
8
+ export declare function pathToZip(path: string): Promise<AdmZip>;
@@ -1,9 +1,11 @@
1
- import { existsSync } from 'node:fs';
2
- import { rm, stat } from 'node:fs/promises';
1
+ import fs, { existsSync } from 'node:fs';
2
+ import { stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
- import { MAX_ASSET_SIZE } from '../../constants.js';
4
+ import AdmZip from 'adm-zip';
5
+ import { CONVERT_BYTES_TO_MB, MAX_ASSET_SIZE } from '../../constants.js';
6
+ import { getDependencyInfo, takeAutoInstallManifest, } from './dependency-info.js';
5
7
  import { detectNativeModules, errorMessage } from './detect-native-modules.js';
6
- import { getFolderSize } from './get-folder-size.js';
8
+ import { isFolderLargerThan } from './is-folder-larger-than.js';
7
9
  import { resolveResourceDependencies } from './resolve-dependencies.js';
8
10
  import { shouldAutoResolveDependencies } from './should-auto-resolve-deps.js';
9
11
  import { shouldTranspileFunction } from './should-transpile.js';
@@ -15,6 +17,8 @@ export async function prepareAsset({ blueprintFileDir, resource, installer, }) {
15
17
  let functionPath = resolvedFunctionPath;
16
18
  let cleanup = async () => { };
17
19
  let wasBundled = false;
20
+ let dependenciesInstalled = false;
21
+ let autoInstall;
18
22
  const shouldTranspile = await shouldTranspileFunction(resource, functionPath);
19
23
  if (shouldTranspile.transpile) {
20
24
  try {
@@ -41,7 +45,8 @@ export async function prepareAsset({ blueprintFileDir, resource, installer, }) {
41
45
  transpiled: shouldTranspile.transpile,
42
46
  installer,
43
47
  });
44
- await removeArcAutoInstallPackageJson(functionPath);
48
+ autoInstall = await takeAutoInstallManifest(functionPath);
49
+ dependenciesInstalled = true;
45
50
  }
46
51
  if (existsSync(path.join(functionPath, 'node_modules'))) {
47
52
  const modulesPath = path.join(functionPath, 'node_modules');
@@ -51,29 +56,60 @@ export async function prepareAsset({ blueprintFileDir, resource, installer, }) {
51
56
  }
52
57
  }
53
58
  try {
54
- const size = getFolderSize(functionPath);
55
- if (size > MAX_ASSET_SIZE) {
59
+ // First check the size before trying to create the zip file
60
+ const result = isFolderLargerThan(functionPath, MAX_ASSET_SIZE);
61
+ if (result) {
56
62
  throw new Error('Resource is larger than max asset size of 200 MB.');
57
63
  }
58
- return { success: true, outputPath: functionPath, cleanup };
64
+ const zip = await pathToZip(functionPath);
65
+ // Add up the uncompressed sizes of all entries to get a more accurate size of the asset being uploaded
66
+ const zipEntrySize = zip
67
+ .getEntries()
68
+ .map((entry) => entry.header.size)
69
+ .reduce((a, b) => a + b, 0);
70
+ if (zipEntrySize > MAX_ASSET_SIZE) {
71
+ throw new Error(`Asset size of ${(zipEntrySize / CONVERT_BYTES_TO_MB).toFixed(2)} MB exceeds the maximum allowed size of 200 MB.`);
72
+ }
73
+ return {
74
+ success: true,
75
+ outputBuffer: await zip.toBufferPromise(),
76
+ fileExtension: 'zip',
77
+ assetParameter: 'src',
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
+ },
90
+ };
59
91
  }
60
92
  catch (err) {
61
93
  return { success: false, cleanup, error: err instanceof Error ? err.message : `${err}` };
62
94
  }
63
95
  }
64
- /**
65
- * Remove file that contains the current date to prevent the hash from changing in the final asset.
66
- * @param functionPath The path to the built function
67
- */
68
- async function removeArcAutoInstallPackageJson(functionPath) {
69
- const arcAutoInstallPackageJson = path.join(functionPath, 'node_modules', '_arc-autoinstall', 'package.json');
70
- try {
71
- const fileStat = await stat(arcAutoInstallPackageJson);
72
- if (fileStat.isFile()) {
73
- await rm(arcAutoInstallPackageJson);
74
- }
75
- }
76
- catch {
77
- // ignore errors
78
- }
96
+ export async function pathToZip(path) {
97
+ const stats = await stat(path);
98
+ const zip = new AdmZip(undefined, {
99
+ fs: {
100
+ ...fs,
101
+ // @ts-expect-error monkey patching to return matching mtime for builds
102
+ statSync(path, options) {
103
+ const s = fs.statSync(path, options);
104
+ if (s)
105
+ s.mtime = new Date(0);
106
+ return s;
107
+ },
108
+ },
109
+ });
110
+ if (stats.isDirectory())
111
+ zip.addLocalFolder(path);
112
+ else
113
+ zip.addLocalFile(path, '', 'index.js');
114
+ return zip;
79
115
  }
@@ -1,16 +1,16 @@
1
1
  import { createWriteStream } from 'node:fs';
2
- import { mkdtemp, rm, stat } from 'node:fs/promises';
2
+ import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  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 ?? '/';
@@ -105,11 +108,18 @@ export async function prepareStudioAsset({ blueprintFileDir, resource, }) {
105
108
  }
106
109
  // tar/gzip/write the built output
107
110
  await pipeline(pack(dirname(outDir), { entries: [basename(outDir)] }), createGzip(), createWriteStream(outFile));
111
+ const buffer = await readFile(outFile);
108
112
  return {
109
113
  success: true,
110
114
  cleanup,
111
- // studio must be pre built (e.g. with `npm run build`) and tarred/gzipped
112
- outputPath: outFile,
115
+ outputBuffer: buffer,
116
+ fileExtension: 'tar.gz',
117
+ assetParameter: 'src',
118
+ metadata: {
119
+ sanityVersion: await getInstalledSanityVersion(studioPath),
120
+ mismatchedPackages,
121
+ assetBytes: buffer.length,
122
+ },
113
123
  };
114
124
  }
115
125
  catch (error) {
@@ -137,3 +147,20 @@ function createSilentOutput() {
137
147
  },
138
148
  };
139
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
+ }
@@ -20,8 +20,6 @@ export type FunctionResource = FunctionGroqResource | BlueprintPubSubFunctionRes
20
20
  export type EventFunctionResource = Exclude<FunctionResource, BlueprintPubSubFunctionResource>;
21
21
  /** The `event` of any function resource that declares one */
22
22
  export type FunctionResourceEvent = EventFunctionResource['event'];
23
- /** Resource types that carry a deployable asset (have src: string) */
24
- export type AssetResource = FunctionResource | BlueprintStudioResource;
25
23
  /** @internal */
26
24
  export interface AuthParams {
27
25
  token: string;
@@ -52,7 +50,6 @@ export declare function isRoleResource(r: unknown): r is BlueprintRoleResource;
52
50
  export declare function isDatasetResource(r: unknown): r is BlueprintDatasetResource;
53
51
  export declare function isStudioResource(r: BlueprintResource): r is BlueprintStudioResource;
54
52
  export declare function isDurableFunctionResource(r: unknown): r is BlueprintDurableFunctionResource;
55
- export declare function isAssetResource(r: BlueprintResource): r is AssetResource;
56
53
  export declare function isWebhookResource(r: unknown): r is BlueprintDocumentWebhookResource;
57
54
  interface StackBase {
58
55
  id: string;
@@ -90,7 +87,22 @@ export interface StackMutation {
90
87
  };
91
88
  userMessage?: string;
92
89
  preDeploy?: boolean;
90
+ metadata?: DeployMetadata;
93
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>;
94
106
  /** @internal */
95
107
  export interface BuildPayloadOptions {
96
108
  data?: string;
@@ -301,13 +313,32 @@ export declare const INSTALLER_OPTIONS: readonly ['npm', 'pnpm', 'yarn'];
301
313
  export type InstallerType = 'npm' | 'pnpm' | 'yarn';
302
314
  export type AssetPrepResult = {
303
315
  success: true;
304
- outputPath: string;
316
+ outputBuffer: Buffer;
317
+ fileExtension: string;
318
+ assetParameter: string;
305
319
  cleanup: () => Promise<void>;
320
+ /** Facts learned about the resource while preparing its asset. Sent as deploy metadata. */
321
+ metadata?: Record<string, unknown>;
306
322
  } | {
307
323
  success: false;
308
324
  cleanup: () => Promise<void>;
309
325
  error: string;
310
326
  };
327
+ export type PrepAssetFn<R extends BlueprintResource> = (options: {
328
+ resource: R;
329
+ blueprintFileDir: string;
330
+ installer?: InstallerType;
331
+ }) => Promise<AssetPrepResult>;
332
+ export type PreDeployConfig = {
333
+ /** The resource type this config applies to or a function that returns true if this config applies to the given type. */
334
+ resourceType: string | ((type: string) => boolean);
335
+ /** Function that takes a resource and returns a buffer to be sent to blueprints. */
336
+ prepareAsset?: PrepAssetFn<BlueprintResource>;
337
+ };
338
+ export type AssetResource<R extends BlueprintResource> = {
339
+ resource: R;
340
+ prepareAsset: PrepAssetFn<R>;
341
+ };
311
342
  export type Warning = {
312
343
  code: string;
313
344
  suggestions: string[];
@@ -31,9 +31,6 @@ export function isStudioResource(r) {
31
31
  export function isDurableFunctionResource(r) {
32
32
  return validateDurableFunction(r).length === 0;
33
33
  }
34
- export function isAssetResource(r) {
35
- return isLocalFunctionResource(r) || isStudioResource(r);
36
- }
37
34
  export function isWebhookResource(r) {
38
35
  return validateDocumentWebhook(r).length === 0;
39
36
  }