@sanity/cli 3.77.3-server-side-schemas.26 → 3.78.1-mcp.17
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/lib/_chunks-cjs/cli.js +286 -87
- package/lib/_chunks-cjs/cli.js.map +1 -1
- package/package.json +9 -8
- package/src/actions/init-project/initProject.ts +117 -40
- package/src/commands/functions/devFunctionsCommand.ts +42 -0
- package/src/commands/functions/functionsGroup.ts +11 -0
- package/src/commands/functions/logsFunctionsCommand.ts +46 -0
- package/src/commands/functions/testFunctionsCommand.ts +62 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/init/initCommand.ts +0 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@sanity/cli",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.78.1-mcp.17+40192fd3e0",
|
4
4
|
"description": "Sanity CLI tool for managing Sanity installations, managing plugins, schemas and datasets",
|
5
5
|
"keywords": [
|
6
6
|
"sanity",
|
@@ -58,10 +58,11 @@
|
|
58
58
|
"dependencies": {
|
59
59
|
"@babel/traverse": "^7.23.5",
|
60
60
|
"@sanity/client": "^6.28.2",
|
61
|
-
"@sanity/codegen": "3.
|
61
|
+
"@sanity/codegen": "3.78.1-mcp.17+40192fd3e0",
|
62
|
+
"@sanity/runtime-cli": "^1.1.1",
|
62
63
|
"@sanity/telemetry": "^0.7.7",
|
63
64
|
"@sanity/template-validator": "^2.4.0",
|
64
|
-
"@sanity/util": "3.
|
65
|
+
"@sanity/util": "3.78.1-mcp.17+40192fd3e0",
|
65
66
|
"chalk": "^4.1.2",
|
66
67
|
"debug": "^4.3.4",
|
67
68
|
"decompress": "^4.2.0",
|
@@ -75,13 +76,13 @@
|
|
75
76
|
"validate-npm-package-name": "^3.0.0"
|
76
77
|
},
|
77
78
|
"devDependencies": {
|
78
|
-
"@repo/package.config": "3.
|
79
|
-
"@repo/test-config": "3.
|
79
|
+
"@repo/package.config": "3.78.0",
|
80
|
+
"@repo/test-config": "3.78.0",
|
80
81
|
"@rexxars/gitconfiglocal": "^3.0.1",
|
81
82
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
82
83
|
"@sanity/eslint-config-studio": "^4.0.0",
|
83
84
|
"@sanity/generate-help-url": "^3.0.0",
|
84
|
-
"@sanity/types": "3.
|
85
|
+
"@sanity/types": "3.78.1-mcp.17+40192fd3e0",
|
85
86
|
"@types/babel__traverse": "^7.20.5",
|
86
87
|
"@types/configstore": "^5.0.1",
|
87
88
|
"@types/cpx": "^1.5.2",
|
@@ -115,7 +116,7 @@
|
|
115
116
|
"minimist": "^1.2.5",
|
116
117
|
"open": "^8.4.0",
|
117
118
|
"ora": "^8.0.1",
|
118
|
-
"p-
|
119
|
+
"p-map": "^4.0.0",
|
119
120
|
"p-timeout": "^4.0.0",
|
120
121
|
"preferred-pm": "^3.0.3",
|
121
122
|
"promise-props-recursive": "^2.0.2",
|
@@ -133,5 +134,5 @@
|
|
133
134
|
"engines": {
|
134
135
|
"node": ">=18"
|
135
136
|
},
|
136
|
-
"gitHead": "
|
137
|
+
"gitHead": "40192fd3e0e097005847f83caca774d9b0af9145"
|
137
138
|
}
|
@@ -8,7 +8,7 @@ import {type detectFrameworkRecord} from '@vercel/fs-detectors'
|
|
8
8
|
import dotenv from 'dotenv'
|
9
9
|
import execa, {type CommonOptions} from 'execa'
|
10
10
|
import {deburr, noop} from 'lodash'
|
11
|
-
import
|
11
|
+
import pMap from 'p-map'
|
12
12
|
import resolveFrom from 'resolve-from'
|
13
13
|
import semver from 'semver'
|
14
14
|
|
@@ -108,6 +108,16 @@ export interface ProjectOrganization {
|
|
108
108
|
slug: string
|
109
109
|
}
|
110
110
|
|
111
|
+
interface OrganizationCreateResponse {
|
112
|
+
id: string
|
113
|
+
name: string
|
114
|
+
createdByUserId: string
|
115
|
+
slug: string | null
|
116
|
+
defaultRoleName: string | null
|
117
|
+
members: unknown[]
|
118
|
+
features: unknown[]
|
119
|
+
}
|
120
|
+
|
111
121
|
// eslint-disable-next-line max-statements, complexity
|
112
122
|
export default async function initSanity(
|
113
123
|
args: CliCommandArguments<InitFlags>,
|
@@ -257,13 +267,14 @@ export default async function initSanity(
|
|
257
267
|
const hasToken = userConfig.get('authToken')
|
258
268
|
|
259
269
|
debug(hasToken ? 'User already has a token' : 'User has no token')
|
270
|
+
let user: SanityUser | undefined
|
260
271
|
if (hasToken) {
|
261
272
|
trace.log({step: 'login', alreadyLoggedIn: true})
|
262
|
-
|
273
|
+
user = await getUserData(apiClient)
|
263
274
|
success('You are logged in as %s using %s', user.email, getProviderName(user.provider))
|
264
275
|
} else if (!unattended) {
|
265
276
|
trace.log({step: 'login'})
|
266
|
-
await getOrCreateUser()
|
277
|
+
user = await getOrCreateUser()
|
267
278
|
}
|
268
279
|
|
269
280
|
// skip project / dataset prompting
|
@@ -712,6 +723,7 @@ export default async function initSanity(
|
|
712
723
|
const {extOptions, ...otherArgs} = args
|
713
724
|
const loginArgs: CliCommandArguments<LoginFlags> = {...otherArgs, extOptions: {}}
|
714
725
|
await login(loginArgs, {...context, telemetry: trace.newContext('login')})
|
726
|
+
return getUserData(apiClient)
|
715
727
|
}
|
716
728
|
|
717
729
|
async function getProjectDetails(): Promise<{
|
@@ -858,7 +870,22 @@ export default async function initSanity(
|
|
858
870
|
? 'No projects found for user, prompting for name'
|
859
871
|
: 'Using a coupon - skipping project selection',
|
860
872
|
)
|
861
|
-
const projectName = await prompt.single({
|
873
|
+
const projectName = await prompt.single({
|
874
|
+
type: 'input',
|
875
|
+
message: 'Project name:',
|
876
|
+
default: 'My Sanity Project',
|
877
|
+
validate(input) {
|
878
|
+
if (!input || input.trim() === '') {
|
879
|
+
return 'Project name cannot be empty'
|
880
|
+
}
|
881
|
+
|
882
|
+
if (input.length > 80) {
|
883
|
+
return 'Project name cannot be longer than 80 characters'
|
884
|
+
}
|
885
|
+
|
886
|
+
return true
|
887
|
+
},
|
888
|
+
})
|
862
889
|
|
863
890
|
return createProject(apiClient, {
|
864
891
|
displayName: projectName,
|
@@ -1242,47 +1269,90 @@ export default async function initSanity(
|
|
1242
1269
|
return cliFlags
|
1243
1270
|
}
|
1244
1271
|
|
1272
|
+
async function createOrganization(
|
1273
|
+
props: {name?: string} = {},
|
1274
|
+
): Promise<OrganizationCreateResponse> {
|
1275
|
+
const name =
|
1276
|
+
props.name ||
|
1277
|
+
(await prompt.single({
|
1278
|
+
type: 'input',
|
1279
|
+
message: 'Organization name:',
|
1280
|
+
default: user ? user.name : undefined,
|
1281
|
+
validate(input) {
|
1282
|
+
if (input.length === 0) {
|
1283
|
+
return 'Organization name cannot be empty'
|
1284
|
+
} else if (input.length > 100) {
|
1285
|
+
return 'Organization name cannot be longer than 100 characters'
|
1286
|
+
}
|
1287
|
+
return true
|
1288
|
+
},
|
1289
|
+
}))
|
1290
|
+
|
1291
|
+
const spinner = context.output.spinner('Creating organization').start()
|
1292
|
+
const client = apiClient({requireProject: false, requireUser: true})
|
1293
|
+
const organization = await client.request({
|
1294
|
+
uri: '/organizations',
|
1295
|
+
method: 'POST',
|
1296
|
+
body: {name},
|
1297
|
+
})
|
1298
|
+
spinner.succeed()
|
1299
|
+
|
1300
|
+
return organization
|
1301
|
+
}
|
1302
|
+
|
1245
1303
|
async function getOrganizationId(organizations: ProjectOrganization[]) {
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1304
|
+
// In unattended mode, if the user hasn't specified an organization, sending null as
|
1305
|
+
// organization ID to the API will create a new organization for the user with their
|
1306
|
+
// user name. If they _have_ specified an organization, we'll use that.
|
1307
|
+
if (unattended || flags.organization) {
|
1308
|
+
return flags.organization || undefined
|
1249
1309
|
}
|
1250
1310
|
|
1251
|
-
|
1252
|
-
if
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
debug('User lacks project attach grant in all organizations, not prompting')
|
1257
|
-
return undefined
|
1258
|
-
}
|
1311
|
+
// If the user has no organizations, prompt them to create one with the same name as
|
1312
|
+
// their user, but allow them to customize it if they want
|
1313
|
+
if (organizations.length === 0) {
|
1314
|
+
return createOrganization().then((org) => org.id)
|
1315
|
+
}
|
1259
1316
|
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1317
|
+
// If the user has organizations, let them choose from them, but also allow them to
|
1318
|
+
// create a new one in case they do not have access to any of them, or they want to
|
1319
|
+
// create a personal/other organization.
|
1320
|
+
debug(`User has ${organizations.length} organization(s), checking attach access`)
|
1321
|
+
const withGrantInfo = await getOrganizationsWithAttachGrantInfo(organizations)
|
1322
|
+
const withAttach = withGrantInfo.filter(({hasAttachGrant}) => hasAttachGrant)
|
1323
|
+
|
1324
|
+
debug('User has attach access to %d organizations.', withAttach.length)
|
1325
|
+
const organizationChoices = [
|
1326
|
+
...withGrantInfo.map(({organization, hasAttachGrant}) => ({
|
1327
|
+
value: organization.id,
|
1328
|
+
name: `${organization.name} [${organization.id}]`,
|
1329
|
+
disabled: hasAttachGrant ? false : 'Insufficient permissions',
|
1330
|
+
})),
|
1331
|
+
new prompt.Separator(),
|
1332
|
+
{value: '-new-', name: 'Create new organization'},
|
1333
|
+
new prompt.Separator(),
|
1334
|
+
]
|
1335
|
+
|
1336
|
+
// If the user only has a single organization (and they have attach access to it),
|
1337
|
+
// we'll default to that one. Otherwise, we'll default to the organization with the
|
1338
|
+
// same name as the user if it exists.
|
1339
|
+
const defaultOrganizationId =
|
1340
|
+
withAttach.length === 1
|
1341
|
+
? withAttach[0].organization.id
|
1342
|
+
: organizations.find((org) => org.name === user?.name)?.id
|
1343
|
+
|
1344
|
+
const chosenOrg = await prompt.single({
|
1345
|
+
message: 'Select organization:',
|
1346
|
+
type: 'list',
|
1347
|
+
default: defaultOrganizationId || undefined,
|
1348
|
+
choices: organizationChoices,
|
1349
|
+
})
|
1275
1350
|
|
1276
|
-
|
1277
|
-
|
1278
|
-
}
|
1279
|
-
} else if (orgId) {
|
1280
|
-
debug(`User has defined organization flag explicitly (%s)`, orgId)
|
1281
|
-
} else if (organizations.length === 0) {
|
1282
|
-
debug('User has no organizations, skipping selection prompt')
|
1351
|
+
if (chosenOrg === '-new-') {
|
1352
|
+
return createOrganization().then((org) => org.id)
|
1283
1353
|
}
|
1284
1354
|
|
1285
|
-
return
|
1355
|
+
return chosenOrg || undefined
|
1286
1356
|
}
|
1287
1357
|
|
1288
1358
|
async function hasProjectAttachGrant(orgId: string) {
|
@@ -1301,8 +1371,15 @@ export default async function initSanity(
|
|
1301
1371
|
)
|
1302
1372
|
}
|
1303
1373
|
|
1304
|
-
function
|
1305
|
-
return
|
1374
|
+
function getOrganizationsWithAttachGrantInfo(organizations: ProjectOrganization[]) {
|
1375
|
+
return pMap(
|
1376
|
+
organizations,
|
1377
|
+
async (organization) => ({
|
1378
|
+
hasAttachGrant: await hasProjectAttachGrant(organization.id),
|
1379
|
+
organization,
|
1380
|
+
}),
|
1381
|
+
{concurrency: 3},
|
1382
|
+
)
|
1306
1383
|
}
|
1307
1384
|
|
1308
1385
|
async function createOrAppendEnvVars(
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import open from 'open'
|
2
|
+
|
3
|
+
import {type CliCommandDefinition} from '../../types'
|
4
|
+
|
5
|
+
const helpText = `
|
6
|
+
Options
|
7
|
+
--port <port> Port to start emulator on
|
8
|
+
|
9
|
+
Examples
|
10
|
+
# Start dev server on default port
|
11
|
+
sanity functions dev
|
12
|
+
|
13
|
+
# Start dev server on specific port
|
14
|
+
sanity functions dev --port 3333
|
15
|
+
`
|
16
|
+
|
17
|
+
const defaultFlags = {
|
18
|
+
port: 8080,
|
19
|
+
}
|
20
|
+
|
21
|
+
const devFunctionsCommand: CliCommandDefinition = {
|
22
|
+
name: 'dev',
|
23
|
+
group: 'functions',
|
24
|
+
helpText,
|
25
|
+
signature: '',
|
26
|
+
description: 'Start the Sanity Function emulator',
|
27
|
+
hideFromHelp: true,
|
28
|
+
async action(args, context) {
|
29
|
+
const {output} = context
|
30
|
+
const {print} = output
|
31
|
+
const flags = {...defaultFlags, ...args.extOptions}
|
32
|
+
|
33
|
+
const {devAction} = await import('@sanity/runtime-cli')
|
34
|
+
|
35
|
+
devAction(flags.port)
|
36
|
+
|
37
|
+
print(`Server is running on port ${flags.port}\n`)
|
38
|
+
open(`http://localhost:${flags.port}`)
|
39
|
+
},
|
40
|
+
}
|
41
|
+
|
42
|
+
export default devFunctionsCommand
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import {type CliCommandGroupDefinition} from '../../types'
|
2
|
+
|
3
|
+
const functionsGroup: CliCommandGroupDefinition = {
|
4
|
+
name: 'functions',
|
5
|
+
signature: '[COMMAND]',
|
6
|
+
isGroupRoot: true,
|
7
|
+
description: 'Test Sanity Functions locally and retrieve logs',
|
8
|
+
hideFromHelp: true,
|
9
|
+
}
|
10
|
+
|
11
|
+
export default functionsGroup
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import {type CliCommandDefinition} from '../../types'
|
2
|
+
|
3
|
+
const helpText = `
|
4
|
+
Options
|
5
|
+
--id <id> The ID of the function to retrieve logs for
|
6
|
+
|
7
|
+
Examples
|
8
|
+
# Retrieve logs for Sanity Function abcd1234
|
9
|
+
sanity functions logs --id abcd1234
|
10
|
+
`
|
11
|
+
|
12
|
+
const defaultFlags = {
|
13
|
+
id: undefined,
|
14
|
+
}
|
15
|
+
|
16
|
+
const logsFunctionsCommand: CliCommandDefinition = {
|
17
|
+
name: 'logs',
|
18
|
+
group: 'functions',
|
19
|
+
helpText,
|
20
|
+
signature: '',
|
21
|
+
description: 'Retrieve logs for a Sanity Function',
|
22
|
+
hideFromHelp: true,
|
23
|
+
async action(args, context) {
|
24
|
+
const {apiClient, output} = context
|
25
|
+
const {print} = output
|
26
|
+
const flags = {...defaultFlags, ...args.extOptions}
|
27
|
+
|
28
|
+
const client = apiClient({
|
29
|
+
requireUser: true,
|
30
|
+
requireProject: false,
|
31
|
+
})
|
32
|
+
|
33
|
+
if (flags.id) {
|
34
|
+
const token = client.config().token
|
35
|
+
if (token) {
|
36
|
+
const {logsAction} = await import('@sanity/runtime-cli')
|
37
|
+
const result = await logsAction(flags.id, token)
|
38
|
+
print(JSON.stringify(result, null, 2))
|
39
|
+
}
|
40
|
+
} else {
|
41
|
+
print('You must provide a function ID')
|
42
|
+
}
|
43
|
+
},
|
44
|
+
}
|
45
|
+
|
46
|
+
export default logsFunctionsCommand
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import {type CliCommandDefinition} from '../../types'
|
2
|
+
|
3
|
+
const helpText = `
|
4
|
+
Options
|
5
|
+
--data <data> Data to send to the function
|
6
|
+
--file <file> Read data from file and send to the function
|
7
|
+
--path <path> Path to your Sanity Function code
|
8
|
+
--timeout <timeout> Execution timeout value in seconds
|
9
|
+
|
10
|
+
Examples
|
11
|
+
# Test function passing event data on command line
|
12
|
+
sanity functions test --path ./test.ts --data '{ "id": 1 }'
|
13
|
+
|
14
|
+
# Test function passing event data via a file
|
15
|
+
sanity functions test -path ./test.js --file 'payload.json'
|
16
|
+
|
17
|
+
# Test function passing event data on command line and cap execution time to 60 seconds
|
18
|
+
sanity functions test -path ./test.ts --data '{ "id": 1 }' --timeout 60
|
19
|
+
`
|
20
|
+
|
21
|
+
const defaultFlags = {
|
22
|
+
data: undefined,
|
23
|
+
file: undefined,
|
24
|
+
path: undefined,
|
25
|
+
timeout: 5, // seconds
|
26
|
+
}
|
27
|
+
|
28
|
+
const testFunctionsCommand: CliCommandDefinition = {
|
29
|
+
name: 'test',
|
30
|
+
group: 'functions',
|
31
|
+
helpText,
|
32
|
+
signature: '',
|
33
|
+
description: 'Invoke a local Sanity Function',
|
34
|
+
hideFromHelp: true,
|
35
|
+
async action(args, context) {
|
36
|
+
const {output} = context
|
37
|
+
const {print} = output
|
38
|
+
const flags = {...defaultFlags, ...args.extOptions}
|
39
|
+
|
40
|
+
if (flags.path) {
|
41
|
+
const {testAction} = await import('@sanity/runtime-cli')
|
42
|
+
const {json, logs, error} = await testAction(flags.path, {
|
43
|
+
data: flags.data,
|
44
|
+
file: flags.file,
|
45
|
+
timeout: flags.timeout,
|
46
|
+
})
|
47
|
+
|
48
|
+
if (error) {
|
49
|
+
print(error.toString())
|
50
|
+
} else {
|
51
|
+
print('Logs:')
|
52
|
+
print(logs)
|
53
|
+
print('Response:')
|
54
|
+
print(JSON.stringify(json, null, 2))
|
55
|
+
}
|
56
|
+
} else {
|
57
|
+
print('You must provide a path to the Sanity Function code')
|
58
|
+
}
|
59
|
+
},
|
60
|
+
}
|
61
|
+
|
62
|
+
export default testFunctionsCommand
|
package/src/commands/index.ts
CHANGED
@@ -2,6 +2,10 @@ import {type CliCommandDefinition, type CliCommandGroupDefinition} from '../type
|
|
2
2
|
import codemodCommand from './codemod/codemodCommand'
|
3
3
|
import debugCommand from './debug/debugCommand'
|
4
4
|
import docsCommand from './docs/docsCommand'
|
5
|
+
import devfunctionsCommand from './functions/devFunctionsCommand'
|
6
|
+
import functionsGroup from './functions/functionsGroup'
|
7
|
+
import logsfunctionsCommand from './functions/logsFunctionsCommand'
|
8
|
+
import testfunctionsCommand from './functions/testFunctionsCommand'
|
5
9
|
import helpCommand from './help/helpCommand'
|
6
10
|
import initCommand from './init/initCommand'
|
7
11
|
import installCommand from './install/installCommand'
|
@@ -39,4 +43,8 @@ export const baseCommands: (CliCommandDefinition | CliCommandGroupDefinition)[]
|
|
39
43
|
telemetryStatusCommand,
|
40
44
|
generateTypegenCommand,
|
41
45
|
typegenGroup,
|
46
|
+
functionsGroup,
|
47
|
+
devfunctionsCommand,
|
48
|
+
logsfunctionsCommand,
|
49
|
+
testfunctionsCommand,
|
42
50
|
]
|
@@ -101,7 +101,6 @@ export const initCommand: CliCommandDefinition<InitFlags> = {
|
|
101
101
|
description: 'Initializes a new Sanity Studio and/or project',
|
102
102
|
helpText,
|
103
103
|
action: async (args, context) => {
|
104
|
-
const {output, chalk} = context
|
105
104
|
const [type] = args.argsWithoutOptions
|
106
105
|
|
107
106
|
// `sanity init whatever`
|