Skip to content

Commit

Permalink
refactor: align core-app structure and start-up more closely with studio
Browse files Browse the repository at this point in the history
  • Loading branch information
cngonzalez committed Jan 31, 2025
1 parent 56cee8b commit a8cf34e
Show file tree
Hide file tree
Showing 26 changed files with 390 additions and 265 deletions.
1 change: 0 additions & 1 deletion packages/@sanity/cli/.depcheckrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"vite",
"@portabletext/toolkit",
"react",
"react-dom",
"@sanity/ui",
"lodash.get",
"@portabletext/types",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {copy} from '../../util/copy'
import {getAndWriteJourneySchemaWorker} from '../../util/journeyConfig'
import {resolveLatestVersions} from '../../util/resolveLatestVersions'
import {createCliConfig} from './createCliConfig'
import {createCoreAppCliConfig} from './createCoreAppCliConfig'
import {createPackageManifest} from './createPackageManifest'
import {createStudioConfig, type GenerateConfigOptions} from './createStudioConfig'
import {determineStudioTemplate} from './determineStudioTemplate'
Expand Down Expand Up @@ -82,16 +83,16 @@ export async function bootstrapLocalTemplate(
// Resolve latest versions of Sanity-dependencies
spinner = output.spinner('Resolving latest module versions').start()
const dependencyVersions = await resolveLatestVersions({
...studioDependencies.dependencies,
...studioDependencies.devDependencies,
...(isStudioTemplate ? studioDependencies.dependencies : {}),
...(isStudioTemplate ? studioDependencies.devDependencies : {}),
...(template.dependencies || {}),
...(template.devDependencies || {}),
})
spinner.succeed()

// Use the resolved version for the given dependency
const dependencies = Object.keys({
...studioDependencies.dependencies,
...(isStudioTemplate ? studioDependencies.dependencies : {}),
...template.dependencies,
}).reduce(
(deps, dependency) => {
Expand All @@ -102,7 +103,7 @@ export async function bootstrapLocalTemplate(
)

const devDependencies = Object.keys({
...studioDependencies.devDependencies,
...(isStudioTemplate ? studioDependencies.devDependencies : {}),
...template.devDependencies,
}).reduce(
(deps, dependency) => {
Expand All @@ -121,17 +122,19 @@ export async function bootstrapLocalTemplate(
})

// ...and a studio config (`sanity.config.[ts|js]`)
const studioConfig = await createStudioConfig({
const studioConfig = createStudioConfig({
template: template.configTemplate,
variables,
})

// ...and a CLI config (`sanity.cli.[ts|js]`)
const cliConfig = await createCliConfig({
projectId: variables.projectId,
dataset: variables.dataset,
autoUpdates: variables.autoUpdates,
})
const cliConfig = isStudioTemplate
? createCliConfig({
projectId: variables.projectId,
dataset: variables.dataset,
autoUpdates: variables.autoUpdates,
})
: createCoreAppCliConfig({appLocation: template.appLocation!})

// Write non-template files to disc
const codeExt = useTypeScript ? 'ts' : 'js'
Expand Down
52 changes: 5 additions & 47 deletions packages/@sanity/cli/src/actions/init-project/createCliConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import traverse from '@babel/traverse'
import {parse, print} from 'recast'
import * as parser from 'recast/parsers/typescript'
import {processTemplate} from './processTemplate'

const defaultTemplate = `
import {defineCliConfig} from 'sanity/cli'
Expand All @@ -25,49 +23,9 @@ export interface GenerateCliConfigOptions {
}

export function createCliConfig(options: GenerateCliConfigOptions): string {
const variables = options
const template = defaultTemplate.trimStart()
const ast = parse(template, {parser})

traverse(ast, {
StringLiteral: {
enter({node}) {
const value = node.value
if (!value.startsWith('%') || !value.endsWith('%')) {
return
}
const variableName = value.slice(1, -1) as keyof GenerateCliConfigOptions
if (!(variableName in variables)) {
throw new Error(`Template variable '${value}' not defined`)
}
const newValue = variables[variableName]
/*
* although there are valid non-strings in our config,
* they're not in StringLiteral nodes, so assume undefined
*/
node.value = typeof newValue === 'string' ? newValue : ''
},
},
Identifier: {
enter(path) {
if (!path.node.name.startsWith('__BOOL__')) {
return
}
const variableName = path.node.name.replace(
/^__BOOL__(.+?)__$/,
'$1',
) as keyof GenerateCliConfigOptions
if (!(variableName in variables)) {
throw new Error(`Template variable '${variableName}' not defined`)
}
const value = variables[variableName]
if (typeof value !== 'boolean') {
throw new Error(`Expected boolean value for '${variableName}'`)
}
path.replaceWith({type: 'BooleanLiteral', value})
},
},
return processTemplate({
template: defaultTemplate,
variables: options,
includeBooleanTransform: true,
})

return print(ast, {quote: 'single'}).code
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {processTemplate} from './processTemplate'

const defaultCoreAppTemplate = `
import {defineCliConfig} from 'sanity/cli'
export default defineCliConfig({
__experimental_coreAppConfiguration: {
framework: 'vite',
appLocation: '%appLocation%'
},
})
`

export interface GenerateCliConfigOptions {
organizationId?: string
appLocation: string
}

export function createCoreAppCliConfig(options: GenerateCliConfigOptions): string {
return processTemplate({
template: defaultCoreAppTemplate,
variables: options,
})
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import traverse from '@babel/traverse'
import {parse, print} from 'recast'
import * as parser from 'recast/parsers/typescript'
import {processTemplate} from './processTemplate'

const defaultTemplate = `
import {defineConfig} from 'sanity'
Expand Down Expand Up @@ -47,29 +45,8 @@ export function createStudioConfig(options: GenerateConfigOptions): string {
return options.template(variables).trimStart()
}

const template = (options.template || defaultTemplate).trimStart()
const ast = parse(template, {parser})
traverse(ast, {
StringLiteral: {
enter({node}) {
const value = node.value
if (!value.startsWith('%') || !value.endsWith('%')) {
return
}

const variableName = value.slice(1, -1) as keyof GenerateConfigOptions['variables']
if (!(variableName in variables)) {
throw new Error(`Template variable '${value}' not defined`)
}
const newValue = variables[variableName]
/*
* although there are valid non-strings in our config,
* they're not in this template, so assume undefined
*/
node.value = typeof newValue === 'string' ? newValue : ''
},
},
return processTemplate({
template: options.template || defaultTemplate,
variables,
})

return print(ast, {quote: 'single'}).code
}
23 changes: 21 additions & 2 deletions packages/@sanity/cli/src/actions/init-project/initProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {createProject} from '../project/createProject'
import {bootstrapLocalTemplate} from './bootstrapLocalTemplate'
import {bootstrapRemoteTemplate} from './bootstrapRemoteTemplate'
import {type GenerateConfigOptions} from './createStudioConfig'
import {determineStudioTemplate} from './determineStudioTemplate'
import {absolutify, validateEmptyPath} from './fsUtils'
import {tryGitInit} from './git'
import {promptForDatasetName} from './promptForDatasetName'
Expand Down Expand Up @@ -97,6 +98,7 @@ export interface ProjectTemplate {
importPrompt?: string
configTemplate?: string | ((variables: GenerateConfigOptions['variables']) => string)
typescriptOnly?: boolean
appLocation?: string
}

export interface ProjectOrganization {
Expand Down Expand Up @@ -271,6 +273,9 @@ export default async function initSanity(
print('')

const flags = await prepareFlags()
// skip project / dataset prompting
const isStudioTemplate = cliFlags.template && determineStudioTemplate(cliFlags.template)

// We're authenticated, now lets select or create a project
const {projectId, displayName, isFirstProject, datasetName, schemaUrl} = await getProjectDetails()

Expand Down Expand Up @@ -655,11 +660,15 @@ export default async function initSanity(
const isCurrentDir = outputPath === process.cwd()
if (isCurrentDir) {
print(`\n${chalk.green('Success!')} Now, use this command to continue:\n`)
print(`${chalk.cyan(devCommand)} - to run Sanity Studio\n`)
print(
`${chalk.cyan(devCommand)} - to run ${isStudioTemplate ? 'Sanity Studio' : 'your Sanity application'}\n`,
)
} else {
print(`\n${chalk.green('Success!')} Now, use these commands to continue:\n`)
print(`First: ${chalk.cyan(`cd ${outputPath}`)} - to enter project’s directory`)
print(`Then: ${chalk.cyan(devCommand)} - to run Sanity Studio\n`)
print(
`Then: ${chalk.cyan(devCommand)} -to run ${isStudioTemplate ? 'Sanity Studio' : 'your Sanity application'}\n`,
)
}

print(`Other helpful commands`)
Expand Down Expand Up @@ -720,6 +729,16 @@ export default async function initSanity(
return data
}

// currently coreApps don't need this information. Return dummy data for now
if (!isStudioTemplate) {
return {
projectId: '',
displayName: '',
isFirstProject: false,
datasetName: '',
}
}

debug('Prompting user to select or create a project')
const project = await getOrCreateProject()
debug(`Project with name ${project.displayName} selected`)
Expand Down
55 changes: 55 additions & 0 deletions packages/@sanity/cli/src/actions/init-project/processTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import traverse from '@babel/traverse'
import {parse, print} from 'recast'
import * as parser from 'recast/parsers/typescript'

interface TemplateOptions<T> {
template: string
variables: T
includeBooleanTransform?: boolean
}

export function processTemplate<T extends object>(options: TemplateOptions<T>): string {
const {template, variables, includeBooleanTransform = false} = options
const ast = parse(template.trimStart(), {parser})

traverse(ast, {
StringLiteral: {
enter({node}) {
const value = node.value
if (!value.startsWith('%') || !value.endsWith('%')) {
return
}
const variableName = value.slice(1, -1) as keyof T
if (!(variableName in variables)) {
throw new Error(`Template variable '${value}' not defined`)
}
const newValue = variables[variableName]
/*
* although there are valid non-strings in our config,
* they're not in StringLiteral nodes, so assume undefined
*/
node.value = typeof newValue === 'string' ? newValue : ''
},
},
...(includeBooleanTransform && {
Identifier: {
enter(path) {
if (!path.node.name.startsWith('__BOOL__')) {
return
}
const variableName = path.node.name.replace(/^__BOOL__(.+?)__$/, '$1') as keyof T
if (!(variableName in variables)) {
throw new Error(`Template variable '${variableName.toString()}' not defined`)
}
const value = variables[variableName]
if (typeof value !== 'boolean') {
throw new Error(`Expected boolean value for '${variableName.toString()}'`)
}
path.replaceWith({type: 'BooleanLiteral', value})
},
},
}),
})

return print(ast, {quote: 'single'}).code
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,24 @@ const coreAppTemplate: ProjectTemplate = {
dependencies: {
'@sanity/sdk': '^0.0.0-alpha',
'@sanity/sdk-react': '^0.0.0-alpha',
'react': '^19',
'react-dom': '^19',
},
devDependencies: {
'vite': '^6',
/*
* this will be changed to eslint-config sanity,
* eslint.config generation will be a fast follow
*/
'@sanity/eslint-config-studio': '^5.0.1',
'sanity': '^3',
'@vitejs/plugin-react': '^4.3.4',
'@types/react': '^18.0.25',
'eslint': '^9.9.0',
'prettier': '^3.0.2',
'typescript': '^5.1.6',
'vite': '^6',
},
appLocation: './src/App.tsx',
}

export default coreAppTemplate
10 changes: 9 additions & 1 deletion packages/@sanity/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,15 @@ export interface CliConfig {

studioHost?: string

isStudioApp?: boolean
/**
* Parameter used to configure other kinds of applications.
* Signals to `sanity` commands that this is not a studio.
* @internal
*/
__experimental_coreAppConfiguration?: {
framework: 'vite' // add others as we make them available, e.g., 'vite' | 'vue'
appLocation?: string
}
}

export type UserViteConfig =
Expand Down
17 changes: 10 additions & 7 deletions packages/@sanity/cli/templates/core-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import {SanityApp} from '@sanity/sdk-react/components'
import {createSanityInstance} from '@sanity/sdk'
import {SanityProvider} from '@sanity/sdk-react/context'

export function App() {

const sanityConfig = {
auth: {
authScope: 'global'
}
/*
* CORE apps can access several different projects!
* Apps can access several different projects!
* Add the below configuration if you want to connect to a specific project.
*/
// projectId: 'my-project-id',
// dataset: 'my-dataset',
auth: {
authScope: 'org'
}
}

const sanityInstance = createSanityInstance(sanityConfig)
return (
<SanityApp sanityConfig={sanityConfig}>
<SanityProvider sanityInstance={sanityInstance}>
Hello world!
</SanityApp>
</SanityProvider>
)
}

Expand Down
Loading

0 comments on commit a8cf34e

Please sign in to comment.