@sanity/cli 8.7.0 → 8.8.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 +396 -0
- package/dist/actions/api/distillApiRoutes.js +1 -1
- package/dist/actions/api/distillApiRoutes.js.map +1 -1
- package/dist/actions/api/resolveEndpoint.js +1 -1
- package/dist/actions/api/resolveEndpoint.js.map +1 -1
- package/dist/actions/backup/archiveDir.js +7 -3
- package/dist/actions/backup/archiveDir.js.map +1 -1
- package/dist/actions/context/resolveOrganizationId.js +17 -0
- package/dist/actions/context/resolveOrganizationId.js.map +1 -0
- package/dist/actions/context/waitForJob.js +24 -0
- package/dist/actions/context/waitForJob.js.map +1 -0
- package/dist/actions/context/watchJob.js +32 -0
- package/dist/actions/context/watchJob.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +6 -6
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +6 -6
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/exec/execScript.js +6 -18
- package/dist/actions/exec/execScript.js.map +1 -1
- package/dist/actions/exec/runScript.worker.js +21 -0
- package/dist/actions/exec/runScript.worker.js.map +1 -0
- package/dist/actions/init/initAction.js +1 -1
- package/dist/actions/init/initAction.js.map +1 -1
- package/dist/actions/media/importMedia.js +4 -2
- package/dist/actions/media/importMedia.js.map +1 -1
- package/dist/commands/backups/download.js +8 -17
- package/dist/commands/backups/download.js.map +1 -1
- package/dist/commands/context/build.js +111 -0
- package/dist/commands/context/build.js.map +1 -0
- package/dist/commands/context/create.js +106 -0
- package/dist/commands/context/create.js.map +1 -0
- package/dist/commands/context/delete.js +68 -0
- package/dist/commands/context/delete.js.map +1 -0
- package/dist/commands/context/get.js +95 -0
- package/dist/commands/context/get.js.map +1 -0
- package/dist/commands/context/imports/create.js +270 -0
- package/dist/commands/context/imports/create.js.map +1 -0
- package/dist/commands/context/imports/delete.js +73 -0
- package/dist/commands/context/imports/delete.js.map +1 -0
- package/dist/commands/context/imports/download.js +65 -0
- package/dist/commands/context/imports/download.js.map +1 -0
- package/dist/commands/context/imports/get.js +93 -0
- package/dist/commands/context/imports/get.js.map +1 -0
- package/dist/commands/context/imports/list.js +105 -0
- package/dist/commands/context/imports/list.js.map +1 -0
- package/dist/commands/context/jobs/get.js +90 -0
- package/dist/commands/context/jobs/get.js.map +1 -0
- package/dist/commands/context/list.js +109 -0
- package/dist/commands/context/list.js.map +1 -0
- package/dist/commands/context/refresh.js +46 -0
- package/dist/commands/context/refresh.js.map +1 -0
- package/dist/commands/context/update.js +127 -0
- package/dist/commands/context/update.js.map +1 -0
- package/dist/commands/datasets/alias/delete.js +4 -9
- package/dist/commands/datasets/alias/delete.js.map +1 -1
- package/dist/commands/datasets/alias/unlink.js +4 -9
- package/dist/commands/datasets/alias/unlink.js.map +1 -1
- package/dist/commands/datasets/delete.js +4 -9
- package/dist/commands/datasets/delete.js.map +1 -1
- package/dist/commands/graphql/undeploy.js +4 -9
- package/dist/commands/graphql/undeploy.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/media/create-aspect.js +5 -6
- package/dist/commands/media/create-aspect.js.map +1 -1
- package/dist/commands/media/delete-aspect.js +7 -23
- package/dist/commands/media/delete-aspect.js.map +1 -1
- package/dist/commands/media/deploy-aspect.js +4 -9
- package/dist/commands/media/deploy-aspect.js.map +1 -1
- package/dist/commands/media/export.js +3 -7
- package/dist/commands/media/export.js.map +1 -1
- package/dist/commands/media/import.js +3 -7
- package/dist/commands/media/import.js.map +1 -1
- package/dist/commands/organizations/create.js +4 -9
- package/dist/commands/organizations/create.js.map +1 -1
- package/dist/commands/schemas/delete.js +3 -8
- package/dist/commands/schemas/delete.js.map +1 -1
- package/dist/commands/tokens/delete.js +4 -7
- package/dist/commands/tokens/delete.js.map +1 -1
- package/dist/commands/users/invite.js +8 -18
- package/dist/commands/users/invite.js.map +1 -1
- package/dist/exports/index.js +1 -1
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +18 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/generated/apiRoutes.js +11 -9
- package/dist/generated/apiRoutes.js.map +1 -1
- package/dist/prompts/promptForOrganization.js +19 -0
- package/dist/prompts/promptForOrganization.js.map +1 -0
- package/dist/services/context.js +132 -0
- package/dist/services/context.js.map +1 -0
- package/dist/util/sharedFlags.js +24 -0
- package/dist/util/sharedFlags.js.map +1 -1
- package/oclif.config.js +1 -0
- package/oclif.manifest.json +1931 -1133
- package/package.json +16 -22
package/README.md
CHANGED
|
@@ -33,6 +33,19 @@ Code for sanity cli
|
|
|
33
33
|
- [`sanity blueprints stacks`](#sanity-blueprints-stacks)
|
|
34
34
|
- [`sanity build [OUTPUTDIR]`](#sanity-build-outputdir)
|
|
35
35
|
- [`sanity codemod [CODEMODNAME]`](#sanity-codemod-codemodname)
|
|
36
|
+
- [`sanity context build KNOWLEDGEBASEID`](#sanity-context-build-knowledgebaseid)
|
|
37
|
+
- [`sanity context create`](#sanity-context-create)
|
|
38
|
+
- [`sanity context delete KNOWLEDGEBASEID`](#sanity-context-delete-knowledgebaseid)
|
|
39
|
+
- [`sanity context get KNOWLEDGEBASEID`](#sanity-context-get-knowledgebaseid)
|
|
40
|
+
- [`sanity context imports create KNOWLEDGEBASEID`](#sanity-context-imports-create-knowledgebaseid)
|
|
41
|
+
- [`sanity context imports delete KNOWLEDGEBASEID IMPORTID`](#sanity-context-imports-delete-knowledgebaseid-importid)
|
|
42
|
+
- [`sanity context imports download KNOWLEDGEBASEID IMPORTID`](#sanity-context-imports-download-knowledgebaseid-importid)
|
|
43
|
+
- [`sanity context imports get KNOWLEDGEBASEID IMPORTID`](#sanity-context-imports-get-knowledgebaseid-importid)
|
|
44
|
+
- [`sanity context imports list KNOWLEDGEBASEID`](#sanity-context-imports-list-knowledgebaseid)
|
|
45
|
+
- [`sanity context jobs get KNOWLEDGEBASEID JOBID`](#sanity-context-jobs-get-knowledgebaseid-jobid)
|
|
46
|
+
- [`sanity context list`](#sanity-context-list)
|
|
47
|
+
- [`sanity context refresh KNOWLEDGEBASEID`](#sanity-context-refresh-knowledgebaseid)
|
|
48
|
+
- [`sanity context update KNOWLEDGEBASEID`](#sanity-context-update-knowledgebaseid)
|
|
36
49
|
- [`sanity cors add ORIGIN`](#sanity-cors-add-origin)
|
|
37
50
|
- [`sanity cors delete [ORIGIN]`](#sanity-cors-delete-origin)
|
|
38
51
|
- [`sanity cors list`](#sanity-cors-list)
|
|
@@ -950,6 +963,389 @@ EXAMPLES
|
|
|
950
963
|
$ sanity codemod reactIconsV3 --dry
|
|
951
964
|
```
|
|
952
965
|
|
|
966
|
+
## `sanity context build KNOWLEDGEBASEID`
|
|
967
|
+
|
|
968
|
+
Build a knowledge base from its imported content
|
|
969
|
+
|
|
970
|
+
```
|
|
971
|
+
USAGE
|
|
972
|
+
$ sanity context build KNOWLEDGEBASEID [--cancel | --watch]
|
|
973
|
+
|
|
974
|
+
ARGUMENTS
|
|
975
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
976
|
+
|
|
977
|
+
FLAGS
|
|
978
|
+
--cancel Cancel the running build instead of starting one
|
|
979
|
+
--watch Wait for the build to finish, exiting non-zero if it fails
|
|
980
|
+
|
|
981
|
+
DESCRIPTION
|
|
982
|
+
Build a knowledge base from its imported content
|
|
983
|
+
|
|
984
|
+
EXAMPLES
|
|
985
|
+
Start a build and return the job ID
|
|
986
|
+
|
|
987
|
+
$ sanity context build kb-abc123
|
|
988
|
+
|
|
989
|
+
Start a build and wait for it to finish (non-zero exit on failure)
|
|
990
|
+
|
|
991
|
+
$ sanity context build kb-abc123 --watch
|
|
992
|
+
|
|
993
|
+
Cancel the running build, if any
|
|
994
|
+
|
|
995
|
+
$ sanity context build kb-abc123 --cancel
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
## `sanity context create`
|
|
999
|
+
|
|
1000
|
+
Create a knowledge base
|
|
1001
|
+
|
|
1002
|
+
```
|
|
1003
|
+
USAGE
|
|
1004
|
+
$ sanity context create [--organization <id>] [--description <value>] [--title <value>]
|
|
1005
|
+
|
|
1006
|
+
FLAGS
|
|
1007
|
+
--description=<value> Knowledge base description
|
|
1008
|
+
--title=<value> Knowledge base title
|
|
1009
|
+
|
|
1010
|
+
OVERRIDE FLAGS
|
|
1011
|
+
--organization=<id> Organization to create the knowledge base in (overrides CLI configuration)
|
|
1012
|
+
|
|
1013
|
+
DESCRIPTION
|
|
1014
|
+
Create a knowledge base
|
|
1015
|
+
|
|
1016
|
+
EXAMPLES
|
|
1017
|
+
Interactively create a knowledge base
|
|
1018
|
+
|
|
1019
|
+
$ sanity context create
|
|
1020
|
+
|
|
1021
|
+
Create a knowledge base in a specific organization
|
|
1022
|
+
|
|
1023
|
+
$ sanity context create --organization org-abc123 --title "Support docs" --description "Product docs and \
|
|
1024
|
+
troubleshooting guides"
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
## `sanity context delete KNOWLEDGEBASEID`
|
|
1028
|
+
|
|
1029
|
+
Delete a knowledge base and its generated content
|
|
1030
|
+
|
|
1031
|
+
```
|
|
1032
|
+
USAGE
|
|
1033
|
+
$ sanity context delete KNOWLEDGEBASEID [-y]
|
|
1034
|
+
|
|
1035
|
+
ARGUMENTS
|
|
1036
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1037
|
+
|
|
1038
|
+
FLAGS
|
|
1039
|
+
-y, --yes Skip confirmation prompt (unattended mode)
|
|
1040
|
+
|
|
1041
|
+
DESCRIPTION
|
|
1042
|
+
Delete a knowledge base and its generated content
|
|
1043
|
+
|
|
1044
|
+
EXAMPLES
|
|
1045
|
+
Delete a knowledge base after confirmation
|
|
1046
|
+
|
|
1047
|
+
$ sanity context delete kb-abc123
|
|
1048
|
+
|
|
1049
|
+
Delete a knowledge base without confirmation prompt
|
|
1050
|
+
|
|
1051
|
+
$ sanity context delete kb-abc123 --yes
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
## `sanity context get KNOWLEDGEBASEID`
|
|
1055
|
+
|
|
1056
|
+
Get details of a knowledge base
|
|
1057
|
+
|
|
1058
|
+
```
|
|
1059
|
+
USAGE
|
|
1060
|
+
$ sanity context get KNOWLEDGEBASEID [--json]
|
|
1061
|
+
|
|
1062
|
+
ARGUMENTS
|
|
1063
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1064
|
+
|
|
1065
|
+
FLAGS
|
|
1066
|
+
--json Output the knowledge base in JSON format
|
|
1067
|
+
|
|
1068
|
+
DESCRIPTION
|
|
1069
|
+
Get details of a knowledge base
|
|
1070
|
+
|
|
1071
|
+
EXAMPLES
|
|
1072
|
+
Get details of a specific knowledge base
|
|
1073
|
+
|
|
1074
|
+
$ sanity context get kb-abc123
|
|
1075
|
+
|
|
1076
|
+
Output the knowledge base as JSON
|
|
1077
|
+
|
|
1078
|
+
$ sanity context get kb-abc123 --json
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
## `sanity context imports create KNOWLEDGEBASEID`
|
|
1082
|
+
|
|
1083
|
+
Import content into a knowledge base
|
|
1084
|
+
|
|
1085
|
+
```
|
|
1086
|
+
USAGE
|
|
1087
|
+
$ sanity context imports create KNOWLEDGEBASEID [--content-type <mime>] [--file <path> | --text <value> | --url <url> |
|
|
1088
|
+
--query <value>] [--sanity-dataset <name> ] [--sanity-project <id> ] [--title <value> ]
|
|
1089
|
+
|
|
1090
|
+
ARGUMENTS
|
|
1091
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1092
|
+
|
|
1093
|
+
FLAGS
|
|
1094
|
+
--content-type=<mime> Content type of the import (--text: text/markdown or text/plain; --file: any MIME type,
|
|
1095
|
+
inferred from the file extension when omitted)
|
|
1096
|
+
--file=<path> Path to a local file to import
|
|
1097
|
+
--query=<value> GROQ query binding a Sanity dataset as a source
|
|
1098
|
+
--sanity-dataset=<name> Sanity dataset for a dataset import
|
|
1099
|
+
--sanity-project=<id> Sanity project ID for a dataset import
|
|
1100
|
+
--text=<value> Inline text content to import (requires --title)
|
|
1101
|
+
--title=<value> Title for an inline text import
|
|
1102
|
+
--url=<url> Website URL to crawl
|
|
1103
|
+
|
|
1104
|
+
DESCRIPTION
|
|
1105
|
+
Import content into a knowledge base
|
|
1106
|
+
|
|
1107
|
+
EXAMPLES
|
|
1108
|
+
Import inline text
|
|
1109
|
+
|
|
1110
|
+
$ sanity context imports create kb-abc123 --text "Refunds are processed within 5 days" --title "Refund policy"
|
|
1111
|
+
|
|
1112
|
+
Upload and import a local file
|
|
1113
|
+
|
|
1114
|
+
$ sanity context imports create kb-abc123 --file ./handbook.pdf
|
|
1115
|
+
|
|
1116
|
+
Crawl a website
|
|
1117
|
+
|
|
1118
|
+
$ sanity context imports create kb-abc123 --url https://example.com/docs
|
|
1119
|
+
|
|
1120
|
+
Bind a Sanity dataset as a source
|
|
1121
|
+
|
|
1122
|
+
$ sanity context imports create kb-abc123 --query '*[_type == "article"]' --sanity-project abc123 \
|
|
1123
|
+
--sanity-dataset production
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
## `sanity context imports delete KNOWLEDGEBASEID IMPORTID`
|
|
1127
|
+
|
|
1128
|
+
Delete an import from a knowledge base
|
|
1129
|
+
|
|
1130
|
+
```
|
|
1131
|
+
USAGE
|
|
1132
|
+
$ sanity context imports delete KNOWLEDGEBASEID IMPORTID [-y]
|
|
1133
|
+
|
|
1134
|
+
ARGUMENTS
|
|
1135
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1136
|
+
IMPORTID Import ID
|
|
1137
|
+
|
|
1138
|
+
FLAGS
|
|
1139
|
+
-y, --yes Skip confirmation prompt (unattended mode)
|
|
1140
|
+
|
|
1141
|
+
DESCRIPTION
|
|
1142
|
+
Delete an import from a knowledge base
|
|
1143
|
+
|
|
1144
|
+
EXAMPLES
|
|
1145
|
+
Delete an import after confirmation
|
|
1146
|
+
|
|
1147
|
+
$ sanity context imports delete kb-abc123 import-def456
|
|
1148
|
+
|
|
1149
|
+
Delete an import without confirmation prompt
|
|
1150
|
+
|
|
1151
|
+
$ sanity context imports delete kb-abc123 import-def456 --yes
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
## `sanity context imports download KNOWLEDGEBASEID IMPORTID`
|
|
1155
|
+
|
|
1156
|
+
Get a short-lived signed URL for the original uploaded bytes of a file import
|
|
1157
|
+
|
|
1158
|
+
```
|
|
1159
|
+
USAGE
|
|
1160
|
+
$ sanity context imports download KNOWLEDGEBASEID IMPORTID [--json]
|
|
1161
|
+
|
|
1162
|
+
ARGUMENTS
|
|
1163
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1164
|
+
IMPORTID Import ID
|
|
1165
|
+
|
|
1166
|
+
FLAGS
|
|
1167
|
+
--json Output the download URL in JSON format
|
|
1168
|
+
|
|
1169
|
+
DESCRIPTION
|
|
1170
|
+
Get a short-lived signed URL for the original uploaded bytes of a file import
|
|
1171
|
+
|
|
1172
|
+
EXAMPLES
|
|
1173
|
+
Get a download URL for an uploaded file
|
|
1174
|
+
|
|
1175
|
+
$ sanity context imports download kb-abc123 import-def456
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
## `sanity context imports get KNOWLEDGEBASEID IMPORTID`
|
|
1179
|
+
|
|
1180
|
+
Get details of an import
|
|
1181
|
+
|
|
1182
|
+
```
|
|
1183
|
+
USAGE
|
|
1184
|
+
$ sanity context imports get KNOWLEDGEBASEID IMPORTID [--json]
|
|
1185
|
+
|
|
1186
|
+
ARGUMENTS
|
|
1187
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1188
|
+
IMPORTID Import ID
|
|
1189
|
+
|
|
1190
|
+
FLAGS
|
|
1191
|
+
--json Output the import in JSON format
|
|
1192
|
+
|
|
1193
|
+
DESCRIPTION
|
|
1194
|
+
Get details of an import
|
|
1195
|
+
|
|
1196
|
+
EXAMPLES
|
|
1197
|
+
Get details of a specific import
|
|
1198
|
+
|
|
1199
|
+
$ sanity context imports get kb-abc123 import-def456
|
|
1200
|
+
|
|
1201
|
+
Output the import as JSON
|
|
1202
|
+
|
|
1203
|
+
$ sanity context imports get kb-abc123 import-def456 --json
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
## `sanity context imports list KNOWLEDGEBASEID`
|
|
1207
|
+
|
|
1208
|
+
List imports for a knowledge base
|
|
1209
|
+
|
|
1210
|
+
```
|
|
1211
|
+
USAGE
|
|
1212
|
+
$ sanity context imports list KNOWLEDGEBASEID [--json]
|
|
1213
|
+
|
|
1214
|
+
ARGUMENTS
|
|
1215
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1216
|
+
|
|
1217
|
+
FLAGS
|
|
1218
|
+
--json Output imports in JSON format
|
|
1219
|
+
|
|
1220
|
+
DESCRIPTION
|
|
1221
|
+
List imports for a knowledge base
|
|
1222
|
+
|
|
1223
|
+
EXAMPLES
|
|
1224
|
+
List all imports for a knowledge base
|
|
1225
|
+
|
|
1226
|
+
$ sanity context imports list kb-abc123
|
|
1227
|
+
|
|
1228
|
+
Output imports as JSON
|
|
1229
|
+
|
|
1230
|
+
$ sanity context imports list kb-abc123 --json
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
## `sanity context jobs get KNOWLEDGEBASEID JOBID`
|
|
1234
|
+
|
|
1235
|
+
Get the status of a knowledge base job
|
|
1236
|
+
|
|
1237
|
+
```
|
|
1238
|
+
USAGE
|
|
1239
|
+
$ sanity context jobs get KNOWLEDGEBASEID JOBID [--json] [--watch]
|
|
1240
|
+
|
|
1241
|
+
ARGUMENTS
|
|
1242
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1243
|
+
JOBID Job ID
|
|
1244
|
+
|
|
1245
|
+
FLAGS
|
|
1246
|
+
--json Output the job in JSON format
|
|
1247
|
+
--watch Poll until the job reaches a terminal state
|
|
1248
|
+
|
|
1249
|
+
DESCRIPTION
|
|
1250
|
+
Get the status of a knowledge base job
|
|
1251
|
+
|
|
1252
|
+
EXAMPLES
|
|
1253
|
+
Get the current status of a job
|
|
1254
|
+
|
|
1255
|
+
$ sanity context jobs get kb-abc123 job-def456
|
|
1256
|
+
|
|
1257
|
+
Wait for the job to finish (non-zero exit if it does not succeed)
|
|
1258
|
+
|
|
1259
|
+
$ sanity context jobs get kb-abc123 job-def456 --watch
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
## `sanity context list`
|
|
1263
|
+
|
|
1264
|
+
List knowledge bases in an organization
|
|
1265
|
+
|
|
1266
|
+
```
|
|
1267
|
+
USAGE
|
|
1268
|
+
$ sanity context list [--organization <id>] [--json]
|
|
1269
|
+
|
|
1270
|
+
FLAGS
|
|
1271
|
+
--json Output knowledge bases in JSON format
|
|
1272
|
+
|
|
1273
|
+
OVERRIDE FLAGS
|
|
1274
|
+
--organization=<id> Organization to list knowledge bases for (overrides CLI configuration)
|
|
1275
|
+
|
|
1276
|
+
DESCRIPTION
|
|
1277
|
+
List knowledge bases in an organization
|
|
1278
|
+
|
|
1279
|
+
EXAMPLES
|
|
1280
|
+
List knowledge bases, selecting the organization interactively
|
|
1281
|
+
|
|
1282
|
+
$ sanity context list
|
|
1283
|
+
|
|
1284
|
+
List knowledge bases in a specific organization
|
|
1285
|
+
|
|
1286
|
+
$ sanity context list --organization org-abc123
|
|
1287
|
+
|
|
1288
|
+
Output knowledge bases as JSON
|
|
1289
|
+
|
|
1290
|
+
$ sanity context list --organization org-abc123 --json
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
## `sanity context refresh KNOWLEDGEBASEID`
|
|
1294
|
+
|
|
1295
|
+
Refresh a knowledge base: re-check sources and apply what changed
|
|
1296
|
+
|
|
1297
|
+
```
|
|
1298
|
+
USAGE
|
|
1299
|
+
$ sanity context refresh KNOWLEDGEBASEID
|
|
1300
|
+
|
|
1301
|
+
ARGUMENTS
|
|
1302
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1303
|
+
|
|
1304
|
+
DESCRIPTION
|
|
1305
|
+
Refresh a knowledge base: re-check sources and apply what changed
|
|
1306
|
+
|
|
1307
|
+
EXAMPLES
|
|
1308
|
+
Start an incremental refresh
|
|
1309
|
+
|
|
1310
|
+
$ sanity context refresh kb-abc123
|
|
1311
|
+
```
|
|
1312
|
+
|
|
1313
|
+
## `sanity context update KNOWLEDGEBASEID`
|
|
1314
|
+
|
|
1315
|
+
Update a knowledge base
|
|
1316
|
+
|
|
1317
|
+
```
|
|
1318
|
+
USAGE
|
|
1319
|
+
$ sanity context update KNOWLEDGEBASEID [--description <value>] [--refresh-enabled] [--refresh-frequency
|
|
1320
|
+
weekly|monthly] [--title <value>]
|
|
1321
|
+
|
|
1322
|
+
ARGUMENTS
|
|
1323
|
+
KNOWLEDGEBASEID Knowledge base ID
|
|
1324
|
+
|
|
1325
|
+
FLAGS
|
|
1326
|
+
--description=<value> New knowledge base description
|
|
1327
|
+
--[no-]refresh-enabled Enable scheduled refresh (--no-refresh-enabled to disable)
|
|
1328
|
+
--refresh-frequency=<option> How often scheduled refresh runs
|
|
1329
|
+
<options: weekly|monthly>
|
|
1330
|
+
--title=<value> New knowledge base title
|
|
1331
|
+
|
|
1332
|
+
DESCRIPTION
|
|
1333
|
+
Update a knowledge base
|
|
1334
|
+
|
|
1335
|
+
EXAMPLES
|
|
1336
|
+
Rename a knowledge base
|
|
1337
|
+
|
|
1338
|
+
$ sanity context update kb-abc123 --title "New title"
|
|
1339
|
+
|
|
1340
|
+
Enable weekly scheduled refresh
|
|
1341
|
+
|
|
1342
|
+
$ sanity context update kb-abc123 --refresh-enabled --refresh-frequency weekly
|
|
1343
|
+
|
|
1344
|
+
Disable scheduled refresh
|
|
1345
|
+
|
|
1346
|
+
$ sanity context update kb-abc123 --no-refresh-enabled
|
|
1347
|
+
```
|
|
1348
|
+
|
|
953
1349
|
## `sanity cors add ORIGIN`
|
|
954
1350
|
|
|
955
1351
|
Add a CORS origin to the project
|
|
@@ -88,7 +88,7 @@ import { API_VERSION_SEGMENT_RE } from './constants.js';
|
|
|
88
88
|
const [pathPart = ''] = path.split('?');
|
|
89
89
|
const segments = pathPart.split('/').filter((segment)=>segment !== '');
|
|
90
90
|
let version;
|
|
91
|
-
if (segments[0] === '{apiVersion}') {
|
|
91
|
+
if (segments[0] === '{apiVersion}' || segments[0] === '{apiV}') {
|
|
92
92
|
segments.shift();
|
|
93
93
|
} else if (segments.length > 0 && API_VERSION_SEGMENT_RE.test(segments[0])) {
|
|
94
94
|
version = segments.shift();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/api/distillApiRoutes.ts"],"sourcesContent":["import {API_VERSION_SEGMENT_RE} from './constants.js'\nimport {type ApiHost, type ApiRouteEntry, type OpenApiDocument} from './types.js'\n\n/**\n * A fetched OpenAPI specification, ready for distillation.\n */\nexport interface SpecSource {\n document: OpenApiDocument\n slug: string\n title: string\n}\n\ninterface ServerBase {\n host: ApiHost\n segments: string[]\n\n defaultApiVersion?: string\n}\n\n/**\n * Distill fetched OpenAPI specifications into the routing manifest used by\n * `sanity api` to decide which host serves a request path and which API\n * version to default to.\n *\n * The output is deterministic (sorted) so the generated manifest can be\n * compared against a fresh distillation to detect drift.\n */\nexport function distillApiRoutes(specs: SpecSource[]): ApiRouteEntry[] {\n const entries: ApiRouteEntry[] = []\n\n for (const {document, slug, title} of specs) {\n const servers = document.servers ?? []\n const paths = Object.keys(document.paths ?? {})\n if (servers.length === 0 || paths.length === 0) continue\n\n // A spec may be served on both hosts (eg the Applications API). Emit one\n // entry per host, each carrying the full set of path patterns.\n const byHost = new Map<ApiHost, ServerBase>()\n for (const server of servers) {\n const base = parseServerUrl(server.url, server.variables)\n if (!base) continue\n if (!byHost.has(base.host)) byHost.set(base.host, base)\n }\n\n for (const base of byHost.values()) {\n const patterns = new Set<string>()\n let defaultApiVersion = base.defaultApiVersion\n\n for (const path of paths) {\n const {segments, version} = normalizePathSegments(path)\n defaultApiVersion = defaultApiVersion ?? version\n const pattern = [...base.segments, ...segments].join('/')\n if (pattern !== '') patterns.add(pattern)\n }\n\n if (patterns.size === 0) continue\n\n entries.push({\n ...(defaultApiVersion === undefined ? {} : {defaultApiVersion}),\n host: base.host,\n pathPatterns: [...patterns].toSorted(),\n slug,\n title,\n })\n }\n }\n\n return entries.toSorted((a, b) => a.slug.localeCompare(b.slug) || a.host.localeCompare(b.host))\n}\n\n/**\n * Parse an OpenAPI server URL template (eg\n * `https://{projectId}.api.sanity.io/{apiVersion}/agent/action`) into a host\n * classification, base path segments and a default API version.\n *\n * Returns `undefined` for URLs that don't look like a Sanity API host.\n */\nfunction parseServerUrl(\n url: string,\n variables?: Record<string, {default?: string}>,\n): ServerBase | undefined {\n const withoutScheme = url.replace(/^https?:\\/\\//, '')\n const [hostname = '', ...pathSegments] = withoutScheme.split('/')\n\n if (!hostname.includes('api.sanity.io') && !hostname.includes('api.sanity.work')) {\n return undefined\n }\n\n const host: ApiHost = hostname.includes('{projectId}') ? 'project' : 'global'\n\n let defaultApiVersion = normalizeVersion(variables?.apiVersion?.default)\n const segments: string[] = []\n for (const segment of pathSegments) {\n if (segment === '') continue\n if (segment === '{apiVersion}') continue\n if (API_VERSION_SEGMENT_RE.test(segment)) {\n defaultApiVersion = defaultApiVersion ?? segment\n continue\n }\n segments.push(segment)\n }\n\n return {defaultApiVersion, host, segments}\n}\n\n/**\n * Normalize an OpenAPI path template into version-less path segments, plus the\n * API version if one was embedded in the path (eg `/v2024-07-01/access/...`).\n *\n * Some specs carry a stray query marker in the path key (eg `/foo/{id}?`) -\n * anything from `?` on is not part of the path and is dropped.\n */\nfunction normalizePathSegments(path: string): {segments: string[]; version?: string} {\n const [pathPart = ''] = path.split('?')\n const segments = pathPart.split('/').filter((segment) => segment !== '')\n let version: string | undefined\n\n if (segments[0] === '{apiVersion}') {\n segments.shift()\n } else if (segments.length > 0 && API_VERSION_SEGMENT_RE.test(segments[0])) {\n version = segments.shift()\n }\n\n return {segments, version}\n}\n\nfunction normalizeVersion(version: string | undefined): string | undefined {\n if (!version) return undefined\n return API_VERSION_SEGMENT_RE.test(version) ? version : undefined\n}\n"],"names":["API_VERSION_SEGMENT_RE","distillApiRoutes","specs","entries","document","slug","title","servers","paths","Object","keys","length","byHost","Map","server","base","parseServerUrl","url","variables","has","host","set","values","patterns","Set","defaultApiVersion","path","segments","version","normalizePathSegments","pattern","join","add","size","push","undefined","pathPatterns","toSorted","a","b","localeCompare","withoutScheme","replace","hostname","pathSegments","split","includes","normalizeVersion","apiVersion","default","segment","test","pathPart","filter","shift"],"mappings":"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
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/api/distillApiRoutes.ts"],"sourcesContent":["import {API_VERSION_SEGMENT_RE} from './constants.js'\nimport {type ApiHost, type ApiRouteEntry, type OpenApiDocument} from './types.js'\n\n/**\n * A fetched OpenAPI specification, ready for distillation.\n */\nexport interface SpecSource {\n document: OpenApiDocument\n slug: string\n title: string\n}\n\ninterface ServerBase {\n host: ApiHost\n segments: string[]\n\n defaultApiVersion?: string\n}\n\n/**\n * Distill fetched OpenAPI specifications into the routing manifest used by\n * `sanity api` to decide which host serves a request path and which API\n * version to default to.\n *\n * The output is deterministic (sorted) so the generated manifest can be\n * compared against a fresh distillation to detect drift.\n */\nexport function distillApiRoutes(specs: SpecSource[]): ApiRouteEntry[] {\n const entries: ApiRouteEntry[] = []\n\n for (const {document, slug, title} of specs) {\n const servers = document.servers ?? []\n const paths = Object.keys(document.paths ?? {})\n if (servers.length === 0 || paths.length === 0) continue\n\n // A spec may be served on both hosts (eg the Applications API). Emit one\n // entry per host, each carrying the full set of path patterns.\n const byHost = new Map<ApiHost, ServerBase>()\n for (const server of servers) {\n const base = parseServerUrl(server.url, server.variables)\n if (!base) continue\n if (!byHost.has(base.host)) byHost.set(base.host, base)\n }\n\n for (const base of byHost.values()) {\n const patterns = new Set<string>()\n let defaultApiVersion = base.defaultApiVersion\n\n for (const path of paths) {\n const {segments, version} = normalizePathSegments(path)\n defaultApiVersion = defaultApiVersion ?? version\n const pattern = [...base.segments, ...segments].join('/')\n if (pattern !== '') patterns.add(pattern)\n }\n\n if (patterns.size === 0) continue\n\n entries.push({\n ...(defaultApiVersion === undefined ? {} : {defaultApiVersion}),\n host: base.host,\n pathPatterns: [...patterns].toSorted(),\n slug,\n title,\n })\n }\n }\n\n return entries.toSorted((a, b) => a.slug.localeCompare(b.slug) || a.host.localeCompare(b.host))\n}\n\n/**\n * Parse an OpenAPI server URL template (eg\n * `https://{projectId}.api.sanity.io/{apiVersion}/agent/action`) into a host\n * classification, base path segments and a default API version.\n *\n * Returns `undefined` for URLs that don't look like a Sanity API host.\n */\nfunction parseServerUrl(\n url: string,\n variables?: Record<string, {default?: string}>,\n): ServerBase | undefined {\n const withoutScheme = url.replace(/^https?:\\/\\//, '')\n const [hostname = '', ...pathSegments] = withoutScheme.split('/')\n\n if (!hostname.includes('api.sanity.io') && !hostname.includes('api.sanity.work')) {\n return undefined\n }\n\n const host: ApiHost = hostname.includes('{projectId}') ? 'project' : 'global'\n\n let defaultApiVersion = normalizeVersion(variables?.apiVersion?.default)\n const segments: string[] = []\n for (const segment of pathSegments) {\n if (segment === '') continue\n if (segment === '{apiVersion}') continue\n if (API_VERSION_SEGMENT_RE.test(segment)) {\n defaultApiVersion = defaultApiVersion ?? segment\n continue\n }\n segments.push(segment)\n }\n\n return {defaultApiVersion, host, segments}\n}\n\n/**\n * Normalize an OpenAPI path template into version-less path segments, plus the\n * API version if one was embedded in the path (eg `/v2024-07-01/access/...`).\n *\n * Some specs carry a stray query marker in the path key (eg `/foo/{id}?`) -\n * anything from `?` on is not part of the path and is dropped.\n */\nfunction normalizePathSegments(path: string): {segments: string[]; version?: string} {\n const [pathPart = ''] = path.split('?')\n const segments = pathPart.split('/').filter((segment) => segment !== '')\n let version: string | undefined\n\n if (segments[0] === '{apiVersion}' || segments[0] === '{apiV}') {\n segments.shift()\n } else if (segments.length > 0 && API_VERSION_SEGMENT_RE.test(segments[0])) {\n version = segments.shift()\n }\n\n return {segments, version}\n}\n\nfunction normalizeVersion(version: string | undefined): string | undefined {\n if (!version) return undefined\n return API_VERSION_SEGMENT_RE.test(version) ? version : undefined\n}\n"],"names":["API_VERSION_SEGMENT_RE","distillApiRoutes","specs","entries","document","slug","title","servers","paths","Object","keys","length","byHost","Map","server","base","parseServerUrl","url","variables","has","host","set","values","patterns","Set","defaultApiVersion","path","segments","version","normalizePathSegments","pattern","join","add","size","push","undefined","pathPatterns","toSorted","a","b","localeCompare","withoutScheme","replace","hostname","pathSegments","split","includes","normalizeVersion","apiVersion","default","segment","test","pathPart","filter","shift"],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ const ALLOWED_URL_HOST_RE = /(?:^|\.)api\.sanity\.(?:io|work)$/;
|
|
|
23
23
|
// An API version embedded in the path (eg `v2021-06-07/projects`) is peeled
|
|
24
24
|
// off and re-applied as the version segment of the final URL.
|
|
25
25
|
let embeddedVersion;
|
|
26
|
-
if (segments[0] === '{apiVersion}') {
|
|
26
|
+
if (segments[0] === '{apiVersion}' || segments[0] === '{apiV}') {
|
|
27
27
|
segments.shift();
|
|
28
28
|
} else if (segments.length > 0 && API_VERSION_SEGMENT_RE.test(segments[0])) {
|
|
29
29
|
embeddedVersion = segments.shift();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/api/resolveEndpoint.ts"],"sourcesContent":["import {parse} from 'node:querystring'\n\nimport {API_DEFAULT_VERSION, API_VERSION_SEGMENT_RE} from './constants.js'\nimport {ApiUsageError, ProjectIdRequiredError} from './errors.js'\nimport {type ApiHost, type ApiRouteEntry} from './types.js'\n\n/**\n * Options for {@link resolveEndpoint}.\n */\nexport interface ResolveEndpointOptions {\n /** The endpoint argument: a path, optionally with placeholders, or a full URL. */\n endpoint: string\n\n /** The generated routing manifest. */\n routes: ApiRouteEntry[]\n\n /** Explicit API version (`--api-version`), wins over everything else. */\n apiVersion?: string\n\n /** Dataset used to fill `{dataset}` / `{datasetName}` placeholders. */\n dataset?: string\n\n /** Force a host family (`--global` / `--project-hosted`), restricting route matching to it. */\n forceHost?: ApiHost\n\n /** Project ID used to fill `{projectId}` placeholders and project-hosted requests. */\n projectId?: string\n}\n\n/**\n * A fully resolved request target.\n */\nexport type ResolvedEndpoint =\n | {\n /** API version path segment, eg `v2021-06-07`. */\n apiVersion: string\n host: ApiHost\n kind: 'path'\n /** Slug of the OpenAPI spec whose paths matched, if any. */\n matchedSlug?: string\n /** Version-less path, without leading slash. */\n path: string\n /** Project ID; always set when `host` is `project`. */\n projectId?: string\n /** Query parameters parsed from the endpoint argument. */\n query: Record<string, string | string[]>\n }\n | {\n kind: 'url'\n query: Record<string, string | string[]>\n /** Absolute URL, used verbatim (query string stripped into `query`). */\n url: string\n }\n\nconst PLACEHOLDER_RE = /^\\{([^{}]+)\\}$/\n\nconst ALLOWED_URL_HOST_RE = /(?:^|\\.)api\\.sanity\\.(?:io|work)$/\n\n/**\n * Resolve the endpoint argument of `sanity api` into a concrete request\n * target: substitute placeholders, split off the query string, determine the\n * host family (via the generated OpenAPI routing manifest) and the API\n * version.\n *\n * Throws {@link ProjectIdRequiredError} when the request needs a project ID\n * that hasn't been provided - the command catches this to resolve one (flags,\n * CLI config or interactive prompt) and retries.\n */\nexport function resolveEndpoint(options: ResolveEndpointOptions): ResolvedEndpoint {\n const {apiVersion, dataset, endpoint, forceHost, projectId, routes} = options\n\n if (/^https?:\\/\\//.test(endpoint)) {\n return resolveUrlEndpoint(endpoint)\n }\n\n const [pathPart = '', ...queryParts] = endpoint.split('?')\n const query = parseQueryString(queryParts.join('?'))\n\n const segments = pathPart.split('/').filter((segment) => segment !== '')\n\n // An API version embedded in the path (eg `v2021-06-07/projects`) is peeled\n // off and re-applied as the version segment of the final URL.\n let embeddedVersion: string | undefined\n if (segments[0] === '{apiVersion}') {\n segments.shift()\n } else if (segments.length > 0 && API_VERSION_SEGMENT_RE.test(segments[0])) {\n embeddedVersion = segments.shift()\n }\n\n // Checked after version peeling so a bare version (eg `v2025-02-19`) is\n // also rejected as an empty path.\n if (segments.length === 0) {\n throw new ApiUsageError('Endpoint path is empty')\n }\n\n const unresolved: string[] = []\n const substituted = segments.map((segment, index) => {\n const placeholder = PLACEHOLDER_RE.exec(segment)?.[1]\n if (!placeholder) return segment\n if (placeholder === 'projectId') {\n if (!projectId) throw new ProjectIdRequiredError()\n return projectId\n }\n // The projects spec names the dataset placeholder `{name}` (eg\n // `projects/{projectId}/datasets/{name}`) - only that context is a\n // dataset; `{name}` elsewhere (eg video renditions) stays unresolved.\n const isDatasetName = placeholder === 'name' && segments[index - 1] === 'datasets'\n if (placeholder === 'dataset' || placeholder === 'datasetName' || isDatasetName) {\n if (!dataset) {\n throw new ApiUsageError(\n `Unable to resolve {${placeholder}} - provide a dataset with --dataset or configure one in sanity.cli.ts`,\n )\n }\n return dataset\n }\n unresolved.push(segment)\n return segment\n })\n\n if (unresolved.length > 0) {\n throw new ApiUsageError(\n `Unable to resolve placeholder(s) ${unresolved.join(', ')} - replace them with actual values`,\n )\n }\n\n // Forcing a host restricts matching to specs served on that host, so the\n // matched spec's default API version still applies when one exists there.\n const candidates = forceHost ? routes.filter((entry) => entry.host === forceHost) : routes\n const match = matchRoutes(substituted, candidates)\n const host = forceHost ?? match?.host ?? 'global'\n\n if (host === 'project' && !projectId) {\n throw new ProjectIdRequiredError()\n }\n\n return {\n apiVersion: apiVersion ?? embeddedVersion ?? match?.defaultApiVersion ?? API_DEFAULT_VERSION,\n host,\n kind: 'path',\n matchedSlug: match?.slug,\n path: substituted.join('/'),\n ...(host === 'project' ? {projectId} : {}),\n query,\n }\n}\n\nfunction resolveUrlEndpoint(endpoint: string): ResolvedEndpoint {\n let url: URL\n try {\n url = new URL(endpoint)\n } catch {\n throw new ApiUsageError(`Invalid URL \"${endpoint}\"`)\n }\n\n // Never put the token on the wire unencrypted\n if (url.protocol !== 'https:') {\n throw new ApiUsageError(\n `Refusing to send requests over \"${url.protocol}//\" - only https:// URLs are supported`,\n )\n }\n\n if (!ALLOWED_URL_HOST_RE.test(url.hostname)) {\n throw new ApiUsageError(\n `Refusing to send authenticated requests to \"${url.hostname}\" - only *.api.sanity.io hosts are supported`,\n )\n }\n\n const query = parseQueryString(url.search.replace(/^\\?/, ''))\n url.search = ''\n\n return {kind: 'url', query, url: url.toString()}\n}\n\ninterface RouteMatch {\n host: ApiHost\n score: number\n slug: string\n\n defaultApiVersion?: string\n}\n\n/**\n * Find the route entry whose path pattern best matches the request segments.\n *\n * A pattern only matches when the request path consumes it completely, and\n * segments are compared pairwise: literal matches score higher than\n * placeholder matches, and a literal mismatch disqualifies the pattern. On\n * equal scores across hosts the global host wins (APIs served on both hosts\n * don't need a project ID there); on equal scores within the same host the\n * first entry wins, keeping the result independent of later entries.\n */\nfunction matchRoutes(segments: string[], routes: ApiRouteEntry[]): RouteMatch | undefined {\n let best: RouteMatch | undefined\n\n for (const entry of routes) {\n for (const pattern of entry.pathPatterns) {\n const score = scorePattern(segments, pattern.split('/'))\n if (score === 0) continue\n const isBetter =\n !best ||\n score > best.score ||\n (score === best.score && best.host === 'project' && entry.host === 'global')\n if (isBetter) {\n best = {\n defaultApiVersion: entry.defaultApiVersion,\n host: entry.host,\n score,\n slug: entry.slug,\n }\n }\n }\n }\n\n return best\n}\n\nfunction scorePattern(segments: string[], patternSegments: string[]): number {\n // A pattern only matches when the request path consumes it completely - a\n // request that is a mere prefix of a longer pattern (eg `data` vs\n // `data/query/{dataset}`) must not inherit that route's host or version.\n if (patternSegments.length > segments.length) return 0\n\n let score = 0\n let literalMatches = 0\n\n for (const [index, patternSegment] of patternSegments.entries()) {\n if (PLACEHOLDER_RE.test(patternSegment)) {\n score += 1\n } else if (patternSegment === segments[index]) {\n score += 2\n literalMatches += 1\n } else {\n return 0\n }\n }\n\n // Require at least one literal segment match so a placeholder-only pattern\n // like `{resourceType}/{resourceId}` can't capture arbitrary paths.\n return literalMatches === 0 ? 0 : score\n}\n\nfunction parseQueryString(queryString: string): Record<string, string | string[]> {\n // node:querystring collects repeated keys into arrays and returns a\n // null-prototype object, so user-supplied keys like `__proto__` stay\n // ordinary own properties. Parsed values are never `undefined`, hence the\n // narrowing cast.\n return parse(queryString) as Record<string, string | string[]>\n}\n"],"names":["parse","API_DEFAULT_VERSION","API_VERSION_SEGMENT_RE","ApiUsageError","ProjectIdRequiredError","PLACEHOLDER_RE","ALLOWED_URL_HOST_RE","resolveEndpoint","options","apiVersion","dataset","endpoint","forceHost","projectId","routes","test","resolveUrlEndpoint","pathPart","queryParts","split","query","parseQueryString","join","segments","filter","segment","embeddedVersion","shift","length","unresolved","substituted","map","index","placeholder","exec","isDatasetName","push","candidates","entry","host","match","matchRoutes","defaultApiVersion","kind","matchedSlug","slug","path","url","URL","protocol","hostname","search","replace","toString","best","pattern","pathPatterns","score","scorePattern","isBetter","patternSegments","literalMatches","patternSegment","entries","queryString"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/api/resolveEndpoint.ts"],"sourcesContent":["import {parse} from 'node:querystring'\n\nimport {API_DEFAULT_VERSION, API_VERSION_SEGMENT_RE} from './constants.js'\nimport {ApiUsageError, ProjectIdRequiredError} from './errors.js'\nimport {type ApiHost, type ApiRouteEntry} from './types.js'\n\n/**\n * Options for {@link resolveEndpoint}.\n */\nexport interface ResolveEndpointOptions {\n /** The endpoint argument: a path, optionally with placeholders, or a full URL. */\n endpoint: string\n\n /** The generated routing manifest. */\n routes: ApiRouteEntry[]\n\n /** Explicit API version (`--api-version`), wins over everything else. */\n apiVersion?: string\n\n /** Dataset used to fill `{dataset}` / `{datasetName}` placeholders. */\n dataset?: string\n\n /** Force a host family (`--global` / `--project-hosted`), restricting route matching to it. */\n forceHost?: ApiHost\n\n /** Project ID used to fill `{projectId}` placeholders and project-hosted requests. */\n projectId?: string\n}\n\n/**\n * A fully resolved request target.\n */\nexport type ResolvedEndpoint =\n | {\n /** API version path segment, eg `v2021-06-07`. */\n apiVersion: string\n host: ApiHost\n kind: 'path'\n /** Slug of the OpenAPI spec whose paths matched, if any. */\n matchedSlug?: string\n /** Version-less path, without leading slash. */\n path: string\n /** Project ID; always set when `host` is `project`. */\n projectId?: string\n /** Query parameters parsed from the endpoint argument. */\n query: Record<string, string | string[]>\n }\n | {\n kind: 'url'\n query: Record<string, string | string[]>\n /** Absolute URL, used verbatim (query string stripped into `query`). */\n url: string\n }\n\nconst PLACEHOLDER_RE = /^\\{([^{}]+)\\}$/\n\nconst ALLOWED_URL_HOST_RE = /(?:^|\\.)api\\.sanity\\.(?:io|work)$/\n\n/**\n * Resolve the endpoint argument of `sanity api` into a concrete request\n * target: substitute placeholders, split off the query string, determine the\n * host family (via the generated OpenAPI routing manifest) and the API\n * version.\n *\n * Throws {@link ProjectIdRequiredError} when the request needs a project ID\n * that hasn't been provided - the command catches this to resolve one (flags,\n * CLI config or interactive prompt) and retries.\n */\nexport function resolveEndpoint(options: ResolveEndpointOptions): ResolvedEndpoint {\n const {apiVersion, dataset, endpoint, forceHost, projectId, routes} = options\n\n if (/^https?:\\/\\//.test(endpoint)) {\n return resolveUrlEndpoint(endpoint)\n }\n\n const [pathPart = '', ...queryParts] = endpoint.split('?')\n const query = parseQueryString(queryParts.join('?'))\n\n const segments = pathPart.split('/').filter((segment) => segment !== '')\n\n // An API version embedded in the path (eg `v2021-06-07/projects`) is peeled\n // off and re-applied as the version segment of the final URL.\n let embeddedVersion: string | undefined\n if (segments[0] === '{apiVersion}' || segments[0] === '{apiV}') {\n segments.shift()\n } else if (segments.length > 0 && API_VERSION_SEGMENT_RE.test(segments[0])) {\n embeddedVersion = segments.shift()\n }\n\n // Checked after version peeling so a bare version (eg `v2025-02-19`) is\n // also rejected as an empty path.\n if (segments.length === 0) {\n throw new ApiUsageError('Endpoint path is empty')\n }\n\n const unresolved: string[] = []\n const substituted = segments.map((segment, index) => {\n const placeholder = PLACEHOLDER_RE.exec(segment)?.[1]\n if (!placeholder) return segment\n if (placeholder === 'projectId') {\n if (!projectId) throw new ProjectIdRequiredError()\n return projectId\n }\n // The projects spec names the dataset placeholder `{name}` (eg\n // `projects/{projectId}/datasets/{name}`) - only that context is a\n // dataset; `{name}` elsewhere (eg video renditions) stays unresolved.\n const isDatasetName = placeholder === 'name' && segments[index - 1] === 'datasets'\n if (placeholder === 'dataset' || placeholder === 'datasetName' || isDatasetName) {\n if (!dataset) {\n throw new ApiUsageError(\n `Unable to resolve {${placeholder}} - provide a dataset with --dataset or configure one in sanity.cli.ts`,\n )\n }\n return dataset\n }\n unresolved.push(segment)\n return segment\n })\n\n if (unresolved.length > 0) {\n throw new ApiUsageError(\n `Unable to resolve placeholder(s) ${unresolved.join(', ')} - replace them with actual values`,\n )\n }\n\n // Forcing a host restricts matching to specs served on that host, so the\n // matched spec's default API version still applies when one exists there.\n const candidates = forceHost ? routes.filter((entry) => entry.host === forceHost) : routes\n const match = matchRoutes(substituted, candidates)\n const host = forceHost ?? match?.host ?? 'global'\n\n if (host === 'project' && !projectId) {\n throw new ProjectIdRequiredError()\n }\n\n return {\n apiVersion: apiVersion ?? embeddedVersion ?? match?.defaultApiVersion ?? API_DEFAULT_VERSION,\n host,\n kind: 'path',\n matchedSlug: match?.slug,\n path: substituted.join('/'),\n ...(host === 'project' ? {projectId} : {}),\n query,\n }\n}\n\nfunction resolveUrlEndpoint(endpoint: string): ResolvedEndpoint {\n let url: URL\n try {\n url = new URL(endpoint)\n } catch {\n throw new ApiUsageError(`Invalid URL \"${endpoint}\"`)\n }\n\n // Never put the token on the wire unencrypted\n if (url.protocol !== 'https:') {\n throw new ApiUsageError(\n `Refusing to send requests over \"${url.protocol}//\" - only https:// URLs are supported`,\n )\n }\n\n if (!ALLOWED_URL_HOST_RE.test(url.hostname)) {\n throw new ApiUsageError(\n `Refusing to send authenticated requests to \"${url.hostname}\" - only *.api.sanity.io hosts are supported`,\n )\n }\n\n const query = parseQueryString(url.search.replace(/^\\?/, ''))\n url.search = ''\n\n return {kind: 'url', query, url: url.toString()}\n}\n\ninterface RouteMatch {\n host: ApiHost\n score: number\n slug: string\n\n defaultApiVersion?: string\n}\n\n/**\n * Find the route entry whose path pattern best matches the request segments.\n *\n * A pattern only matches when the request path consumes it completely, and\n * segments are compared pairwise: literal matches score higher than\n * placeholder matches, and a literal mismatch disqualifies the pattern. On\n * equal scores across hosts the global host wins (APIs served on both hosts\n * don't need a project ID there); on equal scores within the same host the\n * first entry wins, keeping the result independent of later entries.\n */\nfunction matchRoutes(segments: string[], routes: ApiRouteEntry[]): RouteMatch | undefined {\n let best: RouteMatch | undefined\n\n for (const entry of routes) {\n for (const pattern of entry.pathPatterns) {\n const score = scorePattern(segments, pattern.split('/'))\n if (score === 0) continue\n const isBetter =\n !best ||\n score > best.score ||\n (score === best.score && best.host === 'project' && entry.host === 'global')\n if (isBetter) {\n best = {\n defaultApiVersion: entry.defaultApiVersion,\n host: entry.host,\n score,\n slug: entry.slug,\n }\n }\n }\n }\n\n return best\n}\n\nfunction scorePattern(segments: string[], patternSegments: string[]): number {\n // A pattern only matches when the request path consumes it completely - a\n // request that is a mere prefix of a longer pattern (eg `data` vs\n // `data/query/{dataset}`) must not inherit that route's host or version.\n if (patternSegments.length > segments.length) return 0\n\n let score = 0\n let literalMatches = 0\n\n for (const [index, patternSegment] of patternSegments.entries()) {\n if (PLACEHOLDER_RE.test(patternSegment)) {\n score += 1\n } else if (patternSegment === segments[index]) {\n score += 2\n literalMatches += 1\n } else {\n return 0\n }\n }\n\n // Require at least one literal segment match so a placeholder-only pattern\n // like `{resourceType}/{resourceId}` can't capture arbitrary paths.\n return literalMatches === 0 ? 0 : score\n}\n\nfunction parseQueryString(queryString: string): Record<string, string | string[]> {\n // node:querystring collects repeated keys into arrays and returns a\n // null-prototype object, so user-supplied keys like `__proto__` stay\n // ordinary own properties. Parsed values are never `undefined`, hence the\n // narrowing cast.\n return parse(queryString) as Record<string, string | string[]>\n}\n"],"names":["parse","API_DEFAULT_VERSION","API_VERSION_SEGMENT_RE","ApiUsageError","ProjectIdRequiredError","PLACEHOLDER_RE","ALLOWED_URL_HOST_RE","resolveEndpoint","options","apiVersion","dataset","endpoint","forceHost","projectId","routes","test","resolveUrlEndpoint","pathPart","queryParts","split","query","parseQueryString","join","segments","filter","segment","embeddedVersion","shift","length","unresolved","substituted","map","index","placeholder","exec","isDatasetName","push","candidates","entry","host","match","matchRoutes","defaultApiVersion","kind","matchedSlug","slug","path","url","URL","protocol","hostname","search","replace","toString","best","pattern","pathPatterns","score","scorePattern","isBetter","patternSegments","literalMatches","patternSegment","entries","queryString"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createWriteStream } from 'node:fs';
|
|
2
2
|
import { createGzip } from 'node:zlib';
|
|
3
|
-
import {
|
|
3
|
+
import { c as createTar } from 'tar';
|
|
4
4
|
import { backupDownloadDebug } from './backupDownloadDebug.js';
|
|
5
5
|
/**
|
|
6
6
|
* Creates a compressed tarball of the given directory and writes it to the specified file path
|
|
@@ -25,9 +25,13 @@ import { backupDownloadDebug } from './backupDownloadDebug.js';
|
|
|
25
25
|
backupDownloadDebug('Gzip stream error: %s', err.message);
|
|
26
26
|
reject(err);
|
|
27
27
|
});
|
|
28
|
-
const tarStream =
|
|
28
|
+
const tarStream = createTar({
|
|
29
|
+
cwd: tmpOutDir
|
|
30
|
+
}, [
|
|
31
|
+
'.'
|
|
32
|
+
]);
|
|
29
33
|
tarStream.on('error', (err)=>{
|
|
30
|
-
backupDownloadDebug('Tar stream error: %s', err.message);
|
|
34
|
+
backupDownloadDebug('Tar stream error: %s', err instanceof Error ? err.message : String(err));
|
|
31
35
|
reject(err);
|
|
32
36
|
});
|
|
33
37
|
// Track progress by listening to data events
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/backup/archiveDir.ts"],"sourcesContent":["import {createWriteStream} from 'node:fs'\nimport {createGzip} from 'node:zlib'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/backup/archiveDir.ts"],"sourcesContent":["import {createWriteStream} from 'node:fs'\nimport {createGzip} from 'node:zlib'\n\nimport {c as createTar} from 'tar'\n\nimport {backupDownloadDebug} from './backupDownloadDebug.js'\n\n// ProgressCb is a callback that is called with the number of bytes processed so far.\ntype ProgressCb = (processedBytes: number) => void\n\n/**\n * Creates a compressed tarball of the given directory and writes it to the specified file path\n *\n * @param tmpOutDir - The directory to archive\n * @param outFilePath - The output file path for the compressed tarball\n * @param progressCb - Callback function called with the number of bytes processed\n */\nexport function archiveDir(\n tmpOutDir: string,\n outFilePath: string,\n progressCb: ProgressCb,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const archiveDestination = createWriteStream(outFilePath)\n const gzipStream = createGzip()\n let processedBytes = 0\n\n archiveDestination.on('error', (err: Error) => {\n backupDownloadDebug('Archive destination error: %s', err.message)\n reject(err)\n })\n\n archiveDestination.on('close', () => {\n backupDownloadDebug('Archive completed successfully')\n resolve()\n })\n\n gzipStream.on('error', (err: Error) => {\n backupDownloadDebug('Gzip stream error: %s', err.message)\n reject(err)\n })\n\n const tarStream = createTar({cwd: tmpOutDir}, ['.'])\n\n tarStream.on('error', (err) => {\n backupDownloadDebug('Tar stream error: %s', err instanceof Error ? err.message : String(err))\n reject(err)\n })\n\n // Track progress by listening to data events\n tarStream.on('data', (chunk: Buffer) => {\n processedBytes += chunk.length\n progressCb(processedBytes)\n })\n\n // Pipe tar -> gzip -> file\n tarStream.pipe(gzipStream).pipe(archiveDestination)\n })\n}\n"],"names":["createWriteStream","createGzip","c","createTar","backupDownloadDebug","archiveDir","tmpOutDir","outFilePath","progressCb","Promise","resolve","reject","archiveDestination","gzipStream","processedBytes","on","err","message","tarStream","cwd","Error","String","chunk","length","pipe"],"mappings":"AAAA,SAAQA,iBAAiB,QAAO,UAAS;AACzC,SAAQC,UAAU,QAAO,YAAW;AAEpC,SAAQC,KAAKC,SAAS,QAAO,MAAK;AAElC,SAAQC,mBAAmB,QAAO,2BAA0B;AAK5D;;;;;;CAMC,GACD,OAAO,SAASC,WACdC,SAAiB,EACjBC,WAAmB,EACnBC,UAAsB;IAEtB,OAAO,IAAIC,QAAQ,CAACC,SAASC;QAC3B,MAAMC,qBAAqBZ,kBAAkBO;QAC7C,MAAMM,aAAaZ;QACnB,IAAIa,iBAAiB;QAErBF,mBAAmBG,EAAE,CAAC,SAAS,CAACC;YAC9BZ,oBAAoB,iCAAiCY,IAAIC,OAAO;YAChEN,OAAOK;QACT;QAEAJ,mBAAmBG,EAAE,CAAC,SAAS;YAC7BX,oBAAoB;YACpBM;QACF;QAEAG,WAAWE,EAAE,CAAC,SAAS,CAACC;YACtBZ,oBAAoB,yBAAyBY,IAAIC,OAAO;YACxDN,OAAOK;QACT;QAEA,MAAME,YAAYf,UAAU;YAACgB,KAAKb;QAAS,GAAG;YAAC;SAAI;QAEnDY,UAAUH,EAAE,CAAC,SAAS,CAACC;YACrBZ,oBAAoB,wBAAwBY,eAAeI,QAAQJ,IAAIC,OAAO,GAAGI,OAAOL;YACxFL,OAAOK;QACT;QAEA,6CAA6C;QAC7CE,UAAUH,EAAE,CAAC,QAAQ,CAACO;YACpBR,kBAAkBQ,MAAMC,MAAM;YAC9Bf,WAAWM;QACb;QAEA,2BAA2B;QAC3BI,UAAUM,IAAI,CAACX,YAAYW,IAAI,CAACZ;IAClC;AACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { promptForOrganization } from '../../prompts/promptForOrganization.js';
|
|
2
|
+
/** The organization could not be determined and prompting is not possible. */ export class MissingOrganizationError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the organization to operate on: the `--organization` flag wins,
|
|
6
|
+
* then the CLI config's `app.organizationId`, then an interactive prompt.
|
|
7
|
+
*/ export async function resolveOrganizationId(options) {
|
|
8
|
+
const { configuredOrganizationId, flagOrganizationId, unattended } = options;
|
|
9
|
+
if (flagOrganizationId) return flagOrganizationId;
|
|
10
|
+
if (configuredOrganizationId) return configuredOrganizationId;
|
|
11
|
+
if (unattended) {
|
|
12
|
+
throw new MissingOrganizationError('Organization ID is required. Provide it with the --organization flag.');
|
|
13
|
+
}
|
|
14
|
+
return promptForOrganization();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=resolveOrganizationId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/context/resolveOrganizationId.ts"],"sourcesContent":["import {promptForOrganization} from '../../prompts/promptForOrganization.js'\n\n/** The organization could not be determined and prompting is not possible. */\nexport class MissingOrganizationError extends Error {}\n\n/**\n * Resolve the organization to operate on: the `--organization` flag wins,\n * then the CLI config's `app.organizationId`, then an interactive prompt.\n */\nexport async function resolveOrganizationId(options: {\n configuredOrganizationId: string | undefined\n flagOrganizationId: string | undefined\n unattended: boolean\n}): Promise<string> {\n const {configuredOrganizationId, flagOrganizationId, unattended} = options\n\n if (flagOrganizationId) return flagOrganizationId\n if (configuredOrganizationId) return configuredOrganizationId\n\n if (unattended) {\n throw new MissingOrganizationError(\n 'Organization ID is required. Provide it with the --organization flag.',\n )\n }\n\n return promptForOrganization()\n}\n"],"names":["promptForOrganization","MissingOrganizationError","Error","resolveOrganizationId","options","configuredOrganizationId","flagOrganizationId","unattended"],"mappings":"AAAA,SAAQA,qBAAqB,QAAO,yCAAwC;AAE5E,4EAA4E,GAC5E,OAAO,MAAMC,iCAAiCC;AAAO;AAErD;;;CAGC,GACD,OAAO,eAAeC,sBAAsBC,OAI3C;IACC,MAAM,EAACC,wBAAwB,EAAEC,kBAAkB,EAAEC,UAAU,EAAC,GAAGH;IAEnE,IAAIE,oBAAoB,OAAOA;IAC/B,IAAID,0BAA0B,OAAOA;IAErC,IAAIE,YAAY;QACd,MAAM,IAAIN,yBACR;IAEJ;IAEA,OAAOD;AACT"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
+
import { getJob } from '../../services/context.js';
|
|
3
|
+
const JOB_POLL_INTERVAL_MS = 5000;
|
|
4
|
+
const TERMINAL_JOB_STATUSES = new Set([
|
|
5
|
+
'cancelled',
|
|
6
|
+
'failed',
|
|
7
|
+
'succeeded'
|
|
8
|
+
]);
|
|
9
|
+
/**
|
|
10
|
+
* Poll a knowledge base job until it reaches a terminal state
|
|
11
|
+
* (succeeded, failed or cancelled), resolving with the final job.
|
|
12
|
+
*/ export async function waitForJob(knowledgeBaseId, jobId, options) {
|
|
13
|
+
const pollIntervalMs = options?.pollIntervalMs ?? JOB_POLL_INTERVAL_MS;
|
|
14
|
+
while(true){
|
|
15
|
+
const job = await getJob(knowledgeBaseId, jobId);
|
|
16
|
+
if (TERMINAL_JOB_STATUSES.has(job.status)) {
|
|
17
|
+
return job;
|
|
18
|
+
}
|
|
19
|
+
options?.onPoll?.(job);
|
|
20
|
+
await sleep(pollIntervalMs);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=waitForJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/context/waitForJob.ts"],"sourcesContent":["import {setTimeout as sleep} from 'node:timers/promises'\n\nimport {type Context} from '@sanity/client'\n\nimport {getJob} from '../../services/context.js'\n\nconst JOB_POLL_INTERVAL_MS = 5000\n\nconst TERMINAL_JOB_STATUSES: ReadonlySet<Context.Job['status']> = new Set([\n 'cancelled',\n 'failed',\n 'succeeded',\n])\n\n/**\n * Poll a knowledge base job until it reaches a terminal state\n * (succeeded, failed or cancelled), resolving with the final job.\n */\nexport async function waitForJob(\n knowledgeBaseId: string,\n jobId: string,\n options?: {\n onPoll?: (job: Context.Job) => void\n pollIntervalMs?: number\n },\n): Promise<Context.Job> {\n const pollIntervalMs = options?.pollIntervalMs ?? JOB_POLL_INTERVAL_MS\n\n while (true) {\n const job = await getJob(knowledgeBaseId, jobId)\n if (TERMINAL_JOB_STATUSES.has(job.status)) {\n return job\n }\n options?.onPoll?.(job)\n await sleep(pollIntervalMs)\n }\n}\n"],"names":["setTimeout","sleep","getJob","JOB_POLL_INTERVAL_MS","TERMINAL_JOB_STATUSES","Set","waitForJob","knowledgeBaseId","jobId","options","pollIntervalMs","job","has","status","onPoll"],"mappings":"AAAA,SAAQA,cAAcC,KAAK,QAAO,uBAAsB;AAIxD,SAAQC,MAAM,QAAO,4BAA2B;AAEhD,MAAMC,uBAAuB;AAE7B,MAAMC,wBAA4D,IAAIC,IAAI;IACxE;IACA;IACA;CACD;AAED;;;CAGC,GACD,OAAO,eAAeC,WACpBC,eAAuB,EACvBC,KAAa,EACbC,OAGC;IAED,MAAMC,iBAAiBD,SAASC,kBAAkBP;IAElD,MAAO,KAAM;QACX,MAAMQ,MAAM,MAAMT,OAAOK,iBAAiBC;QAC1C,IAAIJ,sBAAsBQ,GAAG,CAACD,IAAIE,MAAM,GAAG;YACzC,OAAOF;QACT;QACAF,SAASK,SAASH;QAClB,MAAMV,MAAMS;IACd;AACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { spinner } from '@sanity/cli-core/ux';
|
|
2
|
+
import { waitForJob } from './waitForJob.js';
|
|
3
|
+
/**
|
|
4
|
+
* Watch a knowledge base job with a spinner until it reaches a terminal
|
|
5
|
+
* state, updating the spinner text with the job status on each poll.
|
|
6
|
+
*
|
|
7
|
+
* Fails the spinner and rethrows on polling errors, and fails it when the
|
|
8
|
+
* terminal status is not `succeeded` — turning either outcome into a
|
|
9
|
+
* command error (and exit code) is the caller's responsibility.
|
|
10
|
+
*/ export async function watchJob(knowledgeBaseId, jobId, options) {
|
|
11
|
+
const { label, successText } = options;
|
|
12
|
+
const spin = spinner(label).start();
|
|
13
|
+
let job;
|
|
14
|
+
try {
|
|
15
|
+
job = await waitForJob(knowledgeBaseId, jobId, {
|
|
16
|
+
onPoll: (pending)=>{
|
|
17
|
+
spin.text = `${label} (${pending.status})`;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
spin.fail();
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
if (job.status === 'succeeded') {
|
|
25
|
+
spin.succeed(successText);
|
|
26
|
+
} else {
|
|
27
|
+
spin.fail();
|
|
28
|
+
}
|
|
29
|
+
return job;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=watchJob.js.map
|