From 8b5186a4a03e2251c5adeceaff7cd97a13066803 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 10:45:11 +0000 Subject: [PATCH] Add 'ls' command to list available project templates Features: - List all templates from NetCoreTemplates and NetFrameworkTemplates - List templates from a specific organization: npx create-net ls - Display templates in 2-column format: name and description - Fetch data from GitHub API with proper error handling - Sort repositories by last updated - Bold section headers and formatted output Command usage: npx create-net ls # List all default templates npx create-net ls NetFrameworkTemplates # List specific org templates Implementation: - Add fetchJSON() function to retrieve data from GitHub API - Add listTemplates() function with optional organization parameter - Update help text to include ls command - Handle both 'ls' and 'list' commands - Graceful error handling for network issues Documentation: - Update README with ls command examples - Add Commands section to README - Show usage examples for both listing and creating --- README.md | 26 +++++++++++ bin/create-net.js | 107 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ecb973..2abaa22 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,18 @@ Create .NET and other projects from NetCoreTemplates GitHub repositories. ## Usage +```bash +npx create-net [ProjectName] +``` + +### Commands + +**List available templates:** +```bash +npx create-net ls [org] +``` + +**Create a project:** ```bash npx create-net [ProjectName] ``` @@ -12,6 +24,20 @@ If `ProjectName` is not specified, the script will use the current directory nam ### Examples +**List all available templates:** + +```bash +npx create-net ls +``` + +Shows all templates from NetCoreTemplates and NetFrameworkTemplates organizations. + +**List templates from a specific organization:** + +```bash +npx create-net ls NetFrameworkTemplates +``` + **Create a project in a new directory:** ```bash diff --git a/bin/create-net.js b/bin/create-net.js index b1fd3c4..b565414 100755 --- a/bin/create-net.js +++ b/bin/create-net.js @@ -10,17 +10,30 @@ const AdmZip = require('adm-zip'); const args = process.argv.slice(2); if (args.length < 1) { - console.error('Usage: npx create-net [ProjectName]'); + console.error('Usage: npx create-net [ProjectName]'); + console.error(''); + console.error('Commands:'); + console.error(' ls [org] List available project templates'); + console.error(' [name] Create a project from a template'); console.error(''); console.error('If ProjectName is not specified, uses current directory name and extracts into current directory.'); console.error(''); console.error('Examples:'); + console.error(' npx create-net ls'); + console.error(' npx create-net ls NetFrameworkTemplates'); console.error(' npx create-net nextjs MyProject'); console.error(' npx create-net NetFrameworkTemplates/web-netfx MyProject'); console.error(' npx create-net nextjs (uses current directory name)'); process.exit(1); } +// Handle ls command to list available templates +if (args[0] === 'ls' || args[0] === 'list') { + const targetOrg = args[1]; // Optional organization/user name + listTemplates(targetOrg); + return; +} + const repo = args[0]; let projectName = args[1]; let extractToCurrentDir = false; @@ -71,6 +84,98 @@ if (extractToCurrentDir) { } } +// Function to fetch JSON from GitHub API +function fetchJSON(url) { + return new Promise((resolve, reject) => { + https.get(url, { + headers: { + 'User-Agent': 'create-net' + } + }, (response) => { + let data = ''; + + // Handle redirects + if (response.statusCode === 301 || response.statusCode === 302) { + return fetchJSON(response.headers.location).then(resolve).catch(reject); + } + + if (response.statusCode !== 200) { + return reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`)); + } + + response.on('data', chunk => data += chunk); + response.on('end', () => { + try { + resolve(JSON.parse(data)); + } catch (err) { + reject(err); + } + }); + }).on('error', reject); + }); +} + +// Function to list available templates +async function listTemplates(targetOrg) { + console.log('Fetching available project templates...\n'); + + let organizations; + + if (targetOrg) { + // List templates from specific organization + organizations = [{ name: targetOrg, title: `${targetOrg} Templates` }]; + } else { + // List templates from default organizations + organizations = [ + { name: 'NetCoreTemplates', title: '.NET Core Templates' }, + { name: 'NetFrameworkTemplates', title: '.NET Framework Templates' } + ]; + } + + try { + for (const org of organizations) { + console.log(`\x1b[1m${org.title}\x1b[0m`); + console.log('─'.repeat(80)); + + try { + const repos = await fetchJSON(`https://api.github.com/orgs/${org.name}/repos?per_page=100&sort=updated`); + + if (!repos || repos.length === 0) { + console.log(' No templates found'); + } else { + // Find the longest repo name for padding + const maxNameLength = Math.max(...repos.map(r => r.name.length)); + const padding = Math.max(maxNameLength + 2, 25); + + repos.forEach(repo => { + const name = repo.name.padEnd(padding); + const description = repo.description || 'No description available'; + console.log(` ${name} ${description}`); + }); + } + } catch (err) { + console.log(` Error fetching templates: ${err.message}`); + } + + console.log(''); + } + + console.log('\x1b[1mUsage:\x1b[0m'); + console.log(' npx create-net [ProjectName]'); + console.log(' npx create-net / [ProjectName]'); + console.log(' npx create-net ls [org]'); + console.log('\n\x1b[1mExamples:\x1b[0m'); + console.log(' npx create-net ls # List all templates'); + console.log(' npx create-net ls NetFrameworkTemplates # List specific org templates'); + console.log(' npx create-net nextjs MyProject # Create from NetCoreTemplates'); + console.log(' npx create-net NetFrameworkTemplates/web-netfx MyApp # Create from specific org'); + + } catch (err) { + console.error('Error listing templates:', err.message); + process.exit(1); + } +} + // Function to download file from URL function downloadFile(url, destination) { return new Promise((resolve, reject) => {