@sanity/cli 7.18.0 → 8.0.1

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 (135) hide show
  1. package/README.md +74 -4
  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/init/sdkAppDependencies.js +1 -1
  29. package/dist/actions/init/sdkAppDependencies.js.map +1 -1
  30. package/dist/actions/init/studioDependencies.js +1 -1
  31. package/dist/actions/init/studioDependencies.js.map +1 -1
  32. package/dist/actions/schema/listSchemas.js +1 -1
  33. package/dist/actions/schema/listSchemas.js.map +1 -1
  34. package/dist/actions/tokens/constants.js +2 -2
  35. package/dist/actions/tokens/constants.js.map +1 -1
  36. package/dist/actions/tokens/expiry.js +35 -0
  37. package/dist/actions/tokens/expiry.js.map +1 -0
  38. package/dist/actions/tokens/types.js.map +1 -1
  39. package/dist/actions/tokens/validateRole.js +5 -2
  40. package/dist/actions/tokens/validateRole.js.map +1 -1
  41. package/dist/actions/typegen/count.js +6 -0
  42. package/dist/actions/typegen/count.js.map +1 -0
  43. package/dist/actions/typegen/formatPath.js +5 -0
  44. package/dist/actions/typegen/formatPath.js.map +1 -0
  45. package/dist/actions/typegen/percent.js +8 -0
  46. package/dist/actions/typegen/percent.js.map +1 -0
  47. package/dist/actions/typegen/renderTypegenProgress.js +88 -0
  48. package/dist/actions/typegen/renderTypegenProgress.js.map +1 -0
  49. package/dist/actions/undeploy/adapters.js +6 -9
  50. package/dist/actions/undeploy/adapters.js.map +1 -1
  51. package/dist/actions/undeploy/runUndeploy.js +12 -4
  52. package/dist/actions/undeploy/runUndeploy.js.map +1 -1
  53. package/dist/actions/undeploy/undeployPlan.js +6 -14
  54. package/dist/actions/undeploy/undeployPlan.js.map +1 -1
  55. package/dist/commands/assets/upload.js +160 -0
  56. package/dist/commands/assets/upload.js.map +1 -0
  57. package/dist/commands/backups/list.js +1 -1
  58. package/dist/commands/backups/list.js.map +1 -1
  59. package/dist/commands/datasets/copy.js +1 -1
  60. package/dist/commands/datasets/copy.js.map +1 -1
  61. package/dist/commands/login.js +2 -8
  62. package/dist/commands/login.js.map +1 -1
  63. package/dist/commands/mcp/configure.js.map +1 -1
  64. package/dist/commands/migrations/list.js +1 -1
  65. package/dist/commands/migrations/list.js.map +1 -1
  66. package/dist/commands/migrations/run.js +67 -3
  67. package/dist/commands/migrations/run.js.map +1 -1
  68. package/dist/commands/new.js +4 -3
  69. package/dist/commands/new.js.map +1 -1
  70. package/dist/commands/organizations/list.js +1 -1
  71. package/dist/commands/organizations/list.js.map +1 -1
  72. package/dist/commands/projects/list.js +39 -12
  73. package/dist/commands/projects/list.js.map +1 -1
  74. package/dist/commands/projects/unclaimed.js +39 -33
  75. package/dist/commands/projects/unclaimed.js.map +1 -1
  76. package/dist/commands/tokens/create.js +92 -7
  77. package/dist/commands/tokens/create.js.map +1 -1
  78. package/dist/commands/tokens/delete.js +2 -2
  79. package/dist/commands/tokens/delete.js.map +1 -1
  80. package/dist/commands/tokens/list.js +12 -11
  81. package/dist/commands/tokens/list.js.map +1 -1
  82. package/dist/commands/tokens/rotate.js +95 -0
  83. package/dist/commands/tokens/rotate.js.map +1 -0
  84. package/dist/commands/typegen/generate.js +222 -0
  85. package/dist/commands/typegen/generate.js.map +1 -0
  86. package/dist/commands/users/list.js +8 -8
  87. package/dist/commands/users/list.js.map +1 -1
  88. package/dist/exports/_internal.d.ts +29 -0
  89. package/dist/exports/_internal.js +1 -0
  90. package/dist/exports/_internal.js.map +1 -1
  91. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +6 -0
  92. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
  93. package/dist/exports/invokeSanityCli/help.js +4 -0
  94. package/dist/exports/invokeSanityCli/help.js.map +1 -1
  95. package/dist/exports/invokeSanityCli/index.js +2 -0
  96. package/dist/exports/invokeSanityCli/index.js.map +1 -1
  97. package/dist/hooks/commandNotFound/topicAliases.js +13 -40
  98. package/dist/hooks/commandNotFound/topicAliases.js.map +1 -1
  99. package/dist/hooks/prerun/setupTelemetry.js +3 -2
  100. package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
  101. package/dist/server/vite/plugin-typegen.js +4 -16
  102. package/dist/server/vite/plugin-typegen.js.map +1 -1
  103. package/dist/services/assets.js +46 -0
  104. package/dist/services/assets.js.map +1 -0
  105. package/dist/services/tokens.js +81 -31
  106. package/dist/services/tokens.js.map +1 -1
  107. package/dist/topicAliases.js +43 -0
  108. package/dist/topicAliases.js.map +1 -1
  109. package/dist/util/parseArguments.js +7 -2
  110. package/dist/util/parseArguments.js.map +1 -1
  111. package/dist/util/readTokenFromStdin.js +17 -0
  112. package/dist/util/readTokenFromStdin.js.map +1 -0
  113. package/dist/util/responsiveTable.js +97 -0
  114. package/dist/util/responsiveTable.js.map +1 -0
  115. package/dist/util/telemetry/commandTelemetry.js +34 -0
  116. package/dist/util/telemetry/commandTelemetry.js.map +1 -0
  117. package/dist/util/unclaimedProjects.js +10 -0
  118. package/dist/util/unclaimedProjects.js.map +1 -1
  119. package/oclif.config.js +2 -4
  120. package/oclif.manifest.json +688 -476
  121. package/package.json +12 -10
  122. package/templates/shopify/components/inputs/ShopifyMetafield.tsx +20 -0
  123. package/templates/shopify/docs/features.md +24 -0
  124. package/templates/shopify/schemaTypes/index.ts +2 -0
  125. package/templates/shopify/schemaTypes/objects/shopify/shopifyCollectionType.ts +18 -1
  126. package/templates/shopify/schemaTypes/objects/shopify/shopifyMetafieldType.ts +47 -0
  127. package/templates/shopify/schemaTypes/objects/shopify/shopifyProductType.ts +17 -0
  128. package/templates/shopify/utils/formatMetafieldValue.ts +30 -0
  129. package/templates/shopify-online-storefront/components/inputs/ShopifyMetafield.tsx +22 -0
  130. package/templates/shopify-online-storefront/schemaTypes/index.ts +4 -0
  131. package/templates/shopify-online-storefront/schemaTypes/objects/shop.ts +16 -0
  132. package/templates/shopify-online-storefront/schemaTypes/objects/shopifyCollection.ts +19 -0
  133. package/templates/shopify-online-storefront/schemaTypes/objects/shopifyMetafield.ts +53 -0
  134. package/templates/shopify-online-storefront/schemaTypes/objects/shopifyProduct.ts +19 -0
  135. package/templates/shopify-online-storefront/utils/formatMetafieldValue.ts +30 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli",
