@sanity/cli 7.17.0 → 8.0.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.
Files changed (133) hide show
  1. package/README.md +81 -6
  2. package/dist/SanityHelp.js +1 -35
  3. package/dist/SanityHelp.js.map +1 -1
  4. package/dist/actions/assets/assetFileError.js +9 -0
  5. package/dist/actions/assets/assetFileError.js.map +1 -0
  6. package/dist/actions/assets/uploadAssetFromFile.js +40 -0
  7. package/dist/actions/assets/uploadAssetFromFile.js.map +1 -0
  8. package/dist/actions/assets/uploadAssetWithProgress.js +76 -0
  9. package/dist/actions/assets/uploadAssetWithProgress.js.map +1 -0
  10. package/dist/actions/deploy/deployApp.js +39 -44
  11. package/dist/actions/deploy/deployApp.js.map +1 -1
  12. package/dist/actions/deploy/deployChecks.js +21 -13
  13. package/dist/actions/deploy/deployChecks.js.map +1 -1
  14. package/dist/actions/deploy/deployRunner.js +8 -7
  15. package/dist/actions/deploy/deployRunner.js.map +1 -1
  16. package/dist/actions/deploy/deployStudio.js +55 -35
  17. package/dist/actions/deploy/deployStudio.js.map +1 -1
  18. package/dist/actions/deploy/deploymentPlan.js +17 -29
  19. package/dist/actions/deploy/deploymentPlan.js.map +1 -1
  20. package/dist/actions/deploy/resolveDeployTarget.js +2 -8
  21. package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
  22. package/dist/actions/dev/servers/getDevServerConfig.js +2 -8
  23. package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
  24. package/dist/actions/dev/servers/startStudioDevServer.js +7 -1
  25. package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
  26. package/dist/actions/init/bootstrapRemoteTemplate.js +2 -2
  27. package/dist/actions/init/bootstrapRemoteTemplate.js.map +1 -1
  28. package/dist/actions/schema/listSchemas.js +1 -1
  29. package/dist/actions/schema/listSchemas.js.map +1 -1
  30. package/dist/actions/tokens/constants.js +2 -2
  31. package/dist/actions/tokens/constants.js.map +1 -1
  32. package/dist/actions/tokens/expiry.js +35 -0
  33. package/dist/actions/tokens/expiry.js.map +1 -0
  34. package/dist/actions/tokens/types.js.map +1 -1
  35. package/dist/actions/tokens/validateRole.js +5 -2
  36. package/dist/actions/tokens/validateRole.js.map +1 -1
  37. package/dist/actions/typegen/count.js +6 -0
  38. package/dist/actions/typegen/count.js.map +1 -0
  39. package/dist/actions/typegen/formatPath.js +5 -0
  40. package/dist/actions/typegen/formatPath.js.map +1 -0
  41. package/dist/actions/typegen/percent.js +8 -0
  42. package/dist/actions/typegen/percent.js.map +1 -0
  43. package/dist/actions/typegen/renderTypegenProgress.js +88 -0
  44. package/dist/actions/typegen/renderTypegenProgress.js.map +1 -0
  45. package/dist/actions/undeploy/adapters.js +6 -9
  46. package/dist/actions/undeploy/adapters.js.map +1 -1
  47. package/dist/actions/undeploy/runUndeploy.js +12 -4
  48. package/dist/actions/undeploy/runUndeploy.js.map +1 -1
  49. package/dist/actions/undeploy/undeployPlan.js +6 -14
  50. package/dist/actions/undeploy/undeployPlan.js.map +1 -1
  51. package/dist/commands/assets/upload.js +126 -0
  52. package/dist/commands/assets/upload.js.map +1 -0
  53. package/dist/commands/backups/list.js +1 -1
  54. package/dist/commands/backups/list.js.map +1 -1
  55. package/dist/commands/datasets/copy.js +1 -1
  56. package/dist/commands/datasets/copy.js.map +1 -1
  57. package/dist/commands/login.js +2 -8
  58. package/dist/commands/login.js.map +1 -1
  59. package/dist/commands/mcp/configure.js.map +1 -1
  60. package/dist/commands/migrations/list.js +1 -1
  61. package/dist/commands/migrations/list.js.map +1 -1
  62. package/dist/commands/migrations/run.js +67 -3
  63. package/dist/commands/migrations/run.js.map +1 -1
  64. package/dist/commands/new.js +56 -4
  65. package/dist/commands/new.js.map +1 -1
  66. package/dist/commands/organizations/list.js +1 -1
  67. package/dist/commands/organizations/list.js.map +1 -1
  68. package/dist/commands/projects/list.js +39 -12
  69. package/dist/commands/projects/list.js.map +1 -1
  70. package/dist/commands/projects/unclaimed.js +39 -33
  71. package/dist/commands/projects/unclaimed.js.map +1 -1
  72. package/dist/commands/tokens/create.js +92 -7
  73. package/dist/commands/tokens/create.js.map +1 -1
  74. package/dist/commands/tokens/delete.js +2 -2
  75. package/dist/commands/tokens/delete.js.map +1 -1
  76. package/dist/commands/tokens/list.js +12 -11
  77. package/dist/commands/tokens/list.js.map +1 -1
  78. package/dist/commands/tokens/rotate.js +95 -0
  79. package/dist/commands/tokens/rotate.js.map +1 -0
  80. package/dist/commands/typegen/generate.js +222 -0
  81. package/dist/commands/typegen/generate.js.map +1 -0
  82. package/dist/commands/users/list.js +8 -8
  83. package/dist/commands/users/list.js.map +1 -1
  84. package/dist/exports/_internal.d.ts +29 -0
  85. package/dist/exports/_internal.js +1 -0
  86. package/dist/exports/_internal.js.map +1 -1
  87. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +29 -14
  88. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
  89. package/dist/exports/invokeSanityCli/help.js +4 -0
  90. package/dist/exports/invokeSanityCli/help.js.map +1 -1
  91. package/dist/exports/invokeSanityCli/index.js +2 -0
  92. package/dist/exports/invokeSanityCli/index.js.map +1 -1
  93. package/dist/hooks/commandNotFound/topicAliases.js +13 -40
  94. package/dist/hooks/commandNotFound/topicAliases.js.map +1 -1
  95. package/dist/hooks/prerun/setupTelemetry.js +3 -2
  96. package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
  97. package/dist/server/vite/plugin-typegen.js +4 -16
  98. package/dist/server/vite/plugin-typegen.js.map +1 -1
  99. package/dist/services/assets.js +46 -0
  100. package/dist/services/assets.js.map +1 -0
  101. package/dist/services/newInstructions.js +48 -0
  102. package/dist/services/newInstructions.js.map +1 -0
  103. package/dist/services/tokens.js +81 -31
  104. package/dist/services/tokens.js.map +1 -1
  105. package/dist/topicAliases.js +43 -0
  106. package/dist/topicAliases.js.map +1 -1
  107. package/dist/util/parseArguments.js +7 -2
  108. package/dist/util/parseArguments.js.map +1 -1
  109. package/dist/util/readTokenFromStdin.js +17 -0
  110. package/dist/util/readTokenFromStdin.js.map +1 -0
  111. package/dist/util/responsiveTable.js +97 -0
  112. package/dist/util/responsiveTable.js.map +1 -0
  113. package/dist/util/telemetry/commandTelemetry.js +34 -0
  114. package/dist/util/telemetry/commandTelemetry.js.map +1 -0
  115. package/dist/util/unclaimedProjects.js +10 -0
  116. package/dist/util/unclaimedProjects.js.map +1 -1
  117. package/oclif.config.js +2 -4
  118. package/oclif.manifest.json +224 -2
  119. package/package.json +12 -10
  120. package/templates/shopify/components/inputs/ShopifyMetafield.tsx +20 -0
  121. package/templates/shopify/docs/features.md +24 -0
  122. package/templates/shopify/schemaTypes/index.ts +2 -0
  123. package/templates/shopify/schemaTypes/objects/shopify/shopifyCollectionType.ts +18 -1
  124. package/templates/shopify/schemaTypes/objects/shopify/shopifyMetafieldType.ts +47 -0
  125. package/templates/shopify/schemaTypes/objects/shopify/shopifyProductType.ts +17 -0
  126. package/templates/shopify/utils/formatMetafieldValue.ts +30 -0
  127. package/templates/shopify-online-storefront/components/inputs/ShopifyMetafield.tsx +22 -0
  128. package/templates/shopify-online-storefront/schemaTypes/index.ts +4 -0
  129. package/templates/shopify-online-storefront/schemaTypes/objects/shop.ts +16 -0
  130. package/templates/shopify-online-storefront/schemaTypes/objects/shopifyCollection.ts +19 -0
  131. package/templates/shopify-online-storefront/schemaTypes/objects/shopifyMetafield.ts +53 -0
  132. package/templates/shopify-online-storefront/schemaTypes/objects/shopifyProduct.ts +19 -0
  133. package/templates/shopify-online-storefront/utils/formatMetafieldValue.ts +30 -0
