@sanity/plugin-kit 0.0.1-studio-v3.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 (194) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +398 -0
  3. package/assets/splat/LICENSE +21 -0
  4. package/assets/splat/editorconfig +13 -0
  5. package/assets/splat/eslint.config.js +5 -0
  6. package/assets/splat/gitignore +55 -0
  7. package/assets/splat/npmignore +9 -0
  8. package/assets/splat/prettierrc.js +6 -0
  9. package/assets/splat/sanity.json +8 -0
  10. package/assets/splat/template-tsconfig.json +23 -0
  11. package/assets/splat/v2-incompatible.js.template +11 -0
  12. package/lib/package.json +127 -0
  13. package/lib/src/actions/init.d.ts +65 -0
  14. package/lib/src/actions/init.js +83 -0
  15. package/lib/src/actions/init.js.map +1 -0
  16. package/lib/src/actions/link-watch.d.ts +3 -0
  17. package/lib/src/actions/link-watch.js +69 -0
  18. package/lib/src/actions/link-watch.js.map +1 -0
  19. package/lib/src/actions/splat.d.ts +26 -0
  20. package/lib/src/actions/splat.js +296 -0
  21. package/lib/src/actions/splat.js.map +1 -0
  22. package/lib/src/actions/verify/types.d.ts +77 -0
  23. package/lib/src/actions/verify/types.js +3 -0
  24. package/lib/src/actions/verify/types.js.map +1 -0
  25. package/lib/src/actions/verify/validations.d.ts +28 -0
  26. package/lib/src/actions/verify/validations.js +379 -0
  27. package/lib/src/actions/verify/validations.js.map +1 -0
  28. package/lib/src/actions/verify/verify-common.d.ts +43 -0
  29. package/lib/src/actions/verify/verify-common.js +88 -0
  30. package/lib/src/actions/verify/verify-common.js.map +1 -0
  31. package/lib/src/actions/verify-package.d.ts +5 -0
  32. package/lib/src/actions/verify-package.js +72 -0
  33. package/lib/src/actions/verify-package.js.map +1 -0
  34. package/lib/src/actions/verify-studio.d.ts +5 -0
  35. package/lib/src/actions/verify-studio.js +55 -0
  36. package/lib/src/actions/verify-studio.js.map +1 -0
  37. package/lib/src/actions/verify.d.ts +0 -0
  38. package/lib/src/actions/verify.js +330 -0
  39. package/lib/src/actions/verify.js.map +1 -0
  40. package/lib/src/cli.d.ts +2 -0
  41. package/lib/src/cli.js +86 -0
  42. package/lib/src/cli.js.map +1 -0
  43. package/lib/src/cmds/index.d.ts +8 -0
  44. package/lib/src/cmds/index.js +12 -0
  45. package/lib/src/cmds/index.js.map +1 -0
  46. package/lib/src/cmds/init.d.ts +4 -0
  47. package/lib/src/cmds/init.js +90 -0
  48. package/lib/src/cmds/init.js.map +1 -0
  49. package/lib/src/cmds/link-watch.d.ts +4 -0
  50. package/lib/src/cmds/link-watch.js +49 -0
  51. package/lib/src/cmds/link-watch.js.map +1 -0
  52. package/lib/src/cmds/splat.d.ts +4 -0
  53. package/lib/src/cmds/splat.js +63 -0
  54. package/lib/src/cmds/splat.js.map +1 -0
  55. package/lib/src/cmds/verify-package.d.ts +4 -0
  56. package/lib/src/cmds/verify-package.js +38 -0
  57. package/lib/src/cmds/verify-package.js.map +1 -0
  58. package/lib/src/cmds/verify-studio.d.ts +4 -0
  59. package/lib/src/cmds/verify-studio.js +38 -0
  60. package/lib/src/cmds/verify-studio.js.map +1 -0
  61. package/lib/src/cmds/verify.d.ts +0 -0
  62. package/lib/src/cmds/verify.js +42 -0
  63. package/lib/src/cmds/verify.js.map +1 -0
  64. package/lib/src/cmds/version.d.ts +4 -0
  65. package/lib/src/cmds/version.js +55 -0
  66. package/lib/src/cmds/version.js.map +1 -0
  67. package/lib/src/configs/buildExtensions.d.ts +1 -0
  68. package/lib/src/configs/buildExtensions.js +5 -0
  69. package/lib/src/configs/buildExtensions.js.map +1 -0
  70. package/lib/src/configs/default-source.d.ts +3 -0
  71. package/lib/src/configs/default-source.js +70 -0
  72. package/lib/src/configs/default-source.js.map +1 -0
  73. package/lib/src/configs/merged-packages.d.ts +1 -0
  74. package/lib/src/configs/merged-packages.js +24 -0
  75. package/lib/src/configs/merged-packages.js.map +1 -0
  76. package/lib/src/configs/uselessFiles.d.ts +1 -0
  77. package/lib/src/configs/uselessFiles.js +33 -0
  78. package/lib/src/configs/uselessFiles.js.map +1 -0
  79. package/lib/src/constants.d.ts +11 -0
  80. package/lib/src/constants.js +15 -0
  81. package/lib/src/constants.js.map +1 -0
  82. package/lib/src/dependencies/find.d.ts +0 -0
  83. package/lib/src/dependencies/find.js +195 -0
  84. package/lib/src/dependencies/find.js.map +1 -0
  85. package/lib/src/dependencies/import-linter.d.ts +3 -0
  86. package/lib/src/dependencies/import-linter.js +112 -0
  87. package/lib/src/dependencies/import-linter.js.map +1 -0
  88. package/lib/src/index.d.ts +2 -0
  89. package/lib/src/index.js +6 -0
  90. package/lib/src/index.js.map +1 -0
  91. package/lib/src/npm/manager.d.ts +7 -0
  92. package/lib/src/npm/manager.js +62 -0
  93. package/lib/src/npm/manager.js.map +1 -0
  94. package/lib/src/npm/package.d.ts +8 -0
  95. package/lib/src/npm/package.js +288 -0
  96. package/lib/src/npm/package.js.map +1 -0
  97. package/lib/src/npm/publish.d.ts +1 -0
  98. package/lib/src/npm/publish.js +14 -0
  99. package/lib/src/npm/publish.js.map +1 -0
  100. package/lib/src/npm/resolveLatestVersions.d.ts +3 -0
  101. package/lib/src/npm/resolveLatestVersions.js +35 -0
  102. package/lib/src/npm/resolveLatestVersions.js.map +1 -0
  103. package/lib/src/sanity/manifest.d.ts +48 -0
  104. package/lib/src/sanity/manifest.js +263 -0
  105. package/lib/src/sanity/manifest.js.map +1 -0
  106. package/lib/src/sharedFlags.d.ts +15 -0
  107. package/lib/src/sharedFlags.js +17 -0
  108. package/lib/src/sharedFlags.js.map +1 -0
  109. package/lib/src/util/command-parser.d.ts +9 -0
  110. package/lib/src/util/command-parser.js +41 -0
  111. package/lib/src/util/command-parser.js.map +1 -0
  112. package/lib/src/util/errorToUndefined.d.ts +1 -0
  113. package/lib/src/util/errorToUndefined.js +11 -0
  114. package/lib/src/util/errorToUndefined.js.map +1 -0
  115. package/lib/src/util/files.d.ts +36 -0
  116. package/lib/src/util/files.js +253 -0
  117. package/lib/src/util/files.js.map +1 -0
  118. package/lib/src/util/log.d.ts +14 -0
  119. package/lib/src/util/log.js +36 -0
  120. package/lib/src/util/log.js.map +1 -0
  121. package/lib/src/util/prompt.d.ts +13 -0
  122. package/lib/src/util/prompt.js +75 -0
  123. package/lib/src/util/prompt.js.map +1 -0
  124. package/lib/src/util/readme.d.ts +5 -0
  125. package/lib/src/util/readme.js +73 -0
  126. package/lib/src/util/readme.js.map +1 -0
  127. package/lib/src/util/request.d.ts +1 -0
  128. package/lib/src/util/request.js +19 -0
  129. package/lib/src/util/request.js.map +1 -0
  130. package/lib/src/util/user.d.ts +10 -0
  131. package/lib/src/util/user.js +106 -0
  132. package/lib/src/util/user.js.map +1 -0
  133. package/lib/test/cli.test.d.ts +1 -0
  134. package/lib/test/cli.test.js +64 -0
  135. package/lib/test/cli.test.js.map +1 -0
  136. package/lib/test/fixture-utils.d.ts +25 -0
  137. package/lib/test/fixture-utils.js +67 -0
  138. package/lib/test/fixture-utils.js.map +1 -0
  139. package/lib/test/init-verify-build.test.d.ts +1 -0
  140. package/lib/test/init-verify-build.test.js +75 -0
  141. package/lib/test/init-verify-build.test.js.map +1 -0
  142. package/lib/test/init.test.d.ts +1 -0
  143. package/lib/test/init.test.js +137 -0
  144. package/lib/test/init.test.js.map +1 -0
  145. package/lib/test/run-test-command.d.ts +1 -0
  146. package/lib/test/run-test-command.js +6 -0
  147. package/lib/test/run-test-command.js.map +1 -0
  148. package/lib/test/verify-package.test.d.ts +1 -0
  149. package/lib/test/verify-package.test.js +81 -0
  150. package/lib/test/verify-package.test.js.map +1 -0
  151. package/lib/test/version.test.d.ts +1 -0
  152. package/lib/test/version.test.js +48 -0
  153. package/lib/test/version.test.js.map +1 -0
  154. package/package.json +127 -0
  155. package/src/actions/init.ts +104 -0
  156. package/src/actions/link-watch.ts +74 -0
  157. package/src/actions/splat.ts +366 -0
  158. package/src/actions/verify/types.ts +84 -0
  159. package/src/actions/verify/validations.ts +401 -0
  160. package/src/actions/verify/verify-common.ts +92 -0
  161. package/src/actions/verify-package.ts +87 -0
  162. package/src/actions/verify-studio.ts +55 -0
  163. package/src/actions/verify.ts +328 -0
  164. package/src/cli.ts +77 -0
  165. package/src/cmds/index.ts +9 -0
  166. package/src/cmds/init.ts +85 -0
  167. package/src/cmds/link-watch.ts +51 -0
  168. package/src/cmds/splat.ts +59 -0
  169. package/src/cmds/verify-package.ts +36 -0
  170. package/src/cmds/verify-studio.ts +36 -0
  171. package/src/cmds/verify.ts +40 -0
  172. package/src/cmds/version.ts +67 -0
  173. package/src/configs/buildExtensions.ts +1 -0
  174. package/src/configs/default-source.ts +68 -0
  175. package/src/configs/merged-packages.ts +20 -0
  176. package/src/configs/uselessFiles.ts +29 -0
  177. package/src/constants.ts +13 -0
  178. package/src/dependencies/find.ts +193 -0
  179. package/src/dependencies/import-linter.ts +103 -0
  180. package/src/index.ts +4 -0
  181. package/src/npm/manager.ts +44 -0
  182. package/src/npm/package.ts +370 -0
  183. package/src/npm/publish.ts +9 -0
  184. package/src/npm/resolveLatestVersions.ts +26 -0
  185. package/src/sanity/manifest.ts +340 -0
  186. package/src/sharedFlags.ts +14 -0
  187. package/src/util/command-parser.ts +31 -0
  188. package/src/util/errorToUndefined.ts +7 -0
  189. package/src/util/files.ts +249 -0
  190. package/src/util/log.ts +44 -0
  191. package/src/util/prompt.ts +70 -0
  192. package/src/util/readme.ts +72 -0
  193. package/src/util/request.ts +13 -0
  194. package/src/util/user.ts +110 -0
