@sanity/cli 3.67.2-corel.454 → 3.68.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/lib/_chunks-cjs/cli.js +100 -143
- package/lib/_chunks-cjs/cli.js.map +1 -1
- package/package.json +10 -10
- package/src/actions/init-project/bootstrapRemoteTemplate.ts +10 -2
- package/src/outputters/cliOutputter.ts +9 -9
- package/src/util/frameworkPort.ts +63 -0
- package/src/util/remoteTemplate.ts +43 -209
- package/templates/get-started/plugins/sanity-plugin-tutorial/GetStartedTutorial.tsx +4 -4
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@sanity/cli",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.68.1",
|
4
4
|
"description": "Sanity CLI tool for managing Sanity installations, managing plugins, schemas and datasets",
|
5
5
|
"keywords": [
|
6
6
|
"sanity",
|
@@ -58,9 +58,10 @@
|
|
58
58
|
"dependencies": {
|
59
59
|
"@babel/traverse": "^7.23.5",
|
60
60
|
"@sanity/client": "^6.24.1",
|
61
|
-
"@sanity/codegen": "3.
|
61
|
+
"@sanity/codegen": "3.68.1",
|
62
62
|
"@sanity/telemetry": "^0.7.7",
|
63
|
-
"@sanity/
|
63
|
+
"@sanity/template-validator": "^1.0.2",
|
64
|
+
"@sanity/util": "3.68.1",
|
64
65
|
"chalk": "^4.1.2",
|
65
66
|
"debug": "^4.3.4",
|
66
67
|
"decompress": "^4.2.0",
|
@@ -72,17 +73,16 @@
|
|
72
73
|
"prettier": "^3.3.0",
|
73
74
|
"semver": "^7.3.5",
|
74
75
|
"silver-fleece": "1.1.0",
|
75
|
-
"validate-npm-package-name": "^3.0.0"
|
76
|
-
"yaml": "^2.6.1"
|
76
|
+
"validate-npm-package-name": "^3.0.0"
|
77
77
|
},
|
78
78
|
"devDependencies": {
|
79
|
-
"@repo/package.config": "3.
|
80
|
-
"@repo/test-config": "3.
|
79
|
+
"@repo/package.config": "3.68.1",
|
80
|
+
"@repo/test-config": "3.68.1",
|
81
81
|
"@rexxars/gitconfiglocal": "^3.0.1",
|
82
82
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
83
83
|
"@sanity/eslint-config-studio": "^4.0.0",
|
84
84
|
"@sanity/generate-help-url": "^3.0.0",
|
85
|
-
"@sanity/types": "3.
|
85
|
+
"@sanity/types": "3.68.1",
|
86
86
|
"@types/babel__traverse": "^7.20.5",
|
87
87
|
"@types/configstore": "^5.0.1",
|
88
88
|
"@types/cpx": "^1.5.2",
|
@@ -127,12 +127,12 @@
|
|
127
127
|
"semver-compare": "^1.0.0",
|
128
128
|
"tar": "^6.1.11",
|
129
129
|
"vite": "^5.4.11",
|
130
|
-
"vitest": "^2.1.
|
130
|
+
"vitest": "^2.1.8",
|
131
131
|
"which": "^2.0.2",
|
132
132
|
"xdg-basedir": "^4.0.0"
|
133
133
|
},
|
134
134
|
"engines": {
|
135
135
|
"node": ">=18"
|
136
136
|
},
|
137
|
-
"gitHead": "
|
137
|
+
"gitHead": "35cd3076fd5a9ff79bfbd34d178e8bb10ecdfa90"
|
138
138
|
}
|
@@ -6,13 +6,15 @@ import {detectFrameworkRecord, LocalFileSystemDetector} from '@vercel/fs-detecto
|
|
6
6
|
|
7
7
|
import {debug} from '../../debug'
|
8
8
|
import {type CliCommandContext} from '../../types'
|
9
|
+
import {getDefaultPortForFramework} from '../../util/frameworkPort'
|
9
10
|
import {
|
10
11
|
applyEnvVariables,
|
11
12
|
checkNeedsReadToken,
|
12
13
|
downloadAndExtractRepo,
|
13
14
|
generateSanityApiReadToken,
|
14
|
-
|
15
|
+
getPackages,
|
15
16
|
type RepoInfo,
|
17
|
+
setCorsOrigin,
|
16
18
|
tryApplyPackageName,
|
17
19
|
validateRemoteTemplate,
|
18
20
|
} from '../../util/remoteTemplate'
|
@@ -40,7 +42,7 @@ export async function bootstrapRemoteTemplate(
|
|
40
42
|
const spinner = output.spinner(`Bootstrapping files from template "${name}"`).start()
|
41
43
|
|
42
44
|
debug('Validating remote template')
|
43
|
-
const packages = await
|
45
|
+
const packages = await getPackages(repoInfo, bearerToken)
|
44
46
|
await validateRemoteTemplate(repoInfo, packages, bearerToken)
|
45
47
|
|
46
48
|
debug('Create new directory "%s"', outputPath)
|
@@ -65,6 +67,12 @@ export async function bootstrapRemoteTemplate(
|
|
65
67
|
fs: new LocalFileSystemDetector(packagePath),
|
66
68
|
frameworkList: frameworks as readonly Framework[],
|
67
69
|
})
|
70
|
+
const port = getDefaultPortForFramework(packageFramework?.slug)
|
71
|
+
|
72
|
+
debug('Setting CORS origin to http://localhost:%d', port)
|
73
|
+
await setCorsOrigin(`http://localhost:${port}`, variables.projectId, apiClient)
|
74
|
+
|
75
|
+
debug('Applying environment variables to %s', pkg)
|
68
76
|
// Next.js uses `.env.local` for local environment variables
|
69
77
|
const envName = packageFramework?.slug === 'nextjs' ? '.env.local' : '.env'
|
70
78
|
await applyEnvVariables(packagePath, {...variables, readToken}, envName)
|
@@ -13,19 +13,19 @@ export default {
|
|
13
13
|
console.log(...args)
|
14
14
|
},
|
15
15
|
|
16
|
-
success(...args: unknown[]): void {
|
17
|
-
console.log(`${SYMBOL_CHECK} ${args
|
16
|
+
success(firstPartOfMessage: unknown, ...args: unknown[]): void {
|
17
|
+
console.log(`${SYMBOL_CHECK} ${firstPartOfMessage}`, ...args)
|
18
18
|
},
|
19
19
|
|
20
|
-
warn(...args: unknown[]): void {
|
21
|
-
console.warn(`${SYMBOL_WARN} ${args
|
20
|
+
warn(firstPartOfMessage: unknown, ...args: unknown[]): void {
|
21
|
+
console.warn(`${SYMBOL_WARN} ${firstPartOfMessage}`, ...args)
|
22
22
|
},
|
23
23
|
|
24
|
-
error(...args: unknown[]): void {
|
25
|
-
if (
|
26
|
-
console.error(`${SYMBOL_FAIL} ${chalk.red(
|
24
|
+
error(firstPartOfMessage: unknown, ...args: unknown[]): void {
|
25
|
+
if (firstPartOfMessage instanceof Error) {
|
26
|
+
console.error(`${SYMBOL_FAIL} ${chalk.red(firstPartOfMessage.stack)}`)
|
27
27
|
} else {
|
28
|
-
console.error(`${SYMBOL_FAIL} ${args
|
28
|
+
console.error(`${SYMBOL_FAIL} ${firstPartOfMessage}`, ...args)
|
29
29
|
}
|
30
30
|
},
|
31
31
|
|
@@ -37,7 +37,7 @@ export default {
|
|
37
37
|
},
|
38
38
|
|
39
39
|
spinner(options: Options): Ora {
|
40
|
-
const spinner = ora(
|
40
|
+
const spinner = ora(options)
|
41
41
|
// Override the default status methods to use custom symbols instead of emojis
|
42
42
|
spinner.succeed = (text?: string) => spinner.stopAndPersist({text, symbol: SYMBOL_CHECK})
|
43
43
|
spinner.warn = (text?: string) => spinner.stopAndPersist({text, symbol: SYMBOL_WARN})
|
@@ -0,0 +1,63 @@
|
|
1
|
+
const FALLBACK_PORT = 3000
|
2
|
+
|
3
|
+
const portMap: Record<string, number> = {
|
4
|
+
'nextjs': 3000,
|
5
|
+
'blitzjs': 3000,
|
6
|
+
'gatsby': 8000,
|
7
|
+
'remix': 3000,
|
8
|
+
'astro': 3000,
|
9
|
+
'hexo': 4000,
|
10
|
+
'eleventy': 8080,
|
11
|
+
'docusaurus': 3000,
|
12
|
+
'docusaurus-2': 3000,
|
13
|
+
'preact': 8080,
|
14
|
+
'solidstart': 3000,
|
15
|
+
'solidstart-1': 3000,
|
16
|
+
'dojo': 3000,
|
17
|
+
'ember': 4200,
|
18
|
+
'vue': 8080,
|
19
|
+
'scully': 1668,
|
20
|
+
'ionic-angular': 4200,
|
21
|
+
'angular': 4200,
|
22
|
+
'polymer': 8081,
|
23
|
+
'svelte': 5000,
|
24
|
+
'sveltekit': 5173,
|
25
|
+
'sveltekit-1': 5173,
|
26
|
+
'ionic-react': 3000,
|
27
|
+
'create-react-app': 3000,
|
28
|
+
'gridsome': 8080,
|
29
|
+
'umijs': 8000,
|
30
|
+
'saber': 3000,
|
31
|
+
'stencil': 3333,
|
32
|
+
'nuxtjs': 3000,
|
33
|
+
'redwoodjs': 8910,
|
34
|
+
'hugo': 1313,
|
35
|
+
'jekyll': 4000,
|
36
|
+
'brunch': 3333,
|
37
|
+
'middleman': 4567,
|
38
|
+
'zola': 1111,
|
39
|
+
'hydrogen': 3000,
|
40
|
+
'vite': 5173,
|
41
|
+
'vitepress': 5173,
|
42
|
+
'vuepress': 8080,
|
43
|
+
'parcel': 1234,
|
44
|
+
'fasthtml': 8000,
|
45
|
+
'sanity': 3333,
|
46
|
+
'sanity-v3': 3333,
|
47
|
+
'storybook': 6006,
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Returns the default development port for a given framework.
|
52
|
+
* Contains default ports for all frameworks supported by `@vercel/frameworks`.
|
53
|
+
* Falls back to port 3000 if framework is not found or not specified.
|
54
|
+
*
|
55
|
+
* @see https://github.com/vercel/vercel/blob/main/packages/frameworks/src/frameworks.ts
|
56
|
+
* for the complete list of supported frameworks
|
57
|
+
*
|
58
|
+
* @param frameworkSlug - The framework identifier from `@vercel/frameworks`
|
59
|
+
* @returns The default port number for the framework
|
60
|
+
*/
|
61
|
+
export function getDefaultPortForFramework(frameworkSlug?: string | null): number {
|
62
|
+
return portMap[frameworkSlug ?? ''] ?? FALLBACK_PORT
|
63
|
+
}
|
@@ -4,25 +4,27 @@ import {Readable} from 'node:stream'
|
|
4
4
|
import {pipeline} from 'node:stream/promises'
|
5
5
|
import {type ReadableStream} from 'node:stream/web'
|
6
6
|
|
7
|
+
import {
|
8
|
+
ENV_TEMPLATE_FILES,
|
9
|
+
getMonoRepo,
|
10
|
+
REQUIRED_ENV_VAR,
|
11
|
+
validateSanityTemplate,
|
12
|
+
} from '@sanity/template-validator'
|
7
13
|
import {x} from 'tar'
|
8
|
-
import {parse as parseYaml} from 'yaml'
|
9
14
|
|
15
|
+
import {debug} from '../debug'
|
10
16
|
import {type CliApiClient, type PackageJson} from '../types'
|
11
17
|
|
18
|
+
const DISALLOWED_PATHS = [
|
19
|
+
// Prevent security risks from unknown GitHub Actions
|
20
|
+
'/.github/',
|
21
|
+
]
|
22
|
+
|
12
23
|
const ENV_VAR = {
|
13
|
-
|
14
|
-
DATASET: /SANITY(?:_STUDIO)?_DATASET/, // Matches SANITY_DATASET and SANITY_STUDIO_DATASET
|
24
|
+
...REQUIRED_ENV_VAR,
|
15
25
|
READ_TOKEN: 'SANITY_API_READ_TOKEN',
|
16
26
|
} as const
|
17
27
|
|
18
|
-
const ENV_FILE = {
|
19
|
-
TEMPLATE: '.env.template',
|
20
|
-
EXAMPLE: '.env.example',
|
21
|
-
LOCAL_EXAMPLE: '.env.local.example',
|
22
|
-
} as const
|
23
|
-
|
24
|
-
const ENV_TEMPLATE_FILES = [ENV_FILE.TEMPLATE, ENV_FILE.EXAMPLE, ENV_FILE.LOCAL_EXAMPLE] as const
|
25
|
-
|
26
28
|
type EnvData = {
|
27
29
|
projectId: string
|
28
30
|
dataset: string
|
@@ -40,6 +42,11 @@ export type RepoInfo = {
|
|
40
42
|
filePath: string
|
41
43
|
}
|
42
44
|
|
45
|
+
function getGitHubRawContentUrl(repoInfo: RepoInfo): string {
|
46
|
+
const {username, name, branch, filePath} = repoInfo
|
47
|
+
return `https://raw.githubusercontent.com/${username}/${name}/${branch}/${filePath}`
|
48
|
+
}
|
49
|
+
|
43
50
|
function isGithubRepoShorthand(value: string): boolean {
|
44
51
|
if (URL.canParse(value)) {
|
45
52
|
return false
|
@@ -180,6 +187,9 @@ export async function downloadAndExtractRepo(
|
|
180
187
|
const pathSegments = posixPath.split(posix.sep)
|
181
188
|
rootPath = pathSegments.length ? pathSegments[0] : null
|
182
189
|
}
|
190
|
+
for (const disallowedPath of DISALLOWED_PATHS) {
|
191
|
+
if (posixPath.includes(disallowedPath)) return false
|
192
|
+
}
|
183
193
|
return posixPath.startsWith(`${rootPath}${filePath ? `/${filePath}/` : '/'}`)
|
184
194
|
},
|
185
195
|
}),
|
@@ -191,222 +201,29 @@ export async function downloadAndExtractRepo(
|
|
191
201
|
* Supports pnpm workspaces, Lerna, Rush, and npm workspaces (package.json).
|
192
202
|
* @returns Promise that resolves to an array of package paths/names if monorepo is detected, undefined otherwise
|
193
203
|
*/
|
194
|
-
export async function
|
204
|
+
export async function getPackages(
|
195
205
|
repoInfo: RepoInfo,
|
196
206
|
bearerToken?: string,
|
197
207
|
): Promise<string[] | undefined> {
|
198
|
-
const {username, name, branch, filePath} = repoInfo
|
199
|
-
const baseUrl = `https://raw.githubusercontent.com/${username}/${name}/${branch}/${filePath}`
|
200
|
-
|
201
208
|
const headers: Record<string, string> = {}
|
202
209
|
if (bearerToken) {
|
203
210
|
headers.Authorization = `Bearer ${bearerToken}`
|
204
211
|
}
|
205
|
-
|
206
|
-
type MonorepoHandler = {
|
207
|
-
check: (content: string) => string[] | undefined
|
208
|
-
}
|
209
|
-
|
210
|
-
const handlers: Record<string, MonorepoHandler> = {
|
211
|
-
'package.json': {
|
212
|
-
check: (content) => {
|
213
|
-
try {
|
214
|
-
const pkg = JSON.parse(content)
|
215
|
-
if (!pkg.workspaces) return undefined
|
216
|
-
return Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages
|
217
|
-
} catch {
|
218
|
-
return undefined
|
219
|
-
}
|
220
|
-
},
|
221
|
-
},
|
222
|
-
'pnpm-workspace.yaml': {
|
223
|
-
check: (content) => {
|
224
|
-
try {
|
225
|
-
const config = parseYaml(content)
|
226
|
-
return config.packages
|
227
|
-
} catch {
|
228
|
-
return undefined
|
229
|
-
}
|
230
|
-
},
|
231
|
-
},
|
232
|
-
'lerna.json': {
|
233
|
-
check: (content) => {
|
234
|
-
try {
|
235
|
-
const config = JSON.parse(content)
|
236
|
-
return config.packages
|
237
|
-
} catch {
|
238
|
-
return undefined
|
239
|
-
}
|
240
|
-
},
|
241
|
-
},
|
242
|
-
'rush.json': {
|
243
|
-
check: (content) => {
|
244
|
-
try {
|
245
|
-
const config = JSON.parse(content)
|
246
|
-
return config.projects?.map((p: {packageName: string}) => p.packageName)
|
247
|
-
} catch {
|
248
|
-
return undefined
|
249
|
-
}
|
250
|
-
},
|
251
|
-
},
|
252
|
-
}
|
253
|
-
|
254
|
-
const fileChecks = await Promise.all(
|
255
|
-
Object.keys(handlers).map(async (file) => {
|
256
|
-
const response = await fetch(`${baseUrl}/${file}`, {headers})
|
257
|
-
return {file, exists: response.status === 200, content: await response.text()}
|
258
|
-
}),
|
259
|
-
)
|
260
|
-
|
261
|
-
for (const check of fileChecks) {
|
262
|
-
if (!check.exists) continue
|
263
|
-
const result = handlers[check.file].check(check.content)
|
264
|
-
if (result) return result
|
265
|
-
}
|
266
|
-
|
267
|
-
return undefined
|
212
|
+
return getMonoRepo(getGitHubRawContentUrl(repoInfo), headers)
|
268
213
|
}
|
269
214
|
|
270
|
-
/**
|
271
|
-
* Validates a single package within a repository against required criteria.
|
272
|
-
*/
|
273
|
-
async function validatePackage(
|
274
|
-
baseUrl: string,
|
275
|
-
packagePath: string,
|
276
|
-
headers: Record<string, string>,
|
277
|
-
): Promise<{
|
278
|
-
hasSanityConfig: boolean
|
279
|
-
hasSanityCli: boolean
|
280
|
-
hasEnvFile: boolean
|
281
|
-
hasSanityDep: boolean
|
282
|
-
}> {
|
283
|
-
const packageUrl = packagePath ? `${baseUrl}/${packagePath}` : baseUrl
|
284
|
-
|
285
|
-
const requiredFiles = [
|
286
|
-
'package.json',
|
287
|
-
'sanity.config.ts',
|
288
|
-
'sanity.config.js',
|
289
|
-
'sanity.cli.ts',
|
290
|
-
'sanity.cli.js',
|
291
|
-
...ENV_TEMPLATE_FILES,
|
292
|
-
]
|
293
|
-
|
294
|
-
const fileChecks = await Promise.all(
|
295
|
-
requiredFiles.map(async (file) => {
|
296
|
-
const response = await fetch(`${packageUrl}/${file}`, {headers})
|
297
|
-
return {file, exists: response.status === 200, content: await response.text()}
|
298
|
-
}),
|
299
|
-
)
|
300
|
-
|
301
|
-
const packageJson = fileChecks.find((f) => f.file === 'package.json')
|
302
|
-
if (!packageJson?.exists) {
|
303
|
-
throw new Error(`Package at ${packagePath || 'root'} must include a package.json file`)
|
304
|
-
}
|
305
|
-
|
306
|
-
let hasSanityDep = false
|
307
|
-
try {
|
308
|
-
const pkg: PackageJson = JSON.parse(packageJson.content)
|
309
|
-
hasSanityDep = !!(pkg.dependencies?.sanity || pkg.devDependencies?.sanity)
|
310
|
-
} catch (err) {
|
311
|
-
throw new Error(`Invalid package.json file in ${packagePath || 'root'}`)
|
312
|
-
}
|
313
|
-
|
314
|
-
const hasSanityConfig = fileChecks.some(
|
315
|
-
(f) => f.exists && (f.file === 'sanity.config.ts' || f.file === 'sanity.config.js'),
|
316
|
-
)
|
317
|
-
|
318
|
-
const hasSanityCli = fileChecks.some(
|
319
|
-
(f) => f.exists && (f.file === 'sanity.cli.ts' || f.file === 'sanity.cli.js'),
|
320
|
-
)
|
321
|
-
|
322
|
-
const envFile = fileChecks.find(
|
323
|
-
(f) => f.exists && ENV_TEMPLATE_FILES.includes(f.file as (typeof ENV_TEMPLATE_FILES)[number]),
|
324
|
-
)
|
325
|
-
if (envFile) {
|
326
|
-
const envContent = envFile.content
|
327
|
-
const hasProjectId = envContent.match(ENV_VAR.PROJECT_ID)
|
328
|
-
const hasDataset = envContent.match(ENV_VAR.DATASET)
|
329
|
-
|
330
|
-
if (!hasProjectId || !hasDataset) {
|
331
|
-
const missing = []
|
332
|
-
if (!hasProjectId) missing.push('SANITY_PROJECT_ID or SANITY_STUDIO_PROJECT_ID')
|
333
|
-
if (!hasDataset) missing.push('SANITY_DATASET or SANITY_STUDIO_DATASET')
|
334
|
-
throw new Error(
|
335
|
-
`Environment template in ${
|
336
|
-
packagePath || 'repo'
|
337
|
-
} must include the following variables: ${missing.join(', ')}`,
|
338
|
-
)
|
339
|
-
}
|
340
|
-
}
|
341
|
-
|
342
|
-
return {
|
343
|
-
hasSanityConfig,
|
344
|
-
hasSanityCli,
|
345
|
-
hasEnvFile: Boolean(envFile),
|
346
|
-
hasSanityDep,
|
347
|
-
}
|
348
|
-
}
|
349
|
-
|
350
|
-
/**
|
351
|
-
* Validates a GitHub repository template against required criteria.
|
352
|
-
* Supports both monorepo and single-package repositories.
|
353
|
-
*
|
354
|
-
* For monorepos:
|
355
|
-
* - Each package must have a valid package.json
|
356
|
-
* - At least one package must include 'sanity' in dependencies or devDependencies
|
357
|
-
* - At least one package must have sanity.config.js/ts and sanity.cli.js/ts
|
358
|
-
* - Each package must have a .env.template, .env.example, or .env.local.example
|
359
|
-
*
|
360
|
-
* For single-package repositories:
|
361
|
-
* - Must have a valid package.json with 'sanity' dependency
|
362
|
-
* - Must have sanity.config.js/ts and sanity.cli.js/ts
|
363
|
-
* - Must have .env.template, .env.example, or .env.local.example
|
364
|
-
*
|
365
|
-
* Environment files must include:
|
366
|
-
* - SANITY_PROJECT_ID or SANITY_STUDIO_PROJECT_ID variable
|
367
|
-
* - SANITY_DATASET or SANITY_STUDIO_DATASET variable
|
368
|
-
*
|
369
|
-
* @throws Error if validation fails with specific reason
|
370
|
-
*/
|
371
215
|
export async function validateRemoteTemplate(
|
372
216
|
repoInfo: RepoInfo,
|
373
217
|
packages: string[] = [''],
|
374
218
|
bearerToken?: string,
|
375
219
|
): Promise<void> {
|
376
|
-
const {username, name, branch, filePath} = repoInfo
|
377
|
-
const baseUrl = `https://raw.githubusercontent.com/${username}/${name}/${branch}/${filePath}`
|
378
|
-
|
379
220
|
const headers: Record<string, string> = {}
|
380
221
|
if (bearerToken) {
|
381
222
|
headers.Authorization = `Bearer ${bearerToken}`
|
382
223
|
}
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
)
|
387
|
-
|
388
|
-
const hasSanityDep = validations.some((v) => v.hasSanityDep)
|
389
|
-
if (!hasSanityDep) {
|
390
|
-
throw new Error('At least one package must include "sanity" as a dependency in package.json')
|
391
|
-
}
|
392
|
-
|
393
|
-
const hasSanityConfig = validations.some((v) => v.hasSanityConfig)
|
394
|
-
if (!hasSanityConfig) {
|
395
|
-
throw new Error('At least one package must include a sanity.config.js or sanity.config.ts file')
|
396
|
-
}
|
397
|
-
|
398
|
-
const hasSanityCli = validations.some((v) => v.hasSanityCli)
|
399
|
-
if (!hasSanityCli) {
|
400
|
-
throw new Error('At least one package must include a sanity.cli.js or sanity.cli.ts file')
|
401
|
-
}
|
402
|
-
|
403
|
-
const missingEnvPackages = packages.filter((pkg, i) => !validations[i].hasEnvFile)
|
404
|
-
if (missingEnvPackages.length > 0) {
|
405
|
-
throw new Error(
|
406
|
-
`The following packages are missing .env.template, .env.example, or .env.local.example files: ${missingEnvPackages.join(
|
407
|
-
', ',
|
408
|
-
)}`,
|
409
|
-
)
|
224
|
+
const result = await validateSanityTemplate(getGitHubRawContentUrl(repoInfo), packages, headers)
|
225
|
+
if (!result.isValid) {
|
226
|
+
throw new Error(result.errors.join('\n'))
|
410
227
|
}
|
411
228
|
}
|
412
229
|
|
@@ -510,3 +327,20 @@ export async function generateSanityApiReadToken(
|
|
510
327
|
})
|
511
328
|
return response.key
|
512
329
|
}
|
330
|
+
|
331
|
+
export async function setCorsOrigin(
|
332
|
+
origin: string,
|
333
|
+
projectId: string,
|
334
|
+
apiClient: CliApiClient,
|
335
|
+
): Promise<void> {
|
336
|
+
try {
|
337
|
+
await apiClient({api: {projectId}}).request({
|
338
|
+
method: 'POST',
|
339
|
+
url: '/cors',
|
340
|
+
body: {origin: origin, allowCredentials: false},
|
341
|
+
})
|
342
|
+
} catch (error) {
|
343
|
+
// Silent fail, it most likely means that the origin is already set
|
344
|
+
debug('Failed to set CORS origin', error)
|
345
|
+
}
|
346
|
+
}
|
@@ -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 && (
|