@sanity/runtime-cli 14.10.1 → 14.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -41
- package/dist/actions/blueprints/blueprint.d.ts +32 -16
- package/dist/actions/blueprints/blueprint.js +92 -138
- package/dist/actions/blueprints/resolve.d.ts +46 -0
- package/dist/actions/blueprints/resolve.js +42 -0
- package/dist/actions/blueprints/resources.js +35 -12
- package/dist/baseCommands.d.ts +52 -22
- package/dist/baseCommands.js +216 -52
- package/dist/commands/blueprints/info.d.ts +4 -2
- package/dist/commands/blueprints/info.js +6 -3
- package/dist/commands/functions/add.js +1 -1
- package/dist/constants.d.ts +15 -2
- package/dist/constants.js +14 -10
- package/dist/cores/functions/add.js +11 -7
- package/dist/server/static/components/api-base.js +1 -0
- package/dist/server/static/components/filters.js +10 -3
- package/dist/server/static/components/function-list.js +1 -0
- package/dist/server/static/components/payload-panel.js +55 -5
- package/dist/server/static/components/rule-panel.js +22 -11
- package/dist/utils/child-process-wrapper.js +6 -1
- package/dist/utils/invoke-local.js +1 -1
- package/oclif.manifest.json +508 -66
- package/package.json +2 -2
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ScopeType } from '../../utils/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Source of a resolved config value.
|
|
4
|
+
* - flags: from CLI flags like `--project-id`
|
|
5
|
+
* - env: from the environment - usually CLI var like `SANITY_ORGANIZATION_ID`
|
|
6
|
+
* - module: from the blueprint module - undocumented escape hatch
|
|
7
|
+
* - config: from the config file - .sanity/blueprint.config.json most common
|
|
8
|
+
* - inferred: legacy `ST-<projectId>` stacks from launch
|
|
9
|
+
*/
|
|
10
|
+
export type ConfigSource = 'flags' | 'env' | 'module' | 'config' | 'inferred';
|
|
11
|
+
/** A set of optional IDs from a single source. */
|
|
12
|
+
export interface IdValues {
|
|
13
|
+
organizationId?: string;
|
|
14
|
+
projectId?: string;
|
|
15
|
+
stackId?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sources to resolve IDs from, in descending priority.
|
|
19
|
+
* Each key maps to a source; the first non-empty value wins.
|
|
20
|
+
*/
|
|
21
|
+
export interface IdSources {
|
|
22
|
+
flags?: IdValues;
|
|
23
|
+
env?: IdValues;
|
|
24
|
+
module?: IdValues;
|
|
25
|
+
config?: IdValues;
|
|
26
|
+
}
|
|
27
|
+
export interface ResolvedIds {
|
|
28
|
+
organizationId?: string;
|
|
29
|
+
projectId?: string;
|
|
30
|
+
stackId?: string;
|
|
31
|
+
scopeType?: ScopeType;
|
|
32
|
+
scopeId?: string;
|
|
33
|
+
sources: {
|
|
34
|
+
organizationId?: ConfigSource;
|
|
35
|
+
projectId?: ConfigSource;
|
|
36
|
+
stackId?: ConfigSource;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve organization, project, and stack IDs from a prioritized set of sources.
|
|
41
|
+
* Precedence: flags > env > module > config.
|
|
42
|
+
* Derives scopeType/scopeId from the resolved IDs (project > organization).
|
|
43
|
+
*
|
|
44
|
+
* This is a pure, synchronous function -- no I/O, no side effects.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveIds(sources: IdSources): ResolvedIds;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve organization, project, and stack IDs from a prioritized set of sources.
|
|
3
|
+
* Precedence: flags > env > module > config.
|
|
4
|
+
* Derives scopeType/scopeId from the resolved IDs (project > organization).
|
|
5
|
+
*
|
|
6
|
+
* This is a pure, synchronous function -- no I/O, no side effects.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveIds(sources) {
|
|
9
|
+
const ordered = [
|
|
10
|
+
{ source: 'flags', values: sources.flags },
|
|
11
|
+
{ source: 'env', values: sources.env },
|
|
12
|
+
{ source: 'module', values: sources.module },
|
|
13
|
+
{ source: 'config', values: sources.config },
|
|
14
|
+
];
|
|
15
|
+
const result = { sources: {} };
|
|
16
|
+
for (const { source, values } of ordered) {
|
|
17
|
+
if (!values)
|
|
18
|
+
continue;
|
|
19
|
+
if (!result.organizationId && values.organizationId) {
|
|
20
|
+
result.organizationId = values.organizationId;
|
|
21
|
+
result.sources.organizationId = source;
|
|
22
|
+
}
|
|
23
|
+
if (!result.projectId && values.projectId) {
|
|
24
|
+
result.projectId = values.projectId;
|
|
25
|
+
result.sources.projectId = source;
|
|
26
|
+
}
|
|
27
|
+
if (!result.stackId && values.stackId) {
|
|
28
|
+
result.stackId = values.stackId;
|
|
29
|
+
result.sources.stackId = source;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Scope is as specific as possible; project > organization
|
|
33
|
+
if (result.projectId) {
|
|
34
|
+
result.scopeType = 'project';
|
|
35
|
+
result.scopeId = result.projectId;
|
|
36
|
+
}
|
|
37
|
+
else if (result.organizationId) {
|
|
38
|
+
result.scopeType = 'organization';
|
|
39
|
+
result.scopeId = result.organizationId;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { cwd } from 'node:process';
|
|
5
|
+
import { MAP_EVENT_TO_FUNCTION_TYPE, SANITY_FUNCTION_DOCUMENT, SANITY_FUNCTION_MEDIA_LIBRARY_ASSET, SANITY_FUNCTION_SCHEDULED, SANITY_FUNCTION_SYNC_TAG_INVALIDATE, } from '../../constants.js';
|
|
5
6
|
import { styleText } from '../../utils/style-text.js';
|
|
6
7
|
import { writeOrUpdateNodeDependency } from '../node.js';
|
|
7
8
|
import { addResourceToBlueprint } from './blueprint.js';
|
|
@@ -21,6 +22,20 @@ export const handler = scheduledEventHandler(async ({ context }) => {
|
|
|
21
22
|
const time = new Date().toLocaleTimeString()
|
|
22
23
|
console.log(\`Your scheduled Sanity Function was called at \${time}\`)
|
|
23
24
|
})`;
|
|
25
|
+
const DEFAULT_SYNC_TAG_HELPER_FUNCTION_TEMPLATE = /*ts*/ `import { syncTagInvalidateEventHandler } from '@sanity/functions'
|
|
26
|
+
|
|
27
|
+
export const handler = syncTagInvalidateEventHandler(async ({ context, event, done }) => {
|
|
28
|
+
const time = new Date().toLocaleTimeString()
|
|
29
|
+
console.log(\`Your sync tag invalidate Sanity Function was called at \${time}\`)
|
|
30
|
+
// TODO: add code to do something with the invalidated sync tags provided to you in \`event.data.syncTags\`
|
|
31
|
+
try {
|
|
32
|
+
// notify Sanity that you have completed invalidation
|
|
33
|
+
const response = await done(event.data.syncTags)
|
|
34
|
+
console.log('Invalidation complete, Sanity responded with an HTTP', response.status)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.error('Error invoking Sanity invalidation done endpoint!', e)
|
|
37
|
+
}
|
|
38
|
+
})`;
|
|
24
39
|
/**
|
|
25
40
|
* Creates a new function resource file and adds it to the blueprint
|
|
26
41
|
*/
|
|
@@ -47,16 +62,18 @@ export async function createFunctionResource(options, logger) {
|
|
|
47
62
|
throw Error(`Unsupported language: ${lang}`);
|
|
48
63
|
// type looks like 'document-publish', 'media-library-asset-delete' or 'scheduled-function'
|
|
49
64
|
// and we are guaranteed to have the same leading words (typeName below) for all provided type strings (via guards in the call site for this method).
|
|
50
|
-
|
|
51
|
-
const typeName = type[0].substring(0, type[0].lastIndexOf('-'));
|
|
65
|
+
const functionType = MAP_EVENT_TO_FUNCTION_TYPE[type[0]];
|
|
52
66
|
// Create index.<lang> with default template
|
|
53
67
|
const indexPath = join(functionDir, `index.${lang}`);
|
|
54
68
|
let template = DEFAULT_FUNCTION_TEMPLATE;
|
|
55
69
|
if (addHelpers) {
|
|
56
|
-
switch (
|
|
57
|
-
case
|
|
70
|
+
switch (functionType) {
|
|
71
|
+
case SANITY_FUNCTION_SCHEDULED:
|
|
58
72
|
template = DEFAULT_SCHEDULED_HELPER_FUNCTION_TEMPLATE;
|
|
59
73
|
break;
|
|
74
|
+
case SANITY_FUNCTION_SYNC_TAG_INVALIDATE:
|
|
75
|
+
template = DEFAULT_SYNC_TAG_HELPER_FUNCTION_TEMPLATE;
|
|
76
|
+
break;
|
|
60
77
|
default:
|
|
61
78
|
template = DEFAULT_HELPER_FUNCTION_TEMPLATE;
|
|
62
79
|
break;
|
|
@@ -80,39 +97,45 @@ export async function createFunctionResource(options, logger) {
|
|
|
80
97
|
const eventsOn = type.map((t) => t.substring(t.lastIndexOf('-') + 1));
|
|
81
98
|
// Create resource definition
|
|
82
99
|
let resourceJson;
|
|
83
|
-
switch (
|
|
84
|
-
case
|
|
100
|
+
switch (functionType) {
|
|
101
|
+
case SANITY_FUNCTION_DOCUMENT:
|
|
85
102
|
resourceJson = {
|
|
86
103
|
name,
|
|
87
104
|
src: `functions/${name}`,
|
|
88
|
-
type:
|
|
105
|
+
type: SANITY_FUNCTION_DOCUMENT,
|
|
89
106
|
event: {
|
|
90
107
|
on: eventsOn,
|
|
91
108
|
},
|
|
92
109
|
};
|
|
93
110
|
break;
|
|
94
|
-
case
|
|
111
|
+
case SANITY_FUNCTION_MEDIA_LIBRARY_ASSET:
|
|
95
112
|
resourceJson = {
|
|
96
113
|
name,
|
|
97
114
|
src: `functions/${name}`,
|
|
98
|
-
type:
|
|
115
|
+
type: SANITY_FUNCTION_MEDIA_LIBRARY_ASSET,
|
|
99
116
|
event: {
|
|
100
117
|
on: eventsOn,
|
|
101
118
|
resource: { type: 'media-library', id: 'my-media-library-id' },
|
|
102
119
|
},
|
|
103
120
|
};
|
|
104
121
|
break;
|
|
105
|
-
case
|
|
122
|
+
case SANITY_FUNCTION_SCHEDULED:
|
|
106
123
|
resourceJson = {
|
|
107
124
|
name,
|
|
108
125
|
src: `functions/${name}`,
|
|
109
|
-
type:
|
|
126
|
+
type: SANITY_FUNCTION_SCHEDULED,
|
|
110
127
|
event: {
|
|
111
128
|
expression: '0 0 * * *',
|
|
112
129
|
},
|
|
113
130
|
};
|
|
114
131
|
break;
|
|
115
|
-
|
|
132
|
+
case SANITY_FUNCTION_SYNC_TAG_INVALIDATE:
|
|
133
|
+
resourceJson = {
|
|
134
|
+
name,
|
|
135
|
+
src: `functions/${name}`,
|
|
136
|
+
type: SANITY_FUNCTION_SYNC_TAG_INVALIDATE,
|
|
137
|
+
};
|
|
138
|
+
break;
|
|
116
139
|
}
|
|
117
140
|
if (!resourceJson) {
|
|
118
141
|
throw new Error('Could not create function resource based on selections');
|
package/dist/baseCommands.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Interfaces } from '@oclif/core';
|
|
2
2
|
import { Command } from '@oclif/core';
|
|
3
|
-
import type
|
|
3
|
+
import { type ReadBlueprintResult } from './actions/blueprints/blueprint.js';
|
|
4
4
|
import type { CoreResult } from './cores/index.js';
|
|
5
5
|
import type { AuthParams, ScopeType, Stack } from './utils/types.js';
|
|
6
6
|
export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof RuntimeCommand)['baseFlags'] & T['flags']>;
|
|
@@ -69,40 +69,70 @@ export declare abstract class RuntimeCommand<T extends typeof Command> extends C
|
|
|
69
69
|
}): Promise<unknown>;
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
72
|
+
* Context a command can declare it needs.
|
|
73
|
+
* The base class resolves each from the best available source during init().
|
|
74
|
+
* Dependencies are resolved implicitly -- declaring 'deployedStack' implies
|
|
75
|
+
* token, scope, and stackId without listing them.
|
|
76
|
+
*
|
|
77
|
+
* - token: Authenticated API token
|
|
78
|
+
* - blueprint: Parsed local blueprint file (filesystem)
|
|
79
|
+
* - scope: scopeType + scopeId (from flags, env, config file, or blueprint)
|
|
80
|
+
* - stackId: stackId (from flags, env, config file, blueprint, or inferred)
|
|
81
|
+
* - deployedStack: Remote Stack object fetched from the API (implies token, scope, stackId)
|
|
82
|
+
*/
|
|
83
|
+
export type Need = 'token' | 'blueprint' | 'scope' | 'stackId' | 'deployedStack';
|
|
84
|
+
/**
|
|
85
|
+
* Base command that resolves context declaratively from a `static needs` array.
|
|
86
|
+
* Commands declare what they *directly use*; the base class resolves transitive
|
|
87
|
+
* dependencies automatically (e.g. 'deployedStack' implies token, scope, stackId).
|
|
88
|
+
*
|
|
89
|
+
* When a command needs only remote context (e.g. `['deployedStack']`),
|
|
90
|
+
* no local blueprint file is required -- flags and config are sufficient.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* class InfoCommand extends ResolvedCommand<typeof InfoCommand> {
|
|
95
|
+
* static needs = ['deployedStack'] as const
|
|
96
|
+
* // token, scope, stackId resolved automatically
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
74
100
|
* @extends RuntimeCommand
|
|
75
101
|
*/
|
|
76
|
-
export declare abstract class
|
|
77
|
-
|
|
78
|
-
protected blueprint: ReadBlueprintResult;
|
|
102
|
+
export declare abstract class ResolvedCommand<T extends typeof Command> extends RuntimeCommand<T> {
|
|
103
|
+
static needs: readonly Need[];
|
|
79
104
|
static baseFlags: {
|
|
105
|
+
stack: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
106
|
+
'project-id': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
107
|
+
'organization-id': Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
80
108
|
json: Interfaces.BooleanFlag<boolean>;
|
|
81
109
|
path: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
82
110
|
trace: Interfaces.BooleanFlag<boolean>;
|
|
83
111
|
'validate-resources': Interfaces.BooleanFlag<boolean>;
|
|
84
112
|
verbose: Interfaces.BooleanFlag<boolean>;
|
|
85
113
|
};
|
|
114
|
+
protected token: string;
|
|
115
|
+
protected blueprint: ReadBlueprintResult;
|
|
116
|
+
protected scopeType: ScopeType;
|
|
117
|
+
protected scopeId: string;
|
|
118
|
+
protected stackId: string;
|
|
119
|
+
protected auth: AuthParams;
|
|
120
|
+
protected deployedStack: Stack;
|
|
86
121
|
init(): Promise<void>;
|
|
87
122
|
}
|
|
88
123
|
/**
|
|
89
|
-
* Guarantees
|
|
90
|
-
*
|
|
124
|
+
* Guarantees token and blueprint.
|
|
125
|
+
* @extends ResolvedCommand
|
|
126
|
+
*/
|
|
127
|
+
export declare abstract class LocalBlueprintCommand<T extends typeof Command> extends ResolvedCommand<T> {
|
|
128
|
+
static needs: Need[];
|
|
129
|
+
/** @deprecated Use `this.token` instead. */
|
|
130
|
+
protected get sanityToken(): string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Guarantees token, scope, stackId, deployedStack, and blueprint.
|
|
91
134
|
* @extends LocalBlueprintCommand
|
|
92
135
|
*/
|
|
93
136
|
export declare abstract class DeployedStackCommand<T extends typeof Command> extends LocalBlueprintCommand<T> {
|
|
94
|
-
|
|
95
|
-
protected deployedStack: Stack;
|
|
96
|
-
protected scopeType: ScopeType;
|
|
97
|
-
protected scopeId: string;
|
|
98
|
-
protected stackId: string;
|
|
99
|
-
static baseFlags: {
|
|
100
|
-
stack: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
101
|
-
json: Interfaces.BooleanFlag<boolean>;
|
|
102
|
-
path: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
|
|
103
|
-
trace: Interfaces.BooleanFlag<boolean>;
|
|
104
|
-
'validate-resources': Interfaces.BooleanFlag<boolean>;
|
|
105
|
-
verbose: Interfaces.BooleanFlag<boolean>;
|
|
106
|
-
};
|
|
107
|
-
init(): Promise<void>;
|
|
137
|
+
static needs: Need[];
|
|
108
138
|
}
|
package/dist/baseCommands.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// * https://oclif.io/docs/base_class
|
|
2
|
+
import { env } from 'node:process';
|
|
2
3
|
import { Command, CommandHelp, Flags as OclifFlags } from '@oclif/core';
|
|
3
|
-
import {
|
|
4
|
+
import { findBlueprintFile, loadBlueprintFile, parseBlueprintContent, } from './actions/blueprints/blueprint.js';
|
|
5
|
+
import { backfillProjectBasedStackId, readConfigFile } from './actions/blueprints/config.js';
|
|
6
|
+
import { resolveIds } from './actions/blueprints/resolve.js';
|
|
7
|
+
import { getStack, resolveStackIdByNameOrId } from './actions/blueprints/stacks.js';
|
|
8
|
+
import { presentBlueprintParserErrors } from './utils/display/errors.js';
|
|
4
9
|
import { Logger } from './utils/logger.js';
|
|
10
|
+
import { validTokenOrErrorMessage } from './utils/validated-token.js';
|
|
5
11
|
/**
|
|
6
12
|
* Fallback error-to-hint patterns for RuntimeCommand.catch().
|
|
7
13
|
* Each entry maps a regex to a function that returns suggestion strings.
|
|
@@ -155,69 +161,227 @@ export class RuntimeCommand extends Command {
|
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
164
|
+
* Base command that resolves context declaratively from a `static needs` array.
|
|
165
|
+
* Commands declare what they *directly use*; the base class resolves transitive
|
|
166
|
+
* dependencies automatically (e.g. 'deployedStack' implies token, scope, stackId).
|
|
167
|
+
*
|
|
168
|
+
* When a command needs only remote context (e.g. `['deployedStack']`),
|
|
169
|
+
* no local blueprint file is required -- flags and config are sufficient.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* class InfoCommand extends ResolvedCommand<typeof InfoCommand> {
|
|
174
|
+
* static needs = ['deployedStack'] as const
|
|
175
|
+
* // token, scope, stackId resolved automatically
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
160
179
|
* @extends RuntimeCommand
|
|
161
180
|
*/
|
|
162
|
-
export class
|
|
163
|
-
|
|
181
|
+
export class ResolvedCommand extends RuntimeCommand {
|
|
182
|
+
static needs = [];
|
|
183
|
+
static baseFlags = {
|
|
184
|
+
...baseFlags,
|
|
185
|
+
stack: stackFlag,
|
|
186
|
+
'project-id': OclifFlags.string({ ...projectIdFlagConfig, hidden: true }),
|
|
187
|
+
'organization-id': OclifFlags.string({ ...organizationIdFlagConfig, hidden: true }),
|
|
188
|
+
};
|
|
189
|
+
// Populated by init() according to `needs`
|
|
190
|
+
token;
|
|
164
191
|
blueprint;
|
|
165
|
-
|
|
192
|
+
scopeType;
|
|
193
|
+
scopeId;
|
|
194
|
+
stackId;
|
|
195
|
+
auth;
|
|
196
|
+
deployedStack;
|
|
166
197
|
async init() {
|
|
167
|
-
await super.init();
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
198
|
+
await super.init(); // RuntimeCommand: parse flags + args
|
|
199
|
+
const needs = new Set(this.constructor.needs);
|
|
200
|
+
if (needs.size === 0)
|
|
201
|
+
return;
|
|
202
|
+
const log = Logger(this.log.bind(this), this.flags);
|
|
203
|
+
const needsToken = needs.has('token') || needs.has('scope') || needs.has('stackId') || needs.has('deployedStack');
|
|
204
|
+
const needsScope = needs.has('scope') || needs.has('deployedStack');
|
|
205
|
+
const needsStack = needs.has('stackId') || needs.has('deployedStack');
|
|
206
|
+
const needsBlueprint = needs.has('blueprint');
|
|
207
|
+
const needsDeployedStack = needs.has('deployedStack');
|
|
208
|
+
// 1. Token
|
|
209
|
+
if (needsToken) {
|
|
210
|
+
const check = await validTokenOrErrorMessage(log);
|
|
211
|
+
if (!check.ok) {
|
|
212
|
+
this.error(check.error.message, {
|
|
213
|
+
suggestions: ['Run `npx @sanity/cli login` to authenticate.'],
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
this.token = check.value;
|
|
217
|
+
}
|
|
218
|
+
// 2. Gather ID sources
|
|
219
|
+
const flagIds = {
|
|
220
|
+
projectId: this.flags['project-id'],
|
|
221
|
+
organizationId: this.flags['organization-id'],
|
|
222
|
+
};
|
|
223
|
+
const envIds = {
|
|
224
|
+
organizationId: env.SANITY_ORGANIZATION_ID,
|
|
225
|
+
projectId: env.SANITY_PROJECT_ID,
|
|
226
|
+
stackId: env.SANITY_BLUEPRINT_STACK_ID,
|
|
227
|
+
};
|
|
228
|
+
// Try to read the config file (cheap single-file JSON read).
|
|
229
|
+
// Anchor to --path flag or the blueprint file location if found, else cwd.
|
|
230
|
+
const blueprintFileInfo = findBlueprintFile(this.flags.path);
|
|
231
|
+
const blueprintConfig = readConfigFile(blueprintFileInfo?.blueprintFilePath);
|
|
232
|
+
const configIds = blueprintConfig
|
|
233
|
+
? {
|
|
234
|
+
organizationId: blueprintConfig.organizationId,
|
|
235
|
+
projectId: blueprintConfig.projectId,
|
|
236
|
+
stackId: blueprintConfig.stackId,
|
|
237
|
+
}
|
|
238
|
+
: undefined;
|
|
239
|
+
// 3. Blueprint (if needed)
|
|
240
|
+
let moduleIds;
|
|
241
|
+
if (needsBlueprint) {
|
|
242
|
+
if (!blueprintFileInfo) {
|
|
243
|
+
this.error('Could not find Blueprint file! Use the `blueprints init` command.', {
|
|
244
|
+
suggestions: [
|
|
245
|
+
`Run \`${this.config.bin} blueprints init\` to create one.`,
|
|
246
|
+
`Run \`${this.config.bin} blueprints doctor\` to check your configuration.`,
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const loaded = await loadBlueprintFile(blueprintFileInfo);
|
|
251
|
+
const parsed = parseBlueprintContent(loaded.rawBlueprint, {
|
|
252
|
+
validateResources: this.flags['validate-resources'],
|
|
253
|
+
});
|
|
254
|
+
if (parsed.errors.length > 0) {
|
|
255
|
+
log(presentBlueprintParserErrors(parsed.errors));
|
|
256
|
+
this.error('Blueprint file contains errors.', {
|
|
257
|
+
suggestions: [
|
|
258
|
+
'Fix the Blueprint errors listed above.',
|
|
259
|
+
`Run \`${this.config.bin} blueprints doctor\` to check your configuration.`,
|
|
260
|
+
],
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (loaded.module) {
|
|
264
|
+
moduleIds = {
|
|
265
|
+
organizationId: loaded.module.organizationId,
|
|
266
|
+
projectId: loaded.module.projectId,
|
|
267
|
+
stackId: loaded.module.stackId,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// Assemble the full ReadBlueprintResult -- will be completed with resolved IDs below
|
|
271
|
+
this.blueprint = {
|
|
272
|
+
fileInfo: blueprintFileInfo,
|
|
273
|
+
blueprintConfig,
|
|
274
|
+
rawBlueprint: loaded.rawBlueprint,
|
|
275
|
+
...parsed,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// 4. Resolve IDs
|
|
279
|
+
const resolved = resolveIds({
|
|
280
|
+
flags: flagIds,
|
|
281
|
+
env: envIds,
|
|
282
|
+
module: moduleIds,
|
|
283
|
+
config: configIds,
|
|
173
284
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
285
|
+
// Legacy stack ID inference (only when we have a blueprint file path for the write side-effect)
|
|
286
|
+
if (!resolved.stackId && resolved.projectId && blueprintFileInfo) {
|
|
287
|
+
try {
|
|
288
|
+
const inferred = await backfillProjectBasedStackId({
|
|
289
|
+
blueprintFilePath: blueprintFileInfo.blueprintFilePath,
|
|
290
|
+
projectId: resolved.projectId,
|
|
291
|
+
logger: log,
|
|
292
|
+
});
|
|
293
|
+
if (inferred) {
|
|
294
|
+
resolved.stackId = inferred;
|
|
295
|
+
resolved.sources.stackId = 'inferred';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// assumption was wrong; leave stackId undefined
|
|
178
300
|
}
|
|
179
|
-
|
|
180
|
-
|
|
301
|
+
}
|
|
302
|
+
// Backfill resolved IDs onto the blueprint result if it was loaded
|
|
303
|
+
if (this.blueprint) {
|
|
304
|
+
Object.assign(this.blueprint, {
|
|
305
|
+
organizationId: resolved.organizationId,
|
|
306
|
+
projectId: resolved.projectId,
|
|
307
|
+
stackId: resolved.stackId,
|
|
308
|
+
scopeType: resolved.scopeType,
|
|
309
|
+
scopeId: resolved.scopeId,
|
|
310
|
+
sources: resolved.sources,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// 5. Scope
|
|
314
|
+
if (needsScope) {
|
|
315
|
+
if (!resolved.scopeType || !resolved.scopeId) {
|
|
316
|
+
this.error('Missing scope: provide --project-id or --organization-id, or configure a Blueprint.', {
|
|
317
|
+
suggestions: [
|
|
318
|
+
`Run \`${this.config.bin} blueprints doctor\` to check your configuration.`,
|
|
319
|
+
],
|
|
320
|
+
});
|
|
181
321
|
}
|
|
182
|
-
|
|
183
|
-
this.
|
|
322
|
+
this.scopeType = resolved.scopeType;
|
|
323
|
+
this.scopeId = resolved.scopeId;
|
|
324
|
+
}
|
|
325
|
+
// 6. Stack
|
|
326
|
+
if (needsStack) {
|
|
327
|
+
let { stackId } = resolved;
|
|
328
|
+
const stackOverride = this.flags.stack;
|
|
329
|
+
if (stackOverride) {
|
|
330
|
+
const auth = {
|
|
331
|
+
token: this.token,
|
|
332
|
+
scopeType: this.scopeType,
|
|
333
|
+
scopeId: this.scopeId,
|
|
334
|
+
};
|
|
335
|
+
stackId = await resolveStackIdByNameOrId(stackOverride, auth, log);
|
|
336
|
+
}
|
|
337
|
+
if (!stackId) {
|
|
338
|
+
this.error('Missing Stack: provide --stack, or configure a Blueprint with a Stack.', {
|
|
339
|
+
suggestions: [
|
|
340
|
+
`Run \`${this.config.bin} blueprints doctor\` to check your configuration.`,
|
|
341
|
+
],
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
this.stackId = stackId;
|
|
345
|
+
}
|
|
346
|
+
// 7. Auth
|
|
347
|
+
if (needsScope) {
|
|
348
|
+
this.auth = { token: this.token, scopeType: this.scopeType, scopeId: this.scopeId };
|
|
349
|
+
}
|
|
350
|
+
// 8. Deployed Stack
|
|
351
|
+
if (needsDeployedStack) {
|
|
352
|
+
const spinner = log.ora('Loading Stack deployment...').start();
|
|
353
|
+
const response = await getStack({ stackId: this.stackId, auth: this.auth, logger: log });
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
spinner.fail('Could not load Stack deployment');
|
|
356
|
+
this.error('Missing Stack deployment', {
|
|
357
|
+
suggestions: [
|
|
358
|
+
`Run \`${this.config.bin} blueprints doctor\` to check your configuration.`,
|
|
359
|
+
],
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
spinner.stop().clear();
|
|
363
|
+
this.deployedStack = response.stack;
|
|
184
364
|
}
|
|
185
|
-
this.sanityToken = result.value.token;
|
|
186
|
-
this.blueprint = result.value.blueprint;
|
|
187
365
|
}
|
|
188
366
|
}
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// Legacy base classes -- thin wrappers around ResolvedCommand
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
189
370
|
/**
|
|
190
|
-
* Guarantees
|
|
191
|
-
*
|
|
371
|
+
* Guarantees token and blueprint.
|
|
372
|
+
* @extends ResolvedCommand
|
|
373
|
+
*/
|
|
374
|
+
export class LocalBlueprintCommand extends ResolvedCommand {
|
|
375
|
+
static needs = ['token', 'blueprint'];
|
|
376
|
+
/** @deprecated Use `this.token` instead. */
|
|
377
|
+
get sanityToken() {
|
|
378
|
+
return this.token;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Guarantees token, scope, stackId, deployedStack, and blueprint.
|
|
192
383
|
* @extends LocalBlueprintCommand
|
|
193
384
|
*/
|
|
194
385
|
export class DeployedStackCommand extends LocalBlueprintCommand {
|
|
195
|
-
|
|
196
|
-
deployedStack;
|
|
197
|
-
scopeType;
|
|
198
|
-
scopeId;
|
|
199
|
-
stackId;
|
|
200
|
-
static baseFlags = { ...baseFlags, stack: stackFlag };
|
|
201
|
-
async init() {
|
|
202
|
-
await super.init();
|
|
203
|
-
const result = await initDeployedBlueprintConfig({
|
|
204
|
-
bin: this.config.bin,
|
|
205
|
-
blueprint: this.blueprint,
|
|
206
|
-
log: Logger(this.log.bind(this), this.flags),
|
|
207
|
-
token: this.sanityToken,
|
|
208
|
-
validateToken: false,
|
|
209
|
-
validateResources: this.flags['validate-resources'],
|
|
210
|
-
stackOverride: this.flags.stack,
|
|
211
|
-
});
|
|
212
|
-
if (!result.ok) {
|
|
213
|
-
this.error(result.error, {
|
|
214
|
-
suggestions: [`Run \`${this.config.bin} blueprints doctor\` to check your configuration.`],
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
this.scopeType = result.value.scopeType;
|
|
218
|
-
this.scopeId = result.value.scopeId;
|
|
219
|
-
this.stackId = result.value.stackId;
|
|
220
|
-
this.auth = result.value.auth;
|
|
221
|
-
this.deployedStack = result.value.deployedStack;
|
|
222
|
-
}
|
|
386
|
+
static needs = ['deployedStack', 'blueprint'];
|
|
223
387
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class InfoCommand extends
|
|
1
|
+
import { ResolvedCommand } from '../../baseCommands.js';
|
|
2
|
+
export default class InfoCommand extends ResolvedCommand<typeof InfoCommand> {
|
|
3
|
+
static needs: readonly ["deployedStack"];
|
|
3
4
|
static summary: string;
|
|
4
5
|
static description: string;
|
|
5
6
|
static examples: string[];
|
|
6
7
|
static flags: {
|
|
7
8
|
stack: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
10
|
};
|
|
9
11
|
run(): Promise<Record<string, unknown> | undefined>;
|
|
10
12
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import {
|
|
2
|
+
import { projectIdFlagConfig, ResolvedCommand } from '../../baseCommands.js';
|
|
3
3
|
import { blueprintInfoCore } from '../../cores/blueprints/info.js';
|
|
4
4
|
import { Logger } from '../../utils/logger.js';
|
|
5
|
-
export default class InfoCommand extends
|
|
5
|
+
export default class InfoCommand extends ResolvedCommand {
|
|
6
|
+
static needs = ['deployedStack'];
|
|
6
7
|
static summary = 'Display the status and resources of the remote Stack deployment';
|
|
7
8
|
static description = `Displays the current state and metadata of your remote Stack deployment, including deployed resources, status, and configuration.
|
|
8
9
|
|
|
@@ -12,12 +13,14 @@ Run 'blueprints stacks' to see all available Stacks in your project or organizat
|
|
|
12
13
|
static examples = [
|
|
13
14
|
'<%= config.bin %> <%= command.id %>',
|
|
14
15
|
'<%= config.bin %> <%= command.id %> --stack <name-or-id>',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> --project-id <id> --stack <name-or-id>',
|
|
15
17
|
];
|
|
16
18
|
static flags = {
|
|
17
19
|
stack: Flags.string({
|
|
18
|
-
description: 'Stack name or ID
|
|
20
|
+
description: 'Stack name or ID',
|
|
19
21
|
aliases: ['id'],
|
|
20
22
|
}),
|
|
23
|
+
'project-id': Flags.string({ ...projectIdFlagConfig }),
|
|
21
24
|
};
|
|
22
25
|
async run() {
|
|
23
26
|
const result = await blueprintInfoCore({
|
|
@@ -8,7 +8,7 @@ export default class AddCommand extends LocalBlueprintCommand {
|
|
|
8
8
|
static summary = 'Add a Function to your Blueprint';
|
|
9
9
|
static description = `Scaffolds a new Function in the functions/ folder and templates a resource for your Blueprint manifest.
|
|
10
10
|
|
|
11
|
-
Functions are serverless handlers triggered by document events (create, update, delete, publish)
|
|
11
|
+
Functions are serverless handlers triggered by document, live content or media-library events (create, update, delete, publish).
|
|
12
12
|
|
|
13
13
|
After adding, use 'functions dev' to test locally, then 'blueprints deploy' to publish.`;
|
|
14
14
|
static examples = [
|