3
- "version": "7.18.0",
3
+ "version": "8.0.1",
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,6 +113,7 @@
113
113
  "skills": "^1.5.9",
114
114
  "smol-toml": "^1.6.1",
115
115
  "string-argv": "^0.3.2",
116
+ "string-width": "^7.2.0",
116
117
  "tar": "^7.5.18",
117
118
  "tar-fs": "^3.1.2",
118
119
  "tar-stream": "^3.2.0",
@@ -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.1",
128
- "@sanity/workbench-cli": "^1.10.0"
128
+ "@sanity/cli-build": "^5.2.0",
129
+ "@sanity/workbench-cli": "^2.0.1",
130
+ "@sanity/cli-core": "^3.0.0"
129
131
  },
130
132
  "devDependencies": {
131
133
  "@eslint/compat": "^2.1.0",
@@ -158,9 +160,9 @@
158
160
  "sanity": "^6.7.0",
159
161
  "typescript": "^6.0.3",
160
162
  "vitest": "^4.1.10",
161
- "@repo/tsconfig": "3.70.0",
162
163
  "@repo/package.config": "0.0.1",
163
- "@sanity/cli-test": "9.0.2",
164
+ "@repo/tsconfig": "3.70.0",
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
+ })
@@ -14,6 +14,14 @@ export default defineType({
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
  // Created at
@@ -98,6 +106,17 @@ export default defineType({
98
106
  title: 'Sort order',
99
107
  type: 'string',
100
108
  }),
109
+ // Metafields
110
+ defineField({
111
+ fieldset: 'metafields',
112
+ name: 'metafields',
113
+ title: 'Metafields',
114
+ type: 'array',
115
+ description: 'Read-only. Replaced in full on every sync',
116
+ // Shopify owns the order, and `readOnly` does not remove the drag handles on its own.
117
+ options: {sortable: false},
118
+ of: [{type: 'shopifyMetafield'}],
119
+ }),
101
120
  // Shop details
102
121
  defineField({
103
122
  name: 'shop',
@@ -0,0 +1,53 @@
1
+ import {TagIcon} from '@sanity/icons/Tag'
2
+ import {defineField, defineType} from 'sanity'
3
+
4
+ import ShopifyMetafield from '../../components/inputs/ShopifyMetafield'
5
+ import {formatMetafieldValue} from '../../utils/formatMetafieldValue'
6
+
7
+ export default defineType({
8
+ title: 'Metafield',
9
+ name: 'shopifyMetafield',
10
+ type: 'object',
11
+ icon: TagIcon,
12
+ readOnly: true,
13
+ components: {
14
+ input: ShopifyMetafield,
15
+ },
16
+ fields: [
17
+ // Namespace
18
+ defineField({
19
+ title: 'Namespace',
20
+ name: 'namespace',
21
+ type: 'string',
22
+ }),
23
+ // Key
24
+ defineField({
25
+ title: 'Key',
26
+ name: 'key',
27
+ type: 'string',
28
+ }),
29
+ // Type
30
+ defineField({
31
+ title: 'Type',
32
+ name: 'type',
33
+ type: 'string',
34
+ }),
35
+ // `value` is deliberately left undeclared: it can be a string, number, boolean, list or object
36
+ // depending on the metafield type, and a declared field is type-checked even when hidden.
37
+ ],
38
+ preview: {
39
+ select: {
40
+ key: 'key',
41
+ namespace: 'namespace',
42
+ type: 'type',
43
+ value: 'value',
44
+ },
45
+ prepare({key, namespace, type, value}) {
46
+ const name = [namespace, key].filter(Boolean).join('.')
47
+ return {
48
+ subtitle: formatMetafieldValue(type, value),
49
+ title: type ? `${name} (${type})` : name,
50
+ }
51
+ },
52
+ },
53
+ })
@@ -29,6 +29,14 @@ export default defineType({
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
  // Created at
@@ -154,6 +162,17 @@ export default defineType({
154
162
  },
155
163
  ],
156
164
  }),
165
+ // Metafields
166
+ defineField({
167
+ fieldset: 'metafields',
168
+ name: 'metafields',
169
+ title: 'Metafields',
170
+ type: 'array',
171
+ description: 'Read-only. Replaced in full on every sync',
172
+ // Shopify owns the order, and `readOnly` does not remove the drag handles on its own.
173
+ options: {sortable: false},
174
+ of: [{type: 'shopifyMetafield'}],
175
+ }),
157
176
  // Shop details
158
177
  defineField({
159
178
  name: 'shop',
@@ -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
+ }