@sanity/runtime-cli 17.3.2 → 17.4.0-blueprinit.g596ac51

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 (83) hide show
  1. package/README.md +50 -47
  2. package/SKILL.md +59 -0
  3. package/dist/actions/blueprinit/manifest.d.ts +15 -0
  4. package/dist/actions/blueprinit/manifest.js +82 -0
  5. package/dist/actions/blueprinit/studio.d.ts +6 -0
  6. package/dist/actions/blueprinit/studio.js +43 -0
  7. package/dist/actions/blueprinit/templates.d.ts +34 -0
  8. package/dist/actions/blueprinit/templates.js +83 -0
  9. package/dist/actions/blueprinit/workspace.d.ts +16 -0
  10. package/dist/actions/blueprinit/workspace.js +143 -0
  11. package/dist/actions/blueprints/pre-deploy.d.ts +15 -6
  12. package/dist/actions/blueprints/pre-deploy.js +29 -70
  13. package/dist/actions/blueprints/process-assets.d.ts +29 -0
  14. package/dist/actions/blueprints/process-assets.js +80 -0
  15. package/dist/actions/functions/list.d.ts +34 -0
  16. package/dist/actions/functions/list.js +33 -0
  17. package/dist/actions/sanity/datasets.d.ts +15 -0
  18. package/dist/actions/sanity/datasets.js +26 -0
  19. package/dist/baseCommands.d.ts +2 -0
  20. package/dist/baseCommands.js +3 -0
  21. package/dist/commands/blueprinit.d.ts +17 -0
  22. package/dist/commands/blueprinit.js +59 -0
  23. package/dist/commands/blueprints/deploy.d.ts +2 -0
  24. package/dist/commands/blueprints/deploy.js +22 -1
  25. package/dist/commands/blueprints/destroy.js +1 -1
  26. package/dist/commands/blueprints/info.d.ts +1 -0
  27. package/dist/commands/blueprints/info.js +5 -0
  28. package/dist/commands/blueprints/init.js +1 -2
  29. package/dist/commands/blueprints/logs.js +1 -0
  30. package/dist/commands/functions/add.js +3 -3
  31. package/dist/commands/functions/dev.js +2 -2
  32. package/dist/commands/functions/list.d.ts +14 -0
  33. package/dist/commands/functions/list.js +38 -0
  34. package/dist/commands/functions/logs.d.ts +1 -1
  35. package/dist/commands/functions/logs.js +3 -0
  36. package/dist/cores/blueprinit/adopt.d.ts +6 -0
  37. package/dist/cores/blueprinit/adopt.js +119 -0
  38. package/dist/cores/blueprinit/finish.d.ts +14 -0
  39. package/dist/cores/blueprinit/finish.js +64 -0
  40. package/dist/cores/blueprinit/fresh.d.ts +3 -0
  41. package/dist/cores/blueprinit/fresh.js +120 -0
  42. package/dist/cores/blueprinit/preview.d.ts +15 -0
  43. package/dist/cores/blueprinit/preview.js +14 -0
  44. package/dist/cores/blueprinit/prompts.d.ts +5 -0
  45. package/dist/cores/blueprinit/prompts.js +22 -0
  46. package/dist/cores/blueprinit/selection.d.ts +15 -0
  47. package/dist/cores/blueprinit/selection.js +90 -0
  48. package/dist/cores/blueprinit/types.d.ts +16 -0
  49. package/dist/cores/blueprinit/types.js +1 -0
  50. package/dist/cores/blueprinit.d.ts +7 -0
  51. package/dist/cores/blueprinit.js +48 -0
  52. package/dist/cores/blueprints/deploy.js +69 -19
  53. package/dist/cores/functions/add.js +1 -1
  54. package/dist/cores/functions/list.d.ts +10 -0
  55. package/dist/cores/functions/list.js +34 -0
  56. package/dist/utils/display/blueprints-formatting.js +3 -3
  57. package/dist/utils/display/functions-formatting.d.ts +7 -0
  58. package/dist/utils/display/functions-formatting.js +95 -0
  59. package/dist/utils/style-text.js +1 -0
  60. package/oclif.manifest.json +271 -15
  61. package/package.json +31 -17
  62. package/skills/_functions-build.md +23 -0
  63. package/skills/_functions-list.md +28 -0
  64. package/skills/blueprinit.md +155 -0
  65. package/skills/blueprints-add.md +8 -0
  66. package/skills/blueprints-config.md +33 -0
  67. package/skills/blueprints-deploy.md +36 -0
  68. package/skills/blueprints-destroy.md +30 -0
  69. package/skills/blueprints-doctor.md +31 -0
  70. package/skills/blueprints-info.md +23 -0
  71. package/skills/blueprints-init.md +48 -0
  72. package/skills/blueprints-logs.md +33 -0
  73. package/skills/blueprints-mint-deploy-token.md +25 -0
  74. package/skills/blueprints-plan.md +28 -0
  75. package/skills/blueprints-promote.md +31 -0
  76. package/skills/blueprints-stacks.md +28 -0
  77. package/skills/functions-add.md +35 -0
  78. package/skills/functions-dev.md +21 -0
  79. package/skills/functions-env-add.md +29 -0
  80. package/skills/functions-env-list.md +21 -0
  81. package/skills/functions-env-remove.md +22 -0
  82. package/skills/functions-logs.md +39 -0
  83. package/skills/functions-test.md +31 -0