package/oclif.config.js CHANGED
@@ -13,10 +13,7 @@ export default {
13
13
  './dist/hooks/prerun/warnings.js',
14
14
  ],
15
15
  },
16
- // Note: do not add '@sanity/migrate' here. The `migrations` commands now ship
17
- // natively (see commands/migrations/); re-adding the plugin would register
18
- // duplicate command ids.
19
- plugins: ['@oclif/plugin-help', '@sanity/runtime-cli', '@sanity/codegen'],
16
+ plugins: ['@oclif/plugin-help', '@sanity/runtime-cli'],
20
17
  topics: {
21
18
  backups: {description: 'Manage dataset backups'},
22
19
  cors: {description: 'Manage CORS origins for your project'},
@@ -36,6 +33,7 @@ export default {
36
33
  skills: {description: 'Install Sanity agent skills for AI agents'},
37
34
  telemetry: {description: 'Manage telemetry consent'},
38
35
  tokens: {description: 'Manage API tokens for your project'},
36
+ typegen: {description: 'Generate TypeScript types for schema and GROQ'},
39
37
  users: {description: 'Manage project users and invitations'},
40
38
  },
41
39
  topicSeparator: ' ',
@@ -1150,7 +1150,7 @@
1150
1150
  "required": false
1151
1151
  }
