sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
177 lines (152 loc) • 5.22 kB
text/typescript
/* eslint-disable max-statements */
import path from 'node:path'
import zlib from 'node:zlib'
import {type CliCommandArguments, type CliCommandContext} from '@sanity/cli'
import tar from 'tar-fs'
import {shouldAutoUpdate} from '../../util/shouldAutoUpdate'
import buildSanityStudio, {type BuildSanityStudioCommandFlags} from '../build/buildAction'
import storeManifestSchemas from '../schema/deploySchemasAction'
import {SCHEMA_STORE_FEATURE_ENABLED} from '../schema/schemaStoreConstants'
import {createManifestExtractor} from '../schema/utils/mainfestExtractor'
import {
checkDir,
createDeployment,
debug,
dirIsEmptyOrNonExistent,
getInstalledSanityVersion,
getOrCreateStudio,
getOrCreateUserApplicationFromConfig,
type UserApplication,
} from './helpers'
export interface DeployStudioActionFlags extends BuildSanityStudioCommandFlags {
'build'?: boolean
'schema-required'?: boolean
'verbose'?: boolean
}
// eslint-disable-next-line complexity
export default async function deployStudioAction(
args: CliCommandArguments<DeployStudioActionFlags>,
context: CliCommandContext,
): Promise<void> {
const {apiClient, workDir, chalk, output, prompt, cliConfig} = context
const flags = {build: true, ...args.extOptions}
const customSourceDir = args.argsWithoutOptions[0]
const sourceDir = path.resolve(process.cwd(), customSourceDir || path.join(workDir, 'dist'))
const isAutoUpdating = shouldAutoUpdate({flags, cliConfig})
const installedSanityVersion = await getInstalledSanityVersion()
const configStudioHost = cliConfig && 'studioHost' in cliConfig && cliConfig.studioHost
const client = apiClient({
requireUser: true,
requireProject: true,
}).withConfig({apiVersion: 'v2024-08-01'})
if (customSourceDir === 'graphql') {
throw new Error('Did you mean `sanity graphql deploy`?')
}
if (customSourceDir) {
let relativeOutput = path.relative(process.cwd(), sourceDir)
if (relativeOutput[0] !== '.') {
relativeOutput = `./${relativeOutput}`
}
const isEmpty = await dirIsEmptyOrNonExistent(sourceDir)
const shouldProceed =
isEmpty ||
(await prompt.single({
type: 'confirm',
message: `"${relativeOutput}" is not empty, do you want to proceed?`,
default: false,
}))
if (!shouldProceed) {
output.print('Cancelled.')
return
}
output.print(`Building to ${relativeOutput}\n`)
}
// Check that the project has a studio hostname
let spinner = output.spinner('Checking project info').start()
let userApplication: UserApplication
try {
// If the user has provided a studioHost in the config, use that
if (configStudioHost) {
userApplication = await getOrCreateUserApplicationFromConfig({
client,
context,
spinner,
appHost: configStudioHost,
})
} else {
userApplication = await getOrCreateStudio({client, context, spinner})
}
} catch (err) {
if (err.message) {
output.error(chalk.red(err.message))
return
}
debug('Error creating user application', err)
throw err
}
// Always build the project, unless --no-build is passed
const shouldBuild = flags.build
if (shouldBuild) {
const buildArgs = {
...args,
extOptions: flags,
argsWithoutOptions: [customSourceDir].filter(Boolean),
}
const {didCompile} = await buildSanityStudio(buildArgs, context, {basePath: '/'})
if (!didCompile) {
return
}
}
if (SCHEMA_STORE_FEATURE_ENABLED) {
await storeManifestSchemas(
{
'extract-manifest': shouldBuild,
'manifest-dir': `${sourceDir}/static`,
'schema-required': flags['schema-required'],
'verbose': flags.verbose,
},
context,
)
} else if (shouldBuild) {
await createManifestExtractor({
...context,
safe: true,
})(`${sourceDir}/static`)
}
// Ensure that the directory exists, is a directory and seems to have valid content
spinner = output.spinner('Verifying local content').start()
try {
await checkDir(sourceDir)
spinner.succeed()
} catch (err) {
spinner.fail()
debug('Error checking directory', err)
throw err
}
// Now create a tarball of the given directory
const parentDir = path.dirname(sourceDir)
const base = path.basename(sourceDir)
const tarball = tar.pack(parentDir, {entries: [base]}).pipe(zlib.createGzip())
spinner = output.spinner('Deploying to Sanity.Studio').start()
try {
const {location} = await createDeployment({
client,
applicationId: userApplication.id,
version: installedSanityVersion,
isAutoUpdating,
tarball,
})
spinner.succeed()
// And let the user know we're done
output.print(`\nSuccess! Studio deployed to ${chalk.cyan(location)}`)
if (!configStudioHost) {
output.print(`\nAdd ${chalk.cyan(`studioHost: '${userApplication.appHost}'`)}`)
output.print(`to defineCliConfig root properties in sanity.cli.js or sanity.cli.ts`)
output.print(`to avoid prompting for hostname on next deploy.`)
}
} catch (err) {
spinner.fail()
debug('Error deploying studio', err)
throw err
}
}