@@ -0,0 +1,90 @@
1
+ import { select } from '@inquirer/prompts';
2
+ export async function chooseOrganization(groups, organizationId, interactive) {
3
+ if (groups.length === 0) {
4
+ return failure('No organizations found for this account.');
5
+ }
6
+ if (organizationId) {
7
+ const organization = groups.find((group) => group.organization.id === organizationId);
8
+ return organization
9
+ ? { ok: true, value: organization }
10
+ : failure(`No organization "${organizationId}" found for this account.`);
11
+ }
12
+ if (groups.length === 1)
13
+ return { ok: true, value: groups[0] };
14
+ if (!interactive) {
15
+ return failure('Multiple organizations found.', ['Pass --organization-id <id> to choose one.']);
16
+ }
17
+ const selectedId = await select({
18
+ message: 'Choose a Sanity organization:',
19
+ choices: groups.map(({ organization, projects }) => ({
20
+ name: organization.name,
21
+ value: organization.id,
22
+ description: `${projects?.length ?? 0} projects`,
23
+ })),
24
+ });
25
+ const selected = groups.find(({ organization }) => organization.id === selectedId);
26
+ return selected ? { ok: true, value: selected } : failure('Organization not found.');
27
+ }
28
+ export async function chooseProject(projects, projectId, organizationName, interactive) {
29
+ if (projects.length === 0) {
30
+ return failure(`No projects found in ${organizationName}.`);
31
+ }
32
+ if (projectId) {
33
+ const project = projects.find(({ id }) => id === projectId);
34
+ return project
35
+ ? { ok: true, value: project }
36
+ : failure(`No project "${projectId}" found in ${organizationName}.`);
37
+ }
38
+ if (projects.length === 1)
39
+ return { ok: true, value: projects[0] };
40
+ if (!interactive) {
41
+ return failure('Multiple projects found.', ['Pass --project-id <id> to choose one.']);
42
+ }
43
+ const selectedId = await select({
44
+ message: 'Choose a Sanity project:',
45
+ choices: projects.map((project) => ({ name: project.displayName, value: project.id })),
46
+ });
47
+ const selected = projects.find(({ id }) => id === selectedId);
48
+ return selected ? { ok: true, value: selected } : failure('Project not found.');
49
+ }
50
+ export async function chooseDataset(datasets, datasetName, projectName, interactive) {
51
+ if (datasets.length === 0) {
52
+ return failure(`No datasets found in ${projectName}.`);
53
+ }
54
+ if (datasetName) {
55
+ const dataset = datasets.find(({ name }) => name === datasetName);
56
+ return dataset
57
+ ? { ok: true, value: dataset }
58
+ : failure(`No dataset "${datasetName}" found in ${projectName}.`);
59
+ }
60
+ if (datasets.length === 1)
61
+ return { ok: true, value: datasets[0] };
62
+ if (!interactive) {
63
+ return failure('Multiple datasets found.', ['Pass --dataset <name> to choose one.']);
64
+ }
65
+ const production = datasets.find(({ name }) => name === 'production');
66
+ const orderedDatasets = production
67
+ ? [production, ...datasets.filter(({ name }) => name !== 'production')]
68
+ : datasets;
69
+ const selectedName = await select({
70
+ message: 'Choose a Sanity dataset:',
71
+ choices: orderedDatasets.map((dataset) => ({
72
+ name: dataset.name,
73
+ value: dataset.name,
74
+ description: dataset.aclMode,
75
+ })),
76
+ });
77
+ const selected = datasets.find(({ name }) => name === selectedName);
78
+ return selected ? { ok: true, value: selected } : failure('Dataset not found.');
79
+ }
80
+ export function findProject(groups, projectId) {
81
+ for (const { organization, projects = [] } of groups) {
82
+ const project = projects.find(({ id }) => id === projectId);
83
+ if (project)
84
+ return { organization, project };
85
+ }
86
+ return undefined;
87
+ }
88
+ function failure(message, suggestions) {
89
+ return { ok: false, error: { message, suggestions } };
90
+ }
@@ -0,0 +1,16 @@
1
+ import type { Logger } from '../../utils/logger.js';
2
+ export interface BlueprinitFlags {
3
+ yes?: boolean;
4
+ organizationId?: string;
5
+ projectId?: string;
6
+ datasetName?: string;
7
+ stackName?: string;
8
+ studioName?: string;
9
+ }
10
+ export interface BlueprinitFlowOptions {
11
+ bin: string;
12
+ dir: string;
13
+ flags: BlueprinitFlags;
14
+ log: Logger;
15
+ token: string;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { BlueprinitFlags } from './blueprinit/types.js';
2
+ import type { CoreConfig, CoreResult } from './index.js';
3
+ export interface BlueprinitOptions extends CoreConfig {
4
+ dir?: string;
5
+ flags?: BlueprinitFlags;
6
+ }
7
+ export declare function blueprinitCore(options: BlueprinitOptions): Promise<CoreResult>;
@@ -0,0 +1,48 @@
1
+ import { cwd } from 'node:process';
2
+ import { detectStudio } from '../actions/blueprinit/studio.js';
3
+ import { findBlueprintFile } from '../actions/blueprints/blueprint.js';
4
+ import { readConfigFile } from '../actions/blueprints/config.js';
5
+ import { validTokenOrErrorMessage } from '../utils/validated-token.js';
6
+ import { adoptFlow } from './blueprinit/adopt.js';
7
+ import { freshFlow } from './blueprinit/fresh.js';
8
+ export async function blueprinitCore(options) {
9
+ const { bin, log, flags = {}, dir = cwd() } = options;
10
+ try {
11
+ const existingBlueprint = findBlueprintFile(dir);
12
+ if (existingBlueprint) {
13
+ const blueprintConfig = readConfigFile(existingBlueprint.blueprintFilePath);
14
+ if (!blueprintConfig?.stackId) {
15
+ return {
16
+ success: false,
17
+ error: `A Blueprint exists here (${existingBlueprint.fileName}), but it is not connected to a Stack.`,
18
+ suggestions: [`Finish setup with: npx ${bin} blueprints init`],
19
+ };
20
+ }
21
+ log(`A blueprint already exists here (${existingBlueprint.fileName}).`);
22
+ log('');
23
+ log('Use these instead:');
24
+ log(` npx ${bin} blueprints config --edit`);
25
+ log(` npx ${bin} blueprints doctor`);
26
+ return {
27
+ success: true,
28
+ json: { alreadyInitialized: true, blueprintFile: existingBlueprint.fileName },
29
+ };
30
+ }
31
+ const tokenCheck = await validTokenOrErrorMessage(log);
32
+ if (!tokenCheck.ok) {
33
+ return {
34
+ success: false,
35
+ error: 'No Sanity session found.',
36
+ suggestions: ['Sign in first: npx sanity login', 'Then run blueprinit again.'],
37
+ };
38
+ }
39
+ log('Setting up your Sanity workspace.');
40
+ log('');
41
+ const flowOptions = { bin, dir, flags, log, token: tokenCheck.value };
42
+ const studio = detectStudio(dir);
43
+ return studio ? await adoptFlow({ ...flowOptions, studio }) : await freshFlow(flowOptions);
44
+ }
45
+ catch (error) {
46
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
47
+ }
48
+ }
@@ -4,21 +4,21 @@ import { getStack, updateStack } from '../../actions/blueprints/stacks.js';
4
4
  import { waitForOperation } from '../../actions/blueprints/wait-for-operation.js';
5
5
  import { checkUserPermission } from '../../actions/sanity/access.js';
6
6
  import { createHintCollector } from '../../utils/blueprints/hints.js';
7
- import { niceId } from '../../utils/display/presenters.js';
7
+ import { check, niceId, severe } from '../../utils/display/presenters.js';
8
8
  import { CODE_OPERATION_UNCONFIRMED, EXIT_OPERATION_UNCONFIRMED } from '../../utils/exit-codes.js';
9
9
  import { styleText } from '../../utils/style-text.js';
10
10
  import { getWarnings } from '../../utils/warnings.js';
11
11
  export async function blueprintDeployCore(options) {
12
- const { bin = 'sanity', log, auth, stackId, scopeType, scopeId, deployedStack, blueprint, flags, } = options;
12
+ const { bin = 'sanity', log: logger, auth, stackId, scopeType, scopeId, deployedStack, blueprint, flags, } = options;
13
13
  const { verbose } = flags;
14
14
  const noWait = flags['no-wait'] || false;
15
15
  const stackName = flags['new-stack-name'] ?? deployedStack.name;
16
16
  const installer = flags['fn-installer'] || undefined;
17
17
  const blueprintFileDir = dirname(blueprint.fileInfo.blueprintFilePath);
18
18
  const workspaceWarnings = getWarnings({ dir: blueprintFileDir });
19
- log(`Deploying "${stackName}" ${niceId(deployedStack.id)}...`);
19
+ logger(`Deploying "${stackName}" ${niceId(deployedStack.id)}...`);
20
20
  try {
21
- const { ok: checkOk, stack: currentStack } = await getStack({ stackId, auth, logger: log });
21
+ const { ok: checkOk, stack: currentStack } = await getStack({ stackId, auth, logger });
22
22
  if (!checkOk) {
23
23
  return { success: false, error: 'Failed to check current Stack status' };
24
24
  }
@@ -37,20 +37,20 @@ export async function blueprintDeployCore(options) {
37
37
  const preDeployResult = await preDeploy({
38
38
  scopeType,
39
39
  scopeId,
40
- resources,
40
+ resources, // mutated by preDeploy
41
41
  stackId,
42
42
  stackName,
43
43
  auth,
44
44
  blueprintFileDir,
45
45
  installer,
46
- log,
46
+ logger,
47
47
  bin,
48
48
  verbose,
49
+ onAssetEvent: createAssetEventHandler(logger),
49
50
  });
50
- if (!preDeployResult?.success) {
51
+ if (!preDeployResult?.success)
51
52
  return preDeployResult;
52
- }
53
- const spinner = log.ora('Deploying...').start();
53
+ const deploySpinner = logger.ora('Deploying...').start();
54
54
  const { ok: deployOk, stack, error: deployError, } = await updateStack({
55
55
  stackId,
56
56
  stackMutation: {
@@ -61,10 +61,10 @@ export async function blueprintDeployCore(options) {
61
61
  userMessage: flags.message,
62
62
  },
63
63
  auth,
64
- logger: log,
64
+ logger,
65
65
  });
66
66
  if (!deployOk) {
67
- spinner.fail(`${styleText('red', 'Failed')} to update Stack deployment`);
67
+ deploySpinner.fail(`${styleText('red', 'Failed')} to update Stack deployment`);
68
68
  const hints = createHintCollector(bin);
69
69
  if (deployError) {
70
70
  hints.inspectMessage(deployError);
@@ -79,18 +79,18 @@ export async function blueprintDeployCore(options) {
79
79
  ],
80
80
  };
81
81
  }
82
- spinner.stop().clear();
82
+ deploySpinner.stop().clear();
83
83
  const legacyPermissionsNoticePromise = CHECK_LEGACY_DEPLOY_PERMISSION
84
84
  ? checkLegacyDeployPermission({
85
85
  auth,
86
86
  scopeType,
87
87
  scopeId,
88
- log,
88
+ log: logger,
89
89
  })
90
90
  : Promise.resolve(null);
91
91
  if (noWait) {
92
- log(styleText(['bold', 'green'], 'Stack deployment started!'));
93
- log(`Use \`npx ${bin} blueprints info\` to check status`);
92
+ logger(styleText(['bold', 'green'], 'Stack deployment started!'));
93
+ logger(`Use \`npx ${bin} blueprints info\` to check status`);
94
94
  const legacyWarning = await legacyPermissionsNoticePromise;
95
95
  const warnings = [...(legacyWarning ? [legacyWarning] : []), ...workspaceWarnings];
96
96
  return {
@@ -100,19 +100,19 @@ export async function blueprintDeployCore(options) {
100
100
  warnings: warnings.length ? warnings : undefined,
101
101
  };
102
102
  }
103
- log(styleText('dim', 'Stack deployment progress:'));
104
- log('');
103
+ logger(styleText('dim', 'Stack deployment progress:'));
104
+ logger('');
105
105
  const outcome = await waitForOperation({
106
106
  stackId,
107
107
  operationId: stack.operationId,
108
108
  auth,
109
- log,
109
+ log: logger,
110
110
  bin,
111
111
  verbose,
112
112
  progressNoun: 'deployment',
113
113
  });
114
114
  if (outcome.type === 'completed') {
115
- log(styleText(['bold', 'green'], 'Stack deployment completed!'));
115
+ logger(styleText(['bold', 'green'], 'Stack deployment completed!'));
116
116
  const legacyWarning = await legacyPermissionsNoticePromise;
117
117
  const warnings = [...(legacyWarning ? [legacyWarning] : []), ...workspaceWarnings];
118
118
  return {
@@ -151,6 +151,56 @@ export async function blueprintDeployCore(options) {
151
151
  return { success: false, error: errorMessage };
152
152
  }
153
153
  }
154
+ function createAssetEventHandler(logger) {
155
+ let spinner;
156
+ const results = [];
157
+ return (event) => {
158
+ switch (event.type) {
159
+ case 'progress': {
160
+ const { completed, total, slow } = event.progress;
161
+ const text = [
162
+ `Processing assets (${completed}/${total})`,
163
+ slow ? ', this can take a moment' : '',
164
+ ].join('');
165
+ if (spinner)
166
+ spinner.text = text;
167
+ else
168
+ spinner = logger.ora(text).start();
169
+ return;
170
+ }
171
+ case 'result': {
172
+ results.push(event.result);
173
+ return;
174
+ }
175
+ case 'complete': {
176
+ spinner?.stop();
177
+ spinner = undefined;
178
+ break;
179
+ }
180
+ }
181
+ // done. print report
182
+ for (const result of results) {
183
+ const { resource } = result;
184
+ if (!result.success) {
185
+ const summary = result.failureKind === 'upload'
186
+ ? `Failed uploading ${resource.name} asset`
187
+ : `Failed processing ${resource.name} asset`;
188
+ logger(` ${severe(summary)}`);
189
+ logger.error(` ${result.error}`);
190
+ continue;
191
+ }
192
+ logger(` ${check(`${resource.name} ${niceId(resource.src)}`)}`);
193
+ logger(` Source: ${resource.src}`);
194
+ if (result.hash) {
195
+ const hash = result.hash;
196
+ const h = hash.length > 24 ? `${hash.slice(0, 8)}...${hash.slice(-12)}` : hash;
197
+ logger.verbose(` Hash: ${h}`);
198
+ }
199
+ if (result.exists)
200
+ logger.verbose(' Asset unchanged');
201
+ }
202
+ };
203
+ }
154
204
  const CHECK_LEGACY_DEPLOY_PERMISSION = false;
155
205
  const BLUEPRINTS_PERMISSION_ENFORCEMENT_DATE = 'July 1, 2026';
156
206
  async function checkLegacyDeployPermission({ auth, scopeType, scopeId, log, }) {
@@ -224,7 +224,7 @@ async function promptForFunctionName() {
224
224
  const functionName = await input({
225
225
  message: 'Enter function name:',
226
226
  validate: (input) => validateFunctionName(input) ||
227
- 'Invalid function name. Must be 6+ characters, no special characters, no spaces',
227
+ 'Invalid function name. Must be 3+ characters, no special characters, no spaces',
228
228
  });
229
229
  return functionName;
230
230
  }
@@ -0,0 +1,10 @@
1
+ import type { AuthParams } from '../../utils/types.js';
2
+ import type { CoreConfig, CoreResult } from '../index.js';
3
+ export interface FunctionListOptions extends Pick<CoreConfig, 'log'> {
4
+ auth: AuthParams;
5
+ flags: {
6
+ json?: boolean;
7
+ verbose?: boolean;
8
+ };
9
+ }
10
+ export declare function functionListCore(options: FunctionListOptions): Promise<CoreResult>;
@@ -0,0 +1,34 @@
1
+ import { listFunctions } from '../../actions/functions/list.js';
2
+ import { formatFunctionsList } from '../../utils/display/functions-formatting.js';
3
+ import { capitalize, niceId } from '../../utils/display/presenters.js';
4
+ import { styleText } from '../../utils/style-text.js';
5
+ export async function functionListCore(options) {
6
+ const { auth, flags, log } = options;
7
+ const { scopeId, scopeType } = auth;
8
+ const spinner = flags.json ? undefined : log.ora('Fetching functions...').start();
9
+ const result = await listFunctions(auth, log);
10
+ if (!result.ok) {
11
+ spinner?.fail('Failed to list functions');
12
+ const suggestions = /access|permission|forbidden/i.test(result.error)
13
+ ? [
14
+ `Verify you have access to ${scopeType} "${scopeId}".`,
15
+ 'Try a different scope with --project-id or --organization-id.',
16
+ ]
17
+ : undefined;
18
+ return { success: false, error: result.error, suggestions };
19
+ }
20
+ spinner?.stop().clear();
21
+ if (!flags.json) {
22
+ if (result.functions.length === 0) {
23
+ log(`No functions found for ${scopeType} ${niceId(scopeId)}`);
24
+ }
25
+ else {
26
+ log(`${styleText('bold', capitalize(scopeType))} ${niceId(scopeId)} ${styleText('bold', 'Functions')} [${result.functions.length}]:`);
27
+ log(formatFunctionsList(result.functions, { verbose: flags.verbose }));
28
+ }
29
+ }
30
+ return {
31
+ success: true,
32
+ json: { functions: result.functions, scopeType, scopeId },
33
+ };
34
+ }
@@ -72,15 +72,15 @@ function resourceName(res, displayNameAttribute) {
72
72
  nameParts.push(styleText('green', `"${displayName}"`));
73
73
  return nameParts.join(' ');
74
74
  }
75
- function deployedResourceName(res, displayNameAttribute, _verbose = false) {
75
+ function deployedResourceName(res, displayNameAttribute, verbose = false) {
76
76
  const nameParts = [styleText(['bold', 'green'], res.name)];
77
77
  const displayName = displayNameAttribute &&
78
78
  typeof res.parameters[displayNameAttribute] === 'string' &&
79
79
  res.parameters[displayNameAttribute];
80
80
  if (displayName && displayName !== res.name)
81
81
  nameParts.push(styleText('green', `"${displayName}"`));
82
- // if (verbose)
83
- nameParts.push(idList(res));
82
+ if (verbose)
83
+ nameParts.push(idList(res));
84
84
  return nameParts.join(' ');
85
85
  }
86
86
  function idList(res) {
@@ -0,0 +1,7 @@
1
+ import type { DeployedFunction } from '../../actions/functions/list.js';
2
+ export declare function formatFunctionsList(functions: DeployedFunction[], options?: {
3
+ verbose?: boolean;
4
+ }): string;
5
+ export declare function formatFunction(fn: DeployedFunction, options?: {
6
+ verbose?: boolean;
7
+ }): string;
@@ -0,0 +1,95 @@
1
+ import { SANITY_FUNCTION_PREFIX } from '../../constants.js';
2
+ import { styleText } from '../style-text.js';
3
+ import { formatDateTime } from './dates.js';
4
+ import { niceId, renderSection } from './presenters.js';
5
+ export function formatFunctionsList(functions, options = {}) {
6
+ if (!options.verbose)
7
+ return formatCompactList(functions);
8
+ return functions.map((fn) => formatFunction(fn, options)).join('\n');
9
+ }
10
+ export function formatFunction(fn, options = {}) {
11
+ if (!options.verbose)
12
+ return formatCompactList([fn]);
13
+ const rows = [];
14
+ if (fn.type)
15
+ rows.push(['Type', shortType(fn.type)]);
16
+ if (fn.updatedAt) {
17
+ const updatedAt = formatDateTime(fn.updatedAt);
18
+ if (updatedAt)
19
+ rows.push(['Updated', updatedAt]);
20
+ }
21
+ if (typeof fn.memory === 'number')
22
+ rows.push(['Memory', `${fn.memory} MB`]);
23
+ if (typeof fn.timeout === 'number')
24
+ rows.push(['Timeout', `${fn.timeout}s`]);
25
+ const rule = formatEventRule(fn.eventRule);
26
+ if (rule)
27
+ rows.push(['Rule', rule]);
28
+ return renderSection(formatTitle(fn), rows);
29
+ }
30
+ function formatCompactList(functions) {
31
+ const titles = functions.map(plainTitle);
32
+ const ids = functions.map((fn) => `<${fn.id}>`);
33
+ const types = functions.map((fn) => (fn.type ? shortType(fn.type) : '-'));
34
+ const updated = functions.map((fn) => (fn.updatedAt ? formatDateTime(fn.updatedAt) || '-' : '-'));
35
+ const titlePad = Math.max(...titles.map((title) => title.length));
36
+ const idPad = Math.max(...ids.map((id) => id.length));
37
+ const typePad = Math.max(...types.map((type) => type.length));
38
+ return functions
39
+ .map((fn, index) => {
40
+ const title = formatIdentity(fn) + ' '.repeat(titlePad - titles[index].length);
41
+ const id = niceId(fn.id) + ' '.repeat(idPad - ids[index].length);
42
+ const type = styleText('dim', types[index].padEnd(typePad));
43
+ const timestamp = styleText('dim', updated[index]);
44
+ return ` ${title} ${id} ${type} ${timestamp}`;
45
+ })
46
+ .join('\n');
47
+ }
48
+ function formatTitle(fn) {
49
+ return `${formatIdentity(fn)} ${niceId(fn.id)}`;
50
+ }
51
+ function formatIdentity(fn) {
52
+ const parts = [];
53
+ if (fn.name)
54
+ parts.push(styleText('bold', fn.name));
55
+ if (fn.displayName && fn.displayName !== fn.name) {
56
+ parts.push(styleText('green', `"${fn.displayName}"`));
57
+ }
58
+ if (parts.length === 0)
59
+ parts.push(styleText('bold', 'Unnamed'));
60
+ return parts.join(' ');
61
+ }
62
+ function plainTitle(fn) {
63
+ const parts = [];
64
+ if (fn.name)
65
+ parts.push(fn.name);
66
+ if (fn.displayName && fn.displayName !== fn.name)
67
+ parts.push(`"${fn.displayName}"`);
68
+ return parts.length > 0 ? parts.join(' ') : 'Unnamed';
69
+ }
70
+ function shortType(type) {
71
+ return type.startsWith(SANITY_FUNCTION_PREFIX) ? type.slice(SANITY_FUNCTION_PREFIX.length) : type;
72
+ }
73
+ function formatEventRule(rule) {
74
+ if (rule === null || rule === undefined)
75
+ return undefined;
76
+ if (typeof rule !== 'object')
77
+ return String(rule);
78
+ if (Array.isArray(rule))
79
+ return rule.map(formatRuleValue).join(', ');
80
+ const entries = Object.entries(rule);
81
+ if (entries.length === 0)
82
+ return undefined;
83
+ return entries.map(([key, value]) => `${key}: ${formatRuleValue(value)}`).join(' · ');
84
+ }
85
+ function formatRuleValue(value) {
86
+ if (typeof value === 'string')
87
+ return value;
88
+ if (value === null || value === undefined)
89
+ return String(value);
90
+ if (Array.isArray(value))
91
+ return value.map(formatRuleValue).join(', ');
92
+ if (typeof value === 'object')
93
+ return JSON.stringify(value);
94
+ return String(value);
95
+ }
@@ -1,3 +1,4 @@
1
+ // TODO: move to ./display
1
2
  import { env } from 'node:process';
2
3
  import { styleText as nodeStyleText } from 'node:util';
3
4
  import { isInteractive } from './interactive.js';