1152
1152
  },
1153
- "description": "Sets up two folders here: ./sanity, a Studio where you write and edit your\ncontent, and ./web, a Next.js website that reads it. Both are already\nconnected to your new project, so you can start them straight away. Use\n--no-scaffold if you just want the project and nothing else.\n\nThe project is real and works immediately, but it is only yours for 72 hours.\nClaim it with a Sanity account before the deadline and everything you have\nbuilt stays exactly as it is. Claiming is free and takes about a minute. Miss\nthe deadline and the project and its content are deleted.\n\nTwo things to keep private: the claim link, because anyone who opens it\nbecomes the owner, and the access token saved in ./sanity/.env.local and\n./web/.env.local, because it can read and change everything in the project.\nKeep both env files out of git, and never put the token in code that runs in\nthe browser.\n\nFetch https://sanity.new for full instructions, or point your AI agent at it.",
1153
+ "description": "Sets up two folders here: ./sanity, a Studio where you write and edit your\ncontent, and ./web, a Next.js website that reads it. Both are already\nconnected to your new project, so you can start them straight away. Use\n--no-scaffold if you just want the project and nothing else.\n\nThe project is real and works immediately, but it is only yours for 72 hours.\nClaim it with a Sanity account before the deadline and everything you have\nbuilt stays exactly as it is. Claiming is free and takes about a minute. Miss\nthe deadline and the project and its content are deleted.\n\nTwo things to keep private: the claim link, because anyone who opens it\nbecomes the owner, and the access token saved in ./sanity/.env.local and\n./web/.env.local, because it can read and change everything in the project.\nKeep both env files out of git, and never put the token in code that runs in\nthe browser.\n\nRun this command with --instructions for the full agent setup guide.",
1154
1154
  "examples": [
1155
1155
  {
1156
1156
  "command": "<%= config.bin %> <%= command.id %>",
@@ -1171,6 +1171,10 @@
1171
1171
  {
1172
1172
  "command": "<%= config.bin %> <%= command.id %> --json",
1173
1173
  "description": "Create a project and print its details as JSON"
1174
+ },
1175
+ {
1176
+ "command": "<%= config.bin %> <%= command.id %> --instructions",
1177
+ "description": "Print the full setup guide for an AI agent, without creating anything"
1174
1178
  }
1175
1179
  ],
1176
1180
  "flags": {
@@ -1181,6 +1185,12 @@
1181
1185
  "allowNo": false,
1182
1186
  "type": "boolean"
1183
1187
  },
1188
+ "instructions": {
1189
+ "description": "Print the full setup guide from https://sanity.new and exit, creating nothing",
1190
+ "name": "instructions",
1191
+ "allowNo": false,
1192
+ "type": "boolean"
1193
+ },
1184
1194
  "scaffold": {
1185
1195
  "description": "Set up a Studio in ./sanity and a Next.js website in ./web (on by default)",
1186
1196
  "name": "scaffold",
@@ -1334,6 +1344,112 @@
1334
1344
  "versions.js"
1335
1345
  ]
1336
1346
  },
