diff --git a/.gitignore b/.gitignore index baeb722..5adf402 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +studio coverage dist .vscode diff --git a/.prettierrc b/.prettierrc index ab68045..2389eff 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "printWidth": 120, + "printWidth": 80, "tabWidth": 2, "useTabs": false, "semi": false, diff --git a/package.json b/package.json index ff303ee..24b57c0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,10 @@ ], "scripts": { "build": "unbuild", + "clean": "rm -rf .nuxt dist node_modules", "dev:prepare": "unbuild --stub", + "preinstall": "pnpm clean", + "postinstall": "pnpm dev:prepare", "lint": "prettier -c src", "lint:fix": "automd && prettier -w src", "prepack": "pnpm build", diff --git a/src/init.ts b/src/commands/init.ts similarity index 64% rename from src/init.ts rename to src/commands/init.ts index 1232d11..329f91a 100644 --- a/src/init.ts +++ b/src/commands/init.ts @@ -7,7 +7,12 @@ import { defineCommand } from 'citty' import { execa } from 'execa' import { titleCase } from 'scule' import { writeFile, symlink } from 'node:fs/promises' -import { createEnvContent, extractProjectId } from './utils' +import { + extractSanityCliToken, + extractSanityProjectId, + createEnvContent, + createSanityCli, +} from '../utils' const TEMPLATE_NAME = 'github:selfawarestudio/greta#template/nuxt-sanity' const PACKAGE_MANAGER = 'pnpm' @@ -26,21 +31,36 @@ export default defineCommand({ }, }, async run(ctx) { + const cwd = resolve('.') + + let dir + let sanityCliToken + let sanityProjectId + let sanityProjectToken + let template: DownloadTemplateResult + consola.start('Checking Sanity CLI login status...') try { - await execa('pnpm', ['dlx', '@sanity/cli', 'projects', 'list'], { stdout: 'pipe' }) + const { stdout } = await execa( + 'pnpm', + ['dlx', '@sanity/cli', 'debug', '--secrets'], + { stdout: 'pipe' }, + ) + + sanityCliToken = extractSanityCliToken(stdout) + + if (!sanityCliToken) { + throw new Error( + 'You must be logged into Sanity CLI. Please run `pnpm dlx @sanity/cli login` and then try again.', + ) + } } catch (err) { - consola.error('You must be logged into Sanity CLI. Please run `pnpm dlx @sanity/cli login` and then try again.') + consola.error((err as Error).toString()) process.exit(1) } - // Now prepare to download our prpject template - const cwd = resolve('.') - - let dir - let sanityProjectId - let template: DownloadTemplateResult + // Now prepare to download our project template if (ctx.args.dir) { dir = ctx.args.dir @@ -63,11 +83,14 @@ export default defineCommand({ } // Next create a new Sanity project and set the project ID and other env vars - const sanityProjectTitle = await consola.prompt('Enter a title for the Sanity project', { - type: 'text', - placeholder: titleCase(dir), - default: titleCase(dir), - }) + const sanityProjectTitle = await consola.prompt( + 'Enter a title for the Sanity project', + { + type: 'text', + placeholder: titleCase(dir), + default: titleCase(dir), + }, + ) consola.start('Initializing a new Sanity project...') try { @@ -87,13 +110,41 @@ export default defineCommand({ { stdout: 'pipe' }, ) - sanityProjectId = extractProjectId(stdout) + sanityProjectId = extractSanityProjectId(stdout) if (!sanityProjectId) { throw new Error('Failed to extract project ID from Sanity CLI output') } - consola.success('Successfully created a new Sanity project. The Project ID is', sanityProjectId) + consola.success( + 'Successfully created a new Sanity project. The Project ID is', + sanityProjectId, + ) + } catch (err) { + consola.error((err as Error).toString()) + process.exit(1) + } + + // Now we can use the Sanity API to create a token for the project that will be used for website previews + consola.start('Setting up Sanity CORS and creating a preview token...') + + const sanityCli = createSanityCli({ + projectId: sanityProjectId, + token: sanityCliToken, + }) + + try { + await sanityCli('/cors', { + origin: 'http://localhost:3000', + allowCredentials: false, + }) + + const { key } = await sanityCli('/tokens', { + label: 'Website preview', + roleName: 'editor', + }) + + sanityProjectToken = key } catch (err) { consola.error((err as Error).toString()) process.exit(1) @@ -103,12 +154,16 @@ export default defineCommand({ // Create a file called .env and write the project ID to it as SANITY_STUDIO_PROJECT_ID then write the file to template.dir const envFile = resolve(template.dir, '.env') const studioEnvFile = resolve(template.dir, 'studio', '.env') - const envContent = createEnvContent(sanityProjectTitle, sanityProjectId) + const envContent = createEnvContent( + sanityProjectTitle, + sanityProjectId, + sanityProjectToken, + ) try { await writeFile(envFile, envContent) await symlink(envFile, studioEnvFile) - consola.success('.env file created and symbollically linked to studio folder.') + consola.success('.env file created and symlinked to studio folder.') } catch (err) { consola.error((err as Error).toString()) process.exit(1) diff --git a/src/index.ts b/src/index.ts index e99e015..7157f27 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ const main = defineCommand({ description: gretaPkg.description, }, subCommands: { - init: () => import('./init').then(_rDefault), + init: () => import('./commands/init').then(_rDefault), }, }) diff --git a/src/utils/create-env-content.ts b/src/utils/create-env-content.ts index cb1e85c..e4ef4a9 100644 --- a/src/utils/create-env-content.ts +++ b/src/utils/create-env-content.ts @@ -1,11 +1,15 @@ -export function createEnvContent(sanityProjectTitle: string, sanityProjectId: string) { +export function createEnvContent( + sanityProjectTitle: string, + sanityProjectId: string, + sanityProjectToken: string, +) { const currentDate = new Date().toISOString().slice(0, 10) return ( `SANITY_STUDIO_PROJECT_TITLE="${sanityProjectTitle}"\n` + `SANITY_STUDIO_PROJECT_ID=${sanityProjectId}\n` + `SANITY_STUDIO_DATASET=production\n` + `SANITY_STUDIO_API_VERSION=${currentDate}\n` + - `SANITY_STUDIO_API_TOKEN=\n` + + `SANITY_STUDIO_API_TOKEN=${sanityProjectToken}\n` + `SANITY_STUDIO_SLUG_INPUT_URL_PREFIX=\n` + `SANITY_STUDIO_PROD_PREVIEW_URL=\n` ) diff --git a/src/utils/extract-sanity-cli-token.ts b/src/utils/extract-sanity-cli-token.ts new file mode 100644 index 0000000..f7e5e8f --- /dev/null +++ b/src/utils/extract-sanity-cli-token.ts @@ -0,0 +1,10 @@ +export function extractSanityCliToken(output: string): string | null { + // This regular expression looks for the phrase "Auth token: " followed by any characters + // that are not whitespace, capturing this sequence into a group. + const regex = /Auth token:\s+(\S+)/ + const match = output.match(regex) + + // If a match is found, return the first captured group, which contains the token, split on single quotes and get the token itself. + // Otherwise, return null if no match is found. + return match?.[1]?.split(`'`)[1] ?? null +} diff --git a/src/utils/extract-project-id.ts b/src/utils/extract-sanity-project-id.ts similarity index 84% rename from src/utils/extract-project-id.ts rename to src/utils/extract-sanity-project-id.ts index 589006c..b1a4914 100644 --- a/src/utils/extract-project-id.ts +++ b/src/utils/extract-sanity-project-id.ts @@ -1,4 +1,4 @@ -export function extractProjectId(output: string): string | null { +export function extractSanityProjectId(output: string): string | null { // This regular expression looks for the phrase "Project ID: " followed by any characters // that are not whitespace, capturing this sequence into a group. const regex = /Project ID:\s+(\S+)/ diff --git a/src/utils/index.ts b/src/utils/index.ts index d6ca58d..0d564d0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,4 @@ -export { extractProjectId } from './extract-project-id' +export { createSanityCli } from './sanity-cli' +export { extractSanityProjectId } from './extract-sanity-project-id' +export { extractSanityCliToken } from './extract-sanity-cli-token' export { createEnvContent } from './create-env-content' diff --git a/src/utils/sanity-cli.ts b/src/utils/sanity-cli.ts new file mode 100644 index 0000000..7a5b72c --- /dev/null +++ b/src/utils/sanity-cli.ts @@ -0,0 +1,27 @@ +export function createSanityCli({ + projectId, + token, +}: { + projectId: string + token: string +}) { + return async (endpoint: string, body: Record) => { + const response = await fetch( + `https://api.sanity.io/v2021-06-07/projects/${projectId}${endpoint}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(body), + }, + ) + + if (!response.ok) { + throw new Error(`The Sanity CLI response was not ok.`) + } + + return await response.json() + } +}