diff --git a/LT-CLI-Reference.md b/LT-CLI-Reference.md new file mode 100644 index 0000000..2b33ee6 --- /dev/null +++ b/LT-CLI-Reference.md @@ -0,0 +1,290 @@ +# LT CLI Tool Reference + +## Overview +The LT CLI is a globally installed code generation tool for TypeScript projects with server modules and objects. It provides commands for creating and modifying backend structures. + +## Available Commands + +### Create New Fullstack Workspace +**Command:** `lt fullstack init` (alias: `lt full init`) + +**Purpose:** Creates a complete fullstack workspace with frontend (Angular/Nuxt), backend (NestJS), and proper project structure. + +**Usage:** +```bash +# Interactive mode (prompts for all inputs) +lt fullstack init + +# Non-interactive mode with CLI arguments +lt fullstack init --name --frontend --git --git-link +``` + +**Arguments:** +- `--name` - Workspace name +- `--frontend` - Frontend framework: "angular" or "nuxt" +- `--git` - Initialize git repository: "true" or "false" +- `--git-link` - Git repository URL (required when --git is true) + +**Examples:** +```bash +# Create workspace with Angular frontend, no git +lt fullstack init --name MyApp --frontend angular --git false + +# Create workspace with Nuxt frontend and git repository +lt fullstack init --name MyProject --frontend nuxt --git true --git-link https://github.com/user/my-project.git + +# Interactive mode (will prompt for inputs) +lt fullstack init +``` + +**What it creates:** +- Clones the lt-monorepo template from GitHub +- Sets up chosen frontend framework (Angular from ng-base-starter or Nuxt using create-nuxt-base) +- Integrates NestJS server starter from nest-server-starter +- Creates proper workspace structure with `/projects/app` and `/projects/api` +- Configures meta.json and environment files +- Replaces secret keys and project-specific configurations +- Optionally initializes git repository with dev branch and pushes to remote +- Installs all packages and runs initialization scripts + +### Create New Server Module +**Command:** `lt server module` (alias: `lt server m`) + +**Purpose:** Creates a complete new server module with all necessary files (model, service, controller, resolver, inputs, outputs). + +**Usage:** +```bash +# Interactive mode (prompts for all inputs) +lt server module + +# Non-interactive mode with CLI arguments +lt server module --name --controller [property-flags] +``` + +**Arguments:** +- `--name` - Module name (required) +- `--controller` - Controller type: "Rest", "GraphQL", or "Both" (required) +- `--skipLint` - Skip lint fix prompt (optional) +- Property arguments (same as add-property command): + - `--prop-name-X` - Property name (X = index: 0, 1, 2...) + - `--prop-type-X` - Property type + - `--prop-nullable-X` - "true" or "false" (default: false) + - `--prop-array-X` - "true" or "false" (default: false) + - `--prop-enum-X` - Enum type name + - `--prop-schema-X` - Schema/object type name + - `--prop-reference-X` - Reference type name for ObjectId properties + +**Examples:** +```bash +# Create User module with REST controller only +lt server module --name User --controller Rest + +# Create Post module with both REST and GraphQL, with properties +lt server module --name Post --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 content --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 author --prop-type-2 ObjectId --prop-reference-2 User + +# Create Product module with GraphQL only and enum property +lt server module --name Product --controller GraphQL \ + --prop-name-0 status --prop-enum-0 ProductStatusEnum \ + --prop-name-1 price --prop-type-1 number + +# Skip lint fix prompt +lt server module --name Category --controller Both --skipLint +``` + +**What it creates:** +- `.model.ts` - MongoDB schema with Mongoose decorators +- `.service.ts` - Business logic service +- `.controller.ts` - REST controller (if Rest or Both) +- `.resolver.ts` - GraphQL resolver (if GraphQL or Both) +- `.module.ts` - NestJS module configuration +- `inputs/.input.ts` - Input DTO for updates +- `inputs/-create.input.ts` - Input DTO for creation +- `outputs/find-and-count-s-result.output.ts` - Output DTO for pagination +- Automatically integrates the module into `server.module.ts` + +### Add Properties to Modules/Objects +**Command:** `lt server addProp` (alias: `lt server ap`) + +**Purpose:** Adds properties to existing modules or objects, updating model files, input files, and create input files automatically with UnifiedField decorators. + +**Usage:** +```bash +# Interactive mode (prompts for all inputs) +lt server addProp + +# Non-interactive mode with CLI arguments +lt server addProp --type --element [property-flags] +``` + +**Arguments:** +- `--type` - "Module" or "Object" (required) +- `--element` - Name of the module/object to modify (required) +- Property definitions (multiple properties supported): + - `--prop-name-X` - Property name (X = index: 0, 1, 2...) + - `--prop-type-X` - Property type (string, number, boolean, ObjectId, Json, etc.) + - `--prop-nullable-X` - "true" or "false" (default: false) + - `--prop-array-X` - "true" or "false" (default: false) + - `--prop-enum-X` - Enum type name (e.g., "UserStatusEnum") + - `--prop-schema-X` - Schema/object type name + - `--prop-reference-X` - Reference type name for ObjectId properties + +**Examples:** +```bash +# Add email and age properties to User module +lt server addProp --type Module --element User \ + --prop-name-0 email --prop-type-0 string \ + --prop-name-1 age --prop-type-1 number --prop-nullable-1 true + +# Add bio to Profile object +lt server addProp --type Object --element Profile \ + --prop-name-0 bio --prop-type-0 string --prop-nullable-0 true + +# Add array of tags to Post module +lt server addProp --type Module --element Post \ + --prop-name-0 tags --prop-type-0 string --prop-array-0 true + +# Add enum property to User module +lt server addProp --type Module --element User \ + --prop-name-0 status --prop-enum-0 UserStatusEnum + +# Add ObjectId reference to Post module +lt server addProp --type Module --element Post \ + --prop-name-0 author --prop-type-0 ObjectId --prop-reference-0 User + +# Add schema/object property to User module +lt server addProp --type Module --element User \ + --prop-name-0 profile --prop-schema-0 UserProfile + +# Add JSON field for metadata +lt server addProp --type Module --element Product \ + --prop-name-0 metadata --prop-type-0 Json --prop-nullable-0 true +``` + +**What it does:** +- Adds properties to `.model.ts` with `@Prop()` and `@UnifiedField()` decorators +- Updates `.input.ts` with `@UnifiedField()` decorator for updates +- Updates `-create.input.ts` with `@UnifiedField()` decorator for creation +- Handles TypeScript typing with proper generics and suffixes +- Automatically handles references (ObjectId → Reference for model, ReferenceInput for input) +- Supports `useDefineForClassFields` TypeScript configuration +- Prompts for lint fix after completion +- Can cascade to create referenced modules/objects if they don't exist + +### Create New Server Object +**Command:** `lt server object` (alias: `lt server o`) + +**Purpose:** Creates a new server object (shared data structure) with input DTOs for use across modules. + +**Usage:** +```bash +# Interactive mode (prompts for all inputs) +lt server object + +# Non-interactive mode with CLI arguments +lt server object --name [property-flags] +``` + +**Arguments:** +- `--name` - Object name (required) +- `--skipLint` - Skip lint fix prompt (optional) +- Property definitions (multiple properties supported): + - `--prop-name-X` - Property name (X = index: 0, 1, 2...) + - `--prop-type-X` - Property type (string, number, boolean, ObjectId, Json, etc.) + - `--prop-nullable-X` - "true" or "false" (default: false) + - `--prop-array-X` - "true" or "false" (default: false) + - `--prop-enum-X` - Enum type name (e.g., "StatusEnum") + - `--prop-schema-X` - Schema/object type name + - `--prop-reference-X` - Reference type name for ObjectId properties + +**Examples:** +```bash +# Create basic Address object +lt server object --name Address + +# Create UserProfile object with properties +lt server object --name UserProfile \ + --prop-name-0 firstName --prop-type-0 string \ + --prop-name-1 lastName --prop-type-1 string \ + --prop-name-2 bio --prop-type-2 string --prop-nullable-2 true + +# Create Contact object with array and reference +lt server object --name Contact \ + --prop-name-0 emails --prop-type-0 string --prop-array-0 true \ + --prop-name-1 owner --prop-type-1 ObjectId --prop-reference-1 User + +# Create Settings object with enum and JSON +lt server object --name Settings \ + --prop-name-0 theme --prop-enum-0 ThemeEnum \ + --prop-name-1 preferences --prop-type-1 Json --prop-nullable-1 true + +# Skip lint fix +lt server object --name Metadata --skipLint \ + --prop-name-0 tags --prop-type-0 string --prop-array-0 true +``` + +**What it creates:** +- `.object.ts` - Object class with UnifiedField decorators +- `.input.ts` - Input DTO for updates with validation +- `-create.input.ts` - Input DTO for creation with validation +- All files in `src/server/common/objects//` directory + +**What it does:** +- Creates reusable data structures that can be embedded in modules +- Adds properties with `@UnifiedField()` decorators for GraphQL/REST APIs +- Generates proper TypeScript typing with generics and suffixes +- Supports `useDefineForClassFields` TypeScript configuration +- Handles references (ObjectId → Reference for object, ReferenceInput for input) +- Can cascade to create referenced modules/objects if they don't exist +- Prompts for lint fix after completion (unless --skipLint is used) + +## Project Structure Requirements +The LT CLI expects this file structure in your project: +``` +src/ + server/ + modules/ + / + .model.ts + inputs/ + .input.ts + -create.input.ts + common/ + objects/ + / + .object.ts + .input.ts + -create.input.ts +``` + +## Property Types Supported +- **Primitive:** string, number, boolean, bigint, null, undefined, etc. +- **Complex:** ObjectId (for references), JSON, custom schemas +- **Arrays:** Any type can be an array with `--prop-array-X true` +- **Nullable:** Any type can be nullable with `--prop-nullable-X true` +- **Enums:** Custom enum references + +## What the Tool Does +1. Parses arguments or runs interactive prompts +2. Locates target files (model, input, create-input) +3. Adds properties with proper TypeScript decorators (@Prop, @UnifiedField) +4. Updates all relevant files maintaining proper structure +5. Formats code automatically +6. Optionally runs lint fixes +7. Handles cascading references and objects + +## Usage Notes +- **IMPORTANT:** Run commands from anywhere within your project directory +- The CLI will locate the nearest `src/` directory above your current location and work from there +- The CLI automatically handles TypeScript decorators and proper formatting with ts-morph +- It maintains existing code structure and adds new properties appropriately +- Interactive mode provides validation and helps with complex property types +- CLI mode is better for automation and scripting scenarios +- Both commands support cascading creation of referenced modules/objects +- The module command automatically integrates new modules into `server.module.ts` +- All commands offer optional lint fixing after completion +- Always backup your code before running, as it modifies files directly +- For fullstack init: Requires git to be installed and accessible via CLI +- Commands use proper kebab-case for file naming and PascalCase for class names \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3e1fee3..9fd2b2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lenne.tech/cli", - "version": "0.0.124", + "version": "0.0.125", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lenne.tech/cli", - "version": "0.0.124", + "version": "0.0.125", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "3.908.0", diff --git a/package.json b/package.json index 90db6ba..82d54db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lenne.tech/cli", - "version": "0.0.124", + "version": "0.0.125", "description": "lenne.Tech CLI: lt", "keywords": [ "lenne.Tech", diff --git a/src/commands/claude/claude.ts b/src/commands/claude/claude.ts new file mode 100644 index 0000000..05d927e --- /dev/null +++ b/src/commands/claude/claude.ts @@ -0,0 +1,15 @@ +import { ExtendedGluegunToolbox } from '../../interfaces/extended-gluegun-toolbox'; + +/** + * Claude commands + */ +module.exports = { + alias: ['c'], + description: 'Claude commands', + hidden: true, + name: 'claude', + run: async (toolbox: ExtendedGluegunToolbox) => { + await toolbox.helper.showMenu('claude'); + return 'claude'; + }, +}; \ No newline at end of file diff --git a/src/commands/claude/install-skill.ts b/src/commands/claude/install-skill.ts new file mode 100644 index 0000000..f07e03f --- /dev/null +++ b/src/commands/claude/install-skill.ts @@ -0,0 +1,98 @@ +import { GluegunCommand } from 'gluegun'; +import { homedir } from 'os'; +import { join } from 'path'; + +import { ExtendedGluegunToolbox } from '../../interfaces/extended-gluegun-toolbox'; + +/** + * Install LT CLI Skill to ~/.claude/skills/lt-cli/ + */ +const NewCommand: GluegunCommand = { + alias: ['skill', 'is'], + description: 'Installs the LT CLI Skill to ~/.claude/skills/ for Claude Code integration. The skill helps Claude generate correct LT CLI commands.', + hidden: false, + name: 'install-skill', + run: async (toolbox: ExtendedGluegunToolbox) => { + // Retrieve the tools we need + const { + filesystem, + print: { error, info, spin, success }, + } = toolbox; + + const installSpinner = spin('Installing LT CLI Skill to ~/.claude/skills/lt-cli/'); + + try { + // Get the CLI installation directory + const cliRoot = join(__dirname, '..', '..'); + const templatesDir = join(cliRoot, 'templates', 'claude-skills', 'lt-cli'); + + // Check if templates exist + if (!filesystem.exists(templatesDir)) { + installSpinner.fail(); + error('Skill templates not found in CLI installation.'); + info(`Expected location: ${templatesDir}`); + info('Please reinstall the CLI or report this issue.'); + return; + } + + // Create ~/.claude/skills/lt-cli directory + const skillsDir = join(homedir(), '.claude', 'skills', 'lt-cli'); + if (!filesystem.exists(skillsDir)) { + filesystem.dir(skillsDir); + } + + // Copy all skill files + const skillFiles = ['SKILL.md', 'examples.md', 'reference.md']; + let copiedCount = 0; + + for (const file of skillFiles) { + const sourcePath = join(templatesDir, file); + const targetPath = join(skillsDir, file); + + if (filesystem.exists(sourcePath)) { + const content = filesystem.read(sourcePath); + filesystem.write(targetPath, content); + copiedCount++; + } else { + info(`Warning: ${file} not found in templates, skipping...`); + } + } + + if (copiedCount === 0) { + installSpinner.fail(); + error('No skill files were copied.'); + return; + } + + installSpinner.succeed(`Successfully installed LT CLI Skill to ${skillsDir}`); + info(''); + success('The LT CLI Skill is now available in Claude Code!'); + info(''); + info('Claude will automatically use this skill when you:'); + info(' • Create server modules, objects, or properties'); + info(' • Work with NestJS/TypeScript backend code'); + info(' • Ask for help with LT CLI commands'); + info(''); + info('Try it out by asking Claude:'); + info(' "Create a User module with email and username"'); + info(''); + info(`Files installed: ${copiedCount} of ${skillFiles.length}`); + info(`Location: ${skillsDir}`); + + } catch (err) { + installSpinner.fail(); + error(`Failed to install skill: ${err.message}`); + info(''); + info('Troubleshooting:'); + info(' • Ensure ~/.claude directory exists and is writable'); + info(' • Check file permissions'); + info(' • Try running with sudo if permission issues persist'); + return; + } + + // For tests + return 'claude install-skill'; + }, +}; + +export default NewCommand; \ No newline at end of file diff --git a/src/commands/fullstack/init.ts b/src/commands/fullstack/init.ts index d6ddbe1..e9eaf33 100644 --- a/src/commands/fullstack/init.ts +++ b/src/commands/fullstack/init.ts @@ -8,7 +8,7 @@ import { ExtendedGluegunToolbox } from '../../interfaces/extended-gluegun-toolbo */ const NewCommand: GluegunCommand = { alias: ['init'], - description: 'Creates a new fullstack workspace', + description: 'Creates a new fullstack workspace. Use --name , --frontend (angular|nuxt), --git (true|false), --git-link for non-interactive mode.', hidden: false, name: 'init', run: async (toolbox: ExtendedGluegunToolbox) => { @@ -36,8 +36,11 @@ const NewCommand: GluegunCommand = { return; } + // Parse CLI arguments + const { frontend: cliFrontend, git: cliGit, 'git-link': cliGitLink, name: cliName } = parameters.options; + // Get name of the workspace - const name = await helper.getInput(parameters.first, { + const name = cliName || await helper.getInput(parameters.first, { name: 'workspace name', showError: true, }); @@ -55,25 +58,42 @@ const NewCommand: GluegunCommand = { return undefined; } - let frontend = ( - await ask({ - message: 'Angular (a) or Nuxt 3 (n)', - name: 'frontend', - type: 'input', - }) - ).frontend; - - if (frontend === 'a') { - frontend = 'angular'; - } else if (frontend === 'n') { - frontend = 'nuxt'; + let frontend; + if (cliFrontend) { + frontend = cliFrontend === 'angular' ? 'angular' : cliFrontend === 'nuxt' ? 'nuxt' : null; + if (!frontend) { + error('Invalid frontend option. Use "angular" or "nuxt".'); + return; + } } else { - process.exit(); + frontend = ( + await ask({ + message: 'Angular (a) or Nuxt 3 (n)', + name: 'frontend', + type: 'input', + }) + ).frontend; + + if (frontend === 'a') { + frontend = 'angular'; + } else if (frontend === 'n') { + frontend = 'nuxt'; + } else { + process.exit(); + } } let addToGit = false; let gitLink; - if (parameters.third !== 'false') { + if (cliGit !== undefined) { + addToGit = cliGit === 'true' || cliGit === true; + if (addToGit && cliGitLink) { + gitLink = cliGitLink; + } else if (addToGit) { + error('--git-link is required when --git is true'); + return; + } + } else if (parameters.third !== 'false') { addToGit = parameters.third === 'true' || (await confirm('Add workspace to a new git repository?')); // Check if git init is active diff --git a/src/commands/server/add-property.ts b/src/commands/server/add-property.ts index 886d8e4..850203e 100644 --- a/src/commands/server/add-property.ts +++ b/src/commands/server/add-property.ts @@ -17,7 +17,7 @@ import genObject from './object'; */ const NewCommand: GluegunCommand = { alias: ['ap'], - description: 'Adds a property to a module', + description: 'Adds a property to a module. Use --type (Module|Object), --element , --prop-name-X , --prop-type-X , --prop-nullable-X (true|false), --prop-array-X (true|false), --prop-enum-X , --prop-schema-X , --prop-reference-X for non-interactive mode.', hidden: false, name: 'addProp', run: async (toolbox: ExtendedGluegunToolbox) => { @@ -31,6 +31,8 @@ const NewCommand: GluegunCommand = { system, } = toolbox; +const argProps = Object.keys(toolbox.parameters.options || {}).filter((key: string) => key.startsWith('prop')); + const declare = server.useDefineForClassFieldsActivated(); function getModules() { @@ -49,7 +51,10 @@ const NewCommand: GluegunCommand = { return filesystem.subdirectories(objectDirs, true); } - const objectOrModule = ( + // Parse CLI arguments + const { element: cliElement, type: cliType } = toolbox.parameters.options; + + const objectOrModule = cliType || ( await ask([ { choices: ['Module', 'Object'], @@ -60,8 +65,7 @@ const NewCommand: GluegunCommand = { ]) ).input; - - const elementToEdit = ( + const elementToEdit = cliElement || ( await ask([ { choices: objectOrModule === 'Module' ? getModules() : getObjects(), @@ -82,7 +86,14 @@ const NewCommand: GluegunCommand = { return undefined; } - const { objectsToAdd, props, referencesToAdd, refsSet, schemaSet } = await server.addProperties(); + const { objectsToAdd, props, referencesToAdd, refsSet, schemaSet } + = await toolbox.parseProperties({ + argProps, + objectsToAdd: [], + parameters: toolbox.parameters, + referencesToAdd: [], + server: toolbox.server, + }); const updateSpinner = spin('Updating files...'); @@ -191,27 +202,38 @@ const NewCommand: GluegunCommand = { }; // Patch model - const lastModelProperty = modelProperties[modelProperties.length - 1]; const newModelProperty: OptionalKind = structuredClone(standardDeclaration); newModelProperty.decorators.push({ arguments: [`${propObj.type === 'ObjectId' || propObj.schema ? `{ ref: () => ${propObj.reference}, type: Schema.Types.ObjectId }` : ''}`], name: 'Prop' }); newModelProperty.decorators.push({ arguments: [constructUnifiedFieldOptions('model')], name: 'UnifiedField' }); newModelProperty.type = `${typeString()}${propObj.isArray ? '[]' : ''}`; - const insertedModelProp = modelDeclaration.insertProperty(lastModelProperty.getChildIndex() + 1, newModelProperty); + + let insertedModelProp; + if (modelProperties.length > 0) { + const lastModelProperty = modelProperties[modelProperties.length - 1]; + insertedModelProp = modelDeclaration.insertProperty(lastModelProperty.getChildIndex() + 1, newModelProperty); + } else { + insertedModelProp = modelDeclaration.addProperty(newModelProperty); + } insertedModelProp.prependWhitespace('\n'); insertedModelProp.appendWhitespace('\n'); // Patch input - const lastInputProperty = inputProperties[inputProperties.length - 1]; const newInputProperty: OptionalKind = structuredClone(standardDeclaration); newInputProperty.decorators.push({ arguments: [constructUnifiedFieldOptions('input')], name: 'UnifiedField' }); const inputSuffix = propObj.type === 'ObjectId' || propObj.schema ? 'Input' : ''; newInputProperty.type = `${typeString()}${inputSuffix}${propObj.isArray ? '[]' : ''}`; - const insertedInputProp = inputDeclaration.insertProperty(lastInputProperty.getChildIndex() + 1, newInputProperty); + + let insertedInputProp; + if (inputProperties.length > 0) { + const lastInputProperty = inputProperties[inputProperties.length - 1]; + insertedInputProp = inputDeclaration.insertProperty(lastInputProperty.getChildIndex() + 1, newInputProperty); + } else { + insertedInputProp = inputDeclaration.addProperty(newInputProperty); + } insertedInputProp.prependWhitespace('\n'); insertedInputProp.appendWhitespace('\n'); // Patch create input - const lastCreateInputProperty = createInputProperties[createInputProperties.length - 1]; const newCreateInputProperty: OptionalKind = structuredClone(standardDeclaration); if (declare) { newCreateInputProperty.hasDeclareKeyword = true; @@ -221,7 +243,14 @@ const NewCommand: GluegunCommand = { newCreateInputProperty.decorators.push({ arguments: [constructUnifiedFieldOptions('create')], name: 'UnifiedField' }); const createSuffix = propObj.type === 'ObjectId' || propObj.schema ? 'CreateInput' : ''; newCreateInputProperty.type = `${typeString()}${createSuffix}${propObj.isArray ? '[]' : ''}`; - const insertedCreateInputProp = createInputDeclaration.insertProperty(lastCreateInputProperty.getChildIndex() + 1, newCreateInputProperty); + + let insertedCreateInputProp; + if (createInputProperties.length > 0) { + const lastCreateInputProperty = createInputProperties[createInputProperties.length - 1]; + insertedCreateInputProp = createInputDeclaration.insertProperty(lastCreateInputProperty.getChildIndex() + 1, newCreateInputProperty); + } else { + insertedCreateInputProp = createInputDeclaration.addProperty(newCreateInputProperty); + } insertedCreateInputProp.prependWhitespace('\n'); insertedCreateInputProp.appendWhitespace('\n'); } diff --git a/src/commands/server/module.ts b/src/commands/server/module.ts index cd7eba3..d7e1e7c 100644 --- a/src/commands/server/module.ts +++ b/src/commands/server/module.ts @@ -9,7 +9,7 @@ import genObject from './object'; */ const NewCommand: ExtendedGluegunCommand = { alias: ['m'], - description: 'Creates a new server module', + description: 'Creates a new server module. Use --name , --controller (Rest|GraphQL|Both), and property flags --prop-name-X, --prop-type-X, etc. for non-interactive mode.', hidden: false, name: 'module', run: async ( @@ -23,13 +23,12 @@ const NewCommand: ExtendedGluegunCommand = { ) => { // Options: - const { currentItem, objectsToAdd, preventExitProcess, referencesToAdd } = { + const { currentItem, preventExitProcess } = { currentItem: '', - objectsToAdd: [], preventExitProcess: false, - referencesToAdd: [], ...options, }; + let { objectsToAdd = [], referencesToAdd = [] } = options || {}; // Retrieve the tools we need const { @@ -55,16 +54,21 @@ const NewCommand: ExtendedGluegunCommand = { info('Create a new server module'); } - const name = await helper.getInput(currentItem || parameters.first, { - initial: currentItem || '', - name: 'module name', - }); + // Parse CLI arguments + const { controller: cliController, name: cliName, skipLint: cliSkipLint } = parameters.options; + + let name = cliName || currentItem || parameters.first; + if (!name) { + name = await helper.getInput(currentItem || parameters.first, { + initial: currentItem || '', + name: 'module name', + }); + } if (!name) { return; } - - const controller = (await ask({ + const controller = cliController || (await ask({ choices: ['Rest', 'GraphQL', 'Both'], message: 'What controller type?', name: 'controller', @@ -91,7 +95,11 @@ const NewCommand: ExtendedGluegunCommand = { return undefined; } - const { props, refsSet, schemaSet } = await server.addProperties({ objectsToAdd, referencesToAdd }); + const { objectsToAdd: newObjects, props, referencesToAdd: newReferences, refsSet, schemaSet } + = await toolbox.parseProperties({ objectsToAdd, referencesToAdd }); + + objectsToAdd = newObjects; + referencesToAdd = newReferences; const generateSpinner = spin('Generate files'); const declare = server.useDefineForClassFieldsActivated(); @@ -228,9 +236,11 @@ const NewCommand: ExtendedGluegunCommand = { } // Lint fix + if (!cliSkipLint) { if (await confirm('Run lint fix?', true)) { await system.run('npm run lint:fix'); } + } divider(); diff --git a/src/commands/server/object.ts b/src/commands/server/object.ts index f109c1b..ddb13db 100644 --- a/src/commands/server/object.ts +++ b/src/commands/server/object.ts @@ -9,7 +9,7 @@ import genModule from './module'; */ const NewCommand: ExtendedGluegunCommand = { alias: ['o'], - description: 'Creates a new server object (with inputs)', + description: 'Creates a new server object (with inputs). Use --name and property flags --prop-name-X, --prop-type-X, etc. for non-interactive mode.', hidden: false, name: 'object', run: async ( @@ -54,11 +54,17 @@ const NewCommand: ExtendedGluegunCommand = { info('Create a new server object (with inputs)'); } + // Parse CLI arguments + const { name: cliName, skipLint: cliSkipLint } = parameters.options; + // Get name - const name = await helper.getInput(currentItem || parameters.first, { - initial: currentItem || '', - name: 'object name', - }); + let name = cliName || currentItem || parameters.first; + if (!name) { + name = await helper.getInput(currentItem || parameters.first, { + initial: currentItem || '', + name: 'object name', + }); + } if (!name) { return; } @@ -83,7 +89,8 @@ const NewCommand: ExtendedGluegunCommand = { return undefined; } - const { props, refsSet, schemaSet } = await server.addProperties({ objectsToAdd, referencesToAdd }); + // Parse properties from CLI or interactive mode + const { props, refsSet, schemaSet } = await toolbox.parseProperties({ objectsToAdd, referencesToAdd }); const generateSpinner = spin('Generate files'); const declare = server.useDefineForClassFieldsActivated(); @@ -122,8 +129,10 @@ const NewCommand: ExtendedGluegunCommand = { generateSpinner.succeed('Files generated'); // Lint fix - if (await confirm('Run lint fix?', true)) { - await system.run('npm run lint:fix'); + if (!cliSkipLint) { + if (await confirm('Run lint fix?', true)) { + await system.run('npm run lint:fix'); + } } // We're done, so show what to do next diff --git a/src/extensions/parse-properties.ts b/src/extensions/parse-properties.ts new file mode 100644 index 0000000..f520bf2 --- /dev/null +++ b/src/extensions/parse-properties.ts @@ -0,0 +1,136 @@ +import { ExtendedGluegunToolbox } from '../interfaces/extended-gluegun-toolbox'; + +export interface ParsedPropsResult { + objectsToAdd: { object: string; property: string }[]; + props: Record; + referencesToAdd: { property: string; reference: string }[]; + refsSet: boolean; + schemaSet: boolean; +} + +/** + * Extend toolbox with parseProperties() helper. + */ +export default (toolbox: ExtendedGluegunToolbox) => { + toolbox.parseProperties = async ( + options?: { + argProps?: string[]; + objectsToAdd?: { object: string; property: string }[]; + parameters?: typeof toolbox.parameters; + referencesToAdd?: { property: string; reference: string }[]; + server?: typeof toolbox.server; + }, + ): Promise => { + const { parameters: globalParameters, server: globalServer } = toolbox; + const { + argProps = Object.keys(globalParameters.options || {}).filter(key => key.startsWith('prop')), + objectsToAdd = [], + parameters = globalParameters, + referencesToAdd = [], + server = globalServer, + } = options || {}; + // --- CLI Mode --- + if (argProps.length > 0) { + const { print } = toolbox; + + // Count how many prop-name flags exist + const propNameFlags = argProps.filter(key => key.startsWith('prop-name')); + const hasMultipleProps = propNameFlags.length > 1; + + // If multiple properties, all must have numeric indices + if (hasMultipleProps) { + const hasNonIndexed = propNameFlags.some(key => !key.match(/^prop-name-\d+$/)); + if (hasNonIndexed) { + print.error('When adding multiple properties, all must use numeric indices (e.g., --prop-name-0, --prop-name-1)'); + print.info(''); + print.info('Example:'); + print.info(' lt server addProp --type Module --element User \\'); + print.info(' --prop-name-0 email --prop-type-0 string \\'); + print.info(' --prop-name-1 age --prop-type-1 number'); + throw new Error('Invalid property flags: Multiple properties require numeric indices'); + } + } + + // Extract index from prop key (e.g., 'prop-name-1' -> 1) + const extractIndex = (key: string, prefix: string): number => { + const match = key.match(new RegExp(`^${prefix}-(\\d+)$`)); + return match ? parseInt(match[1], 10) : -1; + }; + + // Build a map of index -> property data + const propDataByIndex = new Map(); + + for (const key of argProps) { + let index = -1; + let field = ''; + + if (key.startsWith('prop-name-')) { + index = extractIndex(key, 'prop-name'); + field = 'name'; + } else if (key.startsWith('prop-type-')) { + index = extractIndex(key, 'prop-type'); + field = 'type'; + } else if (key.startsWith('prop-nullable-')) { + index = extractIndex(key, 'prop-nullable'); + field = 'nullable'; + } else if (key.startsWith('prop-array-')) { + index = extractIndex(key, 'prop-array'); + field = 'isArray'; + } else if (key.startsWith('prop-enum-')) { + index = extractIndex(key, 'prop-enum'); + field = 'enumRef'; + } else if (key.startsWith('prop-schema-')) { + index = extractIndex(key, 'prop-schema'); + field = 'schema'; + } else if (key.startsWith('prop-reference-')) { + index = extractIndex(key, 'prop-reference'); + field = 'reference'; + } + + if (index >= 0 && field) { + if (!propDataByIndex.has(index)) { + propDataByIndex.set(index, { + enumRef: null, + isArray: false, + name: '', + nullable: false, + reference: null, + schema: null, + type: 'string', + }); + } + + const propData = propDataByIndex.get(index); + const value = parameters.options[key]; + + if (field === 'nullable' || field === 'isArray') { + propData[field] = value === 'true'; + } else { + propData[field] = value; + } + } + } + + // Convert map to props object + const props: Record = {}; + for (const propData of propDataByIndex.values()) { + if (!propData.name) { + continue; + } + props[propData.name] = propData; + } + + return { objectsToAdd, props, referencesToAdd, refsSet: false, schemaSet: false }; + } + + // --- Interactive Mode --- + const result = await server.addProperties({ objectsToAdd, referencesToAdd }); + return { + objectsToAdd: result.objectsToAdd, + props: result.props, + referencesToAdd: result.referencesToAdd, + refsSet: result.refsSet, + schemaSet: result.schemaSet, + }; + }; +}; diff --git a/src/interfaces/extended-gluegun-toolbox.ts b/src/interfaces/extended-gluegun-toolbox.ts index 68b6e8b..d879d6c 100644 --- a/src/interfaces/extended-gluegun-toolbox.ts +++ b/src/interfaces/extended-gluegun-toolbox.ts @@ -1,6 +1,8 @@ import { IHelperExtendedGluegunToolbox } from '@lenne.tech/cli-plugin-helper'; +import { GluegunParameters } from 'gluegun'; import { Git } from '../extensions/git'; +import { ParsedPropsResult } from '../extensions/parse-properties'; import { Server } from '../extensions/server'; import { Tools } from '../extensions/tools'; import { Typescript } from '../extensions/typescript'; @@ -10,6 +12,13 @@ import { Typescript } from '../extensions/typescript'; */ export interface ExtendedGluegunToolbox extends IHelperExtendedGluegunToolbox { git: Git; + parseProperties: (options?: { + argProps?: string[]; + objectsToAdd?: { object: string; property: string }[]; + parameters?: GluegunParameters; + referencesToAdd?: { property: string; reference: string }[]; + server?: Server; + }) => Promise; server: Server; tools: Tools; typescript: Typescript; diff --git a/src/templates/claude-skills/lt-cli/SKILL.md b/src/templates/claude-skills/lt-cli/SKILL.md new file mode 100644 index 0000000..da02e43 --- /dev/null +++ b/src/templates/claude-skills/lt-cli/SKILL.md @@ -0,0 +1,341 @@ +--- +name: lt-cli +description: Expert assistance with lenne.tech CLI for NestJS/TypeScript backend development. Use when creating server modules, objects, or adding properties to NestJS backends. Generates correct lt server commands with proper --prop-name-X syntax. Helps with lt server module, lt server object, lt server addProp, and lt fullstack init commands. +--- + +# LT CLI Expert + +You are an expert in the lenne.tech CLI tool for NestJS/TypeScript backend development. When this skill is active, help users generate correct LT CLI commands and work efficiently with the framework. + +## Available Commands + +### 1. Create Server Module +**Command**: `lt server module` (alias: `lt server m`) + +Creates a complete NestJS module with model, service, controller/resolver, and DTOs. + +**Non-interactive syntax**: +```bash +lt server module --name --controller [property-flags] +``` + +**Property flags** (multiple properties with different indices): +- `--prop-name-X ` - Property name (X = 0, 1, 2...) +- `--prop-type-X ` - string, number, boolean, ObjectId, Json, Date, etc. +- `--prop-nullable-X ` - Optional property +- `--prop-array-X ` - Array type +- `--prop-enum-X ` - Enum reference +- `--prop-schema-X ` - Object/schema reference +- `--prop-reference-X ` - Reference name for ObjectId +- `--skipLint` - Skip lint prompt + +**Example**: +```bash +lt server module --name Post --controller GraphQL \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 content --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 author --prop-type-2 ObjectId --prop-reference-2 User \ + --prop-name-3 tags --prop-type-3 string --prop-array-3 true +``` + +### 2. Add Properties to Existing Module/Object +**Command**: `lt server addProp` (alias: `lt server ap`) + +Adds properties to existing modules or objects, updating model and input files. + +**Non-interactive syntax**: +```bash +lt server addProp --type --element [property-flags] +``` + +**Example**: +```bash +lt server addProp --type Module --element User \ + --prop-name-0 email --prop-type-0 string \ + --prop-name-1 age --prop-type-1 number --prop-nullable-1 true +``` + +### 3. Create Server Object +**Command**: `lt server object` (alias: `lt server o`) + +Creates reusable data structures (objects) for embedding in modules. + +**Non-interactive syntax**: +```bash +lt server object --name [property-flags] [--skipLint] +``` + +**Example**: +```bash +lt server object --name Address \ + --prop-name-0 street --prop-type-0 string \ + --prop-name-1 city --prop-type-1 string \ + --prop-name-2 zipCode --prop-type-2 string +``` + +### 4. Initialize Fullstack Workspace +**Command**: `lt fullstack init` (alias: `lt full init`) + +Creates complete fullstack workspace with frontend and backend. + +**Non-interactive syntax**: +```bash +lt fullstack init --name --frontend --git --git-link +``` + +**Example**: +```bash +lt fullstack init --name MyApp --frontend angular --git true --git-link https://github.com/user/repo.git +``` + +## Critical Rules for Command Generation + +### 1. Index-Based Property Flags +Always use numbered indices for property flags: +```bash +# CORRECT +--prop-name-0 title --prop-type-0 string \ +--prop-name-1 content --prop-type-1 string + +# WRONG - Don't use sequential arrays +--prop-name title --prop-type string +``` + +### 2. Match Indices Across Flags +All flags for one property must use the same index: +```bash +# CORRECT +--prop-name-1 company --prop-type-1 string --prop-nullable-1 false + +# WRONG - Mixed indices +--prop-name-1 company --prop-type-0 string --prop-nullable-2 false +``` + +### 3. ObjectId References +Always include `--prop-reference-X` with ObjectId type: +```bash +--prop-name-0 author --prop-type-0 ObjectId --prop-reference-0 User +``` + +### 4. Schema/Object Properties +Use `--prop-schema-X` for embedding objects: +```bash +--prop-name-0 address --prop-schema-0 Address +``` + +### 5. Boolean Values +Use lowercase string literals: +```bash +--prop-nullable-0 true # CORRECT +--prop-nullable-0 True # WRONG +--prop-nullable-0 TRUE # WRONG +``` + +## Property Types Reference + +### Primitive Types +- `string` - Text values +- `number` - Numeric values +- `boolean` - True/false +- `bigint` - Large integers +- `Date` - Date/time values + +### Special Types +- `ObjectId` - MongoDB reference (requires `--prop-reference-X`) +- `Json` - JSON data for flexible metadata +- Custom objects (requires `--prop-schema-X`) +- Custom enums (requires `--prop-enum-X`) - **Note: CLI generates the reference, but you must create the enum file manually afterwards** + +### Modifiers +- `--prop-nullable-X true` - Makes property optional +- `--prop-array-X true` - Makes property an array type + +## Common Patterns + +### User Authentication +```bash +lt server module --name User --controller Both \ + --prop-name-0 email --prop-type-0 string \ + --prop-name-1 username --prop-type-1 string \ + --prop-name-2 roles --prop-type-2 string --prop-array-2 true \ + --prop-name-3 verified --prop-type-3 boolean +``` + +### Blog Post with Relationships +```bash +lt server module --name Post --controller GraphQL \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 content --prop-type-1 string \ + --prop-name-2 author --prop-type-2 ObjectId --prop-reference-2 User \ + --prop-name-3 tags --prop-type-3 string --prop-array-3 true \ + --prop-name-4 published --prop-type-4 boolean +``` + +### E-commerce Product +```bash +lt server module --name Product --controller Both \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 description --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 price --prop-type-2 number \ + --prop-name-3 stock --prop-type-3 number \ + --prop-name-4 category --prop-enum-4 ProductCategoryEnum \ + --prop-name-5 metadata --prop-type-5 Json --prop-nullable-5 true +``` + +### Nested Object Pattern +```bash +# First create the object +lt server object --name Address \ + --prop-name-0 street --prop-type-0 string \ + --prop-name-1 city --prop-type-1 string \ + --prop-name-2 country --prop-type-2 string + +# Then use it in a module +lt server addProp --type Module --element User \ + --prop-name-0 address --prop-schema-0 Address +``` + +## Project Structure + +The CLI expects this structure: +``` +src/ + server/ + modules/ + / + .model.ts # MongoDB schema + .service.ts # Business logic + .controller.ts # REST controller + .resolver.ts # GraphQL resolver + .module.ts # NestJS module + inputs/ + .input.ts # Update DTO + -create.input.ts # Create DTO + outputs/ + find-and-count-s-result.output.ts + common/ + objects/ + / + .object.ts + .input.ts + -create.input.ts +``` + +## Generated Code Features + +### Model Files (.model.ts) +- MongoDB schema with `@Prop()` decorator +- `@UnifiedField()` decorator for GraphQL/REST +- Mongoose schema definition +- TypeScript typing with proper suffixes + +### Input Files (.input.ts / -create.input.ts) +- `@UnifiedField()` decorator +- Validation decorators +- TypeScript typing +- Generic support for references + +### Service Files (.service.ts) +- CRUD operations +- Pagination support +- Reference handling +- Business logic structure + +### Controller/Resolver Files +- REST endpoints (controller) +- GraphQL queries/mutations (resolver) +- Proper authentication guards +- DTO validation + +## Troubleshooting + +### Property Index Mismatch +**Symptom**: Properties not created correctly or values mixed up +**Cause**: Using wrong indices (e.g., `--prop-name-1` with `--prop-type-0`) +**Solution**: Ensure all flags for one property use the same index + +### ObjectId Without Reference +**Symptom**: TypeScript errors about missing reference +**Cause**: Using `ObjectId` type without `--prop-reference-X` +**Solution**: Always pair ObjectId with reference: +```bash +--prop-type-0 ObjectId --prop-reference-0 User +``` + +### Module Already Exists +**Symptom**: Error that module directory exists +**Solution**: Use `lt server addProp` instead to add to existing modules + +### Empty Property Lists +**Symptom**: "Cannot read properties of undefined" +**Status**: Fixed in latest version +**Solution**: Update to latest CLI version + +## Best Practices + +1. **Plan relationships first**: Sketch entity relationships before generating +2. **Create objects for reusable structures**: Don't duplicate data structures +3. **Use meaningful names**: PascalCase for modules/objects, camelCase for properties +4. **Start with one API type**: Use Rest or GraphQL, add Both later if needed +5. **Create enum files after generation**: CLI generates enum references, you create the actual enum files manually afterwards in `src/server/common/enums/` +6. **Mark truly optional fields**: Only use nullable for genuinely optional data +7. **Use JSON for extensibility**: Metadata and flexible fields work well as JSON +8. **Run lint after generation**: Always run lint fix for code quality +9. **Test incrementally**: Generate one module, test, then continue +10. **Version control**: Commit after successful generation + +## Working with This Skill + +When helping users: + +1. **Clarify requirements**: Ask about API type (REST/GraphQL/Both), relationships, data types +2. **Suggest architecture**: Recommend objects for shared structures, modules for entities +3. **Generate complete commands**: Include all necessary flags with correct syntax +4. **Explain side effects**: Describe what files will be created and where +5. **Provide next steps**: Suggest related modules, testing, or additional properties + +### Example Response Pattern + +User: "Create a Task module with title, description, due date, and assignee" + +Your response: +```bash +# First ensure User module exists, then create Task module +lt server module --name Task --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 description --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 dueDate --prop-type-2 Date --prop-nullable-2 true \ + --prop-name-3 assignee --prop-type-3 ObjectId --prop-reference-3 User \ + --prop-name-4 status --prop-enum-4 TaskStatusEnum + +# This creates: +# ✓ Task model with MongoDB schema +# ✓ Task service with CRUD operations +# ✓ REST controller and GraphQL resolver +# ✓ Input DTOs for create/update + +# Next steps: +# 1. Manually create TaskStatusEnum file in src/server/common/enums/task-status.enum.ts +# 2. Verify User module exists +# 3. Run lint fix +# 4. Add custom business logic to TaskService +``` + +## Integration Details + +- Uses `ts-morph` for AST manipulation +- Integrates with `@lenne.tech/nest-server` package +- Generates `@UnifiedField()` decorators for dual REST/GraphQL support +- Handles `useDefineForClassFields` TypeScript config +- Automatically adds properties to `.model.ts`, `.input.ts`, and `-create.input.ts` +- Manages imports and decorators automatically + +## Important Notes + +- CLI works from anywhere in project directory +- Automatically finds nearest `src/` directory +- Properties are added with proper TypeScript typing +- ObjectId properties become Reference/ReferenceInput in generated code +- The CLI prompts for lint fix after generation (use `--skipLint` to skip) +- Manual imports may be needed for references and schemas +- **Enum files must be created manually**: When using `--prop-enum-X`, the CLI generates the reference in your code, but you must create the actual enum file yourself afterwards in `src/server/common/enums/.enum.ts` \ No newline at end of file diff --git a/src/templates/claude-skills/lt-cli/examples.md b/src/templates/claude-skills/lt-cli/examples.md new file mode 100644 index 0000000..b6bca20 --- /dev/null +++ b/src/templates/claude-skills/lt-cli/examples.md @@ -0,0 +1,312 @@ +# LT CLI Examples + +## Real-World Use Cases + +### 1. Blog System + +#### Step 1: Create User Module +```bash +lt server module --name User --controller Both \ + --prop-name-0 email --prop-type-0 string \ + --prop-name-1 username --prop-type-1 string \ + --prop-name-2 firstName --prop-type-2 string \ + --prop-name-3 lastName --prop-type-3 string \ + --prop-name-4 bio --prop-type-4 string --prop-nullable-4 true +``` + +#### Step 2: Create Category Module +```bash +lt server module --name Category --controller Rest \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 slug --prop-type-1 string \ + --prop-name-2 description --prop-type-2 string --prop-nullable-2 true +``` + +#### Step 3: Create Post Module with References +```bash +lt server module --name Post --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 slug --prop-type-1 string \ + --prop-name-2 content --prop-type-2 string \ + --prop-name-3 excerpt --prop-type-3 string --prop-nullable-3 true \ + --prop-name-4 author --prop-type-4 ObjectId --prop-reference-4 User \ + --prop-name-5 category --prop-type-5 ObjectId --prop-reference-5 Category \ + --prop-name-6 tags --prop-type-6 string --prop-array-6 true \ + --prop-name-7 published --prop-type-7 boolean \ + --prop-name-8 publishedAt --prop-type-8 Date --prop-nullable-8 true +``` + +#### Step 4: Create Comment Module +```bash +lt server module --name Comment --controller GraphQL \ + --prop-name-0 content --prop-type-0 string \ + --prop-name-1 author --prop-type-1 ObjectId --prop-reference-1 User \ + --prop-name-2 post --prop-type-2 ObjectId --prop-reference-2 Post \ + --prop-name-3 approved --prop-type-3 boolean +``` + +--- + +### 2. E-Commerce Platform + +#### Step 1: Create Address Object +```bash +lt server object --name Address \ + --prop-name-0 street --prop-type-0 string \ + --prop-name-1 city --prop-type-1 string \ + --prop-name-2 state --prop-type-2 string \ + --prop-name-3 zipCode --prop-type-3 string \ + --prop-name-4 country --prop-type-4 string +``` + +#### Step 2: Create Customer Module +```bash +lt server module --name Customer --controller Both \ + --prop-name-0 email --prop-type-0 string \ + --prop-name-1 firstName --prop-type-1 string \ + --prop-name-2 lastName --prop-type-2 string \ + --prop-name-3 phone --prop-type-3 string --prop-nullable-3 true \ + --prop-name-4 shippingAddress --prop-schema-4 Address \ + --prop-name-5 billingAddress --prop-schema-5 Address +``` + +#### Step 3: Create Product Module +```bash +lt server module --name Product --controller Both \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 sku --prop-type-1 string \ + --prop-name-2 description --prop-type-2 string \ + --prop-name-3 price --prop-type-3 number \ + --prop-name-4 compareAtPrice --prop-type-4 number --prop-nullable-4 true \ + --prop-name-5 stock --prop-type-5 number \ + --prop-name-6 images --prop-type-6 string --prop-array-6 true \ + --prop-name-7 tags --prop-type-7 string --prop-array-7 true \ + --prop-name-8 active --prop-type-8 boolean +``` + +#### Step 4: Create Order Module +```bash +lt server module --name Order --controller Both \ + --prop-name-0 orderNumber --prop-type-0 string \ + --prop-name-1 customer --prop-type-1 ObjectId --prop-reference-1 Customer \ + --prop-name-2 items --prop-type-2 Json \ + --prop-name-3 subtotal --prop-type-3 number \ + --prop-name-4 tax --prop-type-4 number \ + --prop-name-5 total --prop-type-5 number \ + --prop-name-6 status --prop-enum-6 OrderStatusEnum \ + --prop-name-7 shippingAddress --prop-schema-7 Address +``` + +--- + +### 3. Project Management System + +#### Step 1: Create Team Module +```bash +lt server module --name Team --controller Rest \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 description --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 members --prop-type-2 ObjectId --prop-reference-2 User --prop-array-2 true +``` + +#### Step 2: Create Project Module +```bash +lt server module --name Project --controller Both \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 description --prop-type-1 string \ + --prop-name-2 team --prop-type-2 ObjectId --prop-reference-2 Team \ + --prop-name-3 owner --prop-type-3 ObjectId --prop-reference-3 User \ + --prop-name-4 startDate --prop-type-4 Date \ + --prop-name-5 endDate --prop-type-5 Date --prop-nullable-5 true \ + --prop-name-6 status --prop-enum-6 ProjectStatusEnum +``` + +#### Step 3: Create Task Module +```bash +lt server module --name Task --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 description --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 project --prop-type-2 ObjectId --prop-reference-2 Project \ + --prop-name-3 assignee --prop-type-3 ObjectId --prop-reference-3 User --prop-nullable-3 true \ + --prop-name-4 priority --prop-enum-4 PriorityEnum \ + --prop-name-5 status --prop-enum-5 TaskStatusEnum \ + --prop-name-6 dueDate --prop-type-6 Date --prop-nullable-6 true \ + --prop-name-7 estimatedHours --prop-type-7 number --prop-nullable-7 true +``` + +--- + +### 4. Social Media Platform + +#### Step 1: Create Profile Object +```bash +lt server object --name Profile \ + --prop-name-0 bio --prop-type-0 string --prop-nullable-0 true \ + --prop-name-1 avatar --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 coverImage --prop-type-2 string --prop-nullable-2 true \ + --prop-name-3 website --prop-type-3 string --prop-nullable-3 true \ + --prop-name-4 location --prop-type-4 string --prop-nullable-4 true +``` + +#### Step 2: Create User with Profile +```bash +lt server module --name User --controller Both \ + --prop-name-0 username --prop-type-0 string \ + --prop-name-1 email --prop-type-1 string \ + --prop-name-2 displayName --prop-type-2 string \ + --prop-name-3 profile --prop-schema-3 Profile \ + --prop-name-4 verified --prop-type-4 boolean +``` + +#### Step 3: Create Post Module +```bash +lt server module --name Post --controller Both \ + --prop-name-0 content --prop-type-0 string \ + --prop-name-1 author --prop-type-1 ObjectId --prop-reference-1 User \ + --prop-name-2 images --prop-type-2 string --prop-array-2 true \ + --prop-name-3 likes --prop-type-3 ObjectId --prop-reference-3 User --prop-array-3 true \ + --prop-name-4 hashtags --prop-type-4 string --prop-array-4 true \ + --prop-name-5 visibility --prop-enum-5 VisibilityEnum +``` + +#### Step 4: Add Features to User +```bash +lt server addProp --type Module --element User \ + --prop-name-0 followers --prop-type-0 ObjectId --prop-reference-0 User --prop-array-0 true \ + --prop-name-1 following --prop-type-1 ObjectId --prop-reference-1 User --prop-array-1 true +``` + +--- + +### 5. Learning Management System + +#### Step 1: Create Course Module +```bash +lt server module --name Course --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 description --prop-type-1 string \ + --prop-name-2 instructor --prop-type-2 ObjectId --prop-reference-2 User \ + --prop-name-3 thumbnail --prop-type-3 string --prop-nullable-3 true \ + --prop-name-4 price --prop-type-4 number \ + --prop-name-5 duration --prop-type-5 number \ + --prop-name-6 level --prop-enum-6 CourseLevelEnum \ + --prop-name-7 tags --prop-type-7 string --prop-array-7 true \ + --prop-name-8 published --prop-type-8 boolean +``` + +#### Step 2: Create Lesson Module +```bash +lt server module --name Lesson --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 content --prop-type-1 string \ + --prop-name-2 course --prop-type-2 ObjectId --prop-reference-2 Course \ + --prop-name-3 order --prop-type-3 number \ + --prop-name-4 duration --prop-type-4 number \ + --prop-name-5 videoUrl --prop-type-5 string --prop-nullable-5 true \ + --prop-name-6 resources --prop-type-6 Json --prop-nullable-6 true +``` + +#### Step 3: Create Enrollment Module +```bash +lt server module --name Enrollment --controller Rest \ + --prop-name-0 student --prop-type-0 ObjectId --prop-reference-0 User \ + --prop-name-1 course --prop-type-1 ObjectId --prop-reference-1 Course \ + --prop-name-2 progress --prop-type-2 number \ + --prop-name-3 completedLessons --prop-type-3 ObjectId --prop-reference-3 Lesson --prop-array-3 true \ + --prop-name-4 enrolledAt --prop-type-4 Date \ + --prop-name-5 completedAt --prop-type-5 Date --prop-nullable-5 true +``` + +--- + +## Adding Properties to Existing Modules + +### Add metadata to Product +```bash +lt server addProp --type Module --element Product \ + --prop-name-0 seo --prop-type-0 Json --prop-nullable-0 true \ + --prop-name-1 dimensions --prop-type-1 Json --prop-nullable-1 true +``` + +### Add timestamps to custom module +```bash +lt server addProp --type Module --element CustomModule \ + --prop-name-0 lastModifiedBy --prop-type-0 ObjectId --prop-reference-0 User \ + --prop-name-1 archivedAt --prop-type-1 Date --prop-nullable-1 true +``` + +### Add social features +```bash +lt server addProp --type Module --element Post \ + --prop-name-0 comments --prop-type-0 ObjectId --prop-reference-0 Comment --prop-array-0 true \ + --prop-name-1 shares --prop-type-1 number \ + --prop-name-2 views --prop-type-2 number +``` + +--- + +## Common Object Patterns + +### Contact Information +```bash +lt server object --name ContactInfo \ + --prop-name-0 email --prop-type-0 string \ + --prop-name-1 phone --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 mobile --prop-type-2 string --prop-nullable-2 true \ + --prop-name-3 fax --prop-type-3 string --prop-nullable-3 true +``` + +### Price Range +```bash +lt server object --name PriceRange \ + --prop-name-0 min --prop-type-0 number \ + --prop-name-1 max --prop-type-1 number \ + --prop-name-2 currency --prop-type-2 string +``` + +### Geo Location +```bash +lt server object --name GeoLocation \ + --prop-name-0 latitude --prop-type-0 number \ + --prop-name-1 longitude --prop-type-1 number \ + --prop-name-2 address --prop-type-2 string --prop-nullable-2 true +``` + +### Media File +```bash +lt server object --name MediaFile \ + --prop-name-0 url --prop-type-0 string \ + --prop-name-1 filename --prop-type-1 string \ + --prop-name-2 mimeType --prop-type-2 string \ + --prop-name-3 size --prop-type-3 number +``` + +--- + +## Fullstack Project Initialization + +### Angular Project +```bash +lt fullstack init \ + --name MyAngularApp \ + --frontend angular \ + --git true \ + --git-link https://github.com/myorg/my-angular-app.git +``` + +### Nuxt Project +```bash +lt fullstack init \ + --name MyNuxtApp \ + --frontend nuxt \ + --git true \ + --git-link https://github.com/myorg/my-nuxt-app.git +``` + +### Local Development (No Git) +```bash +lt fullstack init \ + --name LocalDevProject \ + --frontend angular \ + --git false +``` \ No newline at end of file diff --git a/src/templates/claude-skills/lt-cli/reference.md b/src/templates/claude-skills/lt-cli/reference.md new file mode 100644 index 0000000..d73b039 --- /dev/null +++ b/src/templates/claude-skills/lt-cli/reference.md @@ -0,0 +1,332 @@ +# LT CLI Quick Reference + +## Command Cheat Sheet + +### Module Commands +```bash +# Interactive +lt server module +lt server m + +# Non-interactive +lt server module --name --controller [props] +``` + +### Add Property Commands +```bash +# Interactive +lt server addProp +lt server ap + +# Non-interactive +lt server addProp --type --element [props] +``` + +### Object Commands +```bash +# Interactive +lt server object +lt server o + +# Non-interactive +lt server object --name [props] [--skipLint] +``` + +### Fullstack Commands +```bash +# Interactive +lt fullstack init +lt full init + +# Non-interactive +lt fullstack init --name --frontend --git [--git-link ] +``` + +--- + +## Property Flag Reference + +| Flag | Description | Example | Required | +|------|-------------|---------|----------| +| `--prop-name-X` | Property name | `--prop-name-0 title` | Yes | +| `--prop-type-X` | Property type | `--prop-type-0 string` | No (default: string) | +| `--prop-nullable-X` | Is optional | `--prop-nullable-0 true` | No (default: false) | +| `--prop-array-X` | Is array | `--prop-array-0 true` | No (default: false) | +| `--prop-enum-X` | Enum reference | `--prop-enum-0 StatusEnum` | No | +| `--prop-schema-X` | Object reference | `--prop-schema-0 Address` | No | +| `--prop-reference-X` | ObjectId reference | `--prop-reference-0 User` | Yes (with ObjectId) | + +--- + +## Type Mapping + +### Primitive Types +| Type | TypeScript | MongoDB | Use Case | +|------|-----------|---------|----------| +| `string` | `string` | String | Text, names, descriptions | +| `number` | `number` | Number | Integers, floats, counts | +| `boolean` | `boolean` | Boolean | Flags, toggles | +| `Date` | `Date` | Date | Timestamps, dates | +| `bigint` | `bigint` | Long | Large integers | + +### Special Types +| Type | Model Type | Input Type | Notes | +|------|-----------|-----------|--------| +| `ObjectId` | `Reference` | `ReferenceInput` | Requires `--prop-reference-X` | +| `Json` | `JSON` | `JSON` | Flexible metadata | +| Custom Object | `` | `Input` | Requires `--prop-schema-X` | +| Custom Enum | `Enum` | `Enum` | Requires `--prop-enum-X` | + +--- + +## Decorator Reference + +### Model Decorators +```typescript +@Prop() // MongoDB property +@UnifiedField() // GraphQL + REST +@Restricted(RoleEnum.XXX) // Access control +``` + +### Input Decorators +```typescript +@UnifiedField() // GraphQL + REST +@IsOptional() // Validation +@IsEmail() // Email validation +@IsString() // String validation +``` + +--- + +## File Structure Reference + +### Module Structure +``` +src/server/modules// +├── .model.ts # MongoDB schema +├── .service.ts # Business logic +├── .controller.ts # REST endpoints +├── .resolver.ts # GraphQL resolver +├── .module.ts # NestJS module +├── inputs/ +│ ├── .input.ts # Update DTO +│ └── -create.input.ts # Create DTO +└── outputs/ + └── find-and-count-s-result.output.ts +``` + +### Object Structure +``` +src/server/common/objects// +├── .object.ts # Object class +├── .input.ts # Update DTO +└── -create.input.ts # Create DTO +``` + +--- + +## Common Command Patterns + +### Simple Module (No Properties) +```bash +lt server module --name Category --controller Rest +``` + +### Module with Basic Properties +```bash +lt server module --name Product --controller Both \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 price --prop-type-1 number \ + --prop-name-2 active --prop-type-2 boolean +``` + +### Module with Nullable Property +```bash +lt server module --name Post --controller GraphQL \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 subtitle --prop-type-1 string --prop-nullable-1 true +``` + +### Module with Array Property +```bash +lt server module --name Article --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 tags --prop-type-1 string --prop-array-1 true +``` + +### Module with ObjectId Reference +```bash +lt server module --name Comment --controller Rest \ + --prop-name-0 content --prop-type-0 string \ + --prop-name-1 author --prop-type-1 ObjectId --prop-reference-1 User +``` + +### Module with Schema/Object +```bash +lt server module --name Company --controller Both \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 address --prop-schema-1 Address +``` + +### Module with Enum +```bash +lt server module --name Order --controller Both \ + --prop-name-0 orderNumber --prop-type-0 string \ + --prop-name-1 status --prop-enum-1 OrderStatusEnum +``` + +### Module with JSON Metadata +```bash +lt server module --name Product --controller Both \ + --prop-name-0 name --prop-type-0 string \ + --prop-name-1 metadata --prop-type-1 Json --prop-nullable-1 true +``` + +### Complex Module (Multiple Property Types) +```bash +lt server module --name Event --controller Both \ + --prop-name-0 title --prop-type-0 string \ + --prop-name-1 description --prop-type-1 string --prop-nullable-1 true \ + --prop-name-2 organizer --prop-type-2 ObjectId --prop-reference-2 User \ + --prop-name-3 attendees --prop-type-3 ObjectId --prop-reference-3 User --prop-array-3 true \ + --prop-name-4 startDate --prop-type-4 Date \ + --prop-name-5 endDate --prop-type-5 Date --prop-nullable-5 true \ + --prop-name-6 location --prop-schema-6 Location \ + --prop-name-7 status --prop-enum-7 EventStatusEnum \ + --prop-name-8 tags --prop-type-8 string --prop-array-8 true \ + --prop-name-9 metadata --prop-type-9 Json --prop-nullable-9 true +``` + +--- + +## Troubleshooting Guide + +### Error: Cannot read properties of undefined (reading 'getChildIndex') +**Cause**: Input files have no existing properties (fixed in latest version) +**Solution**: Update to latest CLI version or ensure files have at least one property + +### Error: Module directory already exists +**Cause**: Trying to create a module that already exists +**Solution**: Use `lt server addProp` instead + +### Error: No src directory found +**Cause**: Running command outside of project directory +**Solution**: Navigate to project directory (anywhere inside works) + +### TypeScript Errors: Cannot find name 'Reference' +**Cause**: Missing imports for referenced modules +**Solution**: Manually add imports: +```typescript +import { Reference } from '@lenne.tech/nest-server'; +import { User } from '../../user/user.model'; +``` + +### Property Index Mismatch +**Cause**: Using different indices for same property +**Wrong**: +```bash +--prop-name-1 company --prop-type-0 string +``` +**Correct**: +```bash +--prop-name-1 company --prop-type-1 string +``` + +### Boolean Value Errors +**Wrong**: `--prop-nullable-0 True` or `--prop-nullable-0 TRUE` +**Correct**: `--prop-nullable-0 true` (lowercase) + +--- + +## Best Practices Checklist + +- [ ] Plan data model before generating +- [ ] Create objects for reusable structures first +- [ ] Use meaningful, descriptive names +- [ ] Create referenced modules before referencing them +- [ ] Start with one API type (Rest or GraphQL) +- [ ] Mark only truly optional fields as nullable +- [ ] Use arrays for collections +- [ ] Use JSON for flexible/extensible data +- [ ] Create enums before using them +- [ ] Run lint after generation +- [ ] Test incrementally +- [ ] Commit after successful generation +- [ ] Review generated code before modifying +- [ ] Add custom business logic in services +- [ ] Document complex relationships + +--- + +## Naming Conventions + +### Modules & Objects +- **Format**: PascalCase +- **Examples**: `User`, `BlogPost`, `OrderItem`, `ProductCategory` + +### Properties +- **Format**: camelCase +- **Examples**: `firstName`, `emailAddress`, `isActive`, `createdAt` + +### Enum Names +- **Format**: PascalCase + "Enum" suffix +- **Examples**: `UserStatusEnum`, `OrderStatusEnum`, `PriorityEnum` + +### File Names +- **Format**: kebab-case +- **Examples**: `user.model.ts`, `blog-post.service.ts`, `order-item.input.ts` + +--- + +## Controller Type Decision Guide + +### Choose REST when: +- Building traditional CRUD APIs +- Simple data fetching needs +- RESTful conventions are preferred +- Mobile/web clients expect REST + +### Choose GraphQL when: +- Complex data relationships +- Frontend needs flexible queries +- Reducing over-fetching/under-fetching +- Real-time subscriptions needed + +### Choose Both when: +- Supporting multiple client types +- Gradual migration from REST to GraphQL +- Maximum flexibility required +- Unsure about future requirements + +--- + +## Related Technologies + +### Dependencies +- **NestJS**: Node.js framework +- **Mongoose**: MongoDB ODM +- **GraphQL**: Query language +- **TypeScript**: Type-safe JavaScript +- **ts-morph**: TypeScript AST manipulation + +### Generated Decorators +- `@Prop()`: Mongoose schema definition +- `@UnifiedField()`: GraphQL + REST exposure +- `@Restricted()`: Access control +- `@IsOptional()`: Validation +- `@Field()`: GraphQL field + +--- + +## Quick Tips + +1. **Use indices consistently**: All flags for one property use same index +2. **ObjectId always needs reference**: `--prop-reference-X` is required +3. **Quote special characters**: Wrap values with spaces in quotes +4. **Lowercase booleans**: Use `true`/`false`, not `True`/`FALSE` +5. **Run from anywhere**: CLI finds `src/` automatically +6. **Check before creating**: Use `addProp` for existing modules +7. **Plan relationships**: Create referenced modules first +8. **Use objects for reuse**: Don't duplicate structures +9. **Start simple**: Add complexity incrementally +10. **Commit often**: Save after each successful generation \ No newline at end of file