1347
+ "assets:upload": {
1348
+ "aliases": [],
1349
+ "args": {},
1350
+ "description": "Upload one local image or file to a Sanity dataset and print the asset document as JSON",
1351
+ "examples": [
1352
+ {
1353
+ "command": "<%= config.bin %> <%= command.id %> --file ./hero.png --type image --dataset production",
1354
+ "description": "Upload an image using the configured project"
1355
+ },
1356
+ {
1357
+ "command": "<%= config.bin %> <%= command.id %> --file ./brief.pdf --type file --content-type application/pdf --project-id abc123 --dataset production",
1358
+ "description": "Upload a file with explicit project, dataset, and MIME type"
1359
+ }
1360
+ ],
1361
+ "flags": {
1362
+ "project-id": {
1363
+ "char": "p",
1364
+ "description": "Project ID to upload the asset to (overrides CLI configuration)",
1365
+ "helpGroup": "OVERRIDE",
1366
+ "name": "project-id",
1367
+ "hasDynamicHelp": false,
1368
+ "helpValue": "<id>",
1369
+ "multiple": false,
1370
+ "type": "option"
1371
+ },
1372
+ "dataset": {
1373
+ "char": "d",
1374
+ "description": "Dataset to upload the asset to (overrides CLI configuration)",
1375
+ "helpGroup": "OVERRIDE",
1376
+ "name": "dataset",
1377
+ "hasDynamicHelp": false,
1378
+ "helpValue": "<name>",
1379
+ "multiple": false,
1380
+ "type": "option"
1381
+ },
1382
+ "content-type": {
1383
+ "description": "MIME type of the asset, such as image/png or application/pdf",
1384
+ "name": "content-type",
1385
+ "hasDynamicHelp": false,
1386
+ "helpValue": "<mime-type>",
1387
+ "multiple": false,
1388
+ "type": "option"
1389
+ },
1390
+ "file": {
1391
+ "description": "Path to the local file to upload",
1392
+ "name": "file",
1393
+ "required": true,
1394
+ "hasDynamicHelp": false,
1395
+ "helpValue": "<path>",
1396
+ "multiple": false,
1397
+ "type": "option"
1398
+ },
1399
+ "filename": {
1400
+ "description": "Original filename stored on the asset document. Defaults to the local filename",
1401
+ "name": "filename",
1402
+ "hasDynamicHelp": false,
1403
+ "helpValue": "<filename>",
1404
+ "multiple": false,
1405
+ "type": "option"
1406
+ },
1407
+ "type": {
1408
+ "description": "Asset type to create",
1409
+ "name": "type",
1410
+ "default": "image",
1411
+ "hasDynamicHelp": false,
1412
+ "multiple": false,
1413
+ "options": [
1414
+ "image",
1415
+ "file"
1416
+ ],
1417
+ "type": "option"
1418
+ }
1419
+ },
1420
+ "hasDynamicHelp": false,
1421
+ "hiddenAliases": [
1422
+ "asset:upload"
1423
+ ],
1424
+ "id": "assets:upload",
1425
+ "pluginAlias": "@sanity/cli",
1426
+ "pluginName": "@sanity/cli",
1427
+ "pluginType": "core",
1428
+ "strict": true,
1429
+ "telemetry": {
1430
+ "redactedFlags": [
1431
+ {
1432
+ "canonical": "--file",
1433
+ "flags": [
1434
+ "--file"
1435
+ ]
1436
+ },
1437
+ {
1438
+ "canonical": "--filename",
1439
+ "flags": [
1440
+ "--filename"
1441
+ ]
1442
+ }
1443
+ ]
1444
+ },
1445
+ "isESM": true,
1446
+ "relativePath": [
1447
+ "dist",
1448
+ "commands",
1449
+ "assets",
1450
+ "upload.js"
1451
+ ]
1452
+ },
1337
1453
  "backups:disable": {
1338
1454
  "aliases": [],
1339
1455
  "args": {
@@ -5087,6 +5203,10 @@
5087
5203
  "command": "<%= config.bin %> <%= command.id %> \"CI Token\" --role=editor --yes",
5088
5204
  "description": "Create a token in unattended mode"
5089
5205
  },
5206
+ {
5207
+ "command": "<%= config.bin %> <%= command.id %> \"Build Token\" --expires-at 2027-01-01",
5208
+ "description": "Create a token that expires on a given date"
5209
+ },
5090
5210
  {
5091
5211
  "command": "<%= config.bin %> <%= command.id %> \"API Token\" --json",
5092
5212
  "description": "Output token information as JSON"
@@ -5107,6 +5227,14 @@
5107
5227
  "multiple": false,
5108
5228
  "type": "option"
5109
5229
  },
5230
+ "expires-at": {
5231
+ "description": "Date or timestamp the token expires (ISO 8601; tokens never expire by default)",
5232
+ "name": "expires-at",
5233
+ "hasDynamicHelp": false,
5234
+ "helpValue": "2027-01-01",
5235
+ "multiple": false,
5236
+ "type": "option"
5237
+ },
5110
5238
  "json": {
5111
5239
  "description": "Output as JSON",
5112
5240
  "name": "json",
@@ -5268,6 +5396,100 @@
5268
5396
  "list.js"
5269
5397
  ]
5270
5398
  },
