@sanity/cli 3.93.1-next.9.198ab74452 → 3.94.0
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 +1 -1
- package/lib/_chunks-cjs/cli.js.map +1 -1
- package/package.json +10 -11
- package/templates/blog/README.md +1 -1
- package/templates/clean/README.md +1 -1
- package/templates/ecommerce/README.md +1 -1
- package/templates/get-started/README.md +1 -1
- package/templates/moviedb/README.md +1 -1
- package/templates/quickstart/README.md +1 -1
- package/src/CommandRunner.ts +0 -219
- package/src/__telemetry__/cli.telemetry.ts +0 -29
- package/src/__telemetry__/init.telemetry.ts +0 -95
- package/src/actions/codemod/codemodAction.ts +0 -109
- package/src/actions/codemod/mods/deskRename.ts +0 -19
- package/src/actions/codemod/mods/index.ts +0 -9
- package/src/actions/codemod/mods/partsTypeDirective.ts +0 -28
- package/src/actions/codemod/mods/reactIconsV3.ts +0 -54
- package/src/actions/codemod/types.ts +0 -10
- package/src/actions/init-plugin/bootstrapFromTemplate.ts +0 -196
- package/src/actions/init-project/bootstrapLocalTemplate.ts +0 -201
- package/src/actions/init-project/bootstrapRemoteTemplate.ts +0 -118
- package/src/actions/init-project/createAppCliConfig.ts +0 -24
- package/src/actions/init-project/createCliConfig.ts +0 -31
- package/src/actions/init-project/createPackageManifest.ts +0 -102
- package/src/actions/init-project/createStudioConfig.ts +0 -53
- package/src/actions/init-project/determineAppTemplate.ts +0 -13
- package/src/actions/init-project/fsUtils.ts +0 -40
- package/src/actions/init-project/git.ts +0 -59
- package/src/actions/init-project/initProject.ts +0 -1708
- package/src/actions/init-project/processTemplate.ts +0 -55
- package/src/actions/init-project/promptForDatasetName.ts +0 -68
- package/src/actions/init-project/prompts/index.ts +0 -51
- package/src/actions/init-project/prompts/nextjs.ts +0 -59
- package/src/actions/init-project/readPackageJson.ts +0 -17
- package/src/actions/init-project/templates/appQuickstart.ts +0 -23
- package/src/actions/init-project/templates/appSanityUi.ts +0 -25
- package/src/actions/init-project/templates/blog.ts +0 -5
- package/src/actions/init-project/templates/clean.ts +0 -5
- package/src/actions/init-project/templates/getStarted.ts +0 -37
- package/src/actions/init-project/templates/index.ts +0 -24
- package/src/actions/init-project/templates/moviedb.ts +0 -35
- package/src/actions/init-project/templates/nextjs/index.ts +0 -238
- package/src/actions/init-project/templates/nextjs/schemaTypes/blog.ts +0 -252
- package/src/actions/init-project/templates/quickstart.ts +0 -5
- package/src/actions/init-project/templates/shopify.ts +0 -78
- package/src/actions/init-project/templates/shopifyOnline.ts +0 -50
- package/src/actions/init-project/updateInitialTemplateMetadata.ts +0 -24
- package/src/actions/login/login.telemetry.ts +0 -18
- package/src/actions/login/login.ts +0 -350
- package/src/actions/login/types.ts +0 -19
- package/src/actions/project/createProject.ts +0 -36
- package/src/actions/telemetry/setTelemetryConsent.ts +0 -101
- package/src/actions/typegen/generate.telemetry.ts +0 -20
- package/src/actions/typegen/generateAction.ts +0 -210
- package/src/actions/versions/findSanityModuleVersions.ts +0 -185
- package/src/actions/yarn/events.ts +0 -73
- package/src/cli.ts +0 -350
- package/src/cliClient.ts +0 -62
- package/src/commands/blueprints/addBlueprintsCommand.ts +0 -155
- package/src/commands/blueprints/blueprintsGroup.ts +0 -10
- package/src/commands/blueprints/configBlueprintsCommand.ts +0 -86
- package/src/commands/blueprints/deployBlueprintsCommand.ts +0 -63
- package/src/commands/blueprints/destroyBlueprintsCommand.ts +0 -78
- package/src/commands/blueprints/infoBlueprintsCommand.ts +0 -55
- package/src/commands/blueprints/initBlueprintsCommand.ts +0 -99
- package/src/commands/blueprints/logsBlueprintsCommand.ts +0 -66
- package/src/commands/blueprints/planBlueprintsCommand.ts +0 -51
- package/src/commands/blueprints/stacksBlueprintsCommand.ts +0 -66
- package/src/commands/codemod/codemodCommand.ts +0 -32
- package/src/commands/debug/debugCommand.ts +0 -26
- package/src/commands/debug/printDebugInfo.ts +0 -243
- package/src/commands/docs/docsCommand.ts +0 -20
- package/src/commands/functions/devFunctionsCommand.ts +0 -73
- package/src/commands/functions/envFunctionsCommand.ts +0 -105
- package/src/commands/functions/functionsGroup.ts +0 -10
- package/src/commands/functions/logsFunctionsCommand.ts +0 -94
- package/src/commands/functions/testFunctionsCommand.ts +0 -93
- package/src/commands/help/helpCommand.ts +0 -21
- package/src/commands/help/showHelp.ts +0 -58
- package/src/commands/index.ts +0 -72
- package/src/commands/init/initCommand.ts +0 -123
- package/src/commands/install/installCommand.ts +0 -38
- package/src/commands/learn/learnCommand.ts +0 -20
- package/src/commands/login/loginCommand.ts +0 -29
- package/src/commands/logout/logoutCommand.ts +0 -52
- package/src/commands/manage/manageCommand.ts +0 -24
- package/src/commands/projects/listProjectsCommand.ts +0 -62
- package/src/commands/projects/projectsGroup.ts +0 -10
- package/src/commands/telemetry/disableTelemetryCommand.ts +0 -19
- package/src/commands/telemetry/enableTelemetryCommand.ts +0 -19
- package/src/commands/telemetry/telemetryGroup.ts +0 -10
- package/src/commands/telemetry/telemetryStatusCommand.ts +0 -89
- package/src/commands/typegen/generateTypesCommand.ts +0 -56
- package/src/commands/typegen/typegenGroup.ts +0 -10
- package/src/commands/versions/printVersionResult.ts +0 -51
- package/src/commands/versions/versionsCommand.ts +0 -13
- package/src/config.ts +0 -14
- package/src/debug.ts +0 -3
- package/src/index.ts +0 -6
- package/src/outputters/cliOutputter.ts +0 -47
- package/src/packageManager/getInstallCommand.ts +0 -26
- package/src/packageManager/getUpgradeCommand.ts +0 -52
- package/src/packageManager/index.ts +0 -33
- package/src/packageManager/installPackages.ts +0 -99
- package/src/packageManager/packageManagerChoice.ts +0 -204
- package/src/packageManager/uninstallPackages.ts +0 -44
- package/src/packageManager/yarnStub.ts +0 -22
- package/src/prompters/cliPrompter.ts +0 -9
- package/src/run.ts +0 -8
- package/src/studioDependencies.ts +0 -25
- package/src/types.ts +0 -387
- package/src/util/canLaunchBrowser.ts +0 -12
- package/src/util/cliWorker.ts +0 -34
- package/src/util/clientWrapper.ts +0 -119
- package/src/util/copy.ts +0 -50
- package/src/util/createExpiringConfig.ts +0 -80
- package/src/util/createTelemetryStore.ts +0 -204
- package/src/util/detectRuntime.ts +0 -12
- package/src/util/dynamicRequire.ts +0 -17
- package/src/util/frameworkPort.ts +0 -63
- package/src/util/generateCommandsDocumentation.ts +0 -62
- package/src/util/getCliConfig.ts +0 -172
- package/src/util/getCliVersion.ts +0 -22
- package/src/util/getLocalVersion.ts +0 -35
- package/src/util/getProjectDefaults.ts +0 -103
- package/src/util/getProviderName.ts +0 -9
- package/src/util/getUserConfig.ts +0 -15
- package/src/util/isCi.ts +0 -7
- package/src/util/isCommandGroup.ts +0 -7
- package/src/util/isInteractive.ts +0 -3
- package/src/util/isTrueish.ts +0 -8
- package/src/util/journeyConfig.ts +0 -280
- package/src/util/loadEnv.ts +0 -110
- package/src/util/mergeCommands.ts +0 -78
- package/src/util/neatStack.ts +0 -30
- package/src/util/noSuchCommandText.ts +0 -91
- package/src/util/parseArguments.ts +0 -76
- package/src/util/pathExists.ts +0 -14
- package/src/util/readJson.ts +0 -6
- package/src/util/readdirRecursive.ts +0 -26
- package/src/util/remoteTemplate.ts +0 -320
- package/src/util/resolveLatestVersions.ts +0 -24
- package/src/util/resolveRootDir.ts +0 -59
- package/src/util/telemetryDisclosure.ts +0 -45
- package/src/util/updateNotifier.ts +0 -158
- package/src/util/writeJson.ts +0 -6
- package/src/workers/getAndWriteJourneySchema.ts +0 -7
- package/src/workers/getCliConfig.ts +0 -14
- package/src/workers/typegenGenerate.ts +0 -228
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import leven from 'leven'
|
|
3
|
-
|
|
4
|
-
import {type CliCommandDefinition, type CliCommandGroupDefinition} from '../types'
|
|
5
|
-
|
|
6
|
-
const commonMistakes: Record<string, string | undefined> = {get: 'list'}
|
|
7
|
-
const levenThreshold = 3
|
|
8
|
-
const coreCommands = [
|
|
9
|
-
'build',
|
|
10
|
-
'check',
|
|
11
|
-
'configcheck',
|
|
12
|
-
'cors',
|
|
13
|
-
'dataset',
|
|
14
|
-
'deploy',
|
|
15
|
-
'dev',
|
|
16
|
-
'documents',
|
|
17
|
-
'exec',
|
|
18
|
-
'graphql',
|
|
19
|
-
'hook',
|
|
20
|
-
'migration',
|
|
21
|
-
'manifest',
|
|
22
|
-
'preview',
|
|
23
|
-
'schema',
|
|
24
|
-
'start',
|
|
25
|
-
'undeploy',
|
|
26
|
-
'uninstall',
|
|
27
|
-
'users',
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
// These are disabled in v3, but still present because of v2.
|
|
31
|
-
// We don't want to suggest them anymore, though.
|
|
32
|
-
const discouragedCommands = ['upgrade', 'check', 'configcheck', 'uninstall']
|
|
33
|
-
|
|
34
|
-
const helpText = `
|
|
35
|
-
Run the command again within a Sanity project directory, where "sanity"
|
|
36
|
-
is installed as a dependency.`
|
|
37
|
-
|
|
38
|
-
export function getNoSuchCommandText(
|
|
39
|
-
cmdName: string,
|
|
40
|
-
parentGroupName?: string | null,
|
|
41
|
-
groups?: Record<string, (CliCommandDefinition | CliCommandGroupDefinition)[]>,
|
|
42
|
-
): string {
|
|
43
|
-
if (parentGroupName && groups && groups[parentGroupName]) {
|
|
44
|
-
return suggestCommand(cmdName, groups[parentGroupName], parentGroupName)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const isCoreCommand = coreCommands.includes(cmdName)
|
|
48
|
-
if (isCoreCommand) {
|
|
49
|
-
return `Command "${cmdName}" is not available outside of a Sanity project context.${helpText}`
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return suggestCommand(cmdName, groups ? groups.default : [])
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function suggestCommand(
|
|
56
|
-
cmdName: string,
|
|
57
|
-
group: (CliCommandDefinition | CliCommandGroupDefinition)[],
|
|
58
|
-
parentGroupName: string | null = null,
|
|
59
|
-
) {
|
|
60
|
-
// Try to find something similar
|
|
61
|
-
const closest = group
|
|
62
|
-
.filter((command) => !discouragedCommands.includes(command.name))
|
|
63
|
-
.map((command) => leven(command.name, cmdName))
|
|
64
|
-
.reduce(
|
|
65
|
-
(current: {index: number; distance: number}, distance: number, index: number) =>
|
|
66
|
-
distance < current.distance ? {index, distance} : current,
|
|
67
|
-
{index: 0, distance: +Infinity},
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
// Given we are within our target threshold, suggest the command
|
|
71
|
-
let suggestCmd = ''
|
|
72
|
-
if (closest.distance <= levenThreshold) {
|
|
73
|
-
const cmdCandidate = group[closest.index]
|
|
74
|
-
suggestCmd = cmdCandidate.name
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Is this a common mistake that we can suggest an alias for?
|
|
78
|
-
const alternative = commonMistakes[cmdName]
|
|
79
|
-
if (!suggestCmd && alternative) {
|
|
80
|
-
suggestCmd = alternative
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const input = chalk.cyan(`"${cmdName}"`)
|
|
84
|
-
const suggest = chalk.green(`"${suggestCmd}"`)
|
|
85
|
-
const help = chalk.cyan('"sanity --help"')
|
|
86
|
-
|
|
87
|
-
const didYouMean = suggestCmd ? `Did you mean ${suggest}? ` : ' '
|
|
88
|
-
return parentGroupName
|
|
89
|
-
? `${input} is not a subcommand of "sanity ${parentGroupName}". ${didYouMean}See ${help}`
|
|
90
|
-
: `${input} is not a sanity command. ${didYouMean}See ${help}`
|
|
91
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import minimist from 'minimist'
|
|
2
|
-
|
|
3
|
-
export interface ParsedArguments<F = Record<string, string>> {
|
|
4
|
-
/**
|
|
5
|
-
* Group or command name, eg `dataset` (`sanity dataset`) or `import` (`sanity dataset import`)
|
|
6
|
-
*/
|
|
7
|
-
groupOrCommand: string
|
|
8
|
-
|
|
9
|
-
// Raw, forwarded arguments, for commands that want to be more explicit about parsing
|
|
10
|
-
argv: string[]
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Command flags, without the core options (help, debug, version etc)
|
|
14
|
-
*/
|
|
15
|
-
extOptions: F
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Command arguments, eg any arguments after `sanity <command>` (no flags)
|
|
19
|
-
*/
|
|
20
|
-
argsWithoutOptions: string[]
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Arguments after the ended argument list (--)
|
|
24
|
-
*/
|
|
25
|
-
extraArguments: string[]
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Options mostly relevant for the core CLI runner
|
|
29
|
-
*/
|
|
30
|
-
coreOptions: {
|
|
31
|
-
h: boolean
|
|
32
|
-
help: boolean
|
|
33
|
-
|
|
34
|
-
d: boolean
|
|
35
|
-
debug: boolean
|
|
36
|
-
|
|
37
|
-
v: boolean
|
|
38
|
-
version: boolean
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function parseArguments(argv = process.argv): ParsedArguments {
|
|
43
|
-
/* eslint-disable id-length */
|
|
44
|
-
// prettier-ignore
|
|
45
|
-
const {
|
|
46
|
-
_,
|
|
47
|
-
h, help,
|
|
48
|
-
d, debug,
|
|
49
|
-
v, version,
|
|
50
|
-
'--': extraArguments,
|
|
51
|
-
...extOptions
|
|
52
|
-
} = minimist(argv.slice(2), {
|
|
53
|
-
'--': true,
|
|
54
|
-
boolean: ['h', 'help', 'd', 'debug', 'v', 'version'],
|
|
55
|
-
string: ['_']
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
const [groupOrCommand, ...argsWithoutOptions] = _
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
groupOrCommand,
|
|
62
|
-
|
|
63
|
-
argv,
|
|
64
|
-
extOptions,
|
|
65
|
-
argsWithoutOptions,
|
|
66
|
-
extraArguments: extraArguments || [],
|
|
67
|
-
|
|
68
|
-
// prettier-ignore
|
|
69
|
-
coreOptions: {
|
|
70
|
-
h, help,
|
|
71
|
-
d, debug,
|
|
72
|
-
v, version,
|
|
73
|
-
},
|
|
74
|
-
}
|
|
75
|
-
/* eslint-enable id-length */
|
|
76
|
-
}
|
package/src/util/pathExists.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
|
|
3
|
-
export async function pathExists(filePath: string): Promise<boolean> {
|
|
4
|
-
try {
|
|
5
|
-
await fs.access(filePath)
|
|
6
|
-
return true
|
|
7
|
-
} catch (err) {
|
|
8
|
-
if (err.code === 'ENOENT') {
|
|
9
|
-
return false
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
throw err
|
|
13
|
-
}
|
|
14
|
-
}
|
package/src/util/readJson.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
export interface ReaddirItem {
|
|
5
|
-
path: string
|
|
6
|
-
isDir: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function readdirRecursive(dir: string): Promise<ReaddirItem[]> {
|
|
10
|
-
let content: ReaddirItem[] = []
|
|
11
|
-
|
|
12
|
-
const currentPath = path.resolve(dir)
|
|
13
|
-
const dirContent = (await fs.readdir(currentPath)).map((item) => path.join(currentPath, item))
|
|
14
|
-
|
|
15
|
-
for (const subPath of dirContent) {
|
|
16
|
-
const stat = await fs.stat(subPath)
|
|
17
|
-
const isDir = stat.isDirectory()
|
|
18
|
-
content.push({path: subPath, isDir})
|
|
19
|
-
|
|
20
|
-
if (isDir) {
|
|
21
|
-
content = content.concat(await readdirRecursive(subPath))
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return content
|
|
26
|
-
}
|
|
@@ -1,320 +0,0 @@
|
|
|
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 = '', writeToken = ''} = 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
|
-
{pattern: ENV_VAR.WRITE_TOKEN, value: writeToken},
|
|
259
|
-
]
|
|
260
|
-
const useQuotes = templateContent.includes('="')
|
|
261
|
-
|
|
262
|
-
for (const {pattern, value} of vars) {
|
|
263
|
-
envContent = findAndReplaceVariable(envContent, pattern, value, useQuotes)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
await writeFile(join(root, targetName), envContent)
|
|
267
|
-
} catch {
|
|
268
|
-
throw new Error(
|
|
269
|
-
'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.',
|
|
270
|
-
)
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export async function tryApplyPackageName(root: string, name: string): Promise<void> {
|
|
275
|
-
try {
|
|
276
|
-
const packageJson = await readFile(join(root, 'package.json'), 'utf8')
|
|
277
|
-
const pkg: PackageJson = JSON.parse(packageJson)
|
|
278
|
-
pkg.name = name
|
|
279
|
-
|
|
280
|
-
await writeFile(join(root, 'package.json'), JSON.stringify(pkg, null, 2))
|
|
281
|
-
} catch {
|
|
282
|
-
// noop
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export async function generateSanityApiToken(
|
|
287
|
-
label: string,
|
|
288
|
-
type: 'read' | 'write',
|
|
289
|
-
projectId: string,
|
|
290
|
-
apiClient: CliApiClient,
|
|
291
|
-
): Promise<string> {
|
|
292
|
-
const response = await apiClient({requireProject: false, requireUser: true})
|
|
293
|
-
.config({apiVersion: 'v2021-06-07'})
|
|
294
|
-
.request<{key: string}>({
|
|
295
|
-
uri: `/projects/${projectId}/tokens`,
|
|
296
|
-
method: 'POST',
|
|
297
|
-
body: {
|
|
298
|
-
label: `${label} (${Date.now()})`,
|
|
299
|
-
roleName: type === 'read' ? API_READ_TOKEN_ROLE : API_WRITE_TOKEN_ROLE,
|
|
300
|
-
},
|
|
301
|
-
})
|
|
302
|
-
return response.key
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export async function setCorsOrigin(
|
|
306
|
-
origin: string,
|
|
307
|
-
projectId: string,
|
|
308
|
-
apiClient: CliApiClient,
|
|
309
|
-
): Promise<void> {
|
|
310
|
-
try {
|
|
311
|
-
await apiClient({api: {projectId}}).request({
|
|
312
|
-
method: 'POST',
|
|
313
|
-
url: '/cors',
|
|
314
|
-
body: {origin: origin, allowCredentials: true}, // allowCredentials is true to allow for embedded studios if needed
|
|
315
|
-
})
|
|
316
|
-
} catch (error) {
|
|
317
|
-
// Silent fail, it most likely means that the origin is already set
|
|
318
|
-
debug('Failed to set CORS origin', error)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import latestVersion from 'get-latest-version'
|
|
2
|
-
import promiseProps from 'promise-props-recursive'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Resolve the latest versions of given packages within their defined ranges
|
|
6
|
-
*
|
|
7
|
-
* @param pkgs - `{packageName: rangeOrTag}`
|
|
8
|
-
* @returns Object of resolved version numbers
|
|
9
|
-
*/
|
|
10
|
-
export function resolveLatestVersions(
|
|
11
|
-
pkgs: Record<string, string>,
|
|
12
|
-
): Promise<Record<string, string>> {
|
|
13
|
-
const lookups: Record<string, Promise<string> | string> = {}
|
|
14
|
-
for (const [packageName, range] of Object.entries(pkgs)) {
|
|
15
|
-
lookups[packageName] =
|
|
16
|
-
range === 'latest' ? latestVersion(packageName, {range}).then(caretify) : range
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return promiseProps(lookups)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function caretify(version: string | undefined) {
|
|
23
|
-
return version ? `^${version}` : 'latest'
|
|
24
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-sync */
|
|
2
|
-
import fs from 'node:fs'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
|
|
5
|
-
import {debug} from '../debug'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Resolve project root directory, falling back to cwd if it cannot be found
|
|
9
|
-
*/
|
|
10
|
-
export function resolveRootDir(cwd: string): string {
|
|
11
|
-
try {
|
|
12
|
-
return resolveProjectRoot(cwd) || cwd
|
|
13
|
-
} catch (err) {
|
|
14
|
-
throw new Error(`Error occurred trying to resolve project root:\n${err.message}`)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function hasSanityConfig(basePath: string, configName: string): boolean {
|
|
19
|
-
const buildConfigs = [
|
|
20
|
-
fileExists(path.join(basePath, `${configName}.js`)),
|
|
21
|
-
fileExists(path.join(basePath, `${configName}.ts`)),
|
|
22
|
-
isSanityV2StudioRoot(basePath),
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
return buildConfigs.some(Boolean)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function resolveProjectRoot(basePath: string, iterations = 0): string | false {
|
|
29
|
-
const configName = 'sanity.config'
|
|
30
|
-
if (hasSanityConfig(basePath, configName)) {
|
|
31
|
-
return basePath
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const parentDir = path.resolve(basePath, '..')
|
|
35
|
-
if (parentDir === basePath || iterations > 30) {
|
|
36
|
-
// Reached root (or max depth), give up
|
|
37
|
-
return false
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return resolveProjectRoot(parentDir, iterations + 1)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isSanityV2StudioRoot(basePath: string): boolean {
|
|
44
|
-
try {
|
|
45
|
-
const content = fs.readFileSync(path.join(basePath, 'sanity.json'), 'utf8')
|
|
46
|
-
const sanityJson = JSON.parse(content)
|
|
47
|
-
const isRoot = Boolean(sanityJson?.root)
|
|
48
|
-
if (isRoot) {
|
|
49
|
-
debug('Found Sanity v2 studio root at %s', basePath)
|
|
50
|
-
}
|
|
51
|
-
return isRoot
|
|
52
|
-
} catch (err) {
|
|
53
|
-
return false
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function fileExists(filePath: string): boolean {
|
|
58
|
-
return fs.existsSync(filePath)
|
|
59
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import boxen from 'boxen'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
import {telemetryLearnMoreMessage} from '../commands/telemetry/telemetryStatusCommand'
|
|
5
|
-
import {debug} from '../debug'
|
|
6
|
-
import {getUserConfig} from './getUserConfig'
|
|
7
|
-
import {isCi} from './isCi'
|
|
8
|
-
|
|
9
|
-
const TELEMETRY_DISCLOSED_CONFIG_KEY = 'telemetryDisclosed'
|
|
10
|
-
|
|
11
|
-
export function telemetryDisclosure(): void {
|
|
12
|
-
const userConfig = getUserConfig()
|
|
13
|
-
|
|
14
|
-
if (isCi) {
|
|
15
|
-
debug('CI environment detected, skipping telemetry disclosure')
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (userConfig.get(TELEMETRY_DISCLOSED_CONFIG_KEY)) {
|
|
20
|
-
debug('Telemetry disclosure has already been shown')
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Print to stderr to prevent garbling command output
|
|
25
|
-
console.error(
|
|
26
|
-
boxen(
|
|
27
|
-
`The Sanity CLI now collects telemetry data on general usage and errors.
|
|
28
|
-
This helps us improve Sanity and prioritize features.
|
|
29
|
-
|
|
30
|
-
To opt in/out, run ${chalk.cyan('npx sanity telemetry enable/disable')}.
|
|
31
|
-
|
|
32
|
-
${telemetryLearnMoreMessage('unset')}`,
|
|
33
|
-
{
|
|
34
|
-
padding: 1,
|
|
35
|
-
margin: 1,
|
|
36
|
-
borderColor: 'yellow',
|
|
37
|
-
|
|
38
|
-
// Typescript issues forcing these to any
|
|
39
|
-
borderStyle: 'round' as any,
|
|
40
|
-
},
|
|
41
|
-
),
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
userConfig.set(TELEMETRY_DISCLOSED_CONFIG_KEY, Date.now())
|
|
45
|
-
}
|