diff --git a/.gitignore b/.gitignore index 3a21961..980c67e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,10 @@ !.gitignore !.eleventy.js -/node_modules -/_site - +_site/ +node_modules/ +packages/cli/dist/ +packages/css/dist/ # Logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* \ No newline at end of file +npm-debug.log* \ No newline at end of file diff --git a/docs/src/assets/styles.css b/docs/src/assets/styles.css index 22e1555..41e3d1f 100644 --- a/docs/src/assets/styles.css +++ b/docs/src/assets/styles.css @@ -364,6 +364,9 @@ .block { display: block; } + .contents { + display: contents; + } .flex { display: flex; } diff --git a/package.json b/package.json index 971fdf7..d4e8e17 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { - "name": "basecoat-ui", - "version": "0.1.0", + "name": "basecoat", + "version": "0.0.1", "description": "shadcn/ui for the rest of us", "author": "hunvreus", "type": "module", + "private": true, "workspaces": [ "packages/*" ], @@ -42,10 +43,11 @@ }, "homepage": "https://github.com/hunvreus/basecoat#readme", "scripts": { - "css:dev": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch", - "eleventy:serve": "eleventy --serve", - "dev": "concurrently \"npm:css:dev\" \"npm:eleventy:serve\"", - "build": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && eleventy" + "build": "node ./scripts/build.js", + "docs:build": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && eleventy", + "docs:dev:css": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch", + "docs:dev:eleventy": "eleventy --serve", + "docs:dev": "concurrently \"npm:docs:dev:css\" \"npm:docs:dev:eleventy\"" }, "devDependencies": { "@11ty/eleventy": "^3.0.0", diff --git a/packages/cli/index.js b/packages/cli/index.js index 271c143..e33ff0a 100755 --- a/packages/cli/index.js +++ b/packages/cli/index.js @@ -1,24 +1,23 @@ #!/usr/bin/env node -console.log('CLI starting...'); - import { Command } from 'commander'; import inquirer from 'inquirer'; import fs from 'fs-extra'; import path from 'path'; - -console.log('Imports loaded'); +import { fileURLToPath } from 'url'; const program = new Command(); +// Attempt to read version from the CLI's own package.json let packageVersion = 'unknown'; try { - const cliScriptDir = path.dirname(new URL(import.meta.url).pathname); - const packageJsonPath = path.resolve(cliScriptDir, 'package.json'); + const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url)); // .../packages/cli/dist + const cliPackageRootDir = path.resolve(cliScriptRunningDir, '..'); // .../packages/cli + const packageJsonPath = path.join(cliPackageRootDir, 'package.json'); const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); packageVersion = JSON.parse(packageJsonContent).version; } catch (error) { - console.warn('Could not read package version:', error.message); + console.warn('Could not read CLI package version:', error.message); } const DEFAULT_CONFIG = { @@ -26,28 +25,28 @@ const DEFAULT_CONFIG = { templateDest: './components/basecoat', scriptDest: './static/js/basecoat' }; -let config = { ...DEFAULT_CONFIG }; +let config = { ...DEFAULT_CONFIG }; // User-specific config, potentially updated by prompts +// Discovers available components from the CLI's bundled assets. async function getAvailableComponents() { try { - const packageRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..'); - console.log('Package root:', packageRoot); - const jsSourceDir = path.join(packageRoot, 'src', 'js'); - console.log('JS source dir:', jsSourceDir); + const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url)); + const assetsDir = path.resolve(cliScriptRunningDir, 'assets'); + const jsSourceDir = path.join(assetsDir, 'js'); + const jsFiles = await fs.readdir(jsSourceDir); - console.log('Found JS files:', jsFiles); + return jsFiles .filter(file => file.endsWith('.js')) .map(file => path.basename(file, '.js')); } catch (error) { - console.error("Error reading component source directories:", error); + console.error("Error reading component source directories from CLI assets:", error); return []; } } +// Ensures necessary configuration (template engine, destination paths) is set, prompting the user if needed. async function ensureConfiguration() { - console.log('\nChecking configuration...'); - if (!config.templateEngine) { const engineChoice = await inquirer.prompt([ { @@ -60,113 +59,61 @@ async function ensureConfiguration() { config.templateEngine = engineChoice.engine; } - const pathChoices = await inquirer.prompt([ - { - type: 'input', - name: 'templateDest', - message: 'Where should template files be placed?', - default: './', - }, - { - type: 'input', - name: 'scriptDest', - message: 'Where should script files be placed?', - default: './', - } - ]); - config.templateDest = pathChoices.templateDest; - config.scriptDest = pathChoices.scriptDest; + const pathPrompts = []; + if (!config.templateDest || config.templateDest === DEFAULT_CONFIG.templateDest) { + pathPrompts.push({ + type: 'input', + name: 'templateDest', + message: 'Where should template files be placed?', + default: DEFAULT_CONFIG.templateDest, + }); + } + if (!config.scriptDest || config.scriptDest === DEFAULT_CONFIG.scriptDest) { + pathPrompts.push({ + type: 'input', + name: 'scriptDest', + message: 'Where should script files be placed?', + default: DEFAULT_CONFIG.scriptDest, + }); + } - console.log(`Configuration set: Engine=${config.templateEngine}, Templates=${config.templateDest}, Scripts=${config.scriptDest}`); + if (pathPrompts.length > 0) { + const pathChoices = await inquirer.prompt(pathPrompts); + if (pathChoices.templateDest) config.templateDest = pathChoices.templateDest; + if (pathChoices.scriptDest) config.scriptDest = pathChoices.scriptDest; + } } -console.log('Basecoat CLI'); - -program - .name('basecoat') - .description('Add Basecoat components to your project') - .version(packageVersion) - .command('add') - .description('Add one or more components to your project') - .argument('[components...]', 'Names of components to add') - .action(async (componentsArg) => { - let componentsToAdd = componentsArg; - try { - if (!componentsToAdd || componentsToAdd.length === 0) { - const availableComponents = await getAvailableComponents(); - if (availableComponents.length === 0) { - console.error('Error: Could not find any components.'); - return; - } - - const answers = await inquirer.prompt([ - { - type: 'checkbox', - name: 'selectedComponents', - message: 'Which component(s) would you like to add?', - choices: availableComponents, - validate: (input) => input.length > 0 ? true : 'Please select at least one component.' - } - ]); - - componentsToAdd = answers.selectedComponents; - } - - if (!componentsToAdd || componentsToAdd.length === 0) { - console.log('No components selected. Exiting.'); - return; - } - - console.log(`Attempting to add: ${componentsToAdd.join(', ')}`); - - await ensureConfiguration(); - - for (const componentName of componentsToAdd) { - await addComponent(componentName); - } - - console.log('\nComponent addition process finished.'); - } catch (error) { - if (error.isTtyError || error.constructor.name === 'ExitPromptError') { - console.log('\nOperation cancelled.'); - } else { - console.error('\nAn unexpected error occurred:', error); - } - process.exit(1); - } - }); - +// Copies a component's template and script files to the user's project. async function addComponent(componentName) { console.log(`\nProcessing component: ${componentName}...`); - // 1. Determine source paths (relative to package root) - const cliScriptDir = path.dirname(new URL(import.meta.url).pathname); - const packageRoot = path.resolve(cliScriptDir, '..', '..'); + const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url)); + const assetsDir = path.resolve(cliScriptRunningDir, 'assets'); const templateExt = config.templateEngine === 'jinja' ? '.html.jinja' : '.njk'; - const templateSource = path.join(packageRoot, 'src', config.templateEngine, `${componentName}${templateExt}`); - const scriptSource = path.join(packageRoot, 'src', 'js', `${componentName}.js`); + + const templateSource = path.join(assetsDir, config.templateEngine, `${componentName}${templateExt}`); + const scriptSource = path.join(assetsDir, 'js', `${componentName}.js`); - // 2. Determine destination paths (relative to user's current working directory) const templateDestPath = path.resolve(process.cwd(), config.templateDest, `${componentName}${templateExt}`); const scriptDestPath = path.resolve(process.cwd(), config.scriptDest, `${componentName}.js`); try { - // 3. Check if source files exist const templateExists = await fs.pathExists(templateSource); const scriptExists = await fs.pathExists(scriptSource); - if (!templateExists || !scriptExists) { - console.error(` Error: Source files for component '${componentName}' not found. Searched:`); - if (!templateExists) console.error(` - ${templateSource}`); - if (!scriptExists) console.error(` - ${scriptSource}`); - return; // Skip this component + if (!templateExists) { + console.error(` Error: Template file for component '${componentName}' not found in CLI assets. Searched: ${templateSource}`); + // Optionally, list available template engines or files for that component if helpful } + if (!scriptExists) { + console.error(` Error: Script file for component '${componentName}' not found in CLI assets. Searched: ${scriptSource}`); + } + if (!templateExists || !scriptExists) return; // Skip this component if sources are missing - // 4. Ensure destination directories exist await fs.ensureDir(path.dirname(templateDestPath)); await fs.ensureDir(path.dirname(scriptDestPath)); - // 5. Copy files await fs.copyFile(templateSource, templateDestPath); console.log(` -> Copied template to: ${templateDestPath}`); await fs.copyFile(scriptSource, scriptDestPath); @@ -177,4 +124,58 @@ async function addComponent(componentName) { } } +// Setup main CLI program +program + .name('basecoat') + .description('Add Basecoat components to your project') + .version(packageVersion); + +// Define the 'add' command +program + .command('add') + .description('Add one or more Basecoat components to your project') + .argument('[components...]', 'Names of components to add (e.g., dialog select)') + .action(async (componentsArg) => { + let componentsToAdd = componentsArg; + try { + if (!componentsToAdd || componentsToAdd.length === 0) { + const availableComponents = await getAvailableComponents(); + if (availableComponents.length === 0) { + console.error('Error: No components available in the CLI. Build might be corrupted.'); + return; + } + const answers = await inquirer.prompt([ + { + type: 'checkbox', + name: 'selectedComponents', + message: 'Which component(s) would you like to add?', + choices: availableComponents, + validate: (input) => input.length > 0 ? true : 'Please select at least one component.' + } + ]); + componentsToAdd = answers.selectedComponents; + } + + if (!componentsToAdd || componentsToAdd.length === 0) { + console.log('No components selected. Exiting.'); + return; + } + + await ensureConfiguration(); // Confirm/gather destination paths + + for (const componentName of componentsToAdd) { + await addComponent(componentName); + } + console.log('\nComponent addition process finished.'); + + } catch (error) { + if (error.isTtyError || error.constructor?.name === 'ExitPromptError') { // Handle inquirer cancellation + console.log('\nOperation cancelled by user.'); + } else { + console.error('\nAn unexpected error occurred during the add command:', error); + } + process.exit(1); // Exit with error code for unexpected errors + } + }); + program.parse(process.argv); \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index 4d4a460..913c71e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,14 +1,45 @@ { - "name": "@hunvreus/basecoat-cli", + "name": "basecoat-cli", "version": "0.1.0", - "description": "CLI for Basecoat UI components", + "description": "Add Basecoat components to your project", + "author": "hunvreus", + "license": "MIT", "type": "module", "bin": { - "basecoat": "./index.js" + "basecoat": "./dist/index.js" }, + "files": [ + "dist/" + ], "dependencies": { "commander": "^13.1.0", - "inquirer": "^12.6.0", - "fs-extra": "^11.3.0" - } + "fs-extra": "^11.3.0", + "inquirer": "^12.6.0" + }, + "keywords": [ + "cli", + "components", + "component library", + "component system", + "ui", + "ui kit", + "shadcn", + "shadcn/ui", + "tailwind", + "tailwindcss", + "css", + "html", + "jinja", + "nunjucks", + "alpinejs" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/hunvreus/basecoat.git", + "directory": "packages/cli" + }, + "bugs": { + "url": "https://github.com/hunvreus/basecoat/issues" + }, + "homepage": "https://basecoatui.com/installation#install-cli" } \ No newline at end of file diff --git a/packages/css/package.json b/packages/css/package.json new file mode 100644 index 0000000..a9ba9eb --- /dev/null +++ b/packages/css/package.json @@ -0,0 +1,40 @@ +{ + "name": "basecoat-css", + "version": "0.1.0", + "description": "CSS for Basecoat UI components", + "author": { + "name": "hunvreus", + "url": "https://x.com/hunvreus" + }, + "license": "MIT", + "main": "dist/basecoat.css", + "style": "dist/basecoat.css", + "files": [ + "dist/" + ], + "keywords": [ + "components", + "component library", + "component system", + "ui", + "ui kit", + "shadcn", + "shadcn/ui", + "tailwind", + "tailwindcss", + "css", + "html", + "jinja", + "nunjucks", + "alpinejs" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/hunvreus/basecoat.git", + "directory": "packages/css" + }, + "bugs": { + "url": "https://github.com/hunvreus/basecoat/issues" + }, + "homepage": "https://basecoatui.com/installation#install-css" +} diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..97c6fa5 --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,112 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// Resolve project root, assuming this script is in /scripts/ +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, '..'); + +// Ensures a directory exists. If it doesn't, it's created recursively. +async function ensureDir(dirPath) { + try { + await fs.access(dirPath); + } catch (error) { + if (error.code === 'ENOENT') { + await fs.mkdir(dirPath, { recursive: true }); + } else { + throw error; + } + } +} + +// Cleans a directory by removing all its contents (files and subdirectories). +async function cleanDir(directory) { + try { + await fs.access(directory); + const entries = await fs.readdir(directory, { withFileTypes: true }); + for (const entry of entries) { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + await fs.rm(entryPath, { recursive: true, force: true }); + } else { + await fs.unlink(entryPath); + } + } + console.log(`Cleaned directory: ${directory}`); + } catch (error) { + if (error.code === 'ENOENT') { + console.log(`Directory not found, no need to clean: ${directory}`); + } else { + console.error(`Error cleaning directory ${directory}:`, error); + throw error; + } + } +} + +// Recursively copies a directory. +async function copyDirRecursive(src, dest) { + await ensureDir(dest); + const entries = await fs.readdir(src, { withFileTypes: true }); + for (let entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + if (entry.isDirectory()) { + await copyDirRecursive(srcPath, destPath); + } else { + await fs.copyFile(srcPath, destPath); + } + } +} + +// Main build function to prepare packages for publishing. +async function build() { + console.log('Starting build process...'); + + // Define all necessary paths + const cliPackageDir = path.join(projectRoot, 'packages', 'cli'); + const cliDistDir = path.join(cliPackageDir, 'dist'); + const cliDistAssetsDir = path.join(cliDistDir, 'assets'); + + const cssPackageDir = path.join(projectRoot, 'packages', 'css'); + const cssDistDir = path.join(cssPackageDir, 'dist'); + + const srcDir = path.join(projectRoot, 'src'); + const srcCssDir = path.join(srcDir, 'css'); + const srcJsDir = path.join(srcDir, 'js'); + const srcNunjucksDir = path.join(srcDir, 'nunjucks'); + const srcJinjaDir = path.join(srcDir, 'jinja'); + + // Clean previous build artifacts + console.log('Cleaning distribution directories...'); + await cleanDir(cliDistDir); + await cleanDir(cssDistDir); + + // Build CLI package + console.log('Building CLI package...'); + await ensureDir(cliDistDir); // Ensure base dist dir exists + await ensureDir(cliDistAssetsDir); // Ensure assets dir within dist exists + + await fs.copyFile(path.join(cliPackageDir, 'index.js'), path.join(cliDistDir, 'index.js')); + console.log(`Copied CLI index.js to ${cliDistDir}`); + + await copyDirRecursive(srcJsDir, path.join(cliDistAssetsDir, 'js')); + console.log(`Copied JS assets to ${path.join(cliDistAssetsDir, 'js')}`); + await copyDirRecursive(srcNunjucksDir, path.join(cliDistAssetsDir, 'nunjucks')); + console.log(`Copied Nunjucks assets to ${path.join(cliDistAssetsDir, 'nunjucks')}`); + await copyDirRecursive(srcJinjaDir, path.join(cliDistAssetsDir, 'jinja')); + console.log(`Copied Jinja assets to ${path.join(cliDistAssetsDir, 'jinja')}`); + + // Build CSS package + console.log('Building CSS package...'); + await ensureDir(cssDistDir); // Ensure dist dir exists for css package + await fs.copyFile(path.join(srcCssDir, 'basecoat.css'), path.join(cssDistDir, 'basecoat.css')); + console.log(`Copied basecoat.css to ${cssDistDir}`); + + console.log('Build process finished successfully!'); +} + +build().catch(error => { + console.error('Build failed:', error); + process.exit(1); +});