5399
+ "tokens:rotate": {
5400
+ "aliases": [],
5401
+ "args": {},
5402
+ "description": "Rotate an API token, replacing its secret with a new one",
5403
+ "examples": [
5404
+ {
5405
+ "command": "echo \"$SANITY_TOKEN\" | <%= config.bin %> <%= command.id %>",
5406
+ "description": "Rotate the token piped on standard input"
5407
+ },
5408
+ {
5409
+ "command": "<%= config.bin %> <%= command.id %> < token.txt",
5410
+ "description": "Rotate a token read from a file"
5411
+ },
5412
+ {
5413
+ "command": "echo \"$SANITY_TOKEN\" | <%= config.bin %> <%= command.id %> --json",
5414
+ "description": "Output the rotated token as JSON"
5415
+ }
5416
+ ],
5417
+ "flags": {
5418
+ "json": {
5419
+ "description": "Output as JSON",
5420
+ "name": "json",
5421
+ "allowNo": false,
5422
+ "type": "boolean"
5423
+ },
5424
+ "token": {
5425
+ "char": "t",
5426
+ "description": "Token to rotate (prefer standard input to keep it out of shell history)",
5427
+ "name": "token",
5428
+ "hasDynamicHelp": false,
5429
+ "helpValue": "<token>",
5430
+ "multiple": false,
5431
+ "type": "option"
5432
+ }
5433
+ },
5434
+ "hasDynamicHelp": false,
5435
+ "hiddenAliases": [
5436
+ "token:rotate"
5437
+ ],
5438
+ "id": "tokens:rotate",
5439
+ "pluginAlias": "@sanity/cli",
5440
+ "pluginName": "@sanity/cli",
5441
+ "pluginType": "core",
5442
+ "strict": true,
5443
+ "isESM": true,
5444
+ "relativePath": [
5445
+ "dist",
5446
+ "commands",
5447
+ "tokens",
5448
+ "rotate.js"
5449
+ ]
5450
+ },
5451
+ "typegen:generate": {
5452
+ "aliases": [],
5453
+ "args": {},
5454
+ "description": "Sanity TypeGen\n\nConfiguration:\nThis command can utilize configuration settings defined in a `sanity-typegen.json` file. These settings include:\n\n- \"path\": Specifies a glob pattern to locate your TypeScript or JavaScript files.\n Default: \"./src/**/*.{ts,tsx,js,jsx}\"\n\n- \"schema\": Defines the path to your Sanity schema file. This file should be generated using the `sanity schema extract` command.\n Default: \"schema.json\"\n\n- \"generates\": Indicates the path where the generated TypeScript type definitions will be saved.\n Default: \"./sanity.types.ts\"\n\nThe default configuration values listed above are used if not overridden in your `sanity-typegen.json` configuration file. To customize the behavior of the type generation, adjust these properties in the configuration file according to your project's needs.\n\nNote:\n- The `sanity schema extract` command is a prerequisite for extracting your Sanity Studio schema into a `schema.json` file, which is then used by the `sanity typegen generate` command to generate type definitions.",
5455
+ "examples": [
5456
+ {
5457
+ "command": "<%= config.bin %> <%= command.id %>",
5458
+ "description": "Generate TypeScript type definitions from a Sanity Studio schema extracted using the `sanity schema extract` command."
5459
+ }
5460
+ ],
5461
+ "flags": {
5462
+ "config-path": {
5463
+ "description": "[Default: sanity-typegen.json] Specifies the path to the typegen configuration file. This file should be a JSON file that contains settings for the type generation process.",
5464
+ "name": "config-path",
5465
+ "hasDynamicHelp": false,
5466
+ "multiple": false,
5467
+ "type": "option"
5468
+ },
5469
+ "watch": {
5470
+ "description": "[Default: false] Run the typegen in watch mode",
5471
+ "name": "watch",
5472
+ "allowNo": false,
5473
+ "type": "boolean"
5474
+ }
5475
+ },
5476
+ "hasDynamicHelp": false,
5477
+ "hiddenAliases": [
5478
+ "typegen:generate"
5479
+ ],
5480
+ "id": "typegen:generate",
5481
+ "pluginAlias": "@sanity/cli",
5482
+ "pluginName": "@sanity/cli",
5483
+ "pluginType": "core",
5484
+ "strict": true,
5485
+ "isESM": true,
5486
+ "relativePath": [
5487
+ "dist",
5488
+ "commands",
5489
+ "typegen",
5490
+ "generate.js"
5491
+ ]
5492
+ },
5271
5493
  "users:invite": {
5272
5494
  "aliases": [],
5273
5495
  "args": {
@@ -5939,5 +6161,5 @@
5939
6161
  ]
5940
6162
  }
5941
6163
  },
5942
- "version": "7.17.0"
6164
+ "version": "8.0.0"
5943
6165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli",
3
- "version": "7.17.0",
3
+ "version": "8.0.0",
4
4
  "description": "Sanity CLI tool for managing Sanity projects and organizations",
