cli improvements and typescript config changes

This commit is contained in:
2025-08-04 01:50:51 +02:00
parent 15169f7555
commit 12e516eb06
33 changed files with 4102 additions and 188 deletions

View File

@@ -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('<input>.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);
}