@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.
- package/LICENSE +22 -0
- package/README.md +398 -0
- package/assets/splat/LICENSE +21 -0
- package/assets/splat/editorconfig +13 -0
- package/assets/splat/eslint.config.js +5 -0
- package/assets/splat/gitignore +55 -0
- package/assets/splat/npmignore +9 -0
- package/assets/splat/prettierrc.js +6 -0
- package/assets/splat/sanity.json +8 -0
- package/assets/splat/template-tsconfig.json +23 -0
- package/assets/splat/v2-incompatible.js.template +11 -0
- package/lib/package.json +127 -0
- package/lib/src/actions/init.d.ts +65 -0
- package/lib/src/actions/init.js +83 -0
- package/lib/src/actions/init.js.map +1 -0
- package/lib/src/actions/link-watch.d.ts +3 -0
- package/lib/src/actions/link-watch.js +69 -0
- package/lib/src/actions/link-watch.js.map +1 -0
- package/lib/src/actions/splat.d.ts +26 -0
- package/lib/src/actions/splat.js +296 -0
- package/lib/src/actions/splat.js.map +1 -0
- package/lib/src/actions/verify/types.d.ts +77 -0
- package/lib/src/actions/verify/types.js +3 -0
- package/lib/src/actions/verify/types.js.map +1 -0
- package/lib/src/actions/verify/validations.d.ts +28 -0
- package/lib/src/actions/verify/validations.js +379 -0
- package/lib/src/actions/verify/validations.js.map +1 -0
- package/lib/src/actions/verify/verify-common.d.ts +43 -0
- package/lib/src/actions/verify/verify-common.js +88 -0
- package/lib/src/actions/verify/verify-common.js.map +1 -0
- package/lib/src/actions/verify-package.d.ts +5 -0
- package/lib/src/actions/verify-package.js +72 -0
- package/lib/src/actions/verify-package.js.map +1 -0
- package/lib/src/actions/verify-studio.d.ts +5 -0
- package/lib/src/actions/verify-studio.js +55 -0
- package/lib/src/actions/verify-studio.js.map +1 -0
- package/lib/src/actions/verify.d.ts +0 -0
- package/lib/src/actions/verify.js +330 -0
- package/lib/src/actions/verify.js.map +1 -0
- package/lib/src/cli.d.ts +2 -0
- package/lib/src/cli.js +86 -0
- package/lib/src/cli.js.map +1 -0
- package/lib/src/cmds/index.d.ts +8 -0
- package/lib/src/cmds/index.js +12 -0
- package/lib/src/cmds/index.js.map +1 -0
- package/lib/src/cmds/init.d.ts +4 -0
- package/lib/src/cmds/init.js +90 -0
- package/lib/src/cmds/init.js.map +1 -0
- package/lib/src/cmds/link-watch.d.ts +4 -0
- package/lib/src/cmds/link-watch.js +49 -0
- package/lib/src/cmds/link-watch.js.map +1 -0
- package/lib/src/cmds/splat.d.ts +4 -0
- package/lib/src/cmds/splat.js +63 -0
- package/lib/src/cmds/splat.js.map +1 -0
- package/lib/src/cmds/verify-package.d.ts +4 -0
- package/lib/src/cmds/verify-package.js +38 -0
- package/lib/src/cmds/verify-package.js.map +1 -0
- package/lib/src/cmds/verify-studio.d.ts +4 -0
- package/lib/src/cmds/verify-studio.js +38 -0
- package/lib/src/cmds/verify-studio.js.map +1 -0
- package/lib/src/cmds/verify.d.ts +0 -0
- package/lib/src/cmds/verify.js +42 -0
- package/lib/src/cmds/verify.js.map +1 -0
- package/lib/src/cmds/version.d.ts +4 -0
- package/lib/src/cmds/version.js +55 -0
- package/lib/src/cmds/version.js.map +1 -0
- package/lib/src/configs/buildExtensions.d.ts +1 -0
- package/lib/src/configs/buildExtensions.js +5 -0
- package/lib/src/configs/buildExtensions.js.map +1 -0
- package/lib/src/configs/default-source.d.ts +3 -0
- package/lib/src/configs/default-source.js +70 -0
- package/lib/src/configs/default-source.js.map +1 -0
- package/lib/src/configs/merged-packages.d.ts +1 -0
- package/lib/src/configs/merged-packages.js +24 -0
- package/lib/src/configs/merged-packages.js.map +1 -0
- package/lib/src/configs/uselessFiles.d.ts +1 -0
- package/lib/src/configs/uselessFiles.js +33 -0
- package/lib/src/configs/uselessFiles.js.map +1 -0
- package/lib/src/constants.d.ts +11 -0
- package/lib/src/constants.js +15 -0
- package/lib/src/constants.js.map +1 -0
- package/lib/src/dependencies/find.d.ts +0 -0
- package/lib/src/dependencies/find.js +195 -0
- package/lib/src/dependencies/find.js.map +1 -0
- package/lib/src/dependencies/import-linter.d.ts +3 -0
- package/lib/src/dependencies/import-linter.js +112 -0
- package/lib/src/dependencies/import-linter.js.map +1 -0
- package/lib/src/index.d.ts +2 -0
- package/lib/src/index.js +6 -0
- package/lib/src/index.js.map +1 -0
- package/lib/src/npm/manager.d.ts +7 -0
- package/lib/src/npm/manager.js +62 -0
- package/lib/src/npm/manager.js.map +1 -0
- package/lib/src/npm/package.d.ts +8 -0
- package/lib/src/npm/package.js +288 -0
- package/lib/src/npm/package.js.map +1 -0
- package/lib/src/npm/publish.d.ts +1 -0
- package/lib/src/npm/publish.js +14 -0
- package/lib/src/npm/publish.js.map +1 -0
- package/lib/src/npm/resolveLatestVersions.d.ts +3 -0
- package/lib/src/npm/resolveLatestVersions.js +35 -0
- package/lib/src/npm/resolveLatestVersions.js.map +1 -0
- package/lib/src/sanity/manifest.d.ts +48 -0
- package/lib/src/sanity/manifest.js +263 -0
- package/lib/src/sanity/manifest.js.map +1 -0
- package/lib/src/sharedFlags.d.ts +15 -0
- package/lib/src/sharedFlags.js +17 -0
- package/lib/src/sharedFlags.js.map +1 -0
- package/lib/src/util/command-parser.d.ts +9 -0
- package/lib/src/util/command-parser.js +41 -0
- package/lib/src/util/command-parser.js.map +1 -0
- package/lib/src/util/errorToUndefined.d.ts +1 -0
- package/lib/src/util/errorToUndefined.js +11 -0
- package/lib/src/util/errorToUndefined.js.map +1 -0
- package/lib/src/util/files.d.ts +36 -0
- package/lib/src/util/files.js +253 -0
- package/lib/src/util/files.js.map +1 -0
- package/lib/src/util/log.d.ts +14 -0
- package/lib/src/util/log.js +36 -0
- package/lib/src/util/log.js.map +1 -0
- package/lib/src/util/prompt.d.ts +13 -0
- package/lib/src/util/prompt.js +75 -0
- package/lib/src/util/prompt.js.map +1 -0
- package/lib/src/util/readme.d.ts +5 -0
- package/lib/src/util/readme.js +73 -0
- package/lib/src/util/readme.js.map +1 -0
- package/lib/src/util/request.d.ts +1 -0
- package/lib/src/util/request.js +19 -0
- package/lib/src/util/request.js.map +1 -0
- package/lib/src/util/user.d.ts +10 -0
- package/lib/src/util/user.js +106 -0
- package/lib/src/util/user.js.map +1 -0
- package/lib/test/cli.test.d.ts +1 -0
- package/lib/test/cli.test.js +64 -0
- package/lib/test/cli.test.js.map +1 -0
- package/lib/test/fixture-utils.d.ts +25 -0
- package/lib/test/fixture-utils.js +67 -0
- package/lib/test/fixture-utils.js.map +1 -0
- package/lib/test/init-verify-build.test.d.ts +1 -0
- package/lib/test/init-verify-build.test.js +75 -0
- package/lib/test/init-verify-build.test.js.map +1 -0
- package/lib/test/init.test.d.ts +1 -0
- package/lib/test/init.test.js +137 -0
- package/lib/test/init.test.js.map +1 -0
- package/lib/test/run-test-command.d.ts +1 -0
- package/lib/test/run-test-command.js +6 -0
- package/lib/test/run-test-command.js.map +1 -0
- package/lib/test/verify-package.test.d.ts +1 -0
- package/lib/test/verify-package.test.js +81 -0
- package/lib/test/verify-package.test.js.map +1 -0
- package/lib/test/version.test.d.ts +1 -0
- package/lib/test/version.test.js +48 -0
- package/lib/test/version.test.js.map +1 -0
- package/package.json +127 -0
- package/src/actions/init.ts +104 -0
- package/src/actions/link-watch.ts +74 -0
- package/src/actions/splat.ts +366 -0
- package/src/actions/verify/types.ts +84 -0
- package/src/actions/verify/validations.ts +401 -0
- package/src/actions/verify/verify-common.ts +92 -0
- package/src/actions/verify-package.ts +87 -0
- package/src/actions/verify-studio.ts +55 -0
- package/src/actions/verify.ts +328 -0
- package/src/cli.ts +77 -0
- package/src/cmds/index.ts +9 -0
- package/src/cmds/init.ts +85 -0
- package/src/cmds/link-watch.ts +51 -0
- package/src/cmds/splat.ts +59 -0
- package/src/cmds/verify-package.ts +36 -0
- package/src/cmds/verify-studio.ts +36 -0
- package/src/cmds/verify.ts +40 -0
- package/src/cmds/version.ts +67 -0
- package/src/configs/buildExtensions.ts +1 -0
- package/src/configs/default-source.ts +68 -0
- package/src/configs/merged-packages.ts +20 -0
- package/src/configs/uselessFiles.ts +29 -0
- package/src/constants.ts +13 -0
- package/src/dependencies/find.ts +193 -0
- package/src/dependencies/import-linter.ts +103 -0
- package/src/index.ts +4 -0
- package/src/npm/manager.ts +44 -0
- package/src/npm/package.ts +370 -0
- package/src/npm/publish.ts +9 -0
- package/src/npm/resolveLatestVersions.ts +26 -0
- package/src/sanity/manifest.ts +340 -0
- package/src/sharedFlags.ts +14 -0
- package/src/util/command-parser.ts +31 -0
- package/src/util/errorToUndefined.ts +7 -0
- package/src/util/files.ts +249 -0
- package/src/util/log.ts +44 -0
- package/src/util/prompt.ts +70 -0
- package/src/util/readme.ts +72 -0
- package/src/util/request.ts +13 -0
- package/src/util/user.ts +110 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import util from 'util'
|
|
4
|
+
import pkg from '../../package.json'
|
|
5
|
+
import {buildExtensions} from '../configs/buildExtensions'
|
|
6
|
+
import {hasSourceFile, hasCompiledFile, readJsonFile, fileExists} from '../util/files'
|
|
7
|
+
import {errorToUndefined} from '../util/errorToUndefined'
|
|
8
|
+
|
|
9
|
+
const stat = util.promisify(fs.stat)
|
|
10
|
+
const readFile = util.promisify(fs.readFile)
|
|
11
|
+
|
|
12
|
+
const allowedPartProps = ['name', 'implements', 'path', 'description']
|
|
13
|
+
const disallowedPluginProps = ['api', 'project', 'plugins', 'env']
|
|
14
|
+
|
|
15
|
+
export interface SanityV2Manifest {
|
|
16
|
+
root?: boolean
|
|
17
|
+
name: string
|
|
18
|
+
paths: ManifestPaths
|
|
19
|
+
parts?: {
|
|
20
|
+
path: string
|
|
21
|
+
}[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ManifestPaths {
|
|
25
|
+
basePath: string
|
|
26
|
+
compiled?: string
|
|
27
|
+
source?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ManifestOptions {
|
|
31
|
+
isPlugin?: boolean
|
|
32
|
+
validate?: boolean
|
|
33
|
+
pluginName?: string
|
|
34
|
+
basePath: string
|
|
35
|
+
verifySourceParts?: boolean
|
|
36
|
+
verifyCompiledParts?: boolean
|
|
37
|
+
paths?: ManifestPaths
|
|
38
|
+
flags?: Record<string, any>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function getPaths(options: ManifestOptions) {
|
|
42
|
+
const {basePath} = options
|
|
43
|
+
const manifest = await readManifest(options)
|
|
44
|
+
if (!manifest.paths) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return absolutifyPaths(manifest.paths, basePath)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function absolutifyPaths(paths: ManifestPaths | undefined, basePath: string) {
|
|
52
|
+
const getPath = (relative?: string) =>
|
|
53
|
+
relative ? path.resolve(path.join(basePath, relative)) : undefined
|
|
54
|
+
return paths
|
|
55
|
+
? {
|
|
56
|
+
basePath,
|
|
57
|
+
compiled: getPath(paths.compiled),
|
|
58
|
+
source: getPath(paths.source),
|
|
59
|
+
}
|
|
60
|
+
: {basePath}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function readManifest(options: ManifestOptions) {
|
|
64
|
+
const {basePath, validate = true} = options
|
|
65
|
+
const manifestPath = path.normalize(path.join(basePath, 'sanity.json'))
|
|
66
|
+
|
|
67
|
+
let content
|
|
68
|
+
try {
|
|
69
|
+
content = await readFile(manifestPath, 'utf8')
|
|
70
|
+
} catch (err: any) {
|
|
71
|
+
if (err.code === 'ENOENT') {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`No sanity.json found. sanity.json is required for plugins to function. Use \`${pkg.binname} init\` for a new plugin, or create an empty \`sanity.json\` with an empty object (\`{}\`) for existing ones.`
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error(`Failed to read "${manifestPath}": ${err.message}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let parsed
|
|
81
|
+
try {
|
|
82
|
+
parsed = JSON.parse(content)
|
|
83
|
+
} catch (err: any) {
|
|
84
|
+
throw new Error(`Error parsing "${manifestPath}": ${err.message}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (validate) {
|
|
88
|
+
await validateManifest(parsed, options)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return parsed
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function validateManifest(manifest: SanityV2Manifest, opts: ManifestOptions) {
|
|
95
|
+
const options = {isPlugin: true, ...opts}
|
|
96
|
+
|
|
97
|
+
if (!isObject(manifest)) {
|
|
98
|
+
throw new Error(`Invalid sanity.json: Root must be an object`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (options.isPlugin) {
|
|
102
|
+
await validatePluginManifest(manifest, options)
|
|
103
|
+
} else {
|
|
104
|
+
await validateProjectManifest(manifest)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if ('root' in manifest && typeof manifest.root !== 'boolean') {
|
|
108
|
+
throw new Error(`Invalid sanity.json: "root" property must be a boolean if declared`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await validateParts(manifest, {
|
|
112
|
+
...options,
|
|
113
|
+
paths: absolutifyPaths(manifest.paths, options.basePath),
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function validateProjectManifest(manifest: SanityV2Manifest) {
|
|
118
|
+
if ('paths' in manifest) {
|
|
119
|
+
throw new Error(`Invalid sanity.json: "paths" property has no meaning in a project manifest`)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function validatePluginManifest(
|
|
124
|
+
manifest: SanityV2Manifest,
|
|
125
|
+
options: {basePath: string}
|
|
126
|
+
) {
|
|
127
|
+
const disallowed = Object.keys(manifest)
|
|
128
|
+
.filter((key) => disallowedPluginProps.includes(key))
|
|
129
|
+
.map((key) => `"${key}"`)
|
|
130
|
+
|
|
131
|
+
if (disallowed.length > 0) {
|
|
132
|
+
const plural = disallowed.length > 1 ? 's' : ''
|
|
133
|
+
const joined = disallowed.join(', ')
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Invalid sanity.json: Key${plural} ${joined} ${
|
|
136
|
+
plural ? 'are' : 'is'
|
|
137
|
+
} not allowed in a plugin manifest`
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (manifest.root) {
|
|
142
|
+
throw new Error(`Invalid sanity.json: "root" cannot be truthy in a plugin manifest`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await validatePaths(manifest, options)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function validatePaths(manifest: SanityV2Manifest, options: {basePath: string}) {
|
|
149
|
+
if (!('paths' in manifest)) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!isObject(manifest.paths)) {
|
|
154
|
+
throw new Error(`Invalid sanity.json: "paths" must be an object if declared`)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (typeof manifest.paths.compiled !== 'string') {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Invalid sanity.json: "paths" must have a (string) "compiled" property if declared`
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (typeof manifest.paths.source !== 'string') {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Invalid sanity.json: "paths" must have a (string) "source" property if declared`
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const sourcePath = path.resolve(options.basePath, manifest.paths.source)
|
|
170
|
+
let srcStats
|
|
171
|
+
try {
|
|
172
|
+
srcStats = await stat(sourcePath)
|
|
173
|
+
} catch (err: any) {
|
|
174
|
+
if (err.code === 'ENOENT') {
|
|
175
|
+
throw new Error(`sanity.json references "source" path which does not exist: "${sourcePath}"`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!srcStats?.isDirectory()) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`sanity.json references "source" path which is not a directory: "${sourcePath}"`
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function validateParts(manifest: SanityV2Manifest, options: ManifestOptions) {
|
|
187
|
+
if (!('parts' in manifest)) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!Array.isArray(manifest.parts)) {
|
|
192
|
+
throw new Error(`Invalid sanity.json: "parts" must be an array if declared`)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let i = 0
|
|
196
|
+
for (const part of manifest.parts) {
|
|
197
|
+
await validatePart(part, i, options)
|
|
198
|
+
i++
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function validatePart(part: Record<string, any>, index: number, options: ManifestOptions) {
|
|
203
|
+
if (!isObject(part)) {
|
|
204
|
+
throw new Error(`Invalid sanity.json: "parts[${index}]" must be an object`)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
validateAllowedPartKeys(part, index)
|
|
208
|
+
validatePartStringValues(part, index)
|
|
209
|
+
validatePartNames(part, index, options)
|
|
210
|
+
await validatePartFiles(part, index, options)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function validatePartFiles(
|
|
214
|
+
part: {path?: string} | undefined,
|
|
215
|
+
index: number,
|
|
216
|
+
options: ManifestOptions
|
|
217
|
+
) {
|
|
218
|
+
const {verifyCompiledParts, verifySourceParts, paths} = options
|
|
219
|
+
if (!part?.path) {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const ext = path.extname(part.path)
|
|
224
|
+
if (paths?.source && ext && ext !== '.js' && buildExtensions.includes(ext)) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Invalid sanity.json: Part path has extension which is not applicable after compiling. ${ext} becomes .js after compiling. Specify filename without extension (${path.basename(
|
|
227
|
+
part.path
|
|
228
|
+
)}) (parts[${index}])`
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!verifySourceParts && !verifyCompiledParts) {
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const [srcExists, libExists] = await Promise.all([
|
|
237
|
+
hasSourceFile(part.path, paths),
|
|
238
|
+
verifyCompiledParts && hasCompiledFile(part.path, paths),
|
|
239
|
+
])
|
|
240
|
+
|
|
241
|
+
if (!srcExists) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Invalid sanity.json: Part path references file that does not exist in source directory (${
|
|
244
|
+
paths?.source || paths?.basePath
|
|
245
|
+
}) (parts[${index}])`
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (verifyCompiledParts && !libExists) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Invalid sanity.json: Part path references file ("${part.path}") that does not exist in compiled directory (${paths?.compiled}) (parts[${index}])`
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function validatePartNames(
|
|
257
|
+
part: {name?: string; implements?: string} | undefined,
|
|
258
|
+
index: number,
|
|
259
|
+
options: ManifestOptions
|
|
260
|
+
) {
|
|
261
|
+
const pluginName = options.pluginName ? options.pluginName.replace(/^sanity-plugin-/, '') : ''
|
|
262
|
+
if (!part?.name || !part?.name?.startsWith(`part:${pluginName}/`)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`Invalid sanity.json: "name" must be prefixed with "part:${pluginName}/" - got "${part?.name}" (parts[${index}])`
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!part?.implements?.startsWith('part:')) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Invalid sanity.json: "implements" must be prefixed with "part:" - got "${part?.implements}" (parts[${index}])`
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function validateAllowedPartKeys(part: Record<string, any>, index: number) {
|
|
276
|
+
const disallowed = Object.keys(part)
|
|
277
|
+
.filter((key) => !allowedPartProps.includes(key))
|
|
278
|
+
.map((key) => `"${key}"`)
|
|
279
|
+
|
|
280
|
+
if (disallowed.length > 0) {
|
|
281
|
+
const plural = disallowed.length > 1 ? 's' : ''
|
|
282
|
+
const joined = disallowed.join(', ')
|
|
283
|
+
throw new Error(
|
|
284
|
+
`Invalid sanity.json: Key${plural} ${joined} ${
|
|
285
|
+
plural ? 'are' : 'is'
|
|
286
|
+
} not allowed in a part declaration (parts[${index}])`
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function validatePartStringValues(part: Record<string, any>, index: number) {
|
|
292
|
+
const nonStrings = Object.keys(part)
|
|
293
|
+
.filter((key) => typeof part[key] !== 'string')
|
|
294
|
+
.map((key) => `"${key}"`)
|
|
295
|
+
|
|
296
|
+
if (nonStrings.length > 0) {
|
|
297
|
+
const plural = nonStrings.length > 1 ? 's' : ''
|
|
298
|
+
const joined = nonStrings.join(', ')
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Invalid sanity.json: Key${plural} ${joined} should be of type string (parts[${index}])`
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function isObject(obj: any) {
|
|
306
|
+
return !Array.isArray(obj) && obj !== null && typeof obj === 'object'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function getReferencesPartPaths(manifest: SanityV2Manifest, basePath: string) {
|
|
310
|
+
const {paths, parts = []} = manifest
|
|
311
|
+
const compiledPath = path.resolve(basePath, paths?.compiled || '')
|
|
312
|
+
|
|
313
|
+
return parts
|
|
314
|
+
.filter((part) => part.path)
|
|
315
|
+
.map((part) => part.path)
|
|
316
|
+
.map((partPath) => (path.extname(partPath) === '' ? `${partPath}.js` : partPath))
|
|
317
|
+
.map((partPath) =>
|
|
318
|
+
path.isAbsolute(partPath)
|
|
319
|
+
? partPath // Not sure if this ever happens, but :shrugs:
|
|
320
|
+
: path.resolve(compiledPath, partPath)
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function hasSanityJson(basePath: string) {
|
|
325
|
+
const file = await readJsonFile<{root?: boolean}>(path.join(basePath, 'sanity.json')).catch(
|
|
326
|
+
errorToUndefined
|
|
327
|
+
)
|
|
328
|
+
return {exists: Boolean(file), isRoot: Boolean(file && file.root)}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function findStudioV3Config(basePath: string) {
|
|
332
|
+
const jsFile = 'sanity.config.js'
|
|
333
|
+
const jsExists = await fileExists(path.join(basePath, jsFile))
|
|
334
|
+
if (jsExists) {
|
|
335
|
+
return {v3ConfigFile: jsFile}
|
|
336
|
+
}
|
|
337
|
+
const tsFile = 'sanity.config.ts'
|
|
338
|
+
const tsExists = await fileExists(path.join(basePath, tsFile))
|
|
339
|
+
return {v3ConfigFile: tsExists ? tsFile : undefined}
|
|
340
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import childProcess from 'child_process'
|
|
2
|
+
import npmRunPath from 'npm-run-path'
|
|
3
|
+
import log from './log'
|
|
4
|
+
|
|
5
|
+
interface Command {
|
|
6
|
+
command: string
|
|
7
|
+
args: string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseCommand(commandString: string): Command {
|
|
11
|
+
const normalized = commandString.replace(/ +/g, ' ')
|
|
12
|
+
const commandAndArg = normalized.split(' ')
|
|
13
|
+
return {
|
|
14
|
+
command: commandAndArg[0],
|
|
15
|
+
args: commandAndArg.length > 1 ? commandAndArg.slice(1) : [],
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function runCommand(commandString: string): Promise<{code: number}> {
|
|
20
|
+
log.info(`Running command: ${commandString}`)
|
|
21
|
+
const {command, args} = parseCommand(commandString)
|
|
22
|
+
|
|
23
|
+
let options: any = {stdio: 'inherit', env: npmRunPath.env()}
|
|
24
|
+
|
|
25
|
+
// ref: https://stackoverflow.com/questions/37459717/error-spawn-enoent-on-windows/37487465
|
|
26
|
+
options = process.platform === 'win32' ? {...options, shell: true} : options
|
|
27
|
+
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
childProcess.spawn(command, args, options).on('error', reject).on('close', resolve)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import util from 'util'
|
|
4
|
+
import pAny from 'p-any'
|
|
5
|
+
import crypto from 'crypto'
|
|
6
|
+
import {buildExtensions} from '../configs/buildExtensions'
|
|
7
|
+
import {prompt} from './prompt'
|
|
8
|
+
import {InitFlags} from '../actions/init'
|
|
9
|
+
import log from './log'
|
|
10
|
+
import json5 from 'json5'
|
|
11
|
+
import {ManifestPaths} from '../sanity/manifest'
|
|
12
|
+
|
|
13
|
+
export const stat = util.promisify(fs.stat)
|
|
14
|
+
export const mkdir = util.promisify(fs.mkdir)
|
|
15
|
+
export const readdir = util.promisify(fs.readdir)
|
|
16
|
+
export const copyFile = util.promisify(fs.copyFile)
|
|
17
|
+
export const readFile = util.promisify(fs.readFile)
|
|
18
|
+
export const writeFile = util.promisify(fs.writeFile)
|
|
19
|
+
|
|
20
|
+
export function hasSourceEquivalent(compiledFile: string, paths: ManifestPaths) {
|
|
21
|
+
if (!paths.source) {
|
|
22
|
+
return fileExists(
|
|
23
|
+
path.isAbsolute(compiledFile) ? compiledFile : path.resolve(paths.basePath, compiledFile)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// /plugin/lib/MyComponent.js => /plugin/src
|
|
28
|
+
const baseDir = path.dirname(compiledFile.replace(paths.compiled as string, paths.source))
|
|
29
|
+
|
|
30
|
+
// /plugin/lib/MyComponent.js => MyComponent
|
|
31
|
+
const baseName = path.basename(compiledFile, path.extname(compiledFile))
|
|
32
|
+
|
|
33
|
+
// MyComponent => /plugin/src/MyComponent
|
|
34
|
+
const pathStub = path.join(baseDir, baseName)
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* /plugin/src/MyComponent => [
|
|
38
|
+
* /plugin/src/MyComponent.jsx,
|
|
39
|
+
* /plugin/src/MyComponent.mjs,
|
|
40
|
+
* ...
|
|
41
|
+
* ]
|
|
42
|
+
*/
|
|
43
|
+
return buildCandidateExists(pathStub)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Generally used for parts resolving
|
|
47
|
+
export async function hasSourceFile(filePath: string, paths?: ManifestPaths) {
|
|
48
|
+
if (!paths?.source) {
|
|
49
|
+
return fileExists(
|
|
50
|
+
path.isAbsolute(filePath) ? filePath : path.resolve(paths?.basePath ?? '', filePath)
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// filePath: components/SomeInput
|
|
55
|
+
// paths: {source: '/plugin/src'}
|
|
56
|
+
// MyComponent => /plugin/src/MyComponent
|
|
57
|
+
const pathStub = path.isAbsolute(filePath) ? filePath : path.resolve(paths.source, filePath)
|
|
58
|
+
|
|
59
|
+
if (await fileExists(pathStub)) {
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return buildCandidateExists(pathStub)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Generally used for parts resolving
|
|
67
|
+
export function hasCompiledFile(filePath: string, paths?: ManifestPaths) {
|
|
68
|
+
if (!paths?.compiled) {
|
|
69
|
+
return fileExists(
|
|
70
|
+
path.isAbsolute(filePath) ? filePath : path.resolve(paths?.basePath ?? '', filePath)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// filePath: components/SomeInput
|
|
75
|
+
// paths: {compiled: '/plugin/lib'}
|
|
76
|
+
|
|
77
|
+
// components/SomeInput => /plugin/lib/components/SomeInput
|
|
78
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(paths.compiled, filePath)
|
|
79
|
+
|
|
80
|
+
// /plugin/lib/components/SomeInput => /plugin/lib/components/SomeInput.js
|
|
81
|
+
// /plugin/lib/components/SomeInput.js => /plugin/lib/components/SomeInput.js
|
|
82
|
+
// /plugin/lib/components/SomeInput.css => /plugin/lib/components/SomeInput.css
|
|
83
|
+
const fileExt = path.extname(absPath)
|
|
84
|
+
const withExt = fileExt === '' ? `${absPath}.js` : absPath
|
|
85
|
+
|
|
86
|
+
return fileExists(withExt)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function buildCandidateExists(pathStub: string) {
|
|
90
|
+
const candidates = buildExtensions.map((extCandidate) => `${pathStub}${extCandidate}`)
|
|
91
|
+
|
|
92
|
+
return pAny(candidates.map((candidate) => stat(candidate)))
|
|
93
|
+
.then(() => true)
|
|
94
|
+
.catch(() => false)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function fileExists(filePath: string) {
|
|
98
|
+
return stat(filePath)
|
|
99
|
+
.then(() => true)
|
|
100
|
+
.catch(() => false)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function readJsonFile<T>(filePath: string) {
|
|
104
|
+
const content = await readFile(filePath, 'utf8')
|
|
105
|
+
return JSON.parse(content) as T
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function writeJsonFile(filePath: string, content: Record<string, unknown>) {
|
|
109
|
+
const data = JSON.stringify(content, null, 2)
|
|
110
|
+
return writeFile(filePath, data, {encoding: 'utf8'})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function writeFileWithOverwritePrompt(
|
|
114
|
+
filePath: string,
|
|
115
|
+
content: string,
|
|
116
|
+
options: {default?: any; force?: boolean} & fs.ObjectEncodingOptions
|
|
117
|
+
) {
|
|
118
|
+
const {default: defaultVal, force = false, ...writeOptions} = options
|
|
119
|
+
const withinCwd = filePath.startsWith(process.cwd())
|
|
120
|
+
const printablePath = withinCwd ? path.relative(process.cwd(), filePath) : filePath
|
|
121
|
+
|
|
122
|
+
if (await fileEqualsData(filePath, content)) {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
!force &&
|
|
128
|
+
(await fileExists(filePath)) &&
|
|
129
|
+
!(await prompt(`File "${printablePath}" already exists. Overwrite?`, {
|
|
130
|
+
type: 'confirm',
|
|
131
|
+
default: defaultVal,
|
|
132
|
+
}))
|
|
133
|
+
) {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await writeFile(filePath, content, writeOptions)
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function copyFileWithOverwritePrompt(from: string, to: string, flags: InitFlags) {
|
|
142
|
+
const withinCwd = to.startsWith(process.cwd())
|
|
143
|
+
const printablePath = withinCwd ? path.relative(process.cwd(), to) : to
|
|
144
|
+
|
|
145
|
+
if (await filesAreEqual(from, to)) {
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
!flags.force &&
|
|
151
|
+
(await fileExists(to)) &&
|
|
152
|
+
!(await prompt(`File "${printablePath}" already exists. Overwrite?`, {
|
|
153
|
+
type: 'confirm',
|
|
154
|
+
default: false,
|
|
155
|
+
}))
|
|
156
|
+
) {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await copyFile(from, to)
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function fileEqualsData(filePath: string, content: string) {
|
|
165
|
+
const contentHash = crypto.createHash('sha1').update(content).digest('hex')
|
|
166
|
+
const remoteHash = await getFileHash(filePath)
|
|
167
|
+
return contentHash === remoteHash
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function filesAreEqual(file1: string, file2: string) {
|
|
171
|
+
const [hash1, hash2] = await Promise.all([getFileHash(file1, false), getFileHash(file2)])
|
|
172
|
+
return hash1 === hash2
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function getFileHash(filePath: string, allowMissing = true) {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
const hash = crypto.createHash('sha1')
|
|
178
|
+
const stream = fs.createReadStream(filePath)
|
|
179
|
+
stream.on('error', (err) => {
|
|
180
|
+
if ((err as unknown as {code?: string}).code === 'ENOENT' && allowMissing) {
|
|
181
|
+
resolve(null)
|
|
182
|
+
} else {
|
|
183
|
+
reject(err)
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
stream.on('end', () => resolve(hash.digest('hex')))
|
|
188
|
+
stream.on('data', (chunk) => hash.update(chunk))
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function ensureDir(dirPath: string) {
|
|
193
|
+
try {
|
|
194
|
+
await mkdir(dirPath)
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if ((err as unknown as {code?: string}).code !== 'EEXIST') {
|
|
197
|
+
throw err
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function isEmptyish(dirPath: string) {
|
|
203
|
+
const ignoredFiles = ['.git', '.gitignore', 'license', 'readme.md']
|
|
204
|
+
const allFiles = await readdir(dirPath).catch(() => [])
|
|
205
|
+
const files = allFiles.filter((file) => !ignoredFiles.includes(file.toLowerCase()))
|
|
206
|
+
return files.length === 0
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function readFileContent({
|
|
210
|
+
filename,
|
|
211
|
+
basePath,
|
|
212
|
+
}: {
|
|
213
|
+
filename: string
|
|
214
|
+
basePath: string
|
|
215
|
+
}): Promise<string | undefined> {
|
|
216
|
+
const filepath = path.normalize(path.join(basePath, filename))
|
|
217
|
+
try {
|
|
218
|
+
return await readFile(filepath, 'utf8')
|
|
219
|
+
} catch (err: any) {
|
|
220
|
+
if (err.code === 'ENOENT') {
|
|
221
|
+
log.debug(`No ${filename} file found.`)
|
|
222
|
+
return undefined
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`Failed to read "${filepath}": ${err.message}`)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function readJson5File<T>({
|
|
229
|
+
filename,
|
|
230
|
+
basePath,
|
|
231
|
+
}: {
|
|
232
|
+
filename: string
|
|
233
|
+
basePath: string
|
|
234
|
+
}): Promise<T | undefined> {
|
|
235
|
+
const content = await readFileContent({filename, basePath})
|
|
236
|
+
if (!content) {
|
|
237
|
+
return undefined
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return parseJson5<T>(content, filename)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function parseJson5<T>(content: string, errorKey: string): T {
|
|
244
|
+
try {
|
|
245
|
+
return json5.parse<T>(content)
|
|
246
|
+
} catch (err: any) {
|
|
247
|
+
throw new Error(`Error parsing "${errorKey}": ${err.message}`)
|
|
248
|
+
}
|
|
249
|
+
}
|
package/src/util/log.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Note: This is _specifically_ meant for CLI usage,
|
|
2
|
+
// I realize that "singletons" are bad.
|
|
3
|
+
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
|
|
6
|
+
let beQuiet = false
|
|
7
|
+
let beVerbose = false
|
|
8
|
+
|
|
9
|
+
function setVerbosity({verbose, silent}: {verbose: boolean; silent: boolean}) {
|
|
10
|
+
if (silent) {
|
|
11
|
+
beVerbose = false
|
|
12
|
+
beQuiet = true
|
|
13
|
+
} else if (verbose) {
|
|
14
|
+
beVerbose = true
|
|
15
|
+
beQuiet = false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
setVerbosity: setVerbosity,
|
|
21
|
+
|
|
22
|
+
// Bypasses any checks, prints regardless (only use for things like `cli --version`)
|
|
23
|
+
msg: (msg: any, ...args: any[]) => !beQuiet && console.log(msg, ...args),
|
|
24
|
+
|
|
25
|
+
// Debug only printed on --verbose
|
|
26
|
+
debug: (msg: any, ...args: any[]) =>
|
|
27
|
+
!beQuiet && beVerbose && console.debug(`${chalk.bgBlack.white('[debug]')} ${msg}`, ...args),
|
|
28
|
+
|
|
29
|
+
// Success messages only printed if not --silent
|
|
30
|
+
success: (msg: any, ...args: any[]) =>
|
|
31
|
+
!beQuiet && console.info(`${chalk.bgBlack.greenBright('[success]')} ${msg}`, ...args),
|
|
32
|
+
|
|
33
|
+
// Info only printed if not --silent ("standard" level)
|
|
34
|
+
info: (msg: any, ...args: any[]) =>
|
|
35
|
+
!beQuiet && console.info(`${chalk.bgBlack.cyanBright('[info]')} ${msg}`, ...args),
|
|
36
|
+
|
|
37
|
+
// Warning only printed if not --silent
|
|
38
|
+
warn: (msg: any, ...args: any[]) =>
|
|
39
|
+
!beQuiet && console.warn(`${chalk.bgBlack.yellowBright('[warn]')} ${msg}`, ...args),
|
|
40
|
+
|
|
41
|
+
// Errors are always printed
|
|
42
|
+
error: (msg: any, ...args: any[]) =>
|
|
43
|
+
console.error(`${chalk.bgBlack.redBright('[error]')} ${msg}`, ...args),
|
|
44
|
+
}
|