5
5
  "keywords": [
6
6
  "cli",
@@ -63,14 +63,14 @@
63
63
  "@oclif/plugin-help": "^6.2.53",
64
64
  "@oclif/plugin-not-found": "^3.2.88",
65
65
  "@sanity/client": "^7.25.0",
66
- "@sanity/codegen": "^7.0.3",
66
+ "@sanity/codegen": "^8.0.0",
67
67
  "@sanity/descriptors": "^1.3.0",
68
68
  "@sanity/export": "^6.2.0",
69
69
  "@sanity/generate-help-url": "^4.0.0",
70
70
  "@sanity/id-utils": "^1.0.0",
71
71
  "@sanity/import": "^6.0.3",
72
- "@sanity/migrate": "^8.0.1",
73
- "@sanity/runtime-cli": "^17.3.0",
72
+ "@sanity/migrate": "^8.0.2",
73
+ "@sanity/runtime-cli": "^17.4.0",
74
74
  "@sanity/schema": "^6.7.0",
75
75
  "@sanity/telemetry": "^1.1.0",
76
76
  "@sanity/template-validator": "^3.1.0",
@@ -86,7 +86,7 @@
86
86
  "execa": "^9.6.0",
87
87
  "form-data": "^4.0.5",
88
88
  "get-latest-version": "^6.0.1",
89
- "groq-js": "^1.30.3",
89
+ "groq-js": "^2.0.0",
90
90
  "gunzip-maybe": "^1.4.2",
91
91
  "import-meta-resolve": "^4.2.0",
92
92
  "is-installed-globally": "^1.0.0",
@@ -113,7 +113,8 @@
113
113
  "skills": "^1.5.9",
114
114
  "smol-toml": "^1.6.1",
115
115
  "string-argv": "^0.3.2",
116
- "tar": "^7.5.15",
116
+ "string-width": "^7.2.0",
117
+ "tar": "^7.5.18",
117
118
  "tar-fs": "^3.1.2",
118
119
  "tar-stream": "^3.2.0",
119
120
  "tinyglobby": "^0.2.17",
@@ -121,11 +122,12 @@
121
122
  "typeid-js": "^1.2.0",
122
123
  "vite": "^8.2.0",
123
124
  "which": "^6.0.1",
125
+ "wrap-ansi": "^9.0.2",
124
126
  "yaml": "^2.9.0",
125
127
  "zod": "^4.4.3",
126
- "@sanity/cli-build": "^5.1.0",
127
- "@sanity/cli-core": "^2.8.0",
128
- "@sanity/workbench-cli": "^1.10.0"
128
+ "@sanity/cli-build": "^5.2.0",
129
+ "@sanity/workbench-cli": "^2.0.0",
130
+ "@sanity/cli-core": "^3.0.0"
129
131
  },
130
132
  "devDependencies": {
131
133
  "@eslint/compat": "^2.1.0",
@@ -160,7 +162,7 @@
160
162
  "vitest": "^4.1.10",
161
163
  "@repo/package.config": "0.0.1",
162
164
  "@repo/tsconfig": "3.70.0",
163
- "@sanity/cli-test": "9.0.1",
165
+ "@sanity/cli-test": "10.0.0",
164
166
  "@sanity/eslint-config-cli": "1.1.3"
165
167
  },
