@sanity/cli 3.59.2-canary.19 → 3.59.2-corel-fix-presentation-perspective-switching.536
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/lib/_chunks-cjs/cli.js +34359 -26058
- package/lib/_chunks-cjs/cli.js.map +1 -1
- package/lib/_chunks-cjs/cliWorker.js.map +1 -1
- package/lib/_chunks-cjs/generateAction.js.map +1 -1
- package/lib/_chunks-cjs/getCliConfig.js +1 -1
- package/lib/_chunks-cjs/getCliConfig.js.map +1 -1
- package/lib/_chunks-cjs/journeyConfig.js.map +1 -1
- package/lib/_chunks-cjs/loadEnv.js +200 -202
- package/lib/_chunks-cjs/loadEnv.js.map +1 -1
- package/lib/index.d.mts +37 -1
- package/lib/index.d.ts +37 -1
- package/lib/index.esm.js +223 -224
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +223 -224
- package/lib/index.mjs.map +1 -1
- package/lib/workers/getCliConfig.js.map +1 -1
- package/lib/workers/typegenGenerate.js.map +1 -1
- package/package.json +17 -19
- package/src/CommandRunner.ts +1 -2
- package/src/actions/init-project/{bootstrapTemplate.ts → bootstrapLocalTemplate.ts} +8 -21
- package/src/actions/init-project/bootstrapRemoteTemplate.ts +118 -0
- package/src/actions/init-project/git.ts +2 -2
- package/src/actions/init-project/initProject.ts +158 -146
- package/src/actions/init-project/readPackageJson.ts +18 -0
- package/src/actions/init-project/templates/nextjs/index.ts +16 -0
- package/src/actions/init-project/templates/nextjs/schemaTypes/blog.ts +2 -2
- package/src/actions/init-project/updateInitialTemplateMetadata.ts +24 -0
- package/src/actions/login/login.ts +2 -3
- package/src/actions/versions/findSanityModuleVersions.ts +0 -1
- package/src/commands/index.ts +2 -2
- package/src/commands/init/initCommand.ts +7 -67
- package/src/commands/learn/learnCommand.ts +20 -0
- package/src/commands/logout/logoutCommand.ts +1 -1
- package/src/outputters/cliOutputter.ts +21 -8
- package/src/studioDependencies.ts +1 -1
- package/src/types.ts +41 -1
- package/src/util/frameworkPort.ts +63 -0
- package/src/util/generateCommandsDocumentation.ts +7 -4
- package/src/util/getCliConfig.ts +1 -1
- package/src/util/getProviderName.ts +9 -0
- package/src/util/remoteTemplate.ts +319 -0
- package/templates/get-started/plugins/sanity-plugin-tutorial/GetStartedTutorial.tsx +4 -4
- package/src/actions/init-plugin/initPlugin.ts +0 -119
- package/src/actions/init-plugin/pluginTemplates.ts +0 -38
- package/src/actions/init-project/reconfigureV2Project.ts +0 -446
- package/src/commands/upgrade/upgradeCommand.ts +0 -38
- package/src/commands/upgrade/upgradeDependencies.ts +0 -289
@@ -0,0 +1,319 @@
|
|
1
|
+
import {access, readFile, writeFile} from 'node:fs/promises'
|
2
|
+
import {join, posix, sep} from 'node:path'
|
3
|
+
import {Readable} from 'node:stream'
|
4
|
+
import {pipeline} from 'node:stream/promises'
|
5
|
+
import {type ReadableStream} from 'node:stream/web'
|
6
|
+
|
7
|
+
import {ENV_TEMPLATE_FILES, REQUIRED_ENV_VAR} from '@sanity/template-validator'
|
8
|
+
import {x} from 'tar'
|
9
|
+
|
10
|
+
import {debug} from '../debug'
|
11
|
+
import {type CliApiClient, type PackageJson} from '../types'
|
12
|
+
|
13
|
+
const DISALLOWED_PATHS = [
|
14
|
+
// Prevent security risks from unknown GitHub Actions
|
15
|
+
'/.github/',
|
16
|
+
]
|
17
|
+
|
18
|
+
const ENV_VAR = {
|
19
|
+
...REQUIRED_ENV_VAR,
|
20
|
+
READ_TOKEN: 'SANITY_API_READ_TOKEN',
|
21
|
+
WRITE_TOKEN: 'SANITY_API_WRITE_TOKEN',
|
22
|
+
} as const
|
23
|
+
|
24
|
+
const API_READ_TOKEN_ROLE = 'viewer'
|
25
|
+
const API_WRITE_TOKEN_ROLE = 'editor'
|
26
|
+
|
27
|
+
type EnvData = {
|
28
|
+
projectId: string
|
29
|
+
dataset: string
|
30
|
+
readToken?: string
|
31
|
+
writeToken?: string
|
32
|
+
}
|
33
|
+
|
34
|
+
type GithubUrlString =
|
35
|
+
| `https://github.com/${string}/${string}`
|
36
|
+
| `https://www.github.com/${string}/${string}`
|
37
|
+
|
38
|
+
export type RepoInfo = {
|
39
|
+
username: string
|
40
|
+
name: string
|
41
|
+
branch: string
|
42
|
+
filePath: string
|
43
|
+
}
|
44
|
+
|
45
|
+
export function getGitHubRawContentUrl(repoInfo: RepoInfo): string {
|
46
|
+
const {username, name, branch, filePath} = repoInfo
|
47
|
+
return `https://raw.githubusercontent.com/${username}/${name}/${branch}/${filePath}`
|
48
|
+
}
|
49
|
+
|
50
|
+
function isGithubRepoShorthand(value: string): boolean {
|
51
|
+
if (URL.canParse(value)) {
|
52
|
+
return false
|
53
|
+
}
|
54
|
+
// This supports :owner/:repo and :owner/:repo/nested/path, e.g.
|
55
|
+
// sanity-io/sanity
|
56
|
+
// sanity-io/sanity/templates/next-js
|
57
|
+
// sanity-io/templates/live-content-api
|
58
|
+
// sanity-io/sanity/packages/@sanity/cli/test/test-template
|
59
|
+
return /^[\w-]+\/[\w-.]+(\/[@\w-.]+)*$/.test(value)
|
60
|
+
}
|
61
|
+
|
62
|
+
function isGithubRepoUrl(value: string | URL): value is URL | GithubUrlString {
|
63
|
+
if (URL.canParse(value) === false) {
|
64
|
+
return false
|
65
|
+
}
|
66
|
+
const url = new URL(value)
|
67
|
+
const pathSegments = url.pathname.slice(1).split('/')
|
68
|
+
|
69
|
+
return (
|
70
|
+
url.protocol === 'https:' &&
|
71
|
+
url.hostname === 'github.com' &&
|
72
|
+
// The pathname must have at least 2 segments. If it has more than 2, the
|
73
|
+
// third must be "tree" and it must have at least 4 segments.
|
74
|
+
// https://github.com/:owner/:repo
|
75
|
+
// https://github.com/:owner/:repo/tree/:ref
|
76
|
+
pathSegments.length >= 2 &&
|
77
|
+
(pathSegments.length > 2 ? pathSegments[2] === 'tree' && pathSegments.length >= 4 : true)
|
78
|
+
)
|
79
|
+
}
|
80
|
+
|
81
|
+
async function downloadTarStream(url: string, bearerToken?: string): Promise<Readable> {
|
82
|
+
const headers: Record<string, string> = {}
|
83
|
+
if (bearerToken) {
|
84
|
+
headers.Authorization = `Bearer ${bearerToken}`
|
85
|
+
}
|
86
|
+
|
87
|
+
const res = await fetch(url, {headers})
|
88
|
+
|
89
|
+
if (!res.body) {
|
90
|
+
throw new Error(`Failed to download: ${url}`)
|
91
|
+
}
|
92
|
+
|
93
|
+
return Readable.fromWeb(res.body as ReadableStream)
|
94
|
+
}
|
95
|
+
|
96
|
+
export function checkIsRemoteTemplate(templateName?: string): boolean {
|
97
|
+
return templateName?.includes('/') ?? false
|
98
|
+
}
|
99
|
+
|
100
|
+
export async function getGitHubRepoInfo(value: string, bearerToken?: string): Promise<RepoInfo> {
|
101
|
+
let username = ''
|
102
|
+
let name = ''
|
103
|
+
let branch = ''
|
104
|
+
let filePath = ''
|
105
|
+
|
106
|
+
if (isGithubRepoShorthand(value)) {
|
107
|
+
const parts = value.split('/')
|
108
|
+
username = parts[0]
|
109
|
+
name = parts[1]
|
110
|
+
// If there are more segments after owner/repo, they form the file path
|
111
|
+
if (parts.length > 2) {
|
112
|
+
filePath = parts.slice(2).join('/')
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
if (isGithubRepoUrl(value)) {
|
117
|
+
const url = new URL(value)
|
118
|
+
const pathSegments = url.pathname.slice(1).split('/')
|
119
|
+
username = pathSegments[0]
|
120
|
+
name = pathSegments[1]
|
121
|
+
|
122
|
+
// If we have a "tree" segment, everything after branch is the file path
|
123
|
+
if (pathSegments[2] === 'tree') {
|
124
|
+
branch = pathSegments[3]
|
125
|
+
if (pathSegments.length > 4) {
|
126
|
+
filePath = pathSegments.slice(4).join('/')
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
if (!username || !name) {
|
132
|
+
throw new Error('Invalid GitHub repository format')
|
133
|
+
}
|
134
|
+
|
135
|
+
const tokenMessage =
|
136
|
+
'GitHub repository not found. For private repositories, use --template-token to provide an access token.\n\n' +
|
137
|
+
'You can generate a new token at https://github.com/settings/personal-access-tokens/new\n' +
|
138
|
+
'Set the token to "read-only" with repository access and a short expiry (e.g. 7 days) for security.'
|
139
|
+
|
140
|
+
try {
|
141
|
+
const headers: Record<string, string> = {}
|
142
|
+
if (bearerToken) {
|
143
|
+
headers.Authorization = `Bearer ${bearerToken}`
|
144
|
+
}
|
145
|
+
|
146
|
+
const infoResponse = await fetch(`https://api.github.com/repos/${username}/${name}`, {
|
147
|
+
headers,
|
148
|
+
})
|
149
|
+
|
150
|
+
if (infoResponse.status !== 200) {
|
151
|
+
if (infoResponse.status === 404) {
|
152
|
+
throw new Error(tokenMessage)
|
153
|
+
}
|
154
|
+
throw new Error('GitHub repository not found')
|
155
|
+
}
|
156
|
+
|
157
|
+
const info = await infoResponse.json()
|
158
|
+
|
159
|
+
return {
|
160
|
+
username,
|
161
|
+
name,
|
162
|
+
branch: branch || info.default_branch,
|
163
|
+
filePath,
|
164
|
+
}
|
165
|
+
} catch {
|
166
|
+
throw new Error(tokenMessage)
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
export async function downloadAndExtractRepo(
|
171
|
+
root: string,
|
172
|
+
{username, name, branch, filePath}: RepoInfo,
|
173
|
+
bearerToken?: string,
|
174
|
+
): Promise<void> {
|
175
|
+
let rootPath: string | null = null
|
176
|
+
await pipeline(
|
177
|
+
await downloadTarStream(
|
178
|
+
`https://codeload.github.com/${username}/${name}/tar.gz/${branch}`,
|
179
|
+
bearerToken,
|
180
|
+
),
|
181
|
+
x({
|
182
|
+
cwd: root,
|
183
|
+
strip: filePath ? filePath.split('/').length + 1 : 1,
|
184
|
+
filter: (p: string) => {
|
185
|
+
const posixPath = p.split(sep).join(posix.sep)
|
186
|
+
if (rootPath === null) {
|
187
|
+
const pathSegments = posixPath.split(posix.sep)
|
188
|
+
rootPath = pathSegments.length ? pathSegments[0] : null
|
189
|
+
}
|
190
|
+
for (const disallowedPath of DISALLOWED_PATHS) {
|
191
|
+
if (posixPath.includes(disallowedPath)) return false
|
192
|
+
}
|
193
|
+
return posixPath.startsWith(`${rootPath}${filePath ? `/${filePath}/` : '/'}`)
|
194
|
+
},
|
195
|
+
}),
|
196
|
+
)
|
197
|
+
}
|
198
|
+
|
199
|
+
export async function checkIfNeedsApiToken(root: string, type: 'read' | 'write'): Promise<boolean> {
|
200
|
+
try {
|
201
|
+
const templatePath = await Promise.any(
|
202
|
+
ENV_TEMPLATE_FILES.map(async (file) => {
|
203
|
+
await access(join(root, file))
|
204
|
+
return file
|
205
|
+
}),
|
206
|
+
)
|
207
|
+
const templateContent = await readFile(join(root, templatePath), 'utf8')
|
208
|
+
return templateContent.includes(type === 'read' ? ENV_VAR.READ_TOKEN : ENV_VAR.WRITE_TOKEN)
|
209
|
+
} catch {
|
210
|
+
return false
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
export async function applyEnvVariables(
|
215
|
+
root: string,
|
216
|
+
envData: EnvData,
|
217
|
+
targetName = '.env',
|
218
|
+
): Promise<void> {
|
219
|
+
const templatePath = await Promise.any(
|
220
|
+
ENV_TEMPLATE_FILES.map(async (file) => {
|
221
|
+
await access(join(root, file))
|
222
|
+
return file
|
223
|
+
}),
|
224
|
+
).catch(() => undefined)
|
225
|
+
|
226
|
+
if (!templatePath) {
|
227
|
+
return // No template .env file found, skip
|
228
|
+
}
|
229
|
+
|
230
|
+
try {
|
231
|
+
const templateContent = await readFile(join(root, templatePath), 'utf8')
|
232
|
+
const {projectId, dataset, readToken = ''} = envData
|
233
|
+
|
234
|
+
const findAndReplaceVariable = (
|
235
|
+
content: string,
|
236
|
+
varRegex: RegExp | string,
|
237
|
+
value: string,
|
238
|
+
useQuotes: boolean,
|
239
|
+
) => {
|
240
|
+
const varPattern = typeof varRegex === 'string' ? varRegex : varRegex.source
|
241
|
+
const pattern = new RegExp(`.*${varPattern}=.*$`, 'gm')
|
242
|
+
const matches = content.matchAll(pattern)
|
243
|
+
return Array.from(matches).reduce((updatedContent, match) => {
|
244
|
+
if (!match[0]) return updatedContent
|
245
|
+
const varName = match[0].split('=')[0].trim()
|
246
|
+
return updatedContent.replace(
|
247
|
+
new RegExp(`${varName}=.*$`, 'gm'),
|
248
|
+
`${varName}=${useQuotes ? `"${value}"` : value}`,
|
249
|
+
)
|
250
|
+
}, content)
|
251
|
+
}
|
252
|
+
|
253
|
+
let envContent = templateContent
|
254
|
+
const vars = [
|
255
|
+
{pattern: ENV_VAR.PROJECT_ID, value: projectId},
|
256
|
+
{pattern: ENV_VAR.DATASET, value: dataset},
|
257
|
+
{pattern: ENV_VAR.READ_TOKEN, value: readToken},
|
258
|
+
]
|
259
|
+
const useQuotes = templateContent.includes('="')
|
260
|
+
|
261
|
+
for (const {pattern, value} of vars) {
|
262
|
+
envContent = findAndReplaceVariable(envContent, pattern, value, useQuotes)
|
263
|
+
}
|
264
|
+
|
265
|
+
await writeFile(join(root, targetName), envContent)
|
266
|
+
} catch (err) {
|
267
|
+
throw new Error(
|
268
|
+
'Failed to set environment variables. This could be due to file permissions or the .env file format. See https://www.sanity.io/docs/environment-variables for details on environment variable setup.',
|
269
|
+
)
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
export async function tryApplyPackageName(root: string, name: string): Promise<void> {
|
274
|
+
try {
|
275
|
+
const packageJson = await readFile(join(root, 'package.json'), 'utf8')
|
276
|
+
const pkg: PackageJson = JSON.parse(packageJson)
|
277
|
+
pkg.name = name
|
278
|
+
|
279
|
+
await writeFile(join(root, 'package.json'), JSON.stringify(pkg, null, 2))
|
280
|
+
} catch (err) {
|
281
|
+
// noop
|
282
|
+
}
|
283
|
+
}
|
284
|
+
|
285
|
+
export async function generateSanityApiToken(
|
286
|
+
label: string,
|
287
|
+
type: 'read' | 'write',
|
288
|
+
projectId: string,
|
289
|
+
apiClient: CliApiClient,
|
290
|
+
): Promise<string> {
|
291
|
+
const response = await apiClient({requireProject: false, requireUser: true})
|
292
|
+
.config({apiVersion: 'v2021-06-07'})
|
293
|
+
.request<{key: string}>({
|
294
|
+
uri: `/projects/${projectId}/tokens`,
|
295
|
+
method: 'POST',
|
296
|
+
body: {
|
297
|
+
label: `${label} (${Date.now()})`,
|
298
|
+
roleName: type === 'read' ? API_READ_TOKEN_ROLE : API_WRITE_TOKEN_ROLE,
|
299
|
+
},
|
300
|
+
})
|
301
|
+
return response.key
|
302
|
+
}
|
303
|
+
|
304
|
+
export async function setCorsOrigin(
|
305
|
+
origin: string,
|
306
|
+
projectId: string,
|
307
|
+
apiClient: CliApiClient,
|
308
|
+
): Promise<void> {
|
309
|
+
try {
|
310
|
+
await apiClient({api: {projectId}}).request({
|
311
|
+
method: 'POST',
|
312
|
+
url: '/cors',
|
313
|
+
body: {origin: origin, allowCredentials: true}, // allowCredentials is true to allow for embedded studios if needed
|
314
|
+
})
|
315
|
+
} catch (error) {
|
316
|
+
// Silent fail, it most likely means that the origin is already set
|
317
|
+
debug('Failed to set CORS origin', error)
|
318
|
+
}
|
319
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, {useState} from 'react'
|
2
2
|
import {
|
3
3
|
Card,
|
4
4
|
Container,
|
@@ -32,8 +32,8 @@ export const GetStartedTutorial = () => {
|
|
32
32
|
)
|
33
33
|
|
34
34
|
const {sanity} = useTheme()
|
35
|
-
const rootElement =
|
36
|
-
const rect = useElementSize(rootElement
|
35
|
+
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
36
|
+
const rect = useElementSize(rootElement)
|
37
37
|
const width = rect?.content?.width
|
38
38
|
const isSmallScreen = width ? width < sanity.media[1] : false
|
39
39
|
const isProdEnv = process.env.NODE_ENV !== 'development'
|
@@ -48,7 +48,7 @@ export const GetStartedTutorial = () => {
|
|
48
48
|
}
|
49
49
|
|
50
50
|
return (
|
51
|
-
<div ref={
|
51
|
+
<div ref={setRootElement}>
|
52
52
|
<Card tone="primary" padding={isSmallScreen ? 3 : 5} paddingBottom={isSmallScreen ? 4 : 6}>
|
53
53
|
<Flex justify={isSmallScreen ? 'space-between' : 'flex-end'} align="center">
|
54
54
|
{isSmallScreen && (
|
@@ -1,119 +0,0 @@
|
|
1
|
-
import fs from 'node:fs/promises'
|
2
|
-
import path from 'node:path'
|
3
|
-
|
4
|
-
import {type InitFlags} from '../../commands/init/initCommand'
|
5
|
-
import {debug} from '../../debug'
|
6
|
-
import {type CliCommandArguments, type CliCommandContext, type SanityJson} from '../../types'
|
7
|
-
import {bootstrapFromTemplate} from './bootstrapFromTemplate'
|
8
|
-
import {pluginTemplates} from './pluginTemplates'
|
9
|
-
|
10
|
-
export default async function initPlugin(
|
11
|
-
args: CliCommandArguments<InitFlags>,
|
12
|
-
context: CliCommandContext,
|
13
|
-
): Promise<void> {
|
14
|
-
const {output, prompt} = context
|
15
|
-
const [, specifiedTemplateUrl] = args.argsWithoutOptions
|
16
|
-
|
17
|
-
output.print('This utility will walk you through creating a new Sanity plugin.')
|
18
|
-
output.print('Press ^C at any time to quit.\n')
|
19
|
-
|
20
|
-
const hasTemplateUrl = /^https?:\/\//.test(specifiedTemplateUrl || '')
|
21
|
-
|
22
|
-
if (hasTemplateUrl) {
|
23
|
-
debug('User provided template URL: %s', specifiedTemplateUrl)
|
24
|
-
return bootstrapFromUrl(context, specifiedTemplateUrl)
|
25
|
-
}
|
26
|
-
|
27
|
-
let specifiedTemplate = null
|
28
|
-
if (specifiedTemplateUrl) {
|
29
|
-
specifiedTemplate = pluginTemplates.find((tpl) => tpl.value === specifiedTemplateUrl)
|
30
|
-
}
|
31
|
-
|
32
|
-
if (specifiedTemplate) {
|
33
|
-
debug(
|
34
|
-
'User wanted template "%s", match found at %s',
|
35
|
-
specifiedTemplateUrl,
|
36
|
-
specifiedTemplate.url,
|
37
|
-
)
|
38
|
-
|
39
|
-
return bootstrapFromUrl(context, specifiedTemplate.url)
|
40
|
-
} else if (specifiedTemplateUrl) {
|
41
|
-
throw new Error(`Cannot find template with name "${specifiedTemplateUrl}"`)
|
42
|
-
}
|
43
|
-
|
44
|
-
const templateChoices = pluginTemplates.map(({value, name}) => ({value, name}))
|
45
|
-
const selected = await prompt.single({
|
46
|
-
message: 'Select template to use',
|
47
|
-
type: 'list',
|
48
|
-
choices: templateChoices,
|
49
|
-
})
|
50
|
-
|
51
|
-
specifiedTemplate = pluginTemplates.find((tpl) => tpl.value === selected)
|
52
|
-
if (!specifiedTemplate) {
|
53
|
-
throw new Error('No template selected')
|
54
|
-
}
|
55
|
-
|
56
|
-
debug('User selected template URL: %s', specifiedTemplate.url)
|
57
|
-
return bootstrapFromUrl(context, specifiedTemplate.url)
|
58
|
-
}
|
59
|
-
|
60
|
-
async function bootstrapFromUrl(context: CliCommandContext, url: string): Promise<void> {
|
61
|
-
const {output, prompt, yarn, workDir} = context
|
62
|
-
|
63
|
-
debug('Bootstrapping from URL: %s', url)
|
64
|
-
const {name, outputPath, inPluginsPath, dependencies} = await bootstrapFromTemplate(context, url)
|
65
|
-
|
66
|
-
if (inPluginsPath) {
|
67
|
-
const addIt = await prompt.single({
|
68
|
-
type: 'confirm',
|
69
|
-
message: 'Enable plugin in current Sanity installation?',
|
70
|
-
default: true,
|
71
|
-
})
|
72
|
-
|
73
|
-
if (addIt) {
|
74
|
-
await addPluginToManifest(workDir, name.replace(/^sanity-plugin-/, ''))
|
75
|
-
}
|
76
|
-
}
|
77
|
-
|
78
|
-
if (dependencies) {
|
79
|
-
const dependencyString = JSON.stringify(dependencies, null, 2)
|
80
|
-
.split('\n')
|
81
|
-
.slice(1, -1)
|
82
|
-
.join('\n')
|
83
|
-
.replace(/"/g, '')
|
84
|
-
|
85
|
-
output.print('\nThe following dependencies are required for this template:')
|
86
|
-
output.print(`${dependencyString}\n`)
|
87
|
-
}
|
88
|
-
|
89
|
-
if (dependencies && inPluginsPath) {
|
90
|
-
const addDeps = await prompt.single({
|
91
|
-
type: 'confirm',
|
92
|
-
message: 'Install dependencies in current project?',
|
93
|
-
default: true,
|
94
|
-
})
|
95
|
-
|
96
|
-
if (addDeps) {
|
97
|
-
const deps = Object.keys(dependencies).map((dep) => `${dep}@${dependencies[dep]}`)
|
98
|
-
await yarn(['add'].concat(deps), {...output, rootDir: workDir})
|
99
|
-
|
100
|
-
output.print('Dependencies installed.')
|
101
|
-
output.print('Remember to remove them from `package.json` if you no longer need them!')
|
102
|
-
}
|
103
|
-
}
|
104
|
-
|
105
|
-
output.print(`\nSuccess! Plugin initialized at ${outputPath}`)
|
106
|
-
}
|
107
|
-
|
108
|
-
async function addPluginToManifest(sanityDir: string, pluginName: string): Promise<SanityJson> {
|
109
|
-
const manifestPath = path.join(sanityDir, 'sanity.json')
|
110
|
-
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'))
|
111
|
-
|
112
|
-
manifest.plugins = manifest.plugins || []
|
113
|
-
if (manifest.plugins.indexOf(pluginName) === -1) {
|
114
|
-
manifest.plugins.push(pluginName)
|
115
|
-
}
|
116
|
-
|
117
|
-
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2))
|
118
|
-
return manifest
|
119
|
-
}
|
@@ -1,38 +0,0 @@
|
|
1
|
-
export interface PluginTemplate {
|
2
|
-
value: string
|
3
|
-
name: string
|
4
|
-
url: string
|
5
|
-
}
|
6
|
-
|
7
|
-
export const pluginTemplates: PluginTemplate[] = [
|
8
|
-
{
|
9
|
-
value: 'logo',
|
10
|
-
name: 'Studio logo',
|
11
|
-
url: 'https://github.com/sanity-io/plugin-template-logo/archive/master.zip',
|
12
|
-
},
|
13
|
-
{
|
14
|
-
value: 'tool',
|
15
|
-
name: 'Basic, empty tool',
|
16
|
-
url: 'https://github.com/sanity-io/plugin-template-tool/archive/master.zip',
|
17
|
-
},
|
18
|
-
{
|
19
|
-
value: 'toolWithRouting',
|
20
|
-
name: 'Tool with basic routing',
|
21
|
-
url: 'https://github.com/sanity-io/plugin-template-tool-with-routing/archive/master.zip',
|
22
|
-
},
|
23
|
-
{
|
24
|
-
value: 'chessInput',
|
25
|
-
name: 'Chess board input component w/ block preview',
|
26
|
-
url: 'https://github.com/sanity-io/plugin-template-chess-input/archive/master.zip',
|
27
|
-
},
|
28
|
-
{
|
29
|
-
value: 'dashboardWidget',
|
30
|
-
name: 'A Dashboard widget with cats',
|
31
|
-
url: 'https://github.com/sanity-io/plugin-template-dashboard-widget-cats/archive/master.zip',
|
32
|
-
},
|
33
|
-
{
|
34
|
-
value: 'assetSource',
|
35
|
-
name: 'Custom asset source plugin',
|
36
|
-
url: 'https://github.com/sanity-io/plugin-template-asset-source/archive/master.zip',
|
37
|
-
},
|
38
|
-
]
|