@@ -0,0 +1,55 @@
1
+ import {getPackage} from '../npm/package'
2
+ import log from '../util/log'
3
+ import {readJson5File} from '../util/files'
4
+ import {cliName, urls} from '../constants'
5
+ import {validateImports} from '../dependencies/import-linter'
6
+ import outdent from 'outdent'
7
+ import chalk from 'chalk'
8
+ import {
9
+ createValidator,
10
+ runTscMaybe,
11
+ VerifyFlags,
12
+ VerifyPackageConfig,
13
+ } from './verify/verify-common'
14
+ import {PackageJson, TsConfig} from './verify/types'
15
+ import {validateSanityDependencies, validateStudioConfig} from './verify/validations'
16
+
17
+ export async function verifyStudio({basePath, flags}: {basePath: string; flags: VerifyFlags}) {
18
+ let errors: string[] = []
19
+
20
+ const packageJson: PackageJson = await getPackage({basePath, validate: false})
21
+ const verifyConfig: VerifyPackageConfig = packageJson.sanityPlugin?.verifyPackage || {}
22
+
23
+ const validation = createValidator(verifyConfig, flags, errors)
24
+
25
+ const tsConfig = await readJson5File<TsConfig>({basePath, filename: 'tsconfig.json'})
26
+
27
+ await validation('studioConfig', async () => validateStudioConfig({basePath}))
28
+ await validation('dependencies', async () => validateSanityDependencies(packageJson))
29
+ await validation('eslintImports', async () => validateImports({basePath}))
30
+
31
+ if (errors.length) {
32
+ throw new Error(
33
+ outdent`
34
+ Detected validation issues!
35
+ This Sanity Studio is not completely V3 ready. Fix the issues starting from the top, or disable any checks you deem unnecessary.
36
+
37
+ More information is available here:
38
+ - Migration guide: ${urls.migrationGuideStudio}
39
+ - Reference documentation: ${urls.refDocs}
40
+
41
+ ${chalk.grey(
42
+ `To fail-fast on first detected issue run:\nnpx ${cliName} verify-studio --single`
43
+ )}
44
+ `.trimStart()
45
+ )
46
+ }
47
+
48
+ await runTscMaybe(verifyConfig, tsConfig)
49
+
50
+ log.success(
51
+ outdent`
52
+ No outstanding upgrade issues detected. Studio is V3 ready!
53
+ `.trim()
54
+ )
55
+ }
@@ -0,0 +1,328 @@
1
+ /*
2
+ import path from 'path'
3
+ //@ts-expect-error missing types
4
+ import spdxLicenseIds from 'spdx-license-ids'
5
+ import semver from 'semver'
6
+
7
+ import log from '../util/log'
8
+ import {fileExists, readJsonFile} from '../util/files'
9
+ import {readManifest, getReferencesPartPaths} from '../sanity/manifest'
10
+ import {getPackage, getReferencedPaths} from '../npm/package'
11
+ import {getPublishableFiles} from '../npm/publish'
12
+ import {findDependencies} from '../dependencies/find'
13
+ import {uselessFiles} from '../configs/uselessFiles'
14
+
15
+ export async function verify({basePath, flags}) {
16
+ const pkg = await getPackage({basePath, flags})
17
+ const manifest = await readManifest({
18
+ basePath,
19
+ pluginName: pkg.name,
20
+ flags,
21
+ verifyCompiledParts: true,
22
+ verifySourceParts: true,
23
+ })
24
+
25
+ // Get all files intended to be published from npm
26
+ const publishableFiles = await getPublishableFiles(basePath)
27
+
28
+ // Errors
29
+ await verifyPublishableFiles({basePath, pkg, manifest, publishableFiles})
30
+ await verifyLicenseKey(pkg)
31
+ await verifyPluginConfig(basePath)
32
+ await verifyImports({pkg, manifest, basePath})
33
+
34
+ // Warnings
35
+ const hasWarnings = await warnOnUselessFiles(publishableFiles)
36
+
37
+ // Huzzah
38
+ log.success(
39
+ hasWarnings
40
+ ? 'Plugin has warnings, but looks good to publish!'
41
+ : 'Plugin looks good to publish!'
42
+ )
43
+ }
44
+
45
+ async function verifyPublishableFiles({pkg, manifest, basePath, publishableFiles}) {
46
+ // Validate that these files exists, not just that they are publishable
47
+ if (!(await fileExists(path.resolve(basePath, 'README.md')))) {
48
+ throw new Error(
49
+ `This plugin does not contain a README.md, which is required for Sanity plugins.`
50
+ )
51
+ }
52
+
53
+ const [hasLicense, hasLicenseMd] = await Promise.all([
54
+ fileExists(path.resolve(basePath, 'LICENSE')),
55
+ fileExists(path.resolve(basePath, 'LICENSE.md')),
56
+ ])
57
+
58
+ if (!hasLicense && !hasLicenseMd) {
59
+ throw new Error(
60
+ `This plugin does not contain a LICENSE-file, which is required for Sanity plugins.`
61
+ )
62
+ }
63
+
64
+ // Always, uhm... "kindly suggest", to include these files
65
+ const files = ['README.md']
66
+ // Get files from parts as well as the ones references in package.json
67
+ .concat(getReferencesPartPaths(manifest, basePath), getReferencedPaths(pkg, basePath))
68
+ // Make all paths relative to base path
69
+ .map((file) =>
70
+ path.relative(basePath, path.isAbsolute(file) ? file : path.resolve(basePath, file))
71
+ )
72
+ // Remove duplicates
73
+ .filter((file, index, arr) => arr.indexOf(file, index + 1) === -1)
74
+
75
+ // Verify that all explicitly referenced files are publishable
76
+ const unpublishable = files.filter((file) => !publishableFiles.includes(file))
77
+
78
+ // Warn with "default error" for unknowns
79
+ const unknowns = unpublishable.filter((item) => item !== 'README.md').map((file) => `"${file}"`)
80
+
81
+ if (unknowns.length > 0) {
82
+ const paths = unknowns.join(', ')
83
+ throw new Error(
84
+ `This plugin references files that are ignored from being published: ${paths}. Check .gitignore, .npmignore and/or the "files" property of package.json. See https://docs.npmjs.com/using-npm/developers.html#keeping-files-out-of-your-package for more information.`
85
+ )
86
+ }
87
+ }
88
+
89
+ function verifyLicenseKey(pkg) {
90
+ if (!pkg.license) {
91
+ throw new Error(
92
+ `package.json is missing "license" key: see https://docs.npmjs.com/files/package.json#license and make sure it matches your "LICENSE" file. See https://choosealicense.com/ for help on choosing a license.`
93
+ )
94
+ }
95
+
96
+ if (pkg.license !== 'UNLICENSED' && !spdxLicenseIds.includes(pkg.license)) {
97
+ throw new Error(
98
+ `package.json has an invalid "license" key: it should be either an SPDX license ID (https://spdx.org/licenses/) or "UNLICENSE". See https://docs.npmjs.com/files/package.json#license and refer to https://choosealicense.com/ for help on choosing a license.`
99
+ )
100
+ }
101
+ }
102
+
103
+ async function verifyPluginConfig(basePath) {
104
+ const configPath = path.join(basePath, 'config.dist.json')
105
+ if (!(await fileExists(configPath))) {
106
+ return
107
+ }
108
+
109
+ let config
110
+ try {
111
+ config = await readJsonFile(configPath)
112
+ } catch (err: any) {
113
+ throw new Error(`Error reading plugin config (${configPath}): ${err.message}`)
114
+ }
115
+
116
+ if (typeof config !== 'object' || Array.isArray(config) || !config) {
117
+ throw new Error(
118
+ `Error reading plugin config (${configPath}): must be an object, got:\n${JSON.stringify(
119
+ config,
120
+ null,
121
+ 2
122
+ )}`
123
+ )
124
+ }
125
+ }
126
+
127
+ function warnOnUselessFiles(files) {
128
+ const warnFor = files
129
+ .filter(
130
+ (file) =>
131
+ uselessFiles.includes(file) ||
132
+ uselessFiles.some((useless) => file.startsWith(`${useless}/`))
133
+ )
134
+ .map((file) => `"${file}"`)
135
+ .join(', ')
136
+
137
+ if (warnFor.length === 0) {
138
+ return false
139
+ }
140
+
141
+ log.warn(
142
+ `This plugin is set to publish the following files, which are generally not needed in a published npm module: ${warnFor}.`
143
+ )
144
+ log.warn(
145
+ `Consider adding these files to an .npmignore or the package.json "files" property. See https://docs.npmjs.com/using-npm/developers.html#keeping-files-out-of-your-package for more information.`
146
+ )
147
+
148
+ return true
149
+ }
150
+
151
+ async function verifyImports({pkg, manifest, basePath}) {
152
+ // "Entry" as in... code may start here.
153
+ const entries = ([] as string[])
154
+ .concat(
155
+ getReferencedPaths(pkg, basePath), // From npm
156
+ getReferencesPartPaths(manifest, basePath) // From parts
157
+ )
158
+ // Remove duplicates
159
+ .filter((file, index, arr) => arr.indexOf(file, index + 1) === -1)
160
+ // Remove non-javascript/non-css entries
161
+ .filter((file) => ['.js', '.css'].includes(path.extname(file)))
162
+
163
+ const dependencies = findDependencies(entries)
164
+ const modules = dependencies.filter((dep) => !/^(all|part|config|sanity):/.test(dep))
165
+
166
+ await verifyNoUndeclaredDependencies(modules, pkg)
167
+ await verifyReactDependencies(modules, pkg)
168
+ await verifyUiDependencies(modules, pkg)
169
+ await verifyConfigParts(dependencies, pkg, basePath)
170
+ await verifyNoUnusedDependencies(modules, pkg)
171
+ await verifyNoUndeclaredParts(dependencies, pkg, manifest)
172
+ }
173
+
174
+ function verifyNoUndeclaredParts(dependencies, pkg, manifest) {
175
+ const parts = dependencies
176
+ .filter((dep) => dep.startsWith('all:') || dep.startsWith('part:'))
177
+ .map((part) => part.replace(/^all:/, ''))
178
+ .map((part) => part.replace(/\?$/, ''))
179
+ .filter((part) => part.startsWith(`part:${pkg.name.replace(/^sanity-plugin-/, '')}`))
180
+
181
+ const declaredParts = (manifest.parts || []).reduce(
182
+ (partNames, part) => [...partNames, ...[part.name, part.implements].filter(Boolean)],
183
+ []
184
+ )
185
+ const undeclaredParts = parts.filter((partName) => !declaredParts.includes(partName))
186
+
187
+ if (undeclaredParts.length > 0) {
188
+ const aPart = undeclaredParts.length > 1 ? 'parts' : 'a part'
189
+ const is = undeclaredParts.length > 1 ? 'are' : 'is'
190
+ const partList = undeclaredParts.map((part) => `"${part}"`).join(', ')
191
+ throw new Error(
192
+ `Invalid plugin: Source is using ${aPart} that ${is} not declared in "sanity.json":\n\n${partList}.`
193
+ )
194
+ }
195
+ }
196
+
197
+ function verifyNoUnusedDependencies(modules, pkg) {
198
+ const potentiallyUnused = Object.keys(pkg.dependencies || {}).filter(
199
+ (dep) => !modules.includes(dep)
200
+ )
201
+
202
+ if (potentiallyUnused.length > 0) {
203
+ const unusedNames = potentiallyUnused.map((dep) => `"${dep}"`).join(', ')
204
+ log.warn(`Found modules listed as dependencies which seem to be unused by code: ${unusedNames}`)
205
+ }
206
+ }
207
+
208
+ function verifyReactDependencies(modules, pkg) {
209
+ if (modules.includes('react') && 'react' in (pkg.dependencies || {})) {
210
+ throw new Error(
211
+ `Invalid plugin: "react" declared as a dependency - it should be declared as a peerDependency (package.json)`
212
+ )
213
+ }
214
+
215
+ if (modules.includes('react-dom') && 'react-dom' in (pkg.dependencies || {})) {
216
+ throw new Error(
217
+ `Invalid plugin: "react-dom" declared as a dependency - it should be declared as a peerDependency (package.json)`
218
+ )
219
+ }
220
+
221
+ if (modules.includes('prop-types') && 'prop-types' in (pkg.peerDependencies || {})) {
222
+ throw new Error(
223
+ `Invalid plugin: "prop-types" declares as peerDependency - it should be declared as a dependency (package.json)`
224
+ )
225
+ }
226
+ }
227
+
228
+ function verifyUiDependencies(modules, pkg) {
229
+ const peerDependencies = pkg.peerDependencies || {}
230
+ const dependencies = pkg.dependencies || {}
231
+
232
+ if (modules.includes('@sanity/ui') && '@sanity/ui' in peerDependencies) {
233
+ throw new Error(
234
+ `Invalid plugin: "@sanity/ui" declared as a peer dependency - it should be declared as a dependency (package.json)`
235
+ )
236
+ }
237
+
238
+ if (
239
+ modules.includes('@sanity/ui') &&
240
+ dependencies['@sanity/ui'] &&
241
+ semver.lt(semver.minVersion(dependencies['@sanity/ui']), '0.33.1')
242
+ ) {
243
+ throw new Error(
244
+ `Invalid plugin: "@sanity/ui" dependency must use version higher than or equal to 0.33.1 (package.json)`
245
+ )
246
+ }
247
+
248
+ if (modules.includes('@sanity/icons') && '@sanity/icons' in peerDependencies) {
249
+ throw new Error(
250
+ `Invalid plugin: "@sanity/icons" declared as a peer dependency - it should be declared as a dependency (package.json)`
251
+ )
252
+ }
253
+ }
254
+
255
+ async function verifyConfigParts(dependencies, pkg, basePath) {
256
+ const configName = `config:${pkg.name.replace(/^sanity-plugin-/, '')}`
257
+ if (
258
+ dependencies.includes(configName) &&
259
+ !(await fileExists(path.join(basePath, 'config.dist.json')))
260
+ ) {
261
+ throw new Error(
262
+ `Plugin imports plugin config (${configName}) but does not contain a "config.dist.json" file.`
263
+ )
264
+ }
265
+
266
+ const nonPluginConfigs = dependencies.filter(
267
+ (dep) => dep.startsWith('config:') && dep !== configName
268
+ )
269
+
270
+ if (nonPluginConfigs.length > 0) {
271
+ const configs = nonPluginConfigs.length > 1 ? 'configs' : 'config'
272
+ const nonPluginConfigNames = nonPluginConfigs.join(', ')
273
+ log.warn(
274
+ `Found references to external ${configs}: ${nonPluginConfigNames} - this is generally considered unsafe`
275
+ )
276
+ }
277
+ }
278
+
279
+ function verifyNoUndeclaredDependencies(modules, pkg) {
280
+ const undeclared = getUndeclaredDependencies(modules, pkg)
281
+ if (undeclared.length > 0) {
282
+ throw new Error(getUndeclaredDependenciesError(undeclared))
283
+ }
284
+ }
285
+
286
+ function getUndeclaredDependencies(modules, pkg) {
287
+ const deps = ([] as string[]).concat(
288
+ Object.keys(pkg.dependencies || {}),
289
+ Object.keys(pkg.peerDependencies || {})
290
+ )
291
+
292
+ const devDeps = Object.keys(pkg.devDependencies || {})
293
+
294
+ return modules
295
+ .filter((modDep) => !deps.includes(modDep))
296
+ .map((modDep) => ({
297
+ dependency: modDep,
298
+ isDevDep: devDeps.includes(modDep),
299
+ }))
300
+ }
301
+
302
+ function getUndeclaredDependenciesError(undeclared) {
303
+ const baseError = `Invalid plugin`
304
+ const moduleNames = undeclared.map((mod) => mod.dependency)
305
+
306
+ let declaredWhere = `should be declared in package.json under "dependencies" or "peerDependencies".`
307
+ if (moduleNames.length === 1 && moduleNames[0] === 'react') {
308
+ declaredWhere = `should be declared in package.json under "peerDependencies"`
309
+ } else if (moduleNames.includes('react')) {
310
+ declaredWhere = `should either be declared in package.json under "dependencies" or "peerDependencies" (react should be in "peerDependencies").`
311
+ }
312
+
313
+ const devDeps = undeclared.filter((dep) => dep.isDevDep).map((dep) => ` - ${dep.dependency}\n`)
314
+ if (devDeps.length > 0) {
315
+ const modules = devDeps.length > 1 ? 'modules' : 'module'
316
+ const depList = devDeps.join('')
317
+ const target = devDeps.length > 1 ? 'They' : 'It'
318
+ const are = devDeps.length > 1 ? 'are' : 'is'
319
+ return `${baseError}: Source uses ${modules} that ${are} declared in "devDependencies":\n\n${depList}\n${target} ${declaredWhere}`
320
+ }
321
+
322
+ const modules = undeclared.length > 1 ? 'modules' : 'module'
323
+ const depList = undeclared.map((dep) => ` - ${dep.dependency}\n`).join('')
324
+ const target = undeclared.length > 1 ? 'They' : 'It'
325
+ const are = undeclared.length > 1 ? 'are' : 'is'
326
+ return `${baseError}: Source uses ${modules} that ${are} not declared as dependencies:\n\n${depList}\n${target} ${declaredWhere}`
327
+ }
328
+ */
package/src/cli.ts ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+ import meow from 'meow'
4
+ import log from './util/log'
5
+ import commands from './cmds'
6
+ import sharedFlags from './sharedFlags'
7
+ import {cliName} from './constants'
8
+
9
+ export async function cliEntry(argv = process.argv, autoExit = true) {
10
+ const cli = meow(
11
+ `
12
+ Usage
13
+ $ ${cliName} [--help] [--debug] <command> [<args>]
14
+
15
+ These are common commands used in various situations:
16
+
17
+ init Create a new Sanity plugin
18
+ verify-package Check that a Sanity plugin package follows V3 conventions. Prints upgrade steps.
19
+ verify-studio Check that a Sanity Studio follows V3 conventions. Prints upgrade steps.
20
+ link-watch Recompiles plugin automatically on changes and runs yalc push --publish
21
+ version Show the version of ${cliName} currently installed
22
+
23
+ Options
24
+ --silent Do not print info and warning messages
25
+ --verbose Log everything. This option conflicts with --silent
26
+ --debug Print stack trace on errors
27
+ --version Output the version number
28
+ --help Output usage information
29
+
30
+ Examples
31
+ # Init a new plugin in current directory
32
+ $ ${cliName} init
33
+
34
+ # Init a new plugin in my-sanity-plugin directory
35
+ $ ${cliName} init my-sanity-plugin
36
+
37
+ # Check that a Sanity plugin package in current directory follows V3 conventions
38
+ $ ${cliName} verify-package
39
+
40
+ # Check that a Sanity Studio in current directory follows V3 conventions
41
+ $ ${cliName} verify-studio
42
+ `,
43
+ {
44
+ autoHelp: false,
45
+ flags: sharedFlags,
46
+ argv: argv.slice(2),
47
+ }
48
+ )
49
+
50
+ const commandName = cli.input[0]
51
+ if (!commandName) {
52
+ cli.showHelp() // Exits
53
+ }
54
+
55
+ if (!(commandName in commands)) {
56
+ console.error(`Unknown command "${commandName}"`)
57
+ cli.showHelp() // Exits
58
+ }
59
+
60
+ if (cli.flags.silent && cli.flags.verbose) {
61
+ log.error(`--silent and --verbose are mutually exclusive`)
62
+ cli.showHelp() // Exits
63
+ }
64
+
65
+ // Lazy-load command
66
+ const cmd = require(commands[commandName as keyof typeof commands]).default
67
+
68
+ try {
69
+ log.setVerbosity(cli.flags)
70
+ await cmd({argv: argv.slice(3)})
71
+ } catch (err: any) {
72
+ log.error(err instanceof TypeError || cli.flags.debug ? err.stack : err.message)
73
+
74
+ // eslint-disable-next-line no-process-exit
75
+ process.exit(1)
76
+ }
77
+ }
@@ -0,0 +1,9 @@
1
+ export default {
2
+ init: require.resolve('./init'),
3
+ 'link-watch': require.resolve('./link-watch'),
4
+ 'verify-package': require.resolve('./verify-package'),
5
+ 'verify-studio': require.resolve('./verify-studio'),
6
+ version: require.resolve('./version'),
7
+ // wont make it for initial release
8
+ //splat: require.resolve('./splat'),
9
+ }
@@ -0,0 +1,85 @@
1
+ import path from 'path'
2
+ import meow from 'meow'
3
+ import log from '../util/log'
4
+ import {init, initFlags} from '../actions/init'
5
+ import {isEmptyish, ensureDir} from '../util/files'
6
+ import {installDependencies, promptForPackageManager} from '../npm/manager'
7
+ import {findStudioV3Config, hasSanityJson} from '../sanity/manifest'
8
+ import {prompt} from '../util/prompt'
9
+ import {cliName} from '../constants'
10
+
11
+ const description = `Initialize a new Sanity plugin`
12
+
13
+ const help = `
14
+ Usage
15
+ $ ${cliName} init [dir] [<args>]
16
+
17
+ Options
18
+ --no-eslint Disables ESLint config and dependencies from being added
19
+ --no-prettier Disables prettier config and dependencies from being added
20
+ --no-typescript Disables typescript config and dependencies from being added
21
+ --no-license Disables LICENSE + package.json license field from being added
22
+ --no-editorconfig Disables .editorconfig from being added
23
+ --no-gitignore Disables .gitignore from being added
24
+ --no-scripts Disables scripts from being added to package.json
25
+ --no-install Disables automatically running package manager install
26
+
27
+ --name [package-name] Use the provided package-name
28
+ --author [name] Use the provided author
29
+ --repo [url] Use the provided repo url
30
+ --license [spdx] Use the license with the given SPDX identifier
31
+ --force No promt when overwriting files
32
+
33
+ Examples
34
+ # Initialize a new plugin in the current directory
35
+ $ ${cliName} init
36
+
37
+ # Initialize a plugin in the directory ~/my-plugin
38
+ $ ${cliName} init ~/my-plugin
39
+
40
+ # Don't add eslint or prettier
41
+ $ ${cliName} init --no-eslint --no-prettier
42
+ `
43
+
44
+ async function run({argv}: {argv: string[]}) {
45
+ const cli = meow(help, {flags: initFlags, argv, description})
46
+ const basePath = path.resolve(cli.input[0] || process.cwd())
47
+
48
+ const {exists, isRoot} = await hasSanityJson(basePath)
49
+ if (exists && isRoot) {
50
+ throw new Error(
51
+ `sanity.json has a "root" property set to true - are you trying to init into a studio instead of a plugin?`
52
+ )
53
+ }
54
+
55
+ const {v3ConfigFile} = await findStudioV3Config(basePath)
56
+ if (v3ConfigFile) {
57
+ throw new Error(
58
+ `${v3ConfigFile} exsists - are you trying to init into a studio instead of a plugin?`
59
+ )
60
+ }
61
+
62
+ log.info('Initializing new plugin in "%s"', basePath)
63
+ if (
64
+ !cli.flags.force &&
65
+ !(await isEmptyish(basePath)) &&
66
+ !(await prompt('Directory is not empty, proceed?', {type: 'confirm', default: false}))
67
+ ) {
68
+ log.error('Directory is not empty. Cancelled.')
69
+ return
70
+ }
71
+
72
+ await ensureDir(basePath)
73
+ await init({basePath, flags: cli.flags})
74
+ if (cli.flags.install) {
75
+ if (await installDependencies(await promptForPackageManager(), {cwd: basePath})) {
76
+ log.info('Done!')
77
+ } else {
78
+ log.error('Failed to install dependencies, try manually running `npm install`')
79
+ }
80
+ } else {
81
+ log.info('Dependency installation skipped.')
82
+ }
83
+ }
84
+
85
+ export default run
@@ -0,0 +1,51 @@
1
+ import path from 'path'
2
+ import meow from 'meow'
3
+ import pkg from '../../package.json'
4
+ import sharedFlags from '../sharedFlags'
5
+ import {linkWatch} from '../actions/link-watch'
6
+
7
+ const description = `Run the watch command and pushes any changes to yalc`
8
+
9
+ const help = `
10
+ Usage
11
+ $ ${pkg.binname} link-watch [<args>]
12
+
13
+ Options
14
+ --silent Do not print info and warning messages
15
+ --verbose Log everything. This option conflicts with --silent
16
+ --version Output the version number
17
+ --help Output usage information
18
+
19
+ Configuration
20
+ To override the default watch command configuration, provide an override in package.json under sanityPlugin:
21
+ {
22
+ "sanityPlugin": {
23
+ "watchCommand": "microbundle watch --format modern,esm,cjs --jsx React.createElement --jsxImportSource react --css inline",
24
+ "linkWatch": {
25
+ "folder": "lib",
26
+ "command": "npm run watch",
27
+ "extensions": "js,png,svg,gif,jpeg,css"
28
+ }
29
+ }
30
+ }
31
+
32
+ Examples
33
+ # Run the watch command and pushes any changes to yalc
34
+ $ ${pkg.binname} link-watch
35
+ `
36
+
37
+ const flags = {
38
+ ...sharedFlags,
39
+ watch: {
40
+ type: 'boolean',
41
+ default: false,
42
+ },
43
+ } as const
44
+
45
+ function run({argv}: {argv: string[]}) {
46
+ const cli = meow(help, {flags, argv, description})
47
+ const basePath = path.resolve(cli.input[0] || process.cwd())
48
+ return linkWatch({basePath})
49
+ }
50
+
51
+ export default run
@@ -0,0 +1,59 @@
1
+ import path from 'path'
2
+ import meow from 'meow'
3
+ import pkg from '../../package.json'
4
+ import log from '../util/log'
5
+ import {splat} from '../actions/splat'
6
+ import {hasSanityJson} from '../sanity/manifest'
7
+ import sharedFlags from '../sharedFlags'
8
+ import {initFlags} from '../actions/init'
9
+
10
+ const description = `"Splat" configuration into a Sanity plugin`
11
+
12
+ const help = `
13
+ Usage
14
+ $ ${pkg.binname} splat [dir] [<args>]
15
+
16
+ Options
17
+ --no-eslint Disables ESLint config and dependencies from being added
18
+ --no-prettier Disables prettier config and dependencies from being added
19
+ --no-typescript Disables typescript config and dependencies from being added
20
+ --no-license Disables LICENSE + package.json license field from being added
21
+ --no-editorconfig Disables .editorconfig from being added
22
+ --no-gitignore Disables .gitignore from being added
23
+ --no-scripts Disables scripts from being added to package.json
24
+ --license [spdx] Use the license with the given SPDX identifier
25
+
26
+ Examples
27
+ # Splat configuration into the plugin in the current directory
28
+ $ ${pkg.binname} splat
29
+
30
+ # Splat configuration into the plugin in ~/my-plugin
31
+ $ ${pkg.binname} splat ~/my-plugin
32
+
33
+ # Don't add eslint or prettier
34
+ $ ${pkg.binname} splat --no-eslint --no-prettier
35
+ `
36
+
37
+ async function run({argv}: {argv: string[]}) {
38
+ const cli = meow(help, {flags: initFlags, argv, description})
39
+ const basePath = path.resolve(cli.input[0] || process.cwd())
40
+
41
+ const {exists, isRoot} = await hasSanityJson(basePath)
42
+ if (exists && isRoot) {
43
+ throw new Error(
44
+ `sanity.json has a "root" property set to true - are you trying to splat into a studio instead of a plugin?`
45
+ )
46
+ }
47
+
48
+ if (!exists) {
49
+ throw new Error(
50
+ `sanity.json does not exist in this directory, maybe you want "${pkg.binname} init" instead?`
51
+ )
52
+ }
53
+
54
+ await splat({basePath, flags: cli.flags})
55
+
56
+ log.info('Done!')
57
+ }
58
+
59
+ export default run