166
168
  "peerDependencies": {
@@ -0,0 +1,20 @@
1
+ import {Card, Code} from '@sanity/ui'
2
+ import {type ObjectInputProps} from 'sanity'
3
+ import {formatMetafieldValue} from '../../utils/formatMetafieldValue'
4
+
5
+ /**
6
+ * Shows a synced metafield's value. `value` isn't a declared field on `shopifyMetafield` — its shape
7
+ * depends on the Shopify metafield type — so it's read from the raw object value here.
8
+ */
9
+ export default function ShopifyMetafield(props: ObjectInputProps) {
10
+ const {type, value} = (props.value || {}) as {type?: string; value?: unknown}
11
+ const text = formatMetafieldValue(type, value)
12
+
13
+ return (
14
+ <Card border padding={3} radius={1} tone="transparent">
15
+ <Code size={1} style={{margin: 0}}>
16
+ {text}
17
+ </Code>
18
+ </Card>
19
+ )
20
+ }
@@ -12,6 +12,30 @@ Inside `/schemaTypes` you'll find schema definitions for all the content types.
12
12
  - `/schemaTypes/documents/`: Document types determines the shape of the JSON documents that's stored in your content lake. This is where you define the content forms for things like collections, products, product variants, as well as articles.
13
13
  - `/schemaTypes/objects/`: General purpose & re-usable content structures, such as links, custom product options and modules.
14
14
 
15
+ ## Shopify metafields
16
+
17
+ If you've selected metafield namespaces to import in Sanity Connect, your product and collection metafields are synced to `store.metafields` and shown under a **Metafields** fieldset.
18
+
19
+ Each entry holds the metafield's `namespace`, `key`, Shopify `type` and `value`:
20
+
21
+ ```json
22
+ {
23
+ "_key": "custom.snowboard_length",
24
+ "namespace": "custom",
25
+ "key": "snowboard_length",
26
+ "type": "dimension",
27
+ "value": {"value": 180, "unit": "CENTIMETERS"}
28
+ }
29
+ ```
30
+
31
+ `value` isn't declared on the `shopifyMetafield` schema type. Its shape follows the Shopify metafield type — a string, number, boolean, list or object — and no Sanity field type accepts all of those. `/components/inputs/ShopifyMetafield.tsx` reads it from the raw value instead.
32
+
33
+ `/utils/formatMetafieldValue.ts` decides how a value is shown, using a small map keyed by metafield type. `dimension`, `weight`, `volume`, `money`, `rating` and `list.single_line_text_field` are there as examples.
34
+
35
+ Anything not listed falls back to the raw value — a string as-is, anything else as JSON — so your own metafields still display. To show one of them nicely, add an entry to `formatters`. Shopify's [list of data types](https://shopify.dev/docs/apps/build/custom-data/metafields/list-of-data-types) gives the shape of every value.
36
+
37
+ Metafields are read-only and the whole array is replaced on every sync, so edits made here are overwritten. Variant metafields and metaobjects aren't synced.
38
+
15
39
  ## Structure
16
40
 
17
41
  Sanity Studio will automatically list all your [document types][docs-document-types] out of the box. Sometimes you want a more streamlined editor experience. That's why you'll find a custom [structure][docs-structure] that's defined in `/structure`. It does the following things:
@@ -37,6 +37,7 @@ import {productWithVariantType} from './objects/shopify/productWithVariantType'
37
37
  import {proxyStringType} from './objects/shopify/proxyStringType'
38
38
  import {seoType} from './objects/seoType'
39
39
  import {shopifyCollectionType} from './objects/shopify/shopifyCollectionType'
40
+ import {shopifyMetafieldType} from './objects/shopify/shopifyMetafieldType'
40
41
  import {shopifyProductType} from './objects/shopify/shopifyProductType'
41
42
  import {shopifyProductVariantType} from './objects/shopify/shopifyProductVariantType'
42
43
  import {shopType} from './objects/shopify/shopType'
@@ -81,6 +82,7 @@ const objects = [
81
82
  proxyStringType,
82
83
  seoType,
83
84
  shopifyCollectionType,
85
+ shopifyMetafieldType,
84
86
  shopifyProductType,
85
87
  shopifyProductVariantType,
86
88
  shopType,
@@ -1,4 +1,4 @@
1
- import {defineField} from 'sanity'
1
+ import {defineArrayMember, defineField} from 'sanity'
2
2
 
3
3
  export const shopifyCollectionType = defineField({
4
4
  name: 'shopifyCollection',
@@ -14,6 +14,14 @@ export const shopifyCollectionType = defineField({
14
14
  name: 'status',
15
15
  title: 'Status',
16
16
  },
17
+ {
18
+ name: 'metafields',
19
+ title: 'Metafields',
20
+ options: {
21
+ collapsed: true,
22
+ collapsible: true,
23
+ },
24
+ },
17
25
  ],
18
26
  fields: [
19
27
  defineField({
@@ -80,5 +88,14 @@ export const shopifyCollectionType = defineField({
80
88
  name: 'sortOrder',
81
89
  type: 'string',
82
90
  }),
91
+ defineField({
92
+ fieldset: 'metafields',
93
+ name: 'metafields',
94
+ type: 'array',
95
+ description: 'Read-only. Replaced in full on every sync',
96
+ // Shopify owns the order, and `readOnly` does not remove the drag handles on its own.
97
+ options: {sortable: false},
98
+ of: [defineArrayMember({type: 'shopifyMetafield'})],
99
+ }),
83
100
  ],
84
101
  })
@@ -0,0 +1,47 @@
1
+ import {TagIcon} from '@sanity/icons/Tag'
2
+ import {defineField} from 'sanity'
3
+
4
+ import ShopifyMetafield from '../../../components/inputs/ShopifyMetafield'
5
+ import {formatMetafieldValue} from '../../../utils/formatMetafieldValue'
6
+
7
+ export const shopifyMetafieldType = defineField({
8
+ title: 'Metafield',
9
+ name: 'shopifyMetafield',
10
+ type: 'object',
11
+ icon: TagIcon,
12
+ readOnly: true,
13
+ components: {
14
+ input: ShopifyMetafield,
15
+ },
16
+ fields: [
17
+ defineField({
18
+ name: 'namespace',
19
+ type: 'string',
20
+ }),
21
+ defineField({
22
+ name: 'key',
23
+ type: 'string',
24
+ }),
25
+ defineField({
26
+ name: 'type',
27
+ type: 'string',
28
+ }),
29
+ // `value` is deliberately left undeclared: it can be a string, number, boolean, list or object
30
+ // depending on the metafield type, and a declared field is type-checked even when hidden.
31
+ ],
32
+ preview: {
33
+ select: {
34
+ key: 'key',
35
+ namespace: 'namespace',
36
+ type: 'type',
37
+ value: 'value',
38
+ },
39
+ prepare({key, namespace, type, value}) {
40
+ const name = [namespace, key].filter(Boolean).join('.')
41
+ return {
42
+ subtitle: formatMetafieldValue(type, value),
43
+ title: type ? `${name} (${type})` : name,
44
+ }
45
+ },
46
+ },
47
+ })
@@ -29,6 +29,14 @@ export const shopifyProductType = defineField({
29
29
  collapsible: true,
30
30
  },
31
31
  },
32
+ {
33
+ name: 'metafields',
34
+ title: 'Metafields',
35
+ options: {
36
+ collapsed: true,
37
+ collapsible: true,
38
+ },
39
+ },
32
40
  ],
33
41
  fields: [
34
42
  defineField({
@@ -127,5 +135,14 @@ export const shopifyProductType = defineField({
127
135
  }),
128
136
  ],
129
137
  }),
138
+ defineField({
139
+ fieldset: 'metafields',
140
+ name: 'metafields',
141
+ type: 'array',
142
+ description: 'Read-only. Replaced in full on every sync',
143
+ // Shopify owns the order, and `readOnly` does not remove the drag handles on its own.
144
+ options: {sortable: false},
145
+ of: [defineArrayMember({type: 'shopifyMetafield'})],
146
+ }),
130
147
  ],
131
148
  })
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shows a synced Shopify metafield value as text, based on its metafield `type`.
3
+ *
4
+ * The types below are examples. Anything not listed falls back to the raw value, so your own
5
+ * metafields still display — add an entry here to show one of them nicely. Every type and the shape
6
+ * of its value: https://shopify.dev/docs/apps/build/custom-data/metafields/list-of-data-types
7
+ */
8
+ const measurement = ({value, unit}: {value: number; unit: string}) => `${value} ${unit}`
9
+
10
+ const formatters: Record<string, (value: any) => string> = {
11
+ dimension: measurement,
12
+ weight: measurement,
13
+ volume: measurement,
14
+ money: ({amount, currency_code: currencyCode}) => `${amount} ${currencyCode}`,
15
+ rating: ({value, scale_max: scaleMax}) => `${value} / ${scaleMax}`,
16
+ 'list.single_line_text_field': (values: string[]) => values.join(', '),
17
+ }
18
+
19
+ export const formatMetafieldValue = (type: string | undefined, value: unknown) => {
20
+ if (value === null || typeof value === 'undefined') {
21
+ return ''
22
+ }
23
+
24
+ const format = type ? formatters[type] : undefined
25
+ if (format) {
26
+ return format(value)
27
+ }
28
+
29
+ return typeof value === 'string' ? value : JSON.stringify(value)
30
+ }
@@ -0,0 +1,22 @@
1
+ import {Card, Code} from '@sanity/ui'
2
+ import {type ObjectInputProps} from 'sanity'
3
+ import {formatMetafieldValue} from '../../utils/formatMetafieldValue'
4
+
5
+ /**
6
+ * Shows a synced metafield's value. `value` isn't a declared field on `shopifyMetafield` — its shape
7
+ * depends on the Shopify metafield type — so it's read from the raw object value here.
8
+ */
9
+ const ShopifyMetafield = (props: ObjectInputProps) => {
10
+ const {type, value} = (props.value || {}) as {type?: string; value?: unknown}
11
+ const text = formatMetafieldValue(type, value)
12
+
13
+ return (
14
+ <Card border padding={3} radius={1} tone="transparent">
15
+ <Code size={1} style={{margin: 0}}>
16
+ {text}
17
+ </Code>
18
+ </Card>
19
+ )
20
+ }
21
+
22
+ export default ShopifyMetafield
@@ -10,9 +10,11 @@ import callout from './objects/callout'
10
10
  import inventory from './objects/inventory'
