From 12e516eb06bd406c2fdf0f3da8071b2b3fc1fa47 Mon Sep 17 00:00:00 2001 From: vaporvee Date: Mon, 4 Aug 2025 01:50:51 +0200 Subject: [PATCH] cli improvements and typescript config changes --- .gitignore | 12 +- index.js | 84 +- package.json | 2 +- .../apps/client/{.gitignore => gitignore} | 0 .../client/src/lib/asset-to-url-client.ts | 51 - .../components/blocks/sanity-button.svelte | 2 +- .../lib/components/blocks/sanity-image.svelte | 2 +- .../src/lib/components/section/cta.svelte | 5 +- .../src/lib/{ => helper}/asset-to-url.ts | 8 +- .../client/src/lib/{ => helper}/image-url.ts | 29 +- .../lib/{image-helper.ts => helper/image.ts} | 0 .../lib/{link-helper.ts => helper/link.ts} | 4 +- .../apps/client/src/routes/+layout.server.ts | 2 +- template/apps/client/src/routes/+page.svelte | 2 +- template/apps/client/tsconfig.json | 18 +- template/apps/client/wrangler.jsonc | 2 +- .../apps/studio/{.gitignore => gitignore} | 0 template/apps/studio/package.json | 9 +- template/apps/studio/tsconfig.json | 14 +- template/bun.lock | 3918 +++++++++++++++++ template/{.gitignore => gitignore} | 0 template/nx.json | 28 +- template/package.json | 2 +- .../packages/sanity-connection/package.json | 3 +- .../packages/sanity-connection/tsconfig.json | 26 +- template/packages/typescript-config/base.json | 1 + .../typescript-config/react-library.json | 6 + .../typescript-config/react-package.json | 12 + .../packages/typescript-config/sanity.json | 8 + .../packages/typescript-config/sveltekit.json | 13 + template/packages/ui/package.json | 12 +- template/packages/ui/tsconfig.json | 5 +- template/tsconfig.base.json | 10 + 33 files changed, 4102 insertions(+), 188 deletions(-) rename template/apps/client/{.gitignore => gitignore} (100%) delete mode 100644 template/apps/client/src/lib/asset-to-url-client.ts rename template/apps/client/src/lib/{ => helper}/asset-to-url.ts (81%) rename template/apps/client/src/lib/{ => helper}/image-url.ts (63%) rename template/apps/client/src/lib/{image-helper.ts => helper/image.ts} (100%) rename template/apps/client/src/lib/{link-helper.ts => helper/link.ts} (94%) rename template/apps/studio/{.gitignore => gitignore} (100%) create mode 100644 template/bun.lock rename template/{.gitignore => gitignore} (100%) create mode 100644 template/packages/typescript-config/react-library.json create mode 100644 template/packages/typescript-config/react-package.json create mode 100644 template/packages/typescript-config/sanity.json create mode 100644 template/packages/typescript-config/sveltekit.json create mode 100644 template/tsconfig.base.json diff --git a/.gitignore b/.gitignore index 3f4c2a4..90ce5f4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,11 @@ yarn-error.log* *.pem # NX -.nx/cache -.nx/workspace-data -.cursor/rules/nx-rules.mdc -.github/instructions/nx.instructions.md \ No newline at end of file +.nx + +# SvelteKit +.svelte-kit +.wrangler + +# Sanity +.sanity \ No newline at end of file diff --git a/index.js b/index.js index 7927def..10e0920 100755 --- a/index.js +++ b/index.js @@ -72,6 +72,24 @@ async function main() { ], initialValue: 'bun', }), + lowerCaseName: ({ results }) => { + const defaultLowercase = String(results.dirName || results.projectName) + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + return p.text({ + message: `${color.cyan('📓 Enter a lowercase name for your project (e.g., used as')} ${color.yellow('.sanity.studio')}${color.cyan('):')}`, + placeholder: `${defaultLowercase}`, + initialValue: defaultLowercase, + validate: (v) => { + if (v.length < 2) return `${color.red('❌ Too short!')}`; + if (!/^[a-z0-9-]+$/.test(v)) return `${color.red('❌ Only lowercase letters, numbers, and hyphens allowed!')}`; + if (v.startsWith('-') || v.endsWith('-')) return `${color.red('❌ Cannot start or end with hyphen!')}`; + if (v.includes('.sanity.studio')) return `${color.red('❌ Do not include .sanity.studio - it will be added automatically!')}`; + }, + }); + }, faviconPath: () => p.text({ message: `${color.cyan('🖼️ Path to favicon SVG (leave empty to skip):')}`, @@ -107,8 +125,41 @@ async function main() { task: async () => { const __dirname = path.dirname(new URL(import.meta.url).pathname); const templateDir = path.resolve(__dirname, 'template'); + + // Ensure root directory exists with proper permissions await fs.ensureDir(rootDir); - await fs.copy(templateDir, rootDir, { overwrite: true }); + + try { + await fs.copy(templateDir, rootDir, { + overwrite: true, + errorOnExist: false, + preserveTimestamps: false + }); + } catch (error) { + throw new Error(`Failed to copy template files: ${error.message}`); + } + + // Rename gitignore files to .gitignore (dotfiles get lost in npm packages) + async function renameGitignoreFiles(dir) { + try { + const items = await fs.readdir(dir); + for (const item of items) { + const itemPath = path.join(dir, item); + const stat = await fs.stat(itemPath); + + if (stat.isDirectory()) { + await renameGitignoreFiles(itemPath); + } else if (item === 'gitignore') { + const gitignorePath = path.join(dir, '.gitignore'); + await fs.move(itemPath, gitignorePath); + } + } + } catch (error) { + throw new Error(`Failed to rename gitignore files: ${error.message}`); + } + } + + await renameGitignoreFiles(rootDir); return 'Template copied!'; }, }, @@ -205,13 +256,30 @@ async function main() { const wranglerPath = path.join(rootDir, "apps/client/wrangler.jsonc"); if (await fs.pathExists(wranglerPath)) { let wranglerFile = await fs.readFile(wranglerPath, "utf8"); - wranglerFile = wranglerFile.replace(/"name": "[^"]+"/, `"name": "${kebabName}"`); + wranglerFile = wranglerFile.replace(/"name": "[^"]+"/, `"name": "${project.lowerCaseName}"`); await fs.writeFile(wranglerPath, wranglerFile); } return `Sanity project created with ID: ${sanityJson.projectId}`; }, }, + { + title: `${color.blue('🗄️ Setting up production dataset for Sanity CMS')}`, + task: async () => { + if (!(await fs.pathExists(studioDir))) throw new Error(`Studio directory not found at ${studioDir}`); + + try { + await runCommand(pmx, ['sanity', 'dataset', 'create', 'production'], studioDir); + return 'Production dataset created successfully'; + } catch (error) { + // Check if dataset already exists + if (error.message && error.message.includes('already exists')) { + return 'Production dataset already exists'; + } + throw new Error(`Failed to create production dataset: ${error.message}`); + } + }, + }, { title: `${color.green('🔐 Creating Sanity viewer token')}`, task: async () => { @@ -231,8 +299,8 @@ async function main() { let content = await fs.readFile(connPath, 'utf8'); content = content .replace(/publicViewerToken: "[^"]+"/, `publicViewerToken: "${token.key}"`) - .replace(/studioHost: "[^"]+"/, `studioHost: "${kebabName}"`) - .replace(/studioUrl: "[^"]+"/, `studioUrl: "https://${kebabName}.sanity.studio"`); + .replace(/studioHost: "[^"]+"/, `studioHost: "${project.lowerCaseName}"`) + .replace(/studioUrl: "[^"]+"/, `studioUrl: "https://${project.lowerCaseName}.sanity.studio"`); await fs.writeFile(connPath, content); return 'Viewer token created and configured!'; }, @@ -257,7 +325,7 @@ async function main() { 'http://localhost:5173', 'https://*.api.sanity.io', 'wss://*.api.sanity.io', - `https://${kebabName}.sanity.studio`, + `https://${project.lowerCaseName}.sanity.studio`, ]; if (project.extraDomain && project.extraDomain.trim()) { corsOrigins.push(`https://${project.extraDomain.trim()}`); @@ -267,7 +335,7 @@ async function main() { title: `${color.cyan('🌐 Adding Sanity CORS origin:')} ${color.yellow(origin)}`, task: async () => { const args = ['sanity', 'cors', 'add', origin, '--yes']; - if (origin === `https://${kebabName}.sanity.studio`) args.push('--credentials'); + if (origin === `https://${project.lowerCaseName}.sanity.studio`) args.push('--credentials'); await runCommand(pmx, args, studioDir); return `CORS added: ${origin}` + (args.includes('--credentials') ? ' (credentials allowed)' : ''); }, @@ -290,7 +358,7 @@ async function main() { ]); const org = typeof project.gitOrg === 'string' ? project.gitOrg.trim() : ''; if (org) { - const githubUrl = `https://github.com/new?name=${kebabName}&owner=${org}`; + const githubUrl = `https://github.com/new?name=${kebabName}&owner=${org}&visibility=private&description=${encodeURIComponent('This website was built using the official Lumify starter template for building with Sveltekit, Sanity, Bun, and Shadcn UI — bundled into a NX monorepo.')}`; p.note( `Please create a new GitHub repository named\n\`${kebabName}\` under \`${org}\` at:\n\n${color.cyan(githubUrl)}\n\nThe browser will open for you.`, 'GitHub Setup Required' @@ -332,7 +400,7 @@ async function main() { ` • App: ${color.cyan('http://localhost:5173')} ${color.yellow('🌐')}\n` + ` • Studio: ${color.cyan('http://localhost:3333')} ${color.yellow('🛠️')}\n\n` + color.dim('After deploying:\n') + - ` • Studio: ${color.cyan(`https://${kebabName}.sanity.studio`)} ${color.yellow('✨')}` + ` • Studio: ${color.cyan(`https://${project.lowerCaseName}.sanity.studio`)} ${color.yellow('✨')}` ); process.exit(0); } diff --git a/package.json b/package.json index b46fa93..7acc09a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lumify-systems/template-sanity", - "version": "2.0.0", + "version": "2.1.0", "publishConfig": { "access": "restricted", "registry": "https://npm.pkg.github.com" diff --git a/template/apps/client/.gitignore b/template/apps/client/gitignore similarity index 100% rename from template/apps/client/.gitignore rename to template/apps/client/gitignore diff --git a/template/apps/client/src/lib/asset-to-url-client.ts b/template/apps/client/src/lib/asset-to-url-client.ts deleted file mode 100644 index b7679ee..0000000 --- a/template/apps/client/src/lib/asset-to-url-client.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { getImageDimensions, type SanityImageDimensions } from '@sanity/asset-utils'; -import type { ImageWithAlt } from './sanity.types'; -import { generateImageUrl } from './image-url'; -import { client } from './sanity'; - -export type SimpleImage = { - url: string; - alt: string; - dimensions: SanityImageDimensions; -}; - -// Internal helper to fetch image data from Sanity (client-side) -async function fetchImage(assetRef: string | undefined): Promise { - if (!assetRef) return null; - - const image = await client.fetch(`*[_id == $id][0]`, { id: assetRef }); - return image; -} - -// Internal helper to get dimensions safely -function getDimensions(image: ImageWithAlt | any): SanityImageDimensions { - try { - if (image._type === 'imageWithAlt') { - const compatibleImage = { ...image, asset: image.asset }; - return getImageDimensions(compatibleImage as any); - } - return getImageDimensions(image); - } catch { - return { width: 1200, height: 800, aspectRatio: 1.5 }; - } -} - -// Convert a single asset reference to SimpleImage (client-side) -export async function getImageClient(assetRef: string | undefined): Promise { - const image = await fetchImage(assetRef); - if (!image) - return { url: '', alt: '', dimensions: { width: 0, height: 0, aspectRatio: 1 } }; - - const dimensions = getDimensions(image); - - return { - url: generateImageUrl(image), - alt: image.alt || '', - dimensions - }; -} - -// Convert multiple asset references to SimpleImage array (client-side) -export async function getImagesClient(assetRefs: (string | undefined)[]): Promise { - return Promise.all(assetRefs.map((ref) => getImageClient(ref))); -} diff --git a/template/apps/client/src/lib/components/blocks/sanity-button.svelte b/template/apps/client/src/lib/components/blocks/sanity-button.svelte index e37d5a6..1025fd9 100644 --- a/template/apps/client/src/lib/components/blocks/sanity-button.svelte +++ b/template/apps/client/src/lib/components/blocks/sanity-button.svelte @@ -1,5 +1,5 @@