11
11
  import option from './objects/option'
12
12
  import priceRange from './objects/priceRange'
13
+ import shop from './objects/shop'
13
14
  import proxyString from './objects/proxyString'
14
15
  import shopifyCollection from './objects/shopifyCollection'
15
16
  import shopifyCollectionRule from './objects/shopifyCollectionRule'
17
+ import shopifyMetafield from './objects/shopifyMetafield'
16
18
  import shopifyProduct from './objects/shopifyProduct'
17
19
  import shopifyProductVariant from './objects/shopifyProductVariant'
18
20
 
@@ -33,8 +35,10 @@ export const schemaTypes = [
33
35
  option,
34
36
  priceRange,
35
37
  proxyString,
38
+ shop,
36
39
  shopifyCollection,
37
40
  shopifyCollectionRule,
41
+ shopifyMetafield,
38
42
  shopifyProduct,
39
43
  shopifyProductVariant,
40
44
 
@@ -0,0 +1,16 @@
1
+ import {defineField, defineType} from 'sanity'
2
+
3
+ export default defineType({
4
+ name: 'shop',
5
+ title: 'Shop',
6
+ type: 'object',
7
+ readOnly: true,
8
+ fields: [
9
+ // Domain
10
+ defineField({
11
+ name: 'domain',
12
+ title: 'Domain',
13
+ type: 'string',
14
+ }),
15
+ ],
16
+ })