rapidkit 0.21.2 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import {d,c as c$1,g,a as a$1}from'./chunk-D2ZRDZOE.js';import {d as d$1,b as b$1}from'./chunk-RV6HBTFC.js';import {c,a,b}from'./chunk-7LU4Z66R.js';import {Command,Option}from'commander';import u from'chalk';import ue from'inquirer';import w from'path';import {exec,spawn}from'child_process';import S,{promises}from'fs';import*as zt from'os';import zt__default from'os';import {fileURLToPath,pathToFileURL}from'url';import Xt from'validate-npm-package-name';import*as k from'fs-extra';import k__default from'fs-extra';import {execa}from'execa';import Fe from'ora';import lo from'nunjucks';import uo from'crypto';import {promisify}from'util';var Yt=".rapidkitrc.json",Qt=["rapidkit.config.js","rapidkit.config.mjs","rapidkit.config.cjs"];async function z(){let e=w.join(zt__default.homedir(),Yt);try{let o=await promises.readFile(e,"utf-8"),t=JSON.parse(o);return a.debug(`Loaded config from ${e}`),t}catch{return a.debug("No user config found, using defaults"),{}}}async function Ze(e=process.cwd()){let o=e,t=w.parse(o).root;for(;o!==t;){for(let n of Qt){let i=w.join(o,n);try{await promises.access(i),a.debug(`Found config file: ${i}`);let s=await import(pathToFileURL(i).href),c=s.default||s;return a.debug(`Loaded RapidKit config from ${n}`),c}catch{continue}}o=w.dirname(o);}return a.debug("No RapidKit config file found, using defaults"),{}}function et(e,o,t){return {author:t.author||o.workspace?.defaultAuthor||e.author,pythonVersion:o.workspace?.pythonVersion||e.pythonVersion,defaultInstallMethod:t.defaultInstallMethod||o.workspace?.installMethod||e.defaultInstallMethod,defaultKit:t.defaultKit||o.projects?.defaultKit||e.defaultKit,skipGit:t.skipGit??o.projects?.skipGit??e.skipGit,license:t.license||e.license,testRapidKitPath:t.testRapidKitPath||e.testRapidKitPath}}function he(e){return process.env.RAPIDKIT_DEV_PATH||e.testRapidKitPath||void 0}var F=class extends Error{constructor(t,n,i){super(t);this.code=n;this.details=i;this.name="RapidKitError",Error.captureStackTrace(this,this.constructor);}},Z=class extends F{constructor(o,t){let n=t?`Python ${o}+ required, found ${t}`:`Python ${o}+ not found`;super(n,"PYTHON_NOT_FOUND","Please install Python from https://www.python.org/downloads/");}},re=class extends F{constructor(){super("Poetry is not installed","POETRY_NOT_FOUND","Install Poetry from https://python-poetry.org/docs/#installation");}},se=class extends F{constructor(){super("pipx is not installed","PIPX_NOT_FOUND","Install pipx from https://pypa.github.io/pipx/installation/");}},ye=class extends F{constructor(o){super(`Directory "${o}" already exists`,"DIRECTORY_EXISTS","Please choose a different name or remove the existing directory");}},H=class extends F{constructor(o,t){super(`Invalid project name: "${o}"`,"INVALID_PROJECT_NAME",t);}},K=class extends F{constructor(o,t){let n=`Installation failed at: ${o}`,i=`${t.message}
2
+ import {e,d,c as c$1,h as h$1,a as a$1}from'./chunk-74G6C57B.js';import {d as d$1,b as b$2}from'./chunk-RV6HBTFC.js';import {c,a,b as b$1}from'./chunk-7LU4Z66R.js';import {Command,Option}from'commander';import g from'chalk';import te from'inquirer';import h from'path';import {fileURLToPath,pathToFileURL}from'url';import {exec,spawn}from'child_process';import P,{promises}from'fs';import*as wo from'os';import wo__default from'os';import ko from'validate-npm-package-name';import*as b from'fs-extra';import b__default from'fs-extra';import {execa}from'execa';import Qe from'ora';import Ao from'nunjucks';import jo from'crypto';import {promisify}from'util';var vo=".rapidkitrc.json",bo=["rapidkit.config.js","rapidkit.config.mjs","rapidkit.config.cjs"];async function Q(){let e=h.join(wo__default.homedir(),vo);try{let o=await promises.readFile(e,"utf-8"),t=JSON.parse(o);return a.debug(`Loaded config from ${e}`),t}catch{return a.debug("No user config found, using defaults"),{}}}async function yt(e=process.cwd()){let o=e,t=h.parse(o).root;for(;o!==t;){for(let n of bo){let i=h.join(o,n);try{await promises.access(i),a.debug(`Found config file: ${i}`);let a$1=await import(pathToFileURL(i).href),c=a$1.default||a$1;return a.debug(`Loaded RapidKit config from ${n}`),c}catch{continue}}o=h.dirname(o);}return a.debug("No RapidKit config file found, using defaults"),{}}function vt(e,o,t){return {author:t.author||o.workspace?.defaultAuthor||e.author,pythonVersion:o.workspace?.pythonVersion||e.pythonVersion,defaultInstallMethod:t.defaultInstallMethod||o.workspace?.installMethod||e.defaultInstallMethod,defaultKit:t.defaultKit||o.projects?.defaultKit||e.defaultKit,skipGit:t.skipGit??o.projects?.skipGit??e.skipGit,license:t.license||e.license,testRapidKitPath:t.testRapidKitPath||e.testRapidKitPath}}function Re(e){return process.env.RAPIDKIT_DEV_PATH||e.testRapidKitPath||void 0}var F=class extends Error{constructor(t,n,i){super(t);this.code=n;this.details=i;this.name="RapidKitError",Error.captureStackTrace(this,this.constructor);}},re=class extends F{constructor(o,t){let n=t?`Python ${o}+ required, found ${t}`:`Python ${o}+ not found`;super(n,"PYTHON_NOT_FOUND","Please install Python from https://www.python.org/downloads/");}},de=class extends F{constructor(){super("Poetry is not installed","POETRY_NOT_FOUND","Install Poetry from https://python-poetry.org/docs/#installation");}},le=class extends F{constructor(){super("pipx is not installed","PIPX_NOT_FOUND","Install pipx from https://pypa.github.io/pipx/installation/");}},Se=class extends F{constructor(o){super(`Directory "${o}" already exists`,"DIRECTORY_EXISTS","Please choose a different name or remove the existing directory");}},z=class extends F{constructor(o,t){super(`Invalid project name: "${o}"`,"INVALID_PROJECT_NAME",t);}},M=class extends F{constructor(o,t){let n=`Installation failed at: ${o}`,i=`${t.message}
3
3
 
4
4
  Troubleshooting:
5
5
  - Check your internet connection
6
6
  - Verify Python/Poetry installation
7
- - Try running with --debug flag for more details`;super(n,"INSTALLATION_ERROR",i);}},ee=class extends F{constructor(){super("RapidKit Python package is not yet available on PyPI","RAPIDKIT_NOT_AVAILABLE",`Available options:
7
+ - Try running with --debug flag for more details`;super(n,"INSTALLATION_ERROR",i);}},ne=class extends F{constructor(){super("RapidKit Python package is not yet available on PyPI","RAPIDKIT_NOT_AVAILABLE",`Available options:
8
8
  1. Install Python 3.10+ and retry the same command
9
9
  2. Use the core workflow: npx rapidkit create workspace <name>
10
10
  3. Offline fallback (limited): npx rapidkit create project fastapi.standard <name> --output .
11
11
 
12
- Legacy: set RAPIDKIT_SHOW_LEGACY=1 to reveal template-mode flags in help.`);}};function Me(e){let o=Xt(e);if(!o.validForNewPackages){let n=o.errors||[],i=o.warnings||[],r=[...n,...i];throw new H(e,`NPM validation failed: ${r.join(", ")}`)}if(!/^[a-z][a-z0-9_-]*$/.test(e))throw new H(e,"Must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores");if(["test","tests","src","dist","build","lib","python","pip","poetry","node","npm","rapidkit","rapidkit"].includes(e.toLowerCase()))throw new H(e,`"${e}" is a reserved name. Please choose a different name.`);if(e.length<2)throw new H(e,"Name must be at least 2 characters long");if(e.length>214)throw new H(e,"Name must be less than 214 characters");return true}function eo(e){return typeof e=="object"&&e!==null}async function to(e,o,t,n=8e3){try{let i=await execa(e,o,{cwd:t,timeout:n,reject:false,stdio:"pipe"});return {ok:i.exitCode===0,exitCode:i.exitCode,stdout:i.stdout,stderr:i.stderr}}catch(i){return {ok:false,exitCode:void 0,stdout:"",stderr:i instanceof Error?i.message:String(i)}}}async function oo(e,o){let t=["-m","rapidkit",...e],n=["python3","python"];for(let i of n){let r=await to(i,t,o?.cwd,o?.timeoutMs);if(!r.ok)continue;let s=(r.stdout??"").trim();try{let c=JSON.parse(s);return eo(c)?{ok:true,command:i,exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,data:c}:{ok:false,command:i,exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr}}catch{return {ok:false,command:i,exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr}}}return {ok:false}}async function tt(e,o){let t=await oo(["project","detect","--path",e,"--json"],o);return !t.ok||!t.data||t.data.schema_version!==1?{ok:false,command:t.command,exitCode:t.exitCode,stdout:t.stdout,stderr:t.stderr}:t}function we(){return process.platform==="win32"?"python":"python3"}async function it(e,o,t,n){let i=d$1(o,c(),t);n&&(i.metadata||(i.metadata={}),i.metadata.python={version:n}),await b$1(e,i);}async function rt(e){await k.outputFile(w.join(e,".gitignore"),`.venv/
12
+ Legacy: set RAPIDKIT_SHOW_LEGACY=1 to reveal template-mode flags in help.`);}};function Je(e){let o=ko(e);if(!o.validForNewPackages){let n=o.errors||[],i=o.warnings||[],r=[...n,...i];throw new z(e,`NPM validation failed: ${r.join(", ")}`)}if(!/^[a-z][a-z0-9_-]*$/.test(e))throw new z(e,"Must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores");if(["test","tests","src","dist","build","lib","python","pip","poetry","node","npm","rapidkit","rapidkit"].includes(e.toLowerCase()))throw new z(e,`"${e}" is a reserved name. Please choose a different name.`);if(e.length<2)throw new z(e,"Name must be at least 2 characters long");if(e.length>214)throw new z(e,"Name must be less than 214 characters");return true}function So(e){return typeof e=="object"&&e!==null}async function xo(e,o,t,n=8e3){try{let i=await execa(e,o,{cwd:t,timeout:n,reject:false,stdio:"pipe"});return {ok:i.exitCode===0,exitCode:i.exitCode,stdout:i.stdout,stderr:i.stderr}}catch(i){return {ok:false,exitCode:void 0,stdout:"",stderr:i instanceof Error?i.message:String(i)}}}async function _o(e,o){let t=["-m","rapidkit",...e],n=["python3","python"];for(let i of n){let r=await xo(i,t,o?.cwd,o?.timeoutMs);if(!r.ok)continue;let a=(r.stdout??"").trim();try{let c=JSON.parse(a);return So(c)?{ok:true,command:i,exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr,data:c}:{ok:false,command:i,exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr}}catch{return {ok:false,command:i,exitCode:r.exitCode,stdout:r.stdout,stderr:r.stderr}}}return {ok:false}}async function bt(e,o){let t=await _o(["project","detect","--path",e,"--json"],o);return !t.ok||!t.data||t.data.schema_version!==1?{ok:false,command:t.command,exitCode:t.exitCode,stdout:t.stdout,stderr:t.stderr}:t}function xe(){return process.platform==="win32"?"python":"python3"}async function St(e,o,t,n){let i=d$1(o,c(),t);n&&(i.metadata||(i.metadata={}),i.metadata.python={version:n}),await b$2(e,i);}async function xt(e){await b.outputFile(h.join(e,".gitignore"),`.venv/
13
13
  __pycache__/
14
14
  *.pyc
15
15
  .env
16
16
  .rapidkit-workspace/
17
17
 
18
- `,"utf-8");}async function ot(e){try{let{stdout:o}=await execa(e,["--version"],{timeout:3e3}),t=o.match(/Python (\d+\.\d+\.\d+)/);if(t)return t[1]}catch{}return null}async function no(e,o){try{await promises.writeFile(w.join(e,".python-version"),`${o}
19
- `,"utf-8"),a.debug(`Created .python-version with ${o}`);}catch(t){a.warn(`Failed to create .python-version: ${t}`);}}function ae(){let e=w.join(zt.homedir(),".local","bin"),t=(process.env.PATH||"").split(w.delimiter).filter(Boolean);t.includes(e)||(process.env.PATH=[e,...t].join(w.delimiter));}async function ke(e,o){ae(),e.start("Checking pipx installation");try{return await execa("pipx",["--version"]),e.succeed("pipx found"),{kind:"binary"}}catch{}let t=we();try{return await execa(t,["-m","pipx","--version"]),e.succeed("pipx found"),{kind:"python-module",pythonCmd:t}}catch{}if(o)throw new se;let{installPipx:n}=await ue.prompt([{type:"confirm",name:"installPipx",message:"pipx is not installed. Install it now (user install via python -m pip)?",default:true}]);if(!n)throw new se;e.start("Installing pipx (user install)");try{try{await execa(t,["-m","pip","install","--user","--upgrade","pip"]);}catch{}await execa(t,["-m","pip","install","--user","--upgrade","pipx"]);}catch(i){let r=i,s=String(r?.stderr||r?.shortMessage||r?.message||"");throw new K("Install pipx with python -m pip",i instanceof Error?i:new Error(s))}e.succeed("pipx installed"),ae();try{return await execa(t,["-m","pipx","--version"]),{kind:"python-module",pythonCmd:t}}catch(i){let r=i,s=String(r?.stderr||r?.shortMessage||r?.message||"pipx not runnable after install");throw new K("Verify pipx after install",new Error(`${s}
18
+ `,"utf-8");}async function kt(e){try{let{stdout:o}=await execa(e,["--version"],{timeout:3e3}),t=o.match(/Python (\d+\.\d+\.\d+)/);if(t)return t[1]}catch{}return null}async function Co(e,o){try{await promises.writeFile(h.join(e,".python-version"),`${o}
19
+ `,"utf-8"),a.debug(`Created .python-version with ${o}`);}catch(t){a.warn(`Failed to create .python-version: ${t}`);}}function pe(){let e=h.join(wo.homedir(),".local","bin"),t=(process.env.PATH||"").split(h.delimiter).filter(Boolean);t.includes(e)||(process.env.PATH=[e,...t].join(h.delimiter));}async function _e(e,o){pe(),e.start("Checking pipx installation");try{return await execa("pipx",["--version"]),e.succeed("pipx found"),{kind:"binary"}}catch{}let t=xe();try{return await execa(t,["-m","pipx","--version"]),e.succeed("pipx found"),{kind:"python-module",pythonCmd:t}}catch{}if(o)throw new le;let{installPipx:n}=await te.prompt([{type:"confirm",name:"installPipx",message:"pipx is not installed. Install it now (user install via python -m pip)?",default:true}]);if(!n)throw new le;e.start("Installing pipx (user install)");try{try{await execa(t,["-m","pip","install","--user","--upgrade","pip"]);}catch{}await execa(t,["-m","pip","install","--user","--upgrade","pipx"]);}catch(i){let r=i,a=String(r?.stderr||r?.shortMessage||r?.message||"");throw new M("Install pipx with python -m pip",i instanceof Error?i:new Error(a))}e.succeed("pipx installed"),pe();try{return await execa(t,["-m","pipx","--version"]),{kind:"python-module",pythonCmd:t}}catch(i){let r=i,a=String(r?.stderr||r?.shortMessage||r?.message||"pipx not runnable after install");throw new M("Verify pipx after install",new Error(`${a}
20
20
 
21
- Try reopening your terminal or run: python3 -m pipx ensurepath`))}}async function te(e,o){return e.kind==="binary"?execa("pipx",o):execa(e.pythonCmd,["-m","pipx",...o])}async function io(e,o){ae(),e.start("Checking Poetry installation");try{await execa("poetry",["--version"]),e.succeed("Poetry found");return}catch{}if(o)throw new re;let{installPoetry:t}=await ue.prompt([{type:"confirm",name:"installPoetry",message:"Poetry is not installed. Install it now using pipx?",default:true}]);if(!t)throw new re;let n=await ke(e,o);e.start("Installing Poetry with pipx");try{await te(n,["install","poetry"]);}catch(i){let r=i,s=String(r?.stderr||r?.shortMessage||r?.message||"");if(/already\s+installed|already\s+seems\s+to\s+be\s+installed|exists/i.test(s))try{await te(n,["upgrade","poetry"]);}catch{}else throw new K("Install Poetry with pipx",i instanceof Error?i:new Error(s))}e.succeed("Poetry installed"),ae();try{await execa("poetry",["--version"]);}catch(i){let r=i,s=String(r?.stderr||r?.shortMessage||r?.message||"Poetry not found on PATH");throw new K("Verify Poetry after pipx install",new Error(`${s}
21
+ Try reopening your terminal or run: python3 -m pipx ensurepath`))}}async function ie(e,o){return e.kind==="binary"?execa("pipx",o):execa(e.pythonCmd,["-m","pipx",...o])}async function Io(e,o){pe(),e.start("Checking Poetry installation");try{await execa("poetry",["--version"]),e.succeed("Poetry found");return}catch{}if(o)throw new de;let{installPoetry:t}=await te.prompt([{type:"confirm",name:"installPoetry",message:"Poetry is not installed. Install it now using pipx?",default:true}]);if(!t)throw new de;let n=await _e(e,o);e.start("Installing Poetry with pipx");try{await ie(n,["install","poetry"]);}catch(i){let r=i,a=String(r?.stderr||r?.shortMessage||r?.message||"");if(/already\s+installed|already\s+seems\s+to\s+be\s+installed|exists/i.test(a))try{await ie(n,["upgrade","poetry"]);}catch{}else throw new M("Install Poetry with pipx",i instanceof Error?i:new Error(a))}e.succeed("Poetry installed"),pe();try{await execa("poetry",["--version"]);}catch(i){let r=i,a=String(r?.stderr||r?.shortMessage||r?.message||"Poetry not found on PATH");throw new M("Verify Poetry after pipx install",new Error(`${a}
22
22
 
23
- Poetry may be installed but not on PATH yet. Try reopening your terminal or run: pipx ensurepath`))}}function ro(e){let o=e==="poetry";return `#!/usr/bin/env sh
23
+ Poetry may be installed but not on PATH yet. Try reopening your terminal or run: pipx ensurepath`))}}function Eo(e){let o=e==="poetry";return `#!/usr/bin/env sh
24
24
  set -eu
25
25
 
26
26
  SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
@@ -39,7 +39,7 @@ echo "- If you used venv: ensure .venv exists (or re-run the installer)." 1>&2
39
39
  ${o?`echo "- If you used Poetry: run 'poetry install' and retry, or activate the env." 1>&2
40
40
  `:""}echo "Tip: you can also run: ./.venv/bin/rapidkit --help" 1>&2
41
41
  exit 1
42
- `}function so(e){return `@echo off
42
+ `}function Po(e){return `@echo off
43
43
  setlocal
44
44
 
45
45
  set "SCRIPT_DIR=%~dp0"
@@ -58,42 +58,42 @@ if %ERRORLEVEL%==0 if exist "%SCRIPT_DIR%\\pyproject.toml" (
58
58
  `:""}echo RapidKit launcher could not find a local Python CLI. 1>&2
59
59
  echo Tip: run .venv\\Scripts\\rapidkit.exe --help 1>&2
60
60
  exit /b 1
61
- `}async function st(e,o){await k.outputFile(w.join(e,"rapidkit"),ro(o),{encoding:"utf-8",mode:493}),await k.outputFile(w.join(e,"rapidkit.cmd"),so(o),"utf-8");}async function ve(e,o){let{skipGit:t=false,testMode:n=false,demoMode:i=false,dryRun:r=false,yes:s=false,userConfig:c={},installMethod:a$1}=o,l=e||"rapidkit",d=w.resolve(process.cwd(),l);if(await k.pathExists(d))throw new ye(l);if(r){await co(d,l,i,c);return}if(i){await ao(d,l,t);return}let m=s?{pythonVersion:c.pythonVersion||"3.10",installMethod:a$1||c.defaultInstallMethod||"poetry"}:await ue.prompt([{type:"list",name:"pythonVersion",message:"Select Python version for RapidKit:",choices:["3.10","3.11","3.12"],default:c.pythonVersion||"3.10"},{type:"list",name:"installMethod",message:"How would you like to install RapidKit?",choices:[{name:"\u{1F3AF} Poetry (Recommended - includes virtual env)",value:"poetry"},{name:"\u{1F4E6} pip with venv (Standard)",value:"venv"},{name:"\u{1F527} pipx (Global isolated install)",value:"pipx"}],default:c.defaultInstallMethod||"poetry"}]);a.step(1,3,"Setting up RapidKit environment");let p=Fe("Creating directory").start();try{await k.ensureDir(d),p.succeed("Directory created"),p.start("Detecting Python version");let y=null,P=await at(m.pythonVersion);if(P)y=await ot(P),y?(a.info(` Detected Python ${y}`),p.succeed(`Python ${y} detected`)):p.warn("Could not detect exact Python version");else {let v=we();y=await ot(v),y?p.succeed(`Python ${y} detected`):p.warn("Could not detect Python version, proceeding with defaults");}if(await it(d,l,m.installMethod,y||void 0),y&&await no(d,y),await rt(d),m.installMethod==="poetry")try{await ct(d,m.pythonVersion,p,n,c,s);}catch(v){let C=v?.details||v?.message||String(v);if(C.includes("pyenv")||C.includes("exit status 127")||C.includes("returned non-zero exit status 127")){p.warn("Poetry encountered Python discovery issues, trying venv method"),a.debug(`Poetry error (attempting venv fallback): ${C}`);try{await De(d,m.pythonVersion,p,n,c),m.installMethod="venv";}catch(M){throw M}}else throw v}else m.installMethod==="venv"?await De(d,m.pythonVersion,p,n,c):await lt(d,p,n,c,s);if(await st(d,m.installMethod),await dt(d,m.installMethod),p.succeed("RapidKit environment ready!"),!o.skipGit){p.start("Initializing git repository");try{await execa("git",["init"],{cwd:d}),await execa("git",["add","."],{cwd:d}),await execa("git",["commit","-m","Initial commit: RapidKit environment"],{cwd:d}),p.succeed("Git repository initialized");}catch{p.warn("Could not initialize git repository");}}try{let{registerWorkspace:v}=await import('./workspace-LZZGJRGV.js');await v(d,l);}catch{console.warn(u.gray("Note: Could not register workspace in shared registry"));}if(console.log(u.green(`
61
+ `}async function _t(e,o){await b.outputFile(h.join(e,"rapidkit"),Eo(o),{encoding:"utf-8",mode:493}),await b.outputFile(h.join(e,"rapidkit.cmd"),Po(o),"utf-8");}async function Ce(e,o){let{skipGit:t=false,testMode:n=false,demoMode:i=false,dryRun:r=false,yes:a$1=false,userConfig:c={},installMethod:s}=o,d=e||"rapidkit",l=h.resolve(process.cwd(),d);if(await b.pathExists(l))throw new Se(d);if(r){await Oo(l,d,i,c);return}if(i){await To(l,d,t);return}let u=a$1?{pythonVersion:c.pythonVersion||"3.10",installMethod:s||c.defaultInstallMethod||"poetry"}:await te.prompt([{type:"list",name:"pythonVersion",message:"Select Python version for RapidKit:",choices:["3.10","3.11","3.12"],default:c.pythonVersion||"3.10"},{type:"list",name:"installMethod",message:"How would you like to install RapidKit?",choices:[{name:"\u{1F3AF} Poetry (Recommended - includes virtual env)",value:"poetry"},{name:"\u{1F4E6} pip with venv (Standard)",value:"venv"},{name:"\u{1F527} pipx (Global isolated install)",value:"pipx"}],default:c.defaultInstallMethod||"poetry"}]);a.step(1,3,"Setting up RapidKit environment");let p=Qe("Creating directory").start();try{await b.ensureDir(l),p.succeed("Directory created"),p.start("Detecting Python version");let k=null,R=await Ct(u.pythonVersion);if(R)k=await kt(R),k?(a.info(` Detected Python ${k}`),p.succeed(`Python ${k} detected`)):p.warn("Could not detect exact Python version");else {let w=xe();k=await kt(w),k?p.succeed(`Python ${k} detected`):p.warn("Could not detect Python version, proceeding with defaults");}if(await St(l,d,u.installMethod,k||void 0),k&&await Co(l,k),await xt(l),u.installMethod==="poetry")try{await It(l,u.pythonVersion,p,n,c,a$1);}catch(w){let x=w?.details||w?.message||String(w);if(x.includes("pyenv")||x.includes("exit status 127")||x.includes("returned non-zero exit status 127")){p.warn("Poetry encountered Python discovery issues, trying venv method"),a.debug(`Poetry error (attempting venv fallback): ${x}`);try{await ze(l,u.pythonVersion,p,n,c),u.installMethod="venv";}catch(G){throw G}}else throw w}else u.installMethod==="venv"?await ze(l,u.pythonVersion,p,n,c):await Et(l,p,n,c,a$1);if(await _t(l,u.installMethod),await Pt(l,u.installMethod),p.succeed("RapidKit environment ready!"),!o.skipGit){p.start("Initializing git repository");try{await execa("git",["init"],{cwd:l}),await execa("git",["add","."],{cwd:l}),await execa("git",["commit","-m","Initial commit: RapidKit environment"],{cwd:l}),p.succeed("Git repository initialized");}catch{p.warn("Could not initialize git repository");}}try{let{registerWorkspace:w}=await import('./workspace-LZZGJRGV.js');await w(l,d);}catch{console.warn(g.gray("Note: Could not register workspace in shared registry"));}if(console.log(g.green(`
62
62
  \u2728 RapidKit environment created successfully!
63
- `)),console.log(u.cyan("\u{1F4C2} Location:"),u.white(d)),console.log(u.cyan(`\u{1F680} Get started:
64
- `)),console.log(u.white(` cd ${l}`)),m.installMethod==="poetry"){let v="source $(poetry env info --path)/bin/activate";try{ae();let{stdout:C}=await execa("poetry",["--version"]),E=C.match(/Poetry.*?(\d+)\.(\d+)/);E&&(parseInt(E[1])>=2?v="source $(poetry env info --path)/bin/activate":v="poetry shell");}catch{}console.log(u.white(` ${v} # Or: poetry run rapidkit`)),console.log(u.white(" rapidkit create # Interactive mode")),console.log(u.white(" cd <project-name> && rapidkit init && rapidkit dev"));}else m.installMethod==="venv"?(console.log(u.white(" source .venv/bin/activate # On Windows: .venv\\Scripts\\activate")),console.log(u.white(" rapidkit create # Interactive mode")),console.log(u.white(" cd <project-name> && rapidkit init && rapidkit dev"))):(console.log(u.white(" rapidkit create # Interactive mode")),console.log(u.white(" cd <project-name> && rapidkit init && rapidkit dev")));console.log(u.white(`
65
- \u{1F4A1} For more information, check the README.md file.`)),console.log(u.cyan(`
66
- \u{1F4DA} RapidKit commands:`)),console.log(u.white(" rapidkit create - Create a new project (interactive)")),console.log(u.white(" rapidkit dev - Run development server")),console.log(u.white(" rapidkit add module <name> - Add a module (e.g., settings)")),console.log(u.white(" rapidkit list - List available kits")),console.log(u.white(" rapidkit modules - List available modules")),console.log(u.white(` rapidkit --help - Show all commands
67
- `));}catch(y){p.fail("Failed to create RapidKit environment"),console.error(u.red(`
68
- \u274C Error:`),y);try{await k.remove(d);}catch{}throw y}}async function at(e){let o=[];try{let{stdout:t}=await execa("pyenv",["root"]),n=t.trim();o.push(w.join(n,"versions",`${e}.*`,"bin","python"));let[i,r]=e.split(".");o.push(w.join(n,"versions",`${i}.${r}.*`,"bin","python"));}catch{}o.push(`python${e}`,`python3.${e.split(".")[1]}`,"python3","python"),o.push(`/usr/bin/python${e}`,"/usr/bin/python3",`/usr/local/bin/python${e}`,"/usr/local/bin/python3");for(let t of o)try{let n=t;if(t.includes("*")){if(n=(await execa("sh",["-c",`ls -d ${t} 2>/dev/null | head -1`])).stdout.trim(),!n)continue;n=w.join(n.split("/").slice(0,-1).join("/"),"../bin/python");}let{stdout:i}=await execa(n,["--version"],{timeout:2e3}),r=i.match(/Python (\d+\.\d+)/)?.[1];if(r&&parseFloat(r)>=parseFloat(e))return await execa(n,["-c","import sys; sys.exit(0)"],{timeout:2e3}),n}catch{continue}return null}async function ct(e,o,t,n,i,r=false){await io(t,r),t.start("Finding Python interpreter");let s=await at(o);s?(a.debug(`Found working Python: ${s}`),t.succeed("Python found")):t.warn("Could not verify Python path, proceeding with default"),t.start("Initializing Poetry project"),await execa("poetry",["init","--no-interaction","--python",`^${o}`],{cwd:e}),t.succeed("Poetry project initialized");let c=w.join(e,"pyproject.toml"),l=await promises.readFile(c,"utf-8");l.includes("[tool.poetry]")?l=l.replace("[tool.poetry]",`[tool.poetry]
69
- package-mode = false`):l.includes("[project]")&&(l.includes("[build-system]")?l=l.replace("[build-system]",`
63
+ `)),console.log(g.cyan("\u{1F4C2} Location:"),g.white(l)),console.log(g.cyan(`\u{1F680} Get started:
64
+ `)),console.log(g.white(` cd ${d}`)),u.installMethod==="poetry"){let w="source $(poetry env info --path)/bin/activate";try{pe();let{stdout:x}=await execa("poetry",["--version"]),O=x.match(/Poetry.*?(\d+)\.(\d+)/);O&&(parseInt(O[1])>=2?w="source $(poetry env info --path)/bin/activate":w="poetry shell");}catch{}console.log(g.white(` ${w} # Or: poetry run rapidkit`)),console.log(g.white(" rapidkit create # Interactive mode")),console.log(g.white(" cd <project-name> && rapidkit init && rapidkit dev"));}else u.installMethod==="venv"?(console.log(g.white(" source .venv/bin/activate # On Windows: .venv\\Scripts\\activate")),console.log(g.white(" rapidkit create # Interactive mode")),console.log(g.white(" cd <project-name> && rapidkit init && rapidkit dev"))):(console.log(g.white(" rapidkit create # Interactive mode")),console.log(g.white(" cd <project-name> && rapidkit init && rapidkit dev")));console.log(g.white(`
65
+ \u{1F4A1} For more information, check the README.md file.`)),console.log(g.cyan(`
66
+ \u{1F4DA} RapidKit commands:`)),console.log(g.white(" rapidkit create - Create a new project (interactive)")),console.log(g.white(" rapidkit dev - Run development server")),console.log(g.white(" rapidkit add module <name> - Add a module (e.g., settings)")),console.log(g.white(" rapidkit list - List available kits")),console.log(g.white(" rapidkit modules - List available modules")),console.log(g.white(` rapidkit --help - Show all commands
67
+ `));try{let{stdout:w}=await execa("go",["version"],{timeout:3e3}),x=w.match(/go version go(\d+\.\d+(?:\.\d+)?)/),O=x?x[1]:"unknown";console.log(g.gray(`\u{1F439} Go toolchain: Go ${O} detected \u2014 ready for gofiber.standard projects`));}catch{console.log(g.yellow("\u26A0\uFE0F Go toolchain not installed \u2014 needed for gofiber.standard projects")),console.log(g.gray(" Install: https://go.dev/dl/"));}console.log("");}catch(k){p.fail("Failed to create RapidKit environment"),console.error(g.red(`
68
+ \u274C Error:`),k);try{await b.remove(l);}catch{}throw k}}async function Ct(e){let o=[];try{let{stdout:t}=await execa("pyenv",["root"]),n=t.trim();o.push(h.join(n,"versions",`${e}.*`,"bin","python"));let[i,r]=e.split(".");o.push(h.join(n,"versions",`${i}.${r}.*`,"bin","python"));}catch{}o.push(`python${e}`,`python3.${e.split(".")[1]}`,"python3","python"),o.push(`/usr/bin/python${e}`,"/usr/bin/python3",`/usr/local/bin/python${e}`,"/usr/local/bin/python3");for(let t of o)try{let n=t;if(t.includes("*")){if(n=(await execa("sh",["-c",`ls -d ${t} 2>/dev/null | head -1`])).stdout.trim(),!n)continue;n=h.join(n.split("/").slice(0,-1).join("/"),"../bin/python");}let{stdout:i}=await execa(n,["--version"],{timeout:2e3}),r=i.match(/Python (\d+\.\d+)/)?.[1];if(r&&parseFloat(r)>=parseFloat(e))return await execa(n,["-c","import sys; sys.exit(0)"],{timeout:2e3}),n}catch{continue}return null}async function It(e,o,t,n,i,r=false){await Io(t,r),t.start("Finding Python interpreter");let a$1=await Ct(o);a$1?(a.debug(`Found working Python: ${a$1}`),t.succeed("Python found")):t.warn("Could not verify Python path, proceeding with default"),t.start("Initializing Poetry project"),await execa("poetry",["init","--no-interaction","--python",`^${o}`],{cwd:e}),t.succeed("Poetry project initialized");let c=h.join(e,"pyproject.toml"),d=await promises.readFile(c,"utf-8");d.includes("[tool.poetry]")?d=d.replace("[tool.poetry]",`[tool.poetry]
69
+ package-mode = false`):d.includes("[project]")&&(d.includes("[build-system]")?d=d.replace("[build-system]",`
70
70
  [tool.poetry]
71
71
  package-mode = false
72
72
 
73
- [build-system]`):l+=`
73
+ [build-system]`):d+=`
74
74
 
75
75
  [tool.poetry]
76
76
  package-mode = false
77
- `),await promises.writeFile(c,l,"utf-8"),t.start("Configuring Poetry");try{if(await execa("poetry",["config","virtualenvs.in-project","true","--local"],{cwd:e}),s)try{await execa("poetry",["env","use",s],{cwd:e}),a.debug(`Poetry configured to use: ${s}`);}catch(d){a.debug(`Could not set Poetry env to ${s}: ${d}`);}t.succeed("Poetry configured");}catch{t.warn("Could not configure Poetry virtualenvs.in-project");}t.start("Creating virtualenv");try{await execa("poetry",["install","--no-root"],{cwd:e,timeout:3e4}),t.succeed("Virtualenv created");}catch(d){a.debug(`Failed to create virtualenv: ${d}`),t.warn("Could not create virtualenv, proceeding with add command");}if(t.start("Installing RapidKit"),n){let d=he(i||{});if(!d)throw new K("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));a.debug(`Installing from local path: ${d}`),t.text="Installing RapidKit from local path (test mode)",await execa("poetry",["add",d],{cwd:e});}else {t.text="Installing RapidKit from PyPI";let d=false,m=null;for(let p=1;p<=3;p++)try{await execa("poetry",["add","rapidkit-core"],{cwd:e,timeout:6e4*p}),d=true;break}catch(y){m=y,a.debug(`Poetry add attempt ${p} failed: ${y}`),p<3&&(t.text=`Retrying installation (attempt ${p+1}/3)`,await new Promise(P=>setTimeout(P,2e3)));}if(!d){let p=m?.stderr||m?.message||"Unknown error";throw a.debug(`All Poetry install attempts failed. Last error: ${p}`),p.includes("Could not find")||p.includes("No matching distribution")?new ee:new K("Install rapidkit-core with Poetry",new Error(`Failed to install rapidkit-core after 3 attempts.
77
+ `),await promises.writeFile(c,d,"utf-8"),t.start("Configuring Poetry");try{if(await execa("poetry",["config","virtualenvs.in-project","true","--local"],{cwd:e}),a$1)try{await execa("poetry",["env","use",a$1],{cwd:e}),a.debug(`Poetry configured to use: ${a$1}`);}catch(l){a.debug(`Could not set Poetry env to ${a$1}: ${l}`);}t.succeed("Poetry configured");}catch{t.warn("Could not configure Poetry virtualenvs.in-project");}t.start("Creating virtualenv");try{await execa("poetry",["install","--no-root"],{cwd:e,timeout:3e4}),t.succeed("Virtualenv created");}catch(l){a.debug(`Failed to create virtualenv: ${l}`),t.warn("Could not create virtualenv, proceeding with add command");}if(t.start("Installing RapidKit"),n){let l=Re(i||{});if(!l)throw new M("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));a.debug(`Installing from local path: ${l}`),t.text="Installing RapidKit from local path (test mode)",await execa("poetry",["add",l],{cwd:e});}else {t.text="Installing RapidKit from PyPI";let l=false,u=null;for(let p=1;p<=3;p++)try{await execa("poetry",["add","rapidkit-core"],{cwd:e,timeout:6e4*p}),l=true;break}catch(k){u=k,a.debug(`Poetry add attempt ${p} failed: ${k}`),p<3&&(t.text=`Retrying installation (attempt ${p+1}/3)`,await new Promise(R=>setTimeout(R,2e3)));}if(!l){let p=u?.stderr||u?.message||"Unknown error";throw a.debug(`All Poetry install attempts failed. Last error: ${p}`),p.includes("Could not find")||p.includes("No matching distribution")?new ne:new M("Install rapidkit-core with Poetry",new Error(`Failed to install rapidkit-core after 3 attempts.
78
78
  Error: ${p}
79
79
 
80
80
  Possible solutions:
81
81
  1. Check your internet connection
82
- 2. Try installing manually: cd ${w.basename(e)} && poetry add rapidkit-core
83
- 3. Use venv method instead: npx rapidkit ${w.basename(e)} --install-method=venv`))}}t.succeed("RapidKit installed in project virtualenv");try{let{checkRapidkitCoreAvailable:d}=await import('./pythonRapidkitExec-GFCAVUOY.js');if(!await d()&&!n){t.start("Installing RapidKit globally with pipx for CLI access");let p=await ke(t,r);try{await te(p,["install","rapidkit-core"]),t.succeed("RapidKit installed globally");}catch(y){t.warn("Could not install globally (non-fatal, project virtualenv has RapidKit)"),a.debug(`pipx install failed: ${y}`);}}}catch(d){a.debug(`Global install check skipped: ${d}`);}}async function De(e,o,t,n,i,r=false){t.start(`Checking Python ${o}`);let s=we();try{let{stdout:a}=await execa(s,["--version"]),l=a.match(/Python (\d+\.\d+)/)?.[1];if(l&&parseFloat(l)<parseFloat(o))throw new Z(o,l);t.succeed(`Python ${l} found`);}catch(a){throw a instanceof Z?a:new Z(o)}t.start("Creating virtual environment");try{await execa(s,["-m","venv",".venv"],{cwd:e}),t.succeed("Virtual environment created");}catch(a){if(t.fail("Failed to create virtual environment"),(d=>typeof d=="object"&&d!==null&&"stdout"in d&&typeof d.stdout=="string")(a)&&a.stdout.includes("ensurepip is not")){let d=a.stdout.match(/apt install (python[\d.]+-venv)/),m=d?d[1]:"python3-venv";throw new K("Python venv module not available",new Error(`Virtual environment creation failed.
82
+ 2. Try installing manually: cd ${h.basename(e)} && poetry add rapidkit-core
83
+ 3. Use venv method instead: npx rapidkit ${h.basename(e)} --install-method=venv`))}}t.succeed("RapidKit installed in project virtualenv");try{let{checkRapidkitCoreAvailable:l}=await import('./pythonRapidkitExec-YIFUZLND.js');if(!await l()&&!n){t.start("Installing RapidKit globally with pipx for CLI access");let p=await _e(t,r);try{await ie(p,["install","rapidkit-core"]),t.succeed("RapidKit installed globally");}catch(k){t.warn("Could not install globally (non-fatal, project virtualenv has RapidKit)"),a.debug(`pipx install failed: ${k}`);}}}catch(l){a.debug(`Global install check skipped: ${l}`);}}async function ze(e,o,t,n,i,r=false){t.start(`Checking Python ${o}`);let a$1=xe();try{let{stdout:s}=await execa(a$1,["--version"]),d=s.match(/Python (\d+\.\d+)/)?.[1];if(d&&parseFloat(d)<parseFloat(o))throw new re(o,d);t.succeed(`Python ${d} found`);}catch(s){throw s instanceof re?s:new re(o)}t.start("Creating virtual environment");try{await execa(a$1,["-m","venv",".venv"],{cwd:e}),t.succeed("Virtual environment created");}catch(s){if(t.fail("Failed to create virtual environment"),(l=>typeof l=="object"&&l!==null&&"stdout"in l&&typeof l.stdout=="string")(s)&&s.stdout.includes("ensurepip is not")){let l=s.stdout.match(/apt install (python[\d.]+-venv)/),u=l?l[1]:"python3-venv";throw new M("Python venv module not available",new Error(`Virtual environment creation failed.
84
84
 
85
85
  On Debian/Ubuntu systems, install the venv package:
86
- sudo apt install ${m}
86
+ sudo apt install ${u}
87
87
 
88
88
  Or use Poetry instead (recommended):
89
- npx rapidkit ${w.basename(e)} --yes`))}throw new K("Virtual environment creation",a instanceof Error?a:new Error(String(a)))}t.start("Installing RapidKit");let c=w.join(e,".venv",process.platform==="win32"?"Scripts":"bin",process.platform==="win32"?"python.exe":"python");if(await execa(c,["-m","pip","install","--upgrade","pip"],{cwd:e}),n){let a$1=he(i||{});if(!a$1)throw new K("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));a.debug(`Installing from local path: ${a$1}`),t.text="Installing RapidKit from local path (test mode)",await execa(c,["-m","pip","install","-e",a$1],{cwd:e});}else {t.text="Installing RapidKit from PyPI";let a$1=false,l=null;for(let d=1;d<=3;d++)try{await execa(c,["-m","pip","install","rapidkit-core"],{cwd:e,timeout:6e4*d}),a$1=true;break}catch(m){l=m,a.debug(`pip install attempt ${d} failed: ${m}`),d<3&&(t.text=`Retrying installation (attempt ${d+1}/3)`,await new Promise(p=>setTimeout(p,2e3)));}if(!a$1){let d=l?.stderr||l?.message||"Unknown error";throw a.debug(`All pip install attempts failed. Last error: ${d}`),d.includes("Could not find")||d.includes("No matching distribution")?new ee:new K("Install rapidkit-core with pip",new Error(`Failed to install rapidkit-core after 3 attempts.
90
- Error: ${d}
89
+ npx rapidkit ${h.basename(e)} --yes`))}throw new M("Virtual environment creation",s instanceof Error?s:new Error(String(s)))}t.start("Installing RapidKit");let c=h.join(e,".venv",process.platform==="win32"?"Scripts":"bin",process.platform==="win32"?"python.exe":"python");if(await execa(c,["-m","pip","install","--upgrade","pip"],{cwd:e}),n){let s=Re(i||{});if(!s)throw new M("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));a.debug(`Installing from local path: ${s}`),t.text="Installing RapidKit from local path (test mode)",await execa(c,["-m","pip","install","-e",s],{cwd:e});}else {t.text="Installing RapidKit from PyPI";let s=false,d=null;for(let l=1;l<=3;l++)try{await execa(c,["-m","pip","install","rapidkit-core"],{cwd:e,timeout:6e4*l}),s=true;break}catch(u){d=u,a.debug(`pip install attempt ${l} failed: ${u}`),l<3&&(t.text=`Retrying installation (attempt ${l+1}/3)`,await new Promise(p=>setTimeout(p,2e3)));}if(!s){let l=d?.stderr||d?.message||"Unknown error";throw a.debug(`All pip install attempts failed. Last error: ${l}`),l.includes("Could not find")||l.includes("No matching distribution")?new ne:new M("Install rapidkit-core with pip",new Error(`Failed to install rapidkit-core after 3 attempts.
90
+ Error: ${l}
91
91
 
92
92
  Possible solutions:
93
93
  1. Check your internet connection
94
- 2. Try installing manually: cd ${w.basename(e)} && .venv/bin/python -m pip install rapidkit-core
95
- 3. Use Poetry instead: npx rapidkit ${w.basename(e)} --install-method=poetry`))}}t.succeed("RapidKit installed in project virtualenv");try{let{checkRapidkitCoreAvailable:a$1}=await import('./pythonRapidkitExec-GFCAVUOY.js');if(!await a$1()&&!n){t.start("Installing RapidKit globally with pipx for CLI access");let d=await ke(t,r);try{await te(d,["install","rapidkit-core"]),t.succeed("RapidKit installed globally");}catch(m){t.warn("Could not install globally (non-fatal, project virtualenv has RapidKit)"),a.debug(`pipx install failed: ${m}`);}}}catch(a$1){a.debug(`Global install check skipped: ${a$1}`);}}async function lt(e,o,t,n,i=false){let r=await ke(o,i);if(o.start("Installing RapidKit globally with pipx"),t){let s=he(n||{});if(!s)throw new K("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));a.debug(`Installing from local path: ${s}`),o.text="Installing RapidKit from local path (test mode)",await te(r,["install","-e",s]);}else {o.text="Installing RapidKit from PyPI";try{await te(r,["install","rapidkit-core"]);}catch{throw new ee}}o.succeed("RapidKit installed globally"),await k.outputFile(w.join(e,".rapidkit-global"),`RapidKit installed globally with pipx
96
- `,"utf-8");}async function Y(e,o){let{skipGit:t=false,testMode:n=false,userConfig:i={},yes:r=false,installMethod:s,pythonVersion:c="3.10"}=o||{},a=s||i.defaultInstallMethod||"poetry";await it(e,w.basename(e),a),await rt(e);let l=Fe("Registering workspace").start();try{a==="poetry"?await ct(e,c,l,n,i,r):a==="venv"?await De(e,c,l,n,i):await lt(e,l,n,i,r),await st(e,a),await dt(e,a),l.succeed("Workspace registered");try{let{registerWorkspace:d}=await import('./workspace-LZZGJRGV.js');await d(e,w.basename(e));}catch{}if(!t){l.start("Initializing git repository");try{await execa("git",["init"],{cwd:e}),await execa("git",["add","."],{cwd:e}),await execa("git",["commit","-m","Initial commit: RapidKit workspace"],{cwd:e}),l.succeed("Git repository initialized");}catch{l.warn("Could not initialize git repository");}}}catch(d){throw l.fail("Failed to register workspace"),d}}async function dt(e,o){let i=`# RapidKit Workspace
94
+ 2. Try installing manually: cd ${h.basename(e)} && .venv/bin/python -m pip install rapidkit-core
95
+ 3. Use Poetry instead: npx rapidkit ${h.basename(e)} --install-method=poetry`))}}t.succeed("RapidKit installed in project virtualenv");try{let{checkRapidkitCoreAvailable:s}=await import('./pythonRapidkitExec-YIFUZLND.js');if(!await s()&&!n){t.start("Installing RapidKit globally with pipx for CLI access");let l=await _e(t,r);try{await ie(l,["install","rapidkit-core"]),t.succeed("RapidKit installed globally");}catch(u){t.warn("Could not install globally (non-fatal, project virtualenv has RapidKit)"),a.debug(`pipx install failed: ${u}`);}}}catch(s){a.debug(`Global install check skipped: ${s}`);}}async function Et(e,o,t,n,i=false){let r=await _e(o,i);if(o.start("Installing RapidKit globally with pipx"),t){let a$1=Re(n||{});if(!a$1)throw new M("Test mode installation",new Error("No local RapidKit path configured. Set RAPIDKIT_DEV_PATH environment variable."));a.debug(`Installing from local path: ${a$1}`),o.text="Installing RapidKit from local path (test mode)",await ie(r,["install","-e",a$1]);}else {o.text="Installing RapidKit from PyPI";try{await ie(r,["install","rapidkit-core"]);}catch{throw new ne}}o.succeed("RapidKit installed globally"),await b.outputFile(h.join(e,".rapidkit-global"),`RapidKit installed globally with pipx
96
+ `,"utf-8");}async function Z(e,o){let{skipGit:t=false,testMode:n=false,userConfig:i={},yes:r=false,installMethod:a,pythonVersion:c="3.10"}=o||{},s=a||i.defaultInstallMethod||"poetry";await St(e,h.basename(e),s),await xt(e);let d=Qe("Registering workspace").start();try{s==="poetry"?await It(e,c,d,n,i,r):s==="venv"?await ze(e,c,d,n,i):await Et(e,d,n,i,r),await _t(e,s),await Pt(e,s),d.succeed("Workspace registered");try{let{registerWorkspace:l}=await import('./workspace-LZZGJRGV.js');await l(e,h.basename(e));}catch{}if(!t){d.start("Initializing git repository");try{await execa("git",["init"],{cwd:e}),await execa("git",["add","."],{cwd:e}),await execa("git",["commit","-m","Initial commit: RapidKit workspace"],{cwd:e}),d.succeed("Git repository initialized");}catch{d.warn("Could not initialize git repository");}}}catch(l){throw d.fail("Failed to register workspace"),l}}async function Pt(e,o){let i=`# RapidKit Workspace
97
97
 
98
98
  This directory contains a RapidKit development environment.
99
99
 
@@ -204,7 +204,7 @@ If you encounter issues:
204
204
  2. Check RapidKit installation: \`rapidkit --version\`
205
205
  3. Run diagnostics: \`rapidkit doctor\`
206
206
  4. Visit RapidKit documentation or GitHub issues
207
- `;await promises.writeFile(w.join(e,"README.md"),i,"utf-8");}async function ao(e,o,t){let n=Fe("Creating demo workspace").start();try{await k.ensureDir(e),n.succeed("Directory created"),n.start("Setting up demo kit generator");let i=JSON.stringify({name:`${o}-workspace`,version:"1.0.0",private:true,description:"RapidKit demo workspace",scripts:{generate:"node generate-demo.js"}},null,2);await promises.writeFile(w.join(e,"package.json"),i,"utf-8"),await promises.writeFile(w.join(e,"generate-demo.js"),`#!/usr/bin/env node
207
+ `;await promises.writeFile(h.join(e,"README.md"),i,"utf-8");}async function To(e,o,t){let n=Qe("Creating demo workspace").start();try{await b.ensureDir(e),n.succeed("Directory created"),n.start("Setting up demo kit generator");let i=JSON.stringify({name:`${o}-workspace`,version:"1.0.0",private:true,description:"RapidKit demo workspace",scripts:{generate:"node generate-demo.js"}},null,2);await promises.writeFile(h.join(e,"package.json"),i,"utf-8"),await promises.writeFile(h.join(e,"generate-demo.js"),`#!/usr/bin/env node
208
208
  /**
209
209
  * Demo Kit Generator - Create FastAPI demo projects
210
210
  *
@@ -600,7 +600,7 @@ venv/
600
600
  }
601
601
 
602
602
  main().catch(console.error);
603
- `,"utf-8");try{await execa("chmod",["+x",w.join(e,"generate-demo.js")]);}catch{}let s=`# RapidKit Demo Workspace
603
+ `,"utf-8");try{await execa("chmod",["+x",h.join(e,"generate-demo.js")]);}catch{}let a=`# RapidKit Demo Workspace
604
604
 
605
605
  Welcome to your RapidKit demo workspace! This environment lets you generate FastAPI demo projects using bundled RapidKit templates, without needing to install Python RapidKit.
606
606
 
@@ -687,7 +687,7 @@ ${o}/
687
687
  ---
688
688
 
689
689
  **Generated with RapidKit** | [GitHub](https://github.com/getrapidkit/rapidkit-npm)
690
- `;if(await promises.writeFile(w.join(e,"README.md"),s,"utf-8"),n.succeed("Demo workspace setup complete"),!t){n.start("Initializing git repository");try{await execa("git",["init"],{cwd:e}),await k.outputFile(w.join(e,".gitignore"),`# Dependencies
690
+ `;if(await promises.writeFile(h.join(e,"README.md"),a,"utf-8"),n.succeed("Demo workspace setup complete"),!t){n.start("Initializing git repository");try{await execa("git",["init"],{cwd:e}),await b.outputFile(h.join(e,".gitignore"),`# Dependencies
691
691
  node_modules/
692
692
 
693
693
  # Generated projects
@@ -700,19 +700,19 @@ __pycache__/
700
700
  *.pyc
701
701
  .venv/
702
702
  .env
703
- `,"utf-8"),await execa("git",["add","."],{cwd:e}),await execa("git",["commit","-m","Initial commit: Demo workspace"],{cwd:e}),n.succeed("Git repository initialized");}catch{n.warn("Could not initialize git repository");}}console.log(u.green(`
703
+ `,"utf-8"),await execa("git",["add","."],{cwd:e}),await execa("git",["commit","-m","Initial commit: Demo workspace"],{cwd:e}),n.succeed("Git repository initialized");}catch{n.warn("Could not initialize git repository");}}console.log(g.green(`
704
704
  \u2728 Demo workspace created successfully!
705
- `)),console.log(u.cyan("\u{1F4C2} Location:"),u.white(e)),console.log(u.cyan(`\u{1F680} Get started:
706
- `)),console.log(u.white(` cd ${o}`)),console.log(u.white(" node generate-demo.js my-api")),console.log(u.white(" cd my-api")),console.log(u.white(" rapidkit init")),console.log(u.white(" rapidkit dev")),console.log(),console.log(u.yellow("\u{1F4A1} Note:"),"This is a demo workspace. For full RapidKit features:"),console.log(u.cyan(" pipx install rapidkit")),console.log();}catch(i){throw n.fail("Failed to create demo workspace"),i}}async function co(e,o,t,n){console.log(u.cyan(`
705
+ `)),console.log(g.cyan("\u{1F4C2} Location:"),g.white(e)),console.log(g.cyan(`\u{1F680} Get started:
706
+ `)),console.log(g.white(` cd ${o}`)),console.log(g.white(" node generate-demo.js my-api")),console.log(g.white(" cd my-api")),console.log(g.white(" rapidkit init")),console.log(g.white(" rapidkit dev")),console.log(),console.log(g.yellow("\u{1F4A1} Note:"),"This is a demo workspace. For full RapidKit features:"),console.log(g.cyan(" pipx install rapidkit")),console.log();}catch(i){throw n.fail("Failed to create demo workspace"),i}}async function Oo(e,o,t,n){console.log(g.cyan(`
707
707
  \u{1F50D} Dry-run mode - showing what would be created:
708
- `)),console.log(u.white("\u{1F4C2} Project path:"),e),console.log(u.white("\u{1F4E6} Project type:"),t?"Demo workspace":"Full RapidKit environment"),t?(console.log(u.white(`
709
- \u{1F4DD} Files to create:`)),console.log(u.gray(" - package.json")),console.log(u.gray(" - generate-demo.js (project generator)")),console.log(u.gray(" - README.md")),console.log(u.gray(" - .gitignore")),console.log(u.white(`
710
- \u{1F3AF} Capabilities:`)),console.log(u.gray(" - Generate multiple FastAPI demo projects")),console.log(u.gray(" - No Python RapidKit installation required")),console.log(u.gray(" - Bundled templates included"))):(console.log(u.white(`
711
- \u2699\uFE0F Configuration:`)),console.log(u.gray(` - Python version: ${n.pythonVersion||"3.10"}`)),console.log(u.gray(` - Install method: ${n.defaultInstallMethod||"poetry"}`)),console.log(u.gray(` - Git initialization: ${n.skipGit?"No":"Yes"}`)),console.log(u.white(`
712
- \u{1F4DD} Files to create:`)),console.log(u.gray(" - pyproject.toml (Poetry) or .venv/ (venv)")),console.log(u.gray(" - README.md")),console.log(u.gray(" - .gitignore")),console.log(u.white(`
713
- \u{1F3AF} Next steps after creation:`)),console.log(u.gray(" 1. Install RapidKit Python package")),console.log(u.gray(" 2. Create projects with rapidkit CLI")),console.log(u.gray(" 3. Add modules and customize"))),console.log(u.white(`
708
+ `)),console.log(g.white("\u{1F4C2} Project path:"),e),console.log(g.white("\u{1F4E6} Project type:"),t?"Demo workspace":"Full RapidKit environment"),t?(console.log(g.white(`
709
+ \u{1F4DD} Files to create:`)),console.log(g.gray(" - package.json")),console.log(g.gray(" - generate-demo.js (project generator)")),console.log(g.gray(" - README.md")),console.log(g.gray(" - .gitignore")),console.log(g.white(`
710
+ \u{1F3AF} Capabilities:`)),console.log(g.gray(" - Generate multiple FastAPI demo projects")),console.log(g.gray(" - No Python RapidKit installation required")),console.log(g.gray(" - Bundled templates included"))):(console.log(g.white(`
711
+ \u2699\uFE0F Configuration:`)),console.log(g.gray(` - Python version: ${n.pythonVersion||"3.10"}`)),console.log(g.gray(` - Install method: ${n.defaultInstallMethod||"poetry"}`)),console.log(g.gray(` - Git initialization: ${n.skipGit?"No":"Yes"}`)),console.log(g.white(`
712
+ \u{1F4DD} Files to create:`)),console.log(g.gray(" - pyproject.toml (Poetry) or .venv/ (venv)")),console.log(g.gray(" - README.md")),console.log(g.gray(" - .gitignore")),console.log(g.white(`
713
+ \u{1F3AF} Next steps after creation:`)),console.log(g.gray(" 1. Install RapidKit Python package")),console.log(g.gray(" 2. Create projects with rapidkit CLI")),console.log(g.gray(" 3. Add modules and customize"))),console.log(g.white(`
714
714
  \u{1F4A1} To proceed with actual creation, run without --dry-run flag
715
- `));}var mo=fileURLToPath(import.meta.url),go=w.dirname(mo);function fo(e=32){let o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t=uo.randomBytes(e),n="";for(let i=0;i<e;i++)n+=o[t[i]%o.length];return n}async function pt(e,o){let t=o.template||"fastapi",n=t==="fastapi",i=n?"FastAPI":"NestJS",r=Fe(`Generating ${i} project...`).start();try{let s=w.resolve(go,".."),c$1=o.kit_name||`${t}.standard`,a;c$1==="fastapi.ddd"?a="fastapi-ddd":c$1.startsWith("fastapi")?a="fastapi-standard":a="nestjs-standard";let l=w.join(s,"templates","kits",a),d=lo.configure(l,{autoescape:false,trimBlocks:true,lstripBlocks:true});d.addFilter("generate_secret",function(v,C=32){return fo(C)});let m={project_name:o.project_name,author:o.author||"RapidKit User",description:o.description||(n?"FastAPI service generated with RapidKit":"NestJS application generated with RapidKit"),app_version:o.app_version||"0.1.0",license:o.license||"MIT",package_manager:o.package_manager||"npm",node_version:o.node_version||"20.0.0",database_type:o.database_type||"postgresql",include_caching:o.include_caching||false,created_at:new Date().toISOString(),rapidkit_version:c()},p;n?p=["src/main.py.j2","src/__init__.py.j2","src/cli.py.j2","src/routing/__init__.py.j2","src/routing/health.py.j2","src/modules/__init__.py.j2","tests/__init__.py.j2","README.md.j2","pyproject.toml.j2","Makefile.j2",".rapidkit/__init__.py.j2",".rapidkit/project.json.j2",".rapidkit/cli.py.j2",".rapidkit/rapidkit.j2",".rapidkit/activate.j2","rapidkit.j2","rapidkit.cmd.j2"]:p=["src/main.ts.j2","src/app.module.ts.j2","src/app.controller.ts.j2","src/app.service.ts.j2","src/config/configuration.ts.j2","src/config/validation.ts.j2","src/config/index.ts.j2","src/modules/index.ts.j2","src/examples/examples.module.ts.j2","src/examples/examples.controller.ts.j2","src/examples/examples.service.ts.j2","src/examples/dto/create-note.dto.ts.j2","test/app.controller.spec.ts.j2","test/examples.controller.spec.ts.j2","test/app.e2e-spec.ts.j2","test/jest-e2e.json.j2","package.json.j2","tsconfig.json.j2","tsconfig.build.json.j2","nest-cli.json.j2","jest.config.ts.j2","eslint.config.cjs.j2",".env.example.j2","docker-compose.yml.j2","Dockerfile.j2","README.md.j2",".rapidkit/project.json.j2",".rapidkit/rapidkit.j2",".rapidkit/rapidkit.cmd.j2",".rapidkit/activate.j2","rapidkit.j2","rapidkit.cmd.j2"];for(let v of p){let C=w.join(l,v);try{await promises.access(C);}catch{continue}let E=await promises.readFile(C,"utf-8"),M;try{M=d.renderString(E,m);}catch(Jt){throw console.error(`Failed to render template: ${v}`),Jt}let U=v.replace(/\.j2$/,""),Se=w.join(e,U);await promises.mkdir(w.dirname(Se),{recursive:true}),await promises.writeFile(Se,M),(U.endsWith(".rapidkit/rapidkit")||U.endsWith(".rapidkit/cli.py")||U.endsWith(".rapidkit/activate")||U==="rapidkit")&&await promises.chmod(Se,493);}if(n){let v=w.join(l,".rapidkit","context.json"),C=w.join(e,".rapidkit","context.json");try{await promises.mkdir(w.join(e,".rapidkit"),{recursive:true}),await promises.copyFile(v,C);}catch{await promises.mkdir(w.join(e,".rapidkit"),{recursive:true});let M=o.engine||"pip";await promises.writeFile(C,JSON.stringify({engine:M,created_by:"rapidkit-npm-fallback"},null,2));}}let y=n?`# Python
715
+ `));}var Go=fileURLToPath(import.meta.url),$o=h.dirname(Go);function Do(e=32){let o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t=jo.randomBytes(e),n="";for(let i=0;i<e;i++)n+=o[t[i]%o.length];return n}async function Tt(e,o){let t=o.template||"fastapi",n=t==="fastapi",i=n?"FastAPI":"NestJS",r=Qe(`Generating ${i} project...`).start();try{let a=h.resolve($o,".."),c$1=o.kit_name||`${t}.standard`,s;c$1==="fastapi.ddd"?s="fastapi-ddd":c$1.startsWith("fastapi")?s="fastapi-standard":s="nestjs-standard";let d=h.join(a,"templates","kits",s),l=Ao.configure(d,{autoescape:false,trimBlocks:true,lstripBlocks:true});l.addFilter("generate_secret",function(w,x=32){return Do(x)});let u={project_name:o.project_name,author:o.author||"RapidKit User",description:o.description||(n?"FastAPI service generated with RapidKit":"NestJS application generated with RapidKit"),app_version:o.app_version||"0.1.0",license:o.license||"MIT",package_manager:o.package_manager||"npm",node_version:o.node_version||"20.0.0",database_type:o.database_type||"postgresql",include_caching:o.include_caching||false,created_at:new Date().toISOString(),rapidkit_version:c()},p;n?p=["src/main.py.j2","src/__init__.py.j2","src/cli.py.j2","src/routing/__init__.py.j2","src/routing/health.py.j2","src/modules/__init__.py.j2","tests/__init__.py.j2","README.md.j2","pyproject.toml.j2","Makefile.j2",".rapidkit/__init__.py.j2",".rapidkit/project.json.j2",".rapidkit/cli.py.j2",".rapidkit/rapidkit.j2",".rapidkit/activate.j2","rapidkit.j2","rapidkit.cmd.j2"]:p=["src/main.ts.j2","src/app.module.ts.j2","src/app.controller.ts.j2","src/app.service.ts.j2","src/config/configuration.ts.j2","src/config/validation.ts.j2","src/config/index.ts.j2","src/modules/index.ts.j2","src/examples/examples.module.ts.j2","src/examples/examples.controller.ts.j2","src/examples/examples.service.ts.j2","src/examples/dto/create-note.dto.ts.j2","test/app.controller.spec.ts.j2","test/examples.controller.spec.ts.j2","test/app.e2e-spec.ts.j2","test/jest-e2e.json.j2","package.json.j2","tsconfig.json.j2","tsconfig.build.json.j2","nest-cli.json.j2","jest.config.ts.j2","eslint.config.cjs.j2",".env.example.j2","docker-compose.yml.j2","Dockerfile.j2","README.md.j2",".rapidkit/project.json.j2",".rapidkit/rapidkit.j2",".rapidkit/rapidkit.cmd.j2",".rapidkit/activate.j2","rapidkit.j2","rapidkit.cmd.j2"];for(let w of p){let x=h.join(d,w);try{await promises.access(x);}catch{continue}let O=await promises.readFile(x,"utf-8"),G;try{G=l.renderString(O,u);}catch(We){throw console.error(`Failed to render template: ${w}`),We}let V=w.replace(/\.j2$/,""),ve=h.join(e,V);await promises.mkdir(h.dirname(ve),{recursive:true}),await promises.writeFile(ve,G),(V.endsWith(".rapidkit/rapidkit")||V.endsWith(".rapidkit/cli.py")||V.endsWith(".rapidkit/activate")||V==="rapidkit")&&await promises.chmod(ve,493);}if(n){let w=h.join(d,".rapidkit","context.json"),x=h.join(e,".rapidkit","context.json");try{await promises.mkdir(h.join(e,".rapidkit"),{recursive:true}),await promises.copyFile(w,x);}catch{await promises.mkdir(h.join(e,".rapidkit"),{recursive:true});let G=o.engine||"pip";await promises.writeFile(x,JSON.stringify({engine:G,created_by:"rapidkit-npm-fallback"},null,2));}}let k=n?`# Python
716
716
  __pycache__/
717
717
  *.py[cod]
718
718
  *$py.class
@@ -780,15 +780,15 @@ Thumbs.db
780
780
 
781
781
  # Coverage
782
782
  coverage/
783
- `;if(await promises.writeFile(w.join(e,".gitignore"),y),r.succeed(`${i} project generated!`),!o.skipGit){let v=Fe("Initializing git repository...").start();try{await execa("git",["init"],{cwd:e}),await execa("git",["add","."],{cwd:e}),await execa("git",["commit","-m",`Initial commit: ${i} project via RapidKit`],{cwd:e}),v.succeed("Git repository initialized");}catch{v.warn("Could not initialize git repository");}}if(!n&&!o.skipInstall){let v=o.package_manager||"npm",C=Fe(`Installing dependencies with ${v}...`).start();try{await execa(v,v==="yarn"?["install"]:v==="pnpm"?["install"]:["install"],{cwd:e}),C.succeed("Dependencies installed");}catch{C.warn(`Could not install dependencies. Run '${v} install' manually.`);}}let P=w.basename(e);console.log(`
784
- ${u.yellow("\u26A0\uFE0F Limited offline mode:")} This project was created using basic templates.
785
- ${u.gray("For full kit features, install Python 3.10+ and rapidkit-core:")}
786
- ${u.cyan(" sudo apt install python3 python3-pip python3-venv")}
787
- ${u.cyan(" pip install rapidkit-core")}
783
+ `;if(await promises.writeFile(h.join(e,".gitignore"),k),r.succeed(`${i} project generated!`),!o.skipGit){let w=Qe("Initializing git repository...").start();try{await execa("git",["init"],{cwd:e}),await execa("git",["add","."],{cwd:e}),await execa("git",["commit","-m",`Initial commit: ${i} project via RapidKit`],{cwd:e}),w.succeed("Git repository initialized");}catch{w.warn("Could not initialize git repository");}}if(!n&&!o.skipInstall){let w=o.package_manager||"npm",x=Qe(`Installing dependencies with ${w}...`).start();try{await execa(w,w==="yarn"?["install"]:w==="pnpm"?["install"]:["install"],{cwd:e}),x.succeed("Dependencies installed");}catch{x.warn(`Could not install dependencies. Run '${w} install' manually.`);}}let R=h.basename(e);console.log(`
784
+ ${g.yellow("\u26A0\uFE0F Limited offline mode:")} This project was created using basic templates.
785
+ ${g.gray("For full kit features, install Python 3.10+ and rapidkit-core:")}
786
+ ${g.cyan(" sudo apt install python3 python3-pip python3-venv")}
787
+ ${g.cyan(" pip install rapidkit-core")}
788
788
  `),console.log(n?`
789
- ${u.green("\u2728 FastAPI project created successfully!")}
789
+ ${g.green("\u2728 FastAPI project created successfully!")}
790
790
 
791
- ${u.bold("\u{1F4C2} Project structure:")}
791
+ ${g.bold("\u{1F4C2} Project structure:")}
792
792
  ${e}/
793
793
  \u251C\u2500\u2500 .rapidkit/ # RapidKit CLI module
794
794
  \u251C\u2500\u2500 src/
@@ -800,12 +800,12 @@ ${e}/
800
800
  \u251C\u2500\u2500 pyproject.toml # Poetry configuration
801
801
  \u2514\u2500\u2500 README.md
802
802
 
803
- ${u.bold("\u{1F680} Get started:")}
804
- ${u.cyan(`cd ${P}`)}
805
- ${u.cyan("npx rapidkit init")} ${u.gray("# Install dependencies")}
806
- ${u.cyan("npx rapidkit dev")} ${u.gray("# Start dev server")}
803
+ ${g.bold("\u{1F680} Get started:")}
804
+ ${g.cyan(`cd ${R}`)}
805
+ ${g.cyan("npx rapidkit init")} ${g.gray("# Install dependencies")}
806
+ ${g.cyan("npx rapidkit dev")} ${g.gray("# Start dev server")}
807
807
 
808
- ${u.bold("\u{1F4DA} Available commands:")}
808
+ ${g.bold("\u{1F4DA} Available commands:")}
809
809
  npx rapidkit init # Install dependencies (poetry install)
810
810
  npx rapidkit dev # Start dev server with hot reload
811
811
  npx rapidkit start # Start production server
@@ -813,12 +813,12 @@ ${u.bold("\u{1F4DA} Available commands:")}
813
813
  npx rapidkit lint # Lint code
814
814
  npx rapidkit format # Format code
815
815
 
816
- ${u.gray("Alternative: make dev, ./rapidkit dev, poetry run dev")}
817
- ${u.gray("\u{1F4A1} Tip: Install globally (npm i -g rapidkit) to use without npx")}
816
+ ${g.gray("Alternative: make dev, ./rapidkit dev, poetry run dev")}
817
+ ${g.gray("\u{1F4A1} Tip: Install globally (npm i -g rapidkit) to use without npx")}
818
818
  `:`
819
- ${u.green("\u2728 NestJS project created successfully!")}
819
+ ${g.green("\u2728 NestJS project created successfully!")}
820
820
 
821
- ${u.bold("\u{1F4C2} Project structure:")}
821
+ ${g.bold("\u{1F4C2} Project structure:")}
822
822
  ${e}/
823
823
  \u251C\u2500\u2500 .rapidkit/ # RapidKit CLI module
824
824
  \u251C\u2500\u2500 src/
@@ -830,13 +830,13 @@ ${e}/
830
830
  \u251C\u2500\u2500 package.json # Dependencies
831
831
  \u2514\u2500\u2500 README.md
832
832
 
833
- ${u.bold("\u{1F680} Get started:")}
834
- ${u.cyan(`cd ${P}`)}
835
- ${u.cyan("npx rapidkit init")} ${u.gray("# Install dependencies")}
836
- ${u.cyan("cp .env.example .env")}
837
- ${u.cyan("npx rapidkit dev")} ${u.gray("# Start dev server")}
833
+ ${g.bold("\u{1F680} Get started:")}
834
+ ${g.cyan(`cd ${R}`)}
835
+ ${g.cyan("npx rapidkit init")} ${g.gray("# Install dependencies")}
836
+ ${g.cyan("cp .env.example .env")}
837
+ ${g.cyan("npx rapidkit dev")} ${g.gray("# Start dev server")}
838
838
 
839
- ${u.bold("\u{1F4DA} Available commands:")}
839
+ ${g.bold("\u{1F4DA} Available commands:")}
840
840
  npx rapidkit init # Install dependencies
841
841
  npx rapidkit dev # Start dev server with hot reload
842
842
  npx rapidkit start # Start production server
@@ -845,160 +845,3398 @@ ${u.bold("\u{1F4DA} Available commands:")}
845
845
  npx rapidkit lint # Lint code
846
846
  npx rapidkit format # Format code
847
847
 
848
- ${u.bold("\u{1F310} API endpoints:")}
848
+ ${g.bold("\u{1F310} API endpoints:")}
849
849
  http://localhost:8000/health # Health check
850
850
  http://localhost:8000/docs # Swagger docs
851
851
  http://localhost:8000/examples/notes # Example API
852
852
 
853
- ${u.gray("Alternative: npm run start:dev, ./rapidkit dev")}
854
- ${u.gray("\u{1F4A1} Tip: Install globally (npm i -g rapidkit) to use without npx")}
855
- `);}catch(s){throw r.fail(`Failed to generate ${i} project`),s}}async function mt(){let e=process.platform==="win32"?["python","python3"]:["python3","python"];for(let o of e)try{let{stdout:t}=await execa(o,["--version"],{timeout:3e3}),n=t.match(/Python (\d+\.\d+\.\d+)/);if(n){let i=n[1],[r,s]=i.split(".").map(Number);return r<3||r===3&&s<10?{status:"warn",message:`Python ${i} (requires 3.10+)`,details:`${o} found but version is below minimum requirement`}:{status:"ok",message:`Python ${i}`,details:`Using ${o}`}}}catch{continue}return {status:"error",message:"Python not found",details:"Install Python 3.10+ and ensure it's in PATH"}}async function gt(){try{let{stdout:e}=await execa("poetry",["--version"],{timeout:3e3}),o=e.match(/Poetry .*version ([\d.]+)/);return o?{status:"ok",message:`Poetry ${o[1]}`,details:"Available for dependency management"}:{status:"warn",message:"Poetry version unknown"}}catch{return {status:"warn",message:"Poetry not installed",details:"Optional: Install for better dependency management"}}}async function ft(){try{let{stdout:e}=await execa("pipx",["--version"],{timeout:3e3});return {status:"ok",message:`pipx ${e.trim()}`,details:"Available for global tool installation"}}catch{return {status:"warn",message:"pipx not installed",details:"Optional: Install for isolated Python tools"}}}async function ht(){let e=process.env.HOME||process.env.USERPROFILE||"",o=[],t=[{location:"Global (pipx)",path:w.join(e,".local","bin","rapidkit")},{location:"Global (pipx)",path:w.join(e,"AppData","Roaming","Python","Scripts","rapidkit.exe")},{location:"Global (pyenv)",path:w.join(e,".pyenv","shims","rapidkit")},{location:"Global (system)",path:"/usr/local/bin/rapidkit"},{location:"Global (system)",path:"/usr/bin/rapidkit"}],n=[{location:"Workspace (.venv)",path:w.join(process.cwd(),".venv","bin","rapidkit")},{location:"Workspace (.venv)",path:w.join(process.cwd(),".venv","Scripts","rapidkit.exe")}];for(let{location:r,path:s}of [...t,...n])try{if(await k__default.pathExists(s)){let{stdout:c,exitCode:a}=await execa(s,["--version"],{timeout:3e3,reject:false});if(a===0&&(c.includes("RapidKit Version")||c.includes("RapidKit"))){let l=c.match(/v?([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);l&&o.push({location:r,path:s,version:l[1]});}}}catch{continue}if(o.length>0)return {status:"ok",message:`RapidKit Core ${o[0].version}`,paths:o.map(s=>({location:s.location,path:s.path,version:s.version}))};try{let{stdout:r,exitCode:s}=await execa("rapidkit",["--version"],{timeout:3e3,reject:false});if(s===0&&(r.includes("RapidKit Version")||r.includes("RapidKit"))){let c=r.match(/v?([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);if(c)return {status:"ok",message:`RapidKit Core ${c[1]}`,details:"Available via PATH"}}}catch{}try{let{stdout:r,exitCode:s}=await execa("poetry",["run","rapidkit","--version"],{timeout:3e3,reject:false});if(s===0&&(r.includes("RapidKit Version")||r.includes("RapidKit"))){let c=r.match(/v?([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);if(c)return {status:"ok",message:`RapidKit Core ${c[1]}`,details:"Available via Poetry"}}}catch{}let i=process.platform==="win32"?["python","python3"]:["python3","python"];for(let r of i)try{let{stdout:s,exitCode:c}=await execa(r,["-c","import rapidkit_core; print(rapidkit_core.__version__)"],{timeout:3e3,reject:false});if(c===0&&s&&!s.includes("Traceback")&&!s.includes("ModuleNotFoundError")){let a=s.trim();if(a)return {status:"ok",message:`RapidKit Core ${a}`,details:`Available in ${r} environment`}}}catch{continue}return {status:"error",message:"RapidKit Core not installed",details:"Install with: pipx install rapidkit-core"}}async function Te(e,o){let t=w.join(e,"Dockerfile");o.hasDocker=await k__default.pathExists(t);let n=w.join(e,"tests"),i=w.join(e,"test");if(o.hasTests=await k__default.pathExists(n)||await k__default.pathExists(i),o.framework==="NestJS"){let r=w.join(e,".eslintrc.js"),s=w.join(e,".eslintrc.json");o.hasCodeQuality=await k__default.pathExists(r)||await k__default.pathExists(s);}else if(o.framework==="FastAPI"){let r=w.join(e,"ruff.toml"),s=w.join(e,"pyproject.toml");if(await k__default.pathExists(s))try{let c=await k__default.readFile(s,"utf8");o.hasCodeQuality=c.includes("[tool.ruff]")||await k__default.pathExists(r);}catch{o.hasCodeQuality=await k__default.pathExists(r);}}try{if(o.framework==="NestJS"){let{stdout:r}=await execa("npm",["audit","--json"],{cwd:e,reject:false});if(r)try{let c=JSON.parse(r).metadata?.vulnerabilities;c&&(o.vulnerabilities=(c.high||0)+(c.critical||0)+(c.moderate||0));}catch{}}else if(o.framework==="FastAPI"){let r=w.join(e,".venv"),s=process.platform==="win32"?w.join(r,"Scripts","python.exe"):w.join(r,"bin","python");if(await k__default.pathExists(s))try{let{stdout:c}=await execa(s,["-m","pip","list","--format=json"],{timeout:5e3,reject:false});if(c){JSON.parse(c);o.vulnerabilities=0;}}catch{}}}catch{}}async function yo(e){let t={name:w.basename(e),path:e,venvActive:false,depsInstalled:false,coreInstalled:false,issues:[],fixCommands:[]},n=w.join(e,".rapidkit");if(!await k__default.pathExists(n))return t.issues.push("Not a valid RapidKit project (missing .rapidkit directory)"),t;try{let a=w.join(e,"registry.json");if(await k__default.pathExists(a)){let l=await k__default.readJson(a);l.installed_modules&&(t.stats={modules:l.installed_modules.length});}}catch{}try{let a=w.join(n,"project.json");if(await k__default.pathExists(a)){let l=await k__default.readJson(a);l.kit&&(t.kit=l.kit);}}catch{}try{let a=w.join(e,".git");if(await k__default.pathExists(a)){let{stdout:l}=await execa("git",["log","-1","--format=%cr"],{cwd:e,reject:false});l&&(t.lastModified=l.trim());}else {let l=await k__default.stat(e),m=Date.now()-l.mtime.getTime(),p=Math.floor(m/(1e3*60*60*24));t.lastModified=p===0?"today":`${p} day${p>1?"s":""} ago`;}}catch{}let i=w.join(e,"package.json"),r=w.join(e,"pyproject.toml"),s=await k__default.pathExists(i),c=await k__default.pathExists(r);if(s){t.framework="NestJS",t.venvActive=true;let a=w.join(e,"node_modules");if(await k__default.pathExists(a))try{let p=(await k__default.readdir(a)).filter(y=>!y.startsWith(".")&&!y.startsWith("_"));t.depsInstalled=p.length>0;}catch{t.depsInstalled=false;}t.depsInstalled||(t.issues.push("Dependencies not installed (node_modules empty or missing)"),t.fixCommands?.push(`cd ${e} && rapidkit init`)),t.coreInstalled=false;let l=w.join(e,".env");if(t.hasEnvFile=await k__default.pathExists(l),!t.hasEnvFile){let m=w.join(e,".env.example");await k__default.pathExists(m)&&(t.issues.push("Environment file missing (found .env.example)"),t.fixCommands?.push(`cd ${e} && cp .env.example .env`));}let d=w.join(e,"src");if(t.modulesHealthy=true,t.missingModules=[],await k__default.pathExists(d))try{let m=await k__default.readdir(d);t.modulesHealthy=m.length>0;}catch{t.modulesHealthy=false;}return await Te(e,t),t}if(c){t.framework="FastAPI";let a=w.join(e,".venv");if(await k__default.pathExists(a)){t.venvActive=true;let p=process.platform==="win32"?w.join(a,"Scripts","python.exe"):w.join(a,"bin","python");if(await k__default.pathExists(p)){try{let{stdout:y}=await execa(p,["-c","import rapidkit_core; print(rapidkit_core.__version__)"],{timeout:2e3});t.coreInstalled=true,t.coreVersion=y.trim();}catch{t.coreInstalled=false;}try{await execa(p,["-c","import fastapi"],{timeout:2e3}),t.depsInstalled=true;}catch{try{let y=w.join(a,"lib");if(await k__default.pathExists(y)){let v=(await k__default.readdir(y)).find(C=>C.startsWith("python"));if(v){let C=w.join(y,v,"site-packages");if(await k__default.pathExists(C)){let M=(await k__default.readdir(C)).filter(U=>!U.startsWith("_")&&!U.includes("dist-info")&&!["pip","setuptools","wheel","pkg_resources"].includes(U));t.depsInstalled=M.length>0;}}}t.depsInstalled||(t.issues.push("Dependencies not installed"),t.fixCommands?.push(`cd ${e} && rapidkit init`));}catch{t.issues.push("Could not verify dependency installation");}}}else t.issues.push("Virtual environment exists but Python executable not found");}else t.issues.push("Virtual environment not created"),t.fixCommands?.push(`cd ${e} && rapidkit init`);let l=w.join(e,".env");if(t.hasEnvFile=await k__default.pathExists(l),!t.hasEnvFile){let p=w.join(e,".env.example");await k__default.pathExists(p)&&(t.issues.push("Environment file missing (found .env.example)"),t.fixCommands?.push(`cd ${e} && cp .env.example .env`));}let d=w.join(e,"src"),m=w.join(e,"modules");if(t.modulesHealthy=true,t.missingModules=[],await k__default.pathExists(d)){let p=w.join(d,"__init__.py");await k__default.pathExists(p)||(t.modulesHealthy=false,t.missingModules.push("src/__init__.py"));}if(await k__default.pathExists(m))try{let p=await yt(m);for(let y of p){let P=w.join(m,y,"__init__.py");await k__default.pathExists(P)||(t.modulesHealthy=false,t.missingModules.push(`modules/${y}/__init__.py`));}}catch{}return !t.modulesHealthy&&t.missingModules.length>0&&t.issues.push(`Missing module init files: ${t.missingModules.join(", ")}`),await Te(e,t),t}return t.issues.push("Unknown project type (no package.json or pyproject.toml)"),await Te(e,t),t}async function yt(e){try{return (await k__default.readdir(e,{withFileTypes:true})).filter(t=>t.isDirectory()).map(t=>t.name)}catch{try{let o=await k__default.readdir(e),t=[];for(let n of o)try{(await k__default.stat(w.join(e,n))).isDirectory()&&t.push(n);}catch{continue}return t}catch{return []}}}async function Ne(e){let o=w.join(e,".rapidkit");if(!await k__default.pathExists(o))return false;let t=["project.json","context.json","file-hashes.json"];for(let n of t)if(await k__default.pathExists(w.join(o,n)))return true;return false}function wt(e,o){if(o.has(e))return true;let t=e.toLowerCase();return !!(t==="dist"||t.startsWith("dist-")||t.startsWith("dist_")||t==="build"||t.startsWith("build-")||t.startsWith("build_"))}async function wo(e,o,t){let n=new Set,i=[{dir:e,depth:0}];for(;i.length>0;){let r=i.shift();if(!r)break;try{let s=await k__default.readdir(r.dir);for(let c of s){if(wt(c,t))continue;let a=w.join(r.dir,c),l;try{l=await k__default.stat(a);}catch{continue}if(l.isDirectory()){if(await Ne(a)){n.add(a);continue}r.depth<o&&i.push({dir:a,depth:r.depth+1});}}}catch{continue}}return Array.from(n)}async function ko(e){let o=e,t=w.parse(o).root;for(;o!==t;){let n=[w.join(o,".rapidkit-workspace"),w.join(o,".rapidkit","workspace-marker.json"),w.join(o,".rapidkit","config.json")];for(let i of n)if(await k__default.pathExists(i))return o;o=w.dirname(o);}return null}function vo(e,o){let t=0,n=0,i=0;return e.forEach(s=>{s.status==="ok"?t++:s.status==="warn"?n++:s.status==="error"&&i++;}),o.forEach(s=>{s.issues.length===0&&s.venvActive&&s.depsInstalled?t++:s.issues.length>0&&n++;}),{total:t+n+i,passed:t,warnings:n,errors:i}}async function bo(e){let o=w.basename(e);try{let i=w.join(e,".rapidkit-workspace");await k__default.pathExists(i)&&(o=(await k__default.readJSON(i)).name||o);}catch{try{let i=w.join(e,".rapidkit","config.json");o=(await k__default.readJSON(i)).workspace_name||o;}catch{}}let t={workspacePath:e,workspaceName:o,python:await mt(),poetry:await gt(),pipx:await ft(),rapidkitCore:await ht(),projects:[]};try{let i=new Set([".git",".venv","node_modules",".rapidkit","dist","build","coverage","__pycache__"]),r=new Set;await Ne(e)&&r.add(e);let s=async(c,a)=>{if(a<0)return;let l=await yt(c);for(let d of l){if(wt(d,i))continue;let m=w.join(c,d);if(await Ne(m)){r.add(m);continue}a>0&&await s(m,a-1);}};if(await s(e,1),a.debug(`Workspace scan (shallow) found ${r.size} project(s)`),r.size===0){let c=await wo(e,3,i);c.forEach(a=>r.add(a)),a.debug(`Workspace scan (deep fallback) found ${c.length} project(s)`);}r.size>0&&a.debug(`Workspace projects detected: ${Array.from(r).join(", ")}`);for(let c of r){let a=await yo(c);t.projects.push(a);}}catch(i){a.debug(`Failed to scan workspace projects: ${i}`);}let n=[t.python,t.poetry,t.pipx,t.rapidkitCore];if(t.healthScore=vo(n,t.projects),t.rapidkitCore.status==="ok"){let i=t.rapidkitCore.message.match(/([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);i&&(t.coreVersion=i[1]);}return t}function q(e,o){let t=e.status==="ok"?"\u2705":e.status==="warn"?"\u26A0\uFE0F":"\u274C",n=e.status==="ok"?u.green:e.status==="warn"?u.yellow:u.red;console.log(`${t} ${u.bold(o)}: ${n(e.message)}`),e.paths&&e.paths.length>0?e.paths.forEach(i=>{let r=i.version?u.cyan(` -> ${i.version}`):"";console.log(` ${u.cyan("\u2022")} ${u.gray(i.location)}: ${u.dim(i.path)}${r}`);}):e.details&&console.log(` ${u.gray(e.details)}`);}function xo(e){let o=e.issues.length>0,t=o?"\u26A0\uFE0F":"\u2705",n=o?u.yellow:u.green;if(console.log(`
856
- ${t} ${u.bold("Project")}: ${n(e.name)}`),e.framework){let c=e.framework==="FastAPI"?"\u{1F40D}":e.framework==="NestJS"?"\u{1F985}":"\u{1F4E6}";console.log(` ${c} Framework: ${u.cyan(e.framework)}${e.kit?u.gray(` (${e.kit})`):""}`);}if(console.log(` ${u.gray(`Path: ${e.path}`)}`),!(e.venvActive&&!e.coreInstalled)&&(e.venvActive?console.log(` \u2705 Virtual environment: ${u.green("Active")}`):console.log(` \u274C Virtual environment: ${u.red("Not found")}`),e.coreInstalled?console.log(` ${u.dim("\u2139")} RapidKit Core: ${u.gray(e.coreVersion||"In venv")} ${u.dim("(optional)")}`):console.log(` ${u.dim("\u2139")} RapidKit Core: ${u.gray("Using global installation")} ${u.dim("(recommended)")}`)),e.depsInstalled?console.log(` \u2705 Dependencies: ${u.green("Installed")}`):console.log(` \u26A0\uFE0F Dependencies: ${u.yellow("Not installed")}`),e.hasEnvFile!==void 0&&(e.hasEnvFile?console.log(` \u2705 Environment: ${u.green(".env configured")}`):console.log(` \u26A0\uFE0F Environment: ${u.yellow(".env missing")}`)),e.modulesHealthy!==void 0&&(e.modulesHealthy?console.log(` \u2705 Modules: ${u.green("Healthy")}`):e.missingModules&&e.missingModules.length>0&&console.log(` \u26A0\uFE0F Modules: ${u.yellow(`Missing ${e.missingModules.length} init file(s)`)}`)),e.stats){let c=[];e.stats.modules!==void 0&&c.push(`${e.stats.modules} module${e.stats.modules!==1?"s":""}`),c.length>0&&console.log(` \u{1F4CA} Stats: ${u.cyan(c.join(" \u2022 "))}`);}e.lastModified&&console.log(` \u{1F552} Last Modified: ${u.gray(e.lastModified)}`);let s=[];if(e.hasTests!==void 0&&s.push(e.hasTests?"\u2705 Tests":u.dim("\u2298 No tests")),e.hasDocker!==void 0&&s.push(e.hasDocker?"\u2705 Docker":u.dim("\u2298 No Docker")),e.hasCodeQuality!==void 0){let c=e.framework==="NestJS"?"ESLint":"Ruff";s.push(e.hasCodeQuality?`\u2705 ${c}`:u.dim(`\u2298 No ${c}`));}s.length>0&&console.log(` ${s.join(" \u2022 ")}`),e.vulnerabilities!==void 0&&e.vulnerabilities>0&&console.log(` \u26A0\uFE0F Security: ${u.yellow(`${e.vulnerabilities} vulnerability(ies) found`)}`),e.issues.length>0&&(console.log(` ${u.bold("Issues:")}`),e.issues.forEach(c=>{console.log(` \u2022 ${u.yellow(c)}`);}),e.fixCommands&&e.fixCommands.length>0&&(console.log(`
857
- ${u.bold.cyan("\u{1F527} Quick Fix:")}`),e.fixCommands.forEach(c=>{console.log(` ${u.cyan("$")} ${u.white(c)}`);})));}async function ut(e,o=false){let t=e.filter(i=>i.fixCommands&&i.fixCommands.length>0);if(t.length===0){console.log(u.green(`
858
- \u2705 No fixes needed - all projects are healthy!`));return}console.log(u.bold.cyan(`
853
+ ${g.gray("Alternative: npm run start:dev, ./rapidkit dev")}
854
+ ${g.gray("\u{1F4A1} Tip: Install globally (npm i -g rapidkit) to use without npx")}
855
+ `);}catch(a){throw r.fail(`Failed to generate ${i} project`),a}}function Ot(e){return e.split(/[-_\s]+/).map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join("")}async function Mo(e,o){await promises.mkdir(h.dirname(e),{recursive:true}),await promises.writeFile(e,o,"utf8");}function Lo(e){return `package main
856
+
857
+ import (
858
+ "fmt"
859
+ "log/slog"
860
+ "os"
861
+ "os/signal"
862
+ "syscall"
863
+ "time"
864
+
865
+ _ "${e.module_path}/docs"
866
+ "${e.module_path}/internal/config"
867
+ "${e.module_path}/internal/server"
868
+ )
869
+
870
+ // Build-time variables \u2014 injected via -ldflags.
871
+ var (
872
+ version = "dev"
873
+ commit = "none"
874
+ date = "unknown"
875
+ )
876
+
877
+ func main() {
878
+ cfg := config.Load()
879
+
880
+ log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
881
+ Level: config.ParseLogLevel(cfg.LogLevel),
882
+ }))
883
+ slog.SetDefault(log)
884
+
885
+ app := server.NewApp(cfg)
886
+
887
+ // Graceful shutdown on SIGINT / SIGTERM
888
+ quit := make(chan os.Signal, 1)
889
+ signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
890
+
891
+ go func() {
892
+ slog.Info("starting", "port", cfg.Port, "version", version, "commit", commit, "date", date, "env", cfg.Env)
893
+ fmt.Printf("\\n\u{1F680} Server \u2192 http://127.0.0.1:%s\\n", cfg.Port)
894
+ fmt.Printf("\u{1F4D6} Docs \u2192 http://127.0.0.1:%s/docs\\n\\n", cfg.Port)
895
+ if err := app.Listen(":" + cfg.Port); err != nil {
896
+ slog.Error("server error", "err", err)
897
+ os.Exit(1)
898
+ }
899
+ }()
900
+
901
+ <-quit
902
+ slog.Info("shutting down\u2026")
903
+ if err := app.ShutdownWithTimeout(5 * time.Second); err != nil {
904
+ slog.Error("graceful shutdown failed", "err", err)
905
+ os.Exit(1)
906
+ }
907
+ slog.Info("server stopped")
908
+ }
909
+ `}function Fo(e){return `module ${e.module_path}
910
+
911
+ go ${e.go_version}
912
+
913
+ require (
914
+ github.com/gofiber/fiber/v2 v2.52.5
915
+ github.com/swaggo/fiber-swagger v1.3.0
916
+ github.com/swaggo/swag v1.16.3
917
+ )
918
+
919
+ require (
920
+ github.com/KyleBanks/depth v1.2.1 // indirect
921
+ github.com/andybalholm/brotli v1.1.0 // indirect
922
+ github.com/ghodss/yaml v1.0.0 // indirect
923
+ github.com/go-openapi/jsonpointer v0.21.0 // indirect
924
+ github.com/go-openapi/jsonreference v0.21.0 // indirect
925
+ github.com/go-openapi/spec v0.21.0 // indirect
926
+ github.com/go-openapi/swag v0.23.0 // indirect
927
+ github.com/josharian/intern v1.0.0 // indirect
928
+ github.com/klauspost/compress v1.17.6 // indirect
929
+ github.com/mailru/easyjson v0.7.7 // indirect
930
+ github.com/mattn/go-colorable v0.1.13 // indirect
931
+ github.com/mattn/go-isatty v0.0.20 // indirect
932
+ github.com/mattn/go-runewidth v0.0.15 // indirect
933
+ github.com/rivo/uniseg v0.2.0 // indirect
934
+ github.com/swaggo/files v1.0.1 // indirect
935
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
936
+ github.com/valyala/fasthttp v1.52.0 // indirect
937
+ github.com/valyala/tcplisten v1.0.0 // indirect
938
+ golang.org/x/net v0.25.0 // indirect
939
+ golang.org/x/sys v0.16.0 // indirect
940
+ golang.org/x/tools v0.21.0 // indirect
941
+ gopkg.in/yaml.v2 v2.4.0 // indirect
942
+ )
943
+ `}function Ho(e){return `package config
944
+
945
+ import (
946
+ "log/slog"
947
+ "os"
948
+ "strings"
949
+ )
950
+
951
+ // Config holds application configuration loaded from environment variables.
952
+ type Config struct {
953
+ Port string
954
+ Env string
955
+ LogLevel string
956
+ }
957
+
958
+ // Load reads configuration from environment variables with sensible defaults.
959
+ func Load() *Config {
960
+ env := getEnv("APP_ENV", "development")
961
+ return &Config{
962
+ Port: getEnv("PORT", "${e.port}"),
963
+ Env: env,
964
+ LogLevel: getEnv("LOG_LEVEL", defaultLogLevel(env)),
965
+ }
966
+ }
967
+
968
+ // ParseLogLevel maps a level string to the corresponding slog.Level.
969
+ // Falls back to Info for unrecognised values.
970
+ func ParseLogLevel(s string) slog.Level {
971
+ switch strings.ToLower(s) {
972
+ case "debug":
973
+ return slog.LevelDebug
974
+ case "warn", "warning":
975
+ return slog.LevelWarn
976
+ case "error":
977
+ return slog.LevelError
978
+ default:
979
+ return slog.LevelInfo
980
+ }
981
+ }
982
+
983
+ func defaultLogLevel(env string) string {
984
+ if env == "development" {
985
+ return "debug"
986
+ }
987
+ return "info"
988
+ }
989
+
990
+ func getEnv(key, fallback string) string {
991
+ if v, ok := os.LookupEnv(key); ok && v != "" {
992
+ return v
993
+ }
994
+ return fallback
995
+ }
996
+ `}function Ko(e){return `package server
997
+
998
+ import (
999
+ "net/http"
1000
+ "time"
1001
+
1002
+ "github.com/gofiber/fiber/v2"
1003
+ "github.com/gofiber/fiber/v2/middleware/recover"
1004
+ fiberSwagger "github.com/swaggo/fiber-swagger"
1005
+
1006
+ "${e.module_path}/internal/apierr"
1007
+ "${e.module_path}/internal/config"
1008
+ "${e.module_path}/internal/handlers"
1009
+ "${e.module_path}/internal/middleware"
1010
+ )
1011
+
1012
+ // NewApp creates and configures the Fiber application.
1013
+ // Call this from main \u2014 or from tests via server.NewApp(cfg).
1014
+ func NewApp(cfg *config.Config) *fiber.App {
1015
+ app := fiber.New(fiber.Config{
1016
+ AppName: "${e.project_name}",
1017
+ ReadTimeout: 5 * time.Second,
1018
+ WriteTimeout: 10 * time.Second,
1019
+ IdleTimeout: 30 * time.Second,
1020
+ // Override default error handler to always return JSON.
1021
+ // The catch-all middleware returns fiber.ErrNotFound so all 404s
1022
+ // are routed here, keeping error formatting in one place.
1023
+ ErrorHandler: func(c *fiber.Ctx, err error) error {
1024
+ code := fiber.StatusInternalServerError
1025
+ if e, ok := err.(*fiber.Error); ok {
1026
+ code = e.Code
1027
+ }
1028
+ if code == http.StatusNotFound {
1029
+ return apierr.NotFound(c, "route not found")
1030
+ }
1031
+ if code == http.StatusMethodNotAllowed {
1032
+ return apierr.MethodNotAllowed(c)
1033
+ }
1034
+ // Fallback for any unexpected error (e.g. panic-recovered 500).
1035
+ return apierr.InternalError(c, err)
1036
+ },
1037
+ })
1038
+
1039
+ app.Use(recover.New())
1040
+ app.Use(middleware.CORS())
1041
+ app.Use(middleware.RequestID())
1042
+ app.Use(middleware.RateLimit())
1043
+ app.Use(middleware.Logger())
1044
+
1045
+ // Swagger UI \u2014 /docs redirects to /docs/index.html
1046
+ app.Get("/docs", func(c *fiber.Ctx) error { return c.Redirect("/docs/index.html", fiber.StatusFound) })
1047
+ app.Get("/docs/*", fiberSwagger.WrapHandler)
1048
+
1049
+ v1 := app.Group("/api/v1")
1050
+ v1.Get("/health/live", handlers.Liveness)
1051
+ v1.Get("/health/ready", handlers.Readiness)
1052
+ v1.Get("/echo/:name", handlers.EchoParams)
1053
+
1054
+ // 404 catch-all: return fiber.ErrNotFound so it is processed by the
1055
+ // custom ErrorHandler above, keeping all error formatting in one place.
1056
+ app.Use(func(c *fiber.Ctx) error {
1057
+ return fiber.ErrNotFound
1058
+ })
1059
+
1060
+ return app
1061
+ }
1062
+ `}function Vo(){return `package handlers
1063
+
1064
+ import (
1065
+ "time"
1066
+
1067
+ "github.com/gofiber/fiber/v2"
1068
+ )
1069
+
1070
+ // Liveness signals the process is alive (Kubernetes livenessProbe).
1071
+ //
1072
+ // @Summary Liveness probe
1073
+ // @Description Returns 200 when the process is alive.
1074
+ // @Tags health
1075
+ // @Produce json
1076
+ // @Success 200 {object} map[string]string
1077
+ // @Router /api/v1/health/live [get]
1078
+ func Liveness(c *fiber.Ctx) error {
1079
+ return c.JSON(fiber.Map{
1080
+ "status": "ok",
1081
+ "time": time.Now().UTC().Format(time.RFC3339),
1082
+ })
1083
+ }
1084
+
1085
+ // Readiness signals the service can accept traffic (Kubernetes readinessProbe).
1086
+ // Extend this function to check database connectivity, caches, etc.
1087
+ //
1088
+ // @Summary Readiness probe
1089
+ // @Description Returns 200 when the service is ready to accept traffic.
1090
+ // @Tags health
1091
+ // @Produce json
1092
+ // @Success 200 {object} map[string]string
1093
+ // @Router /api/v1/health/ready [get]
1094
+ func Readiness(c *fiber.Ctx) error {
1095
+ return c.JSON(fiber.Map{
1096
+ "status": "ready",
1097
+ "time": time.Now().UTC().Format(time.RFC3339),
1098
+ })
1099
+ }
1100
+ `}function Uo(e){return `package handlers_test
1101
+
1102
+ import (
1103
+ "encoding/json"
1104
+ "io"
1105
+ "net/http"
1106
+ "net/http/httptest"
1107
+ "testing"
1108
+
1109
+ "${e.module_path}/internal/config"
1110
+ "${e.module_path}/internal/server"
1111
+ )
1112
+
1113
+ func TestLiveness(t *testing.T) {
1114
+ app := server.NewApp(config.Load())
1115
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/health/live", nil)
1116
+ resp, err := app.Test(req, -1)
1117
+ if err != nil {
1118
+ t.Fatalf("request error: %v", err)
1119
+ }
1120
+ defer resp.Body.Close()
1121
+
1122
+ if resp.StatusCode != http.StatusOK {
1123
+ data, _ := io.ReadAll(resp.Body)
1124
+ t.Fatalf("expected 200, got %d: %s", resp.StatusCode, data)
1125
+ }
1126
+
1127
+ var body map[string]any
1128
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
1129
+ t.Fatalf("invalid JSON: %v", err)
1130
+ }
1131
+ if body["status"] != "ok" {
1132
+ t.Fatalf("expected ok, got %v", body["status"])
1133
+ }
1134
+ }
1135
+
1136
+ func TestReadiness(t *testing.T) {
1137
+ app := server.NewApp(config.Load())
1138
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/health/ready", nil)
1139
+ resp, err := app.Test(req, -1)
1140
+ if err != nil {
1141
+ t.Fatalf("request error: %v", err)
1142
+ }
1143
+ defer resp.Body.Close()
1144
+
1145
+ if resp.StatusCode != http.StatusOK {
1146
+ t.Fatalf("expected 200, got %d: %s", resp.StatusCode, resp.Status)
1147
+ }
1148
+ }
1149
+ `}function Wo(){return `# \u2500\u2500 Build stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1150
+ FROM golang:1.24-alpine AS builder
1151
+
1152
+ # Build-time version injection
1153
+ ARG VERSION=dev
1154
+ ARG COMMIT=none
1155
+ ARG DATE=unknown
1156
+
1157
+ WORKDIR /app
1158
+ COPY go.mod go.sum ./
1159
+ RUN go mod download
1160
+
1161
+ COPY . .
1162
+ RUN CGO_ENABLED=0 GOOS=linux go build \\
1163
+ -ldflags="-s -w -X main.version=$\${VERSION} -X main.commit=$\${COMMIT} -X main.date=$\${DATE}" \\
1164
+ -o server ./cmd/server
1165
+
1166
+ # \u2500\u2500 Runtime stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1167
+ # alpine includes busybox wget required for the HEALTHCHECK below.
1168
+ FROM alpine:3.21
1169
+
1170
+ RUN addgroup -S app && adduser -S -G app app
1171
+ COPY --from=builder /app/server /server
1172
+ USER app
1173
+
1174
+ EXPOSE 3000
1175
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
1176
+ CMD wget -qO- http://localhost:3000/api/v1/health/live || exit 1
1177
+ ENTRYPOINT ["/server"]
1178
+ `}function Bo(e){return `version: "3.9"
1179
+
1180
+ services:
1181
+ api:
1182
+ build: .
1183
+ container_name: ${e.project_name}
1184
+ ports:
1185
+ - "${e.port}:${e.port}"
1186
+ environment:
1187
+ PORT: "${e.port}"
1188
+ APP_ENV: development
1189
+ LOG_LEVEL: info
1190
+ CORS_ALLOW_ORIGINS: "*"
1191
+ RATE_LIMIT_RPS: "100"
1192
+ restart: unless-stopped
1193
+ `}function Jo(e){return `.PHONY: dev run build test cover lint fmt tidy docs docker-up docker-down
1194
+
1195
+ # Build-time metadata
1196
+ VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
1197
+ COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "none")
1198
+ DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
1199
+ LDFLAGS = -ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE)"
1200
+ # Go tool binaries are installed to GOPATH/bin; include it so \`air\` and \`swag\` are found.
1201
+ GOBIN ?= $(shell go env GOPATH)/bin
1202
+
1203
+ # Hot reload \u2014 installs air on first use
1204
+ dev:
1205
+ @test -x "$(GOBIN)/air" || go install github.com/air-verse/air@latest
1206
+ $(GOBIN)/air
1207
+
1208
+ run:
1209
+ go run $(LDFLAGS) ./cmd/server
1210
+
1211
+ build:
1212
+ go build $(LDFLAGS) -o bin/${e.project_name} ./cmd/server
1213
+
1214
+ test:
1215
+ go test ./... -v -race
1216
+
1217
+ cover:
1218
+ go test ./... -race -coverprofile=coverage.out
1219
+ go tool cover -html=coverage.out -o coverage.html
1220
+ @echo "Coverage report: coverage.html"
1221
+
1222
+ # Generate Swagger docs \u2014 installs swag on first use
1223
+ docs:
1224
+ @test -x "$(GOBIN)/swag" || go install github.com/swaggo/swag/cmd/swag@latest
1225
+ $(GOBIN)/swag init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency
1226
+ tidy:
1227
+ go mod tidy
1228
+
1229
+ docker-up:
1230
+ go mod tidy
1231
+ docker compose up --build \\
1232
+ --build-arg VERSION=$(VERSION) \\
1233
+ --build-arg COMMIT=$(COMMIT) \\
1234
+ --build-arg DATE=$(DATE) \\
1235
+ -d
1236
+
1237
+ docker-down:
1238
+ docker compose down
1239
+ `}function zo(e){return `# Application
1240
+ PORT=${e.port}
1241
+ APP_ENV=development
1242
+ LOG_LEVEL=debug
1243
+
1244
+ # CORS \u2014 comma-separated list of allowed origins, or * to allow all
1245
+ CORS_ALLOW_ORIGINS=*
1246
+
1247
+ # Rate limiting \u2014 max requests per IP per second
1248
+ RATE_LIMIT_RPS=100
1249
+ `}function Yo(){return `# Binaries
1250
+ bin/
1251
+ *.exe
1252
+ *.exe~
1253
+ *.dll
1254
+ *.so
1255
+ *.dylib
1256
+
1257
+ # Test binary
1258
+ *.test
1259
+
1260
+ # Output of go coverage tool
1261
+ *.out
1262
+ coverage.html
1263
+
1264
+ # Go workspace
1265
+ go.work
1266
+ go.work.sum
1267
+
1268
+ # Environment
1269
+ .env
1270
+ .env.local
1271
+
1272
+ # Hot reload (air)
1273
+ tmp/
1274
+
1275
+ # Swagger \u2014 generated files (committed stub docs/doc.go; run \`make docs\` to regenerate)
1276
+ docs/swagger.json
1277
+ docs/swagger.yaml
1278
+ docs/docs.go
1279
+
1280
+ # Editor
1281
+ .idea/
1282
+ .vscode/
1283
+ *.swp
1284
+ *.swo
1285
+
1286
+ # OS
1287
+ .DS_Store
1288
+ Thumbs.db
1289
+ `}function Qo(e){return `name: CI
1290
+
1291
+ on:
1292
+ push:
1293
+ branches: [main, develop]
1294
+ pull_request:
1295
+ branches: [main]
1296
+
1297
+ jobs:
1298
+ test:
1299
+ name: Test
1300
+ runs-on: ubuntu-latest
1301
+
1302
+ steps:
1303
+ - uses: actions/checkout@v4
1304
+
1305
+ - name: Set up Go
1306
+ uses: actions/setup-go@v5
1307
+ with:
1308
+ go-version: "${e.go_version}"
1309
+ cache: true
1310
+
1311
+ - name: Tidy
1312
+ run: go mod tidy
1313
+
1314
+ - name: Build
1315
+ run: go build ./...
1316
+
1317
+ - name: Test
1318
+ run: go test ./... -race -coverprofile=coverage.out
1319
+
1320
+ - name: Upload coverage
1321
+ uses: actions/upload-artifact@v4
1322
+ with:
1323
+ name: coverage
1324
+ path: coverage.out
1325
+
1326
+ lint:
1327
+ name: Lint
1328
+ runs-on: ubuntu-latest
1329
+
1330
+ steps:
1331
+ - uses: actions/checkout@v4
1332
+
1333
+ - name: Set up Go
1334
+ uses: actions/setup-go@v5
1335
+ with:
1336
+ go-version: "${e.go_version}"
1337
+ cache: true
1338
+
1339
+ - name: golangci-lint
1340
+ uses: golangci/golangci-lint-action@v6
1341
+ with:
1342
+ version: latest
1343
+ `}function Xo(e){return `# ${Ot(e.project_name)}
1344
+
1345
+ > ${e.description}
1346
+
1347
+ Built with [Go](https://go.dev/) + [Fiber v2](https://gofiber.io/) \xB7 Scaffolded by [RapidKit](https://getrapidkit.com)
1348
+
1349
+ ## Quick start
1350
+
1351
+ \`\`\`bash
1352
+ # Run locally (hot reload)
1353
+ make dev
1354
+
1355
+ # Run tests
1356
+ make test
1357
+
1358
+ # Build binary
1359
+ make build
1360
+
1361
+ # Generate / refresh Swagger docs
1362
+ make docs
1363
+
1364
+ # Docker
1365
+ make docker-up
1366
+ \`\`\`
1367
+
1368
+ ## Swagger / OpenAPI
1369
+
1370
+ After running \`make docs\`, the interactive UI is available at:
1371
+
1372
+ \`\`\`
1373
+ http://localhost:${e.port}/docs
1374
+ \`\`\`
1375
+
1376
+ The raw OpenAPI spec is served at \`/docs/doc.json\`.
1377
+
1378
+ ## Endpoints
1379
+
1380
+ | Method | Path | Description |
1381
+ |--------|------|-------------|
1382
+ | GET | /api/v1/health/live | Kubernetes livenessProbe |
1383
+ | GET | /api/v1/health/ready | Kubernetes readinessProbe |
1384
+ | GET | /api/v1/echo/:name | Example handler \u2014 remove in production |
1385
+ | GET | /docs/* | Swagger UI (OpenAPI docs) |
1386
+
1387
+ ## Configuration
1388
+
1389
+ All configuration is done through environment variables (see \`.env.example\`):
1390
+
1391
+ | Variable | Default | Description |
1392
+ |----------|---------|-------------|
1393
+ | \`PORT\` | \`${e.port}\` | HTTP listen port |
1394
+ | \`APP_ENV\` | \`development\` | Application environment |
1395
+ | \`LOG_LEVEL\` | \`debug\` / \`info\` | \`debug\` \\| \`info\` \\| \`warn\` \\| \`error\` |
1396
+ | \`CORS_ALLOW_ORIGINS\` | \`*\` | Comma-separated list of allowed origins, or \`*\` |
1397
+ | \`RATE_LIMIT_RPS\` | \`100\` | Max requests per IP per second |
1398
+
1399
+ ## Project structure
1400
+
1401
+ \`\`\`
1402
+ ${e.project_name}/
1403
+ \u251C\u2500\u2500 cmd/
1404
+ \u2502 \u2514\u2500\u2500 server/
1405
+ \u2502 \u2514\u2500\u2500 main.go # Graceful shutdown + version ldflags
1406
+ \u251C\u2500\u2500 docs/ # Swagger generated files (\`make docs\`)
1407
+ \u2502 \u2514\u2500\u2500 doc.go # Package-level OpenAPI annotations
1408
+ \u251C\u2500\u2500 internal/
1409
+ \u2502 \u251C\u2500\u2500 apierr/ # Consistent JSON error envelope
1410
+ \u2502 \u2502 \u251C\u2500\u2500 apierr.go
1411
+ \u2502 \u2502 \u2514\u2500\u2500 apierr_test.go
1412
+ \u2502 \u251C\u2500\u2500 config/ # 12-factor configuration
1413
+ \u2502 \u2502 \u251C\u2500\u2500 config.go
1414
+ \u2502 \u2502 \u2514\u2500\u2500 config_test.go
1415
+ \u2502 \u251C\u2500\u2500 handlers/ # HTTP handlers + tests
1416
+ \u2502 \u2502 \u251C\u2500\u2500 health.go
1417
+ \u2502 \u2502 \u251C\u2500\u2500 health_test.go
1418
+ \u2502 \u2502 \u251C\u2500\u2500 example.go # EchoParams \u2014 replace with your own handlers
1419
+ \u2502 \u2502 \u2514\u2500\u2500 example_test.go
1420
+ \u2502 \u251C\u2500\u2500 middleware/
1421
+ \u2502 \u2502 \u251C\u2500\u2500 requestid.go # X-Request-ID + structured logger
1422
+ \u2502 \u2502 \u251C\u2500\u2500 requestid_test.go
1423
+ \u2502 \u2502 \u251C\u2500\u2500 cors.go # CORS (CORS_ALLOW_ORIGINS)
1424
+ \u2502 \u2502 \u251C\u2500\u2500 cors_test.go
1425
+ \u2502 \u2502 \u251C\u2500\u2500 ratelimit.go # Per-IP limiter (RATE_LIMIT_RPS)
1426
+ \u2502 \u2502 \u2514\u2500\u2500 ratelimit_test.go
1427
+ \u2502 \u2514\u2500\u2500 server/
1428
+ \u2502 \u251C\u2500\u2500 server.go
1429
+ \u2502 \u2514\u2500\u2500 server_test.go
1430
+ \u251C\u2500\u2500 .air.toml # Hot reload
1431
+ \u251C\u2500\u2500 .github/workflows/ci.yml # CI: test + lint
1432
+ \u251C\u2500\u2500 .golangci.yml
1433
+ \u251C\u2500\u2500 Dockerfile # Multi-stage, alpine HEALTHCHECK
1434
+ \u251C\u2500\u2500 docker-compose.yml
1435
+ \u251C\u2500\u2500 Makefile
1436
+ \u2514\u2500\u2500 README.md
1437
+ \`\`\`
1438
+
1439
+ ## Available commands
1440
+
1441
+ | Command | Description |
1442
+ |---------|-------------|
1443
+ | \`make dev\` | Hot reload via [air](https://github.com/air-verse/air) |
1444
+ | \`make run\` | Run without hot reload |
1445
+ | \`make build\` | Binary with version ldflags |
1446
+ | \`make test\` | Run tests with race detector |
1447
+ | \`make cover\` | HTML coverage report |
1448
+ | \`make docs\` | Re-generate Swagger JSON (needs \`swag\`) |
1449
+ | \`make lint\` | golangci-lint |
1450
+ | \`make fmt\` | gofmt |
1451
+ | \`make tidy\` | go mod tidy |
1452
+ | \`make docker-up\` | Build & run via Docker Compose |
1453
+ | \`make docker-down\` | Stop |
1454
+
1455
+ ## License
1456
+
1457
+ ${e.app_version} \xB7 ${e.author}
1458
+ `}function Zo(){return `package middleware
1459
+
1460
+ import (
1461
+ "crypto/rand"
1462
+ "encoding/hex"
1463
+ "log/slog"
1464
+ "time"
1465
+
1466
+ "github.com/gofiber/fiber/v2"
1467
+ )
1468
+
1469
+ const headerRequestID = "X-Request-ID"
1470
+
1471
+ // RequestID injects a unique identifier into every request.
1472
+ // If the caller sends an X-Request-ID header it is reused; otherwise a new one
1473
+ // is generated and written back in the response.
1474
+ func RequestID() fiber.Handler {
1475
+ return func(c *fiber.Ctx) error {
1476
+ id := c.Get(headerRequestID)
1477
+ if id == "" {
1478
+ id = newID()
1479
+ }
1480
+ c.Set(headerRequestID, id)
1481
+ c.Locals("request_id", id)
1482
+ return c.Next()
1483
+ }
1484
+ }
1485
+
1486
+ // Logger emits a structured JSON log line after each request.
1487
+ func Logger() fiber.Handler {
1488
+ return func(c *fiber.Ctx) error {
1489
+ start := time.Now()
1490
+ err := c.Next()
1491
+ slog.Info("http",
1492
+ "method", c.Method(),
1493
+ "path", c.Path(),
1494
+ "status", c.Response().StatusCode(),
1495
+ "bytes", c.Response().Header.ContentLength(),
1496
+ "latency_ms", time.Since(start).Milliseconds(),
1497
+ "ip", c.IP(),
1498
+ "request_id", c.Locals("request_id"),
1499
+ )
1500
+ return err
1501
+ }
1502
+ }
1503
+
1504
+ func newID() string {
1505
+ b := make([]byte, 8)
1506
+ if _, err := rand.Read(b); err != nil {
1507
+ return "unknown"
1508
+ }
1509
+ return hex.EncodeToString(b)
1510
+ }
1511
+ `}function er(e){return `package middleware_test
1512
+
1513
+ import (
1514
+ "net/http"
1515
+ "net/http/httptest"
1516
+ "testing"
1517
+
1518
+ "github.com/gofiber/fiber/v2"
1519
+
1520
+ "${e.module_path}/internal/middleware"
1521
+ )
1522
+
1523
+ func newTestApp() *fiber.App {
1524
+ app := fiber.New()
1525
+ app.Use(middleware.RequestID())
1526
+ app.Use(middleware.Logger())
1527
+ app.Get("/ping", func(c *fiber.Ctx) error {
1528
+ return c.SendString("pong")
1529
+ })
1530
+ return app
1531
+ }
1532
+
1533
+ func TestRequestID_IsGenerated(t *testing.T) {
1534
+ req := httptest.NewRequest(http.MethodGet, "/ping", nil)
1535
+ resp, err := newTestApp().Test(req, -1)
1536
+ if err != nil {
1537
+ t.Fatalf("request error: %v", err)
1538
+ }
1539
+ defer resp.Body.Close()
1540
+
1541
+ id := resp.Header.Get("X-Request-ID")
1542
+ if id == "" {
1543
+ t.Fatal("expected X-Request-ID header to be set")
1544
+ }
1545
+ if len(id) != 16 { // 8 random bytes \u2192 16 hex chars
1546
+ t.Fatalf("unexpected request ID length %d, want 16", len(id))
1547
+ }
1548
+ }
1549
+
1550
+ func TestRequestID_IsReused(t *testing.T) {
1551
+ req := httptest.NewRequest(http.MethodGet, "/ping", nil)
1552
+ req.Header.Set("X-Request-ID", "my-trace-id")
1553
+ resp, err := newTestApp().Test(req, -1)
1554
+ if err != nil {
1555
+ t.Fatalf("request error: %v", err)
1556
+ }
1557
+ defer resp.Body.Close()
1558
+
1559
+ id := resp.Header.Get("X-Request-ID")
1560
+ if id != "my-trace-id" {
1561
+ t.Fatalf("expected X-Request-ID to be reused, got %q", id)
1562
+ }
1563
+ }
1564
+ `}function tr(){return `// Package apierr provides a consistent JSON error envelope for all API responses.
1565
+ //
1566
+ // Every error response looks like:
1567
+ //
1568
+ // {"error": "user not found", "code": "NOT_FOUND", "request_id": "a1b2c3d4..."}
1569
+ package apierr
1570
+
1571
+ import (
1572
+ "net/http"
1573
+
1574
+ "github.com/gofiber/fiber/v2"
1575
+ )
1576
+
1577
+ // Response is the standard error envelope returned by all API endpoints.
1578
+ type Response struct {
1579
+ Error string \`json:"error"\`
1580
+ Code string \`json:"code"\`
1581
+ RequestID string \`json:"request_id,omitempty"\`
1582
+ }
1583
+
1584
+ func reply(c *fiber.Ctx, status int, msg, code string) error {
1585
+ rid, _ := c.Locals("request_id").(string)
1586
+ return c.Status(status).JSON(Response{
1587
+ Error: msg,
1588
+ Code: code,
1589
+ RequestID: rid,
1590
+ })
1591
+ }
1592
+
1593
+ // BadRequest responds with 400 and code "BAD_REQUEST".
1594
+ func BadRequest(c *fiber.Ctx, msg string) error {
1595
+ return reply(c, http.StatusBadRequest, msg, "BAD_REQUEST")
1596
+ }
1597
+
1598
+ // NotFound responds with 404 and code "NOT_FOUND".
1599
+ func NotFound(c *fiber.Ctx, msg string) error {
1600
+ return reply(c, http.StatusNotFound, msg, "NOT_FOUND")
1601
+ }
1602
+
1603
+ // Unauthorized responds with 401 and code "UNAUTHORIZED".
1604
+ func Unauthorized(c *fiber.Ctx) error {
1605
+ return reply(c, http.StatusUnauthorized, "authentication required", "UNAUTHORIZED")
1606
+ }
1607
+
1608
+ // Forbidden responds with 403 and code "FORBIDDEN".
1609
+ func Forbidden(c *fiber.Ctx) error {
1610
+ return reply(c, http.StatusForbidden, "access denied", "FORBIDDEN")
1611
+ }
1612
+
1613
+ // MethodNotAllowed responds with 405 and code "METHOD_NOT_ALLOWED".
1614
+ func MethodNotAllowed(c *fiber.Ctx) error {
1615
+ return reply(c, http.StatusMethodNotAllowed, "method not allowed", "METHOD_NOT_ALLOWED")
1616
+ }
1617
+
1618
+ // InternalError responds with 500 and code "INTERNAL_ERROR".
1619
+ // The original error is intentionally not exposed to the client.
1620
+ func InternalError(c *fiber.Ctx, _ error) error {
1621
+ return reply(c, http.StatusInternalServerError, "an internal error occurred", "INTERNAL_ERROR")
1622
+ }
1623
+
1624
+ // TooManyRequests responds with 429 and code "TOO_MANY_REQUESTS".
1625
+ func TooManyRequests(c *fiber.Ctx, msg string) error {
1626
+ return reply(c, http.StatusTooManyRequests, msg, "TOO_MANY_REQUESTS")
1627
+ }
1628
+ `}function or(e){return `package apierr_test
1629
+
1630
+ import (
1631
+ "encoding/json"
1632
+ "io"
1633
+ "net/http"
1634
+ "net/http/httptest"
1635
+ "testing"
1636
+
1637
+ "github.com/gofiber/fiber/v2"
1638
+
1639
+ "${e.module_path}/internal/apierr"
1640
+ )
1641
+
1642
+ func makeApp(fn func(*fiber.Ctx) error) *fiber.App {
1643
+ app := fiber.New()
1644
+ app.Get("/test", fn)
1645
+ return app
1646
+ }
1647
+
1648
+ func readJSON(t *testing.T, r io.Reader) apierr.Response {
1649
+ t.Helper()
1650
+ var out apierr.Response
1651
+ if err := json.NewDecoder(r).Decode(&out); err != nil {
1652
+ t.Fatalf("invalid JSON: %v", err)
1653
+ }
1654
+ return out
1655
+ }
1656
+
1657
+ func TestBadRequest(t *testing.T) {
1658
+ app := makeApp(func(c *fiber.Ctx) error { return apierr.BadRequest(c, "invalid email") })
1659
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
1660
+ resp, _ := app.Test(req, -1)
1661
+ defer resp.Body.Close()
1662
+
1663
+ if resp.StatusCode != http.StatusBadRequest {
1664
+ t.Fatalf("expected 400, got %d", resp.StatusCode)
1665
+ }
1666
+ body := readJSON(t, resp.Body)
1667
+ if body.Code != "BAD_REQUEST" {
1668
+ t.Fatalf("expected BAD_REQUEST, got %q", body.Code)
1669
+ }
1670
+ if body.Error != "invalid email" {
1671
+ t.Fatalf("unexpected error message: %q", body.Error)
1672
+ }
1673
+ }
1674
+
1675
+ func TestNotFound(t *testing.T) {
1676
+ app := makeApp(func(c *fiber.Ctx) error { return apierr.NotFound(c, "user not found") })
1677
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
1678
+ resp, _ := app.Test(req, -1)
1679
+ defer resp.Body.Close()
1680
+
1681
+ if resp.StatusCode != http.StatusNotFound {
1682
+ t.Fatalf("expected 404, got %d", resp.StatusCode)
1683
+ }
1684
+ body := readJSON(t, resp.Body)
1685
+ if body.Code != "NOT_FOUND" {
1686
+ t.Fatalf("expected NOT_FOUND, got %q", body.Code)
1687
+ }
1688
+ }
1689
+
1690
+ func TestUnauthorized(t *testing.T) {
1691
+ app := makeApp(func(c *fiber.Ctx) error { return apierr.Unauthorized(c) })
1692
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
1693
+ resp, _ := app.Test(req, -1)
1694
+ defer resp.Body.Close()
1695
+
1696
+ if resp.StatusCode != http.StatusUnauthorized {
1697
+ t.Fatalf("expected 401, got %d", resp.StatusCode)
1698
+ }
1699
+ }
1700
+
1701
+ func TestForbidden(t *testing.T) {
1702
+ app := makeApp(func(c *fiber.Ctx) error { return apierr.Forbidden(c) })
1703
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
1704
+ resp, _ := app.Test(req, -1)
1705
+ defer resp.Body.Close()
1706
+
1707
+ if resp.StatusCode != http.StatusForbidden {
1708
+ t.Fatalf("expected 403, got %d", resp.StatusCode)
1709
+ }
1710
+ body := readJSON(t, resp.Body)
1711
+ if body.Code != "FORBIDDEN" {
1712
+ t.Fatalf("expected FORBIDDEN, got %q", body.Code)
1713
+ }
1714
+ }
1715
+
1716
+ func TestMethodNotAllowed(t *testing.T) {
1717
+ app := makeApp(func(c *fiber.Ctx) error { return apierr.MethodNotAllowed(c) })
1718
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
1719
+ resp, _ := app.Test(req, -1)
1720
+ defer resp.Body.Close()
1721
+
1722
+ if resp.StatusCode != http.StatusMethodNotAllowed {
1723
+ t.Fatalf("expected 405, got %d", resp.StatusCode)
1724
+ }
1725
+ body := readJSON(t, resp.Body)
1726
+ if body.Code != "METHOD_NOT_ALLOWED" {
1727
+ t.Fatalf("expected METHOD_NOT_ALLOWED, got %q", body.Code)
1728
+ }
1729
+ }
1730
+
1731
+ func TestInternalError(t *testing.T) {
1732
+ app := makeApp(func(c *fiber.Ctx) error { return apierr.InternalError(c, nil) })
1733
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
1734
+ resp, _ := app.Test(req, -1)
1735
+ defer resp.Body.Close()
1736
+
1737
+ if resp.StatusCode != http.StatusInternalServerError {
1738
+ t.Fatalf("expected 500, got %d", resp.StatusCode)
1739
+ }
1740
+ body := readJSON(t, resp.Body)
1741
+ if body.Code != "INTERNAL_ERROR" {
1742
+ t.Fatalf("expected INTERNAL_ERROR, got %q", body.Code)
1743
+ }
1744
+ }
1745
+
1746
+ func TestTooManyRequests(t *testing.T) {
1747
+ app := makeApp(func(c *fiber.Ctx) error { return apierr.TooManyRequests(c, "slow down") })
1748
+ req := httptest.NewRequest(http.MethodGet, "/test", nil)
1749
+ resp, _ := app.Test(req, -1)
1750
+ defer resp.Body.Close()
1751
+
1752
+ if resp.StatusCode != http.StatusTooManyRequests {
1753
+ t.Fatalf("expected 429, got %d", resp.StatusCode)
1754
+ }
1755
+ body := readJSON(t, resp.Body)
1756
+ if body.Code != "TOO_MANY_REQUESTS" {
1757
+ t.Fatalf("expected TOO_MANY_REQUESTS, got %q", body.Code)
1758
+ }
1759
+ }
1760
+ `}function rr(e){return `// Package docs provides the swaggo-generated OpenAPI specification.
1761
+ //
1762
+ // Run \`make docs\` to regenerate after changing handler annotations.
1763
+ //
1764
+ // @title ${Ot(e.project_name)} API
1765
+ // @version ${e.app_version}
1766
+ // @description ${e.description}
1767
+ // @host localhost:${e.port}
1768
+ // @BasePath /
1769
+ // @schemes http https
1770
+ //
1771
+ // @contact.name ${e.author}
1772
+ // @license.name MIT
1773
+ package docs
1774
+ `}function nr(e){return `package handlers
1775
+
1776
+ import (
1777
+ "net/http"
1778
+
1779
+ "github.com/gofiber/fiber/v2"
1780
+
1781
+ "${e.module_path}/internal/apierr"
1782
+ )
1783
+
1784
+ // EchoResponse is the JSON body returned by EchoParams.
1785
+ type EchoResponse struct {
1786
+ Name string \`json:"name"\`
1787
+ RequestID string \`json:"request_id"\`
1788
+ }
1789
+
1790
+ // EchoParams is an example handler demonstrating how to:
1791
+ // - read URL path parameters
1792
+ // - use apierr for consistent JSON error responses
1793
+ // - access the request ID injected by RequestID middleware
1794
+ //
1795
+ // Replace or remove this file once you add your own business logic.
1796
+ //
1797
+ // @Summary Echo path parameter
1798
+ // @Description Returns the :name path parameter together with the request ID.
1799
+ // @Tags example
1800
+ // @Produce json
1801
+ // @Param name path string true "Name to echo"
1802
+ // @Success 200 {object} handlers.EchoResponse
1803
+ // @Failure 400 {object} apierr.Response
1804
+ // @Router /api/v1/echo/{name} [get]
1805
+ func EchoParams(c *fiber.Ctx) error {
1806
+ name := c.Params("name")
1807
+ if name == "" {
1808
+ return apierr.BadRequest(c, "name parameter is required")
1809
+ }
1810
+ rid, _ := c.Locals("request_id").(string)
1811
+ return c.Status(http.StatusOK).JSON(EchoResponse{
1812
+ Name: name,
1813
+ RequestID: rid,
1814
+ })
1815
+ }
1816
+ `}function ir(e){return `package handlers_test
1817
+
1818
+ import (
1819
+ "encoding/json"
1820
+ "net/http"
1821
+ "net/http/httptest"
1822
+ "testing"
1823
+
1824
+ "github.com/gofiber/fiber/v2"
1825
+
1826
+ "${e.module_path}/internal/handlers"
1827
+ "${e.module_path}/internal/middleware"
1828
+ )
1829
+
1830
+ func newEchoApp() *fiber.App {
1831
+ app := fiber.New()
1832
+ app.Use(middleware.RequestID())
1833
+ app.Get("/echo/:name", handlers.EchoParams)
1834
+ return app
1835
+ }
1836
+
1837
+ func TestEchoParams_Success(t *testing.T) {
1838
+ req := httptest.NewRequest(http.MethodGet, "/echo/alice", nil)
1839
+ resp, err := newEchoApp().Test(req, -1)
1840
+ if err != nil {
1841
+ t.Fatalf("request error: %v", err)
1842
+ }
1843
+ defer resp.Body.Close()
1844
+
1845
+ if resp.StatusCode != http.StatusOK {
1846
+ t.Fatalf("expected 200, got %d", resp.StatusCode)
1847
+ }
1848
+
1849
+ var body map[string]any
1850
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
1851
+ t.Fatalf("invalid JSON: %v", err)
1852
+ }
1853
+ if body["name"] != "alice" {
1854
+ t.Fatalf("expected name=alice, got %v", body["name"])
1855
+ }
1856
+ if body["request_id"] == nil || body["request_id"] == "" {
1857
+ t.Fatal("expected request_id to be set by RequestID middleware")
1858
+ }
1859
+ }
1860
+
1861
+ // TestEchoParams_EmptyName registers EchoParams on a param-free route so that
1862
+ // c.Params("name") returns "" and the 400 guard executes.
1863
+ func TestEchoParams_EmptyName(t *testing.T) {
1864
+ app := fiber.New()
1865
+ app.Get("/echo-bare", handlers.EchoParams)
1866
+ req := httptest.NewRequest(http.MethodGet, "/echo-bare", nil)
1867
+ resp, err := app.Test(req, -1)
1868
+ if err != nil {
1869
+ t.Fatalf("request error: %v", err)
1870
+ }
1871
+ defer resp.Body.Close()
1872
+
1873
+ if resp.StatusCode != http.StatusBadRequest {
1874
+ t.Fatalf("expected 400, got %d", resp.StatusCode)
1875
+ }
1876
+ var body map[string]any
1877
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
1878
+ t.Fatalf("invalid JSON: %v", err)
1879
+ }
1880
+ if body["code"] != "BAD_REQUEST" {
1881
+ t.Fatalf("expected code=BAD_REQUEST, got %v", body["code"])
1882
+ }
1883
+ }
1884
+ `}function ar(e){return `package config_test
1885
+
1886
+ import (
1887
+ "log/slog"
1888
+ "testing"
1889
+
1890
+ "${e.module_path}/internal/config"
1891
+ )
1892
+
1893
+ func TestParseLogLevel(t *testing.T) {
1894
+ tests := []struct {
1895
+ input string
1896
+ want slog.Level
1897
+ }{
1898
+ {"debug", slog.LevelDebug},
1899
+ {"DEBUG", slog.LevelDebug},
1900
+ {"warn", slog.LevelWarn},
1901
+ {"warning", slog.LevelWarn},
1902
+ {"error", slog.LevelError},
1903
+ {"info", slog.LevelInfo},
1904
+ {"", slog.LevelInfo},
1905
+ {"unknown", slog.LevelInfo},
1906
+ }
1907
+ for _, tc := range tests {
1908
+ got := config.ParseLogLevel(tc.input)
1909
+ if got != tc.want {
1910
+ t.Errorf("ParseLogLevel(%q) = %v, want %v", tc.input, got, tc.want)
1911
+ }
1912
+ }
1913
+ }
1914
+
1915
+ func TestLoad_EnvOverride(t *testing.T) {
1916
+ t.Setenv("PORT", "9090")
1917
+ t.Setenv("APP_ENV", "production")
1918
+ t.Setenv("LOG_LEVEL", "warn")
1919
+
1920
+ cfg := config.Load()
1921
+
1922
+ if cfg.Port != "9090" {
1923
+ t.Errorf("expected Port=9090, got %q", cfg.Port)
1924
+ }
1925
+ if cfg.Env != "production" {
1926
+ t.Errorf("expected Env=production, got %q", cfg.Env)
1927
+ }
1928
+ if cfg.LogLevel != "warn" {
1929
+ t.Errorf("expected LogLevel=warn, got %q", cfg.LogLevel)
1930
+ }
1931
+ }
1932
+
1933
+ func TestLoad_Defaults(t *testing.T) {
1934
+ // Empty string forces getEnv() to return the built-in fallback value.
1935
+ t.Setenv("PORT", "")
1936
+ t.Setenv("APP_ENV", "")
1937
+ t.Setenv("LOG_LEVEL", "")
1938
+
1939
+ cfg := config.Load()
1940
+
1941
+ if cfg.Port != "${e.port}" {
1942
+ t.Errorf("expected default Port=${e.port}, got %q", cfg.Port)
1943
+ }
1944
+ if cfg.Env != "development" {
1945
+ t.Errorf("expected default Env=development, got %q", cfg.Env)
1946
+ }
1947
+ // APP_ENV="" \u2192 fallback "development" \u2192 defaultLogLevel \u2192 "debug"
1948
+ if cfg.LogLevel != "debug" {
1949
+ t.Errorf("expected default LogLevel=debug (development env), got %q", cfg.LogLevel)
1950
+ }
1951
+ }
1952
+ `}function sr(){return `package middleware
1953
+
1954
+ import (
1955
+ "os"
1956
+
1957
+ "github.com/gofiber/fiber/v2"
1958
+ "github.com/gofiber/fiber/v2/middleware/cors"
1959
+ )
1960
+
1961
+ // CORS returns a CORS middleware configured via CORS_ALLOW_ORIGINS env var.
1962
+ //
1963
+ // Set CORS_ALLOW_ORIGINS="*" for development (the default when unset).
1964
+ // In production supply a comma-separated list of allowed origins:
1965
+ //
1966
+ // CORS_ALLOW_ORIGINS=https://app.example.com,https://admin.example.com
1967
+ func CORS() fiber.Handler {
1968
+ origins := os.Getenv("CORS_ALLOW_ORIGINS")
1969
+ if origins == "" {
1970
+ origins = "*"
1971
+ }
1972
+ return cors.New(cors.Config{
1973
+ AllowOrigins: origins,
1974
+ AllowMethods: "GET,POST,PUT,PATCH,DELETE,OPTIONS",
1975
+ AllowHeaders: "Origin,Content-Type,Authorization,X-Request-ID",
1976
+ ExposeHeaders: "X-Request-ID",
1977
+ MaxAge: 600,
1978
+ })
1979
+ }
1980
+ `}function cr(e){return `package middleware_test
1981
+
1982
+ import (
1983
+ "net/http"
1984
+ "net/http/httptest"
1985
+ "testing"
1986
+
1987
+ "github.com/gofiber/fiber/v2"
1988
+
1989
+ "${e.module_path}/internal/middleware"
1990
+ )
1991
+
1992
+ func newCORSApp(t *testing.T) *fiber.App {
1993
+ t.Helper()
1994
+ app := fiber.New()
1995
+ app.Use(middleware.CORS())
1996
+ app.Get("/ping", func(c *fiber.Ctx) error { return c.SendStatus(http.StatusOK) })
1997
+ return app
1998
+ }
1999
+
2000
+ func TestCORS_Wildcard(t *testing.T) {
2001
+ t.Setenv("CORS_ALLOW_ORIGINS", "*")
2002
+ req := httptest.NewRequest(http.MethodGet, "/ping", nil)
2003
+ req.Header.Set("Origin", "https://example.com")
2004
+ resp, _ := newCORSApp(t).Test(req, -1)
2005
+ defer resp.Body.Close()
2006
+
2007
+ if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "*" {
2008
+ t.Fatalf("expected ACAO=*, got %q", got)
2009
+ }
2010
+ }
2011
+
2012
+ func TestCORS_Preflight(t *testing.T) {
2013
+ t.Setenv("CORS_ALLOW_ORIGINS", "*")
2014
+ app := fiber.New()
2015
+ app.Use(middleware.CORS())
2016
+
2017
+ req := httptest.NewRequest(http.MethodOptions, "/ping", nil)
2018
+ req.Header.Set("Origin", "https://example.com")
2019
+ req.Header.Set("Access-Control-Request-Method", "POST")
2020
+ resp, _ := app.Test(req, -1)
2021
+ defer resp.Body.Close()
2022
+
2023
+ if resp.StatusCode != http.StatusNoContent {
2024
+ t.Fatalf("expected 204 preflight, got %d", resp.StatusCode)
2025
+ }
2026
+ }
2027
+
2028
+ func TestCORS_SpecificOrigin_Allowed(t *testing.T) {
2029
+ t.Setenv("CORS_ALLOW_ORIGINS", "https://app.example.com")
2030
+ req := httptest.NewRequest(http.MethodGet, "/ping", nil)
2031
+ req.Header.Set("Origin", "https://app.example.com")
2032
+ resp, _ := newCORSApp(t).Test(req, -1)
2033
+ defer resp.Body.Close()
2034
+
2035
+ if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://app.example.com" {
2036
+ t.Fatalf("expected ACAO=https://app.example.com, got %q", got)
2037
+ }
2038
+ }
2039
+
2040
+ func TestCORS_Default_Origin(t *testing.T) {
2041
+ // When CORS_ALLOW_ORIGINS is unset, middleware must default to "*".
2042
+ t.Setenv("CORS_ALLOW_ORIGINS", "")
2043
+ req := httptest.NewRequest(http.MethodGet, "/ping", nil)
2044
+ req.Header.Set("Origin", "https://anywhere.com")
2045
+ resp, _ := newCORSApp(t).Test(req, -1)
2046
+ defer resp.Body.Close()
2047
+
2048
+ if got := resp.Header.Get("Access-Control-Allow-Origin"); got == "" {
2049
+ t.Fatal("expected CORS header when origins defaulting to *")
2050
+ }
2051
+ }
2052
+ `}function dr(e){return `package server_test
2053
+
2054
+ import (
2055
+ "encoding/json"
2056
+ "net/http"
2057
+ "net/http/httptest"
2058
+ "testing"
2059
+
2060
+ "${e.module_path}/internal/config"
2061
+ "${e.module_path}/internal/server"
2062
+ )
2063
+
2064
+ type serverAPIError struct {
2065
+ Code string \`json:"code"\`
2066
+ Message string \`json:"message"\`
2067
+ }
2068
+
2069
+ func TestServer_NotFound_JSON(t *testing.T) {
2070
+ req := httptest.NewRequest(http.MethodGet, "/no-such-route", nil)
2071
+ resp, err := server.NewApp(config.Load()).Test(req, -1)
2072
+ if err != nil {
2073
+ t.Fatalf("request error: %v", err)
2074
+ }
2075
+ defer resp.Body.Close()
2076
+
2077
+ if resp.StatusCode != http.StatusNotFound {
2078
+ t.Fatalf("expected 404, got %d", resp.StatusCode)
2079
+ }
2080
+ var body serverAPIError
2081
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
2082
+ t.Fatalf("expected JSON error body: %v", err)
2083
+ }
2084
+ if body.Code != "NOT_FOUND" {
2085
+ t.Fatalf("expected code=NOT_FOUND, got %q", body.Code)
2086
+ }
2087
+ }
2088
+
2089
+ func TestServer_MethodNotAllowed_JSON(t *testing.T) {
2090
+ // Fiber v2 does not return 405 automatically \u2014 unmatched methods fall
2091
+ // through to the 404 catch-all, which is the expected behaviour.
2092
+ req := httptest.NewRequest(http.MethodPost, "/api/v1/health/live", nil)
2093
+ resp, err := server.NewApp(config.Load()).Test(req, -1)
2094
+ if err != nil {
2095
+ t.Fatalf("request error: %v", err)
2096
+ }
2097
+ defer resp.Body.Close()
2098
+
2099
+ if resp.StatusCode != http.StatusNotFound {
2100
+ t.Fatalf("expected 404 for unmatched method, got %d", resp.StatusCode)
2101
+ }
2102
+ var body serverAPIError
2103
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
2104
+ t.Fatalf("expected JSON error body: %v", err)
2105
+ }
2106
+ if body.Code != "NOT_FOUND" {
2107
+ t.Fatalf("expected code=NOT_FOUND, got %q", body.Code)
2108
+ }
2109
+ }
2110
+
2111
+ func TestServer_CORS_Header(t *testing.T) {
2112
+ t.Setenv("CORS_ALLOW_ORIGINS", "*")
2113
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/health/live", nil)
2114
+ req.Header.Set("Origin", "https://example.com")
2115
+ resp, err := server.NewApp(config.Load()).Test(req, -1)
2116
+ if err != nil {
2117
+ t.Fatalf("request error: %v", err)
2118
+ }
2119
+ defer resp.Body.Close()
2120
+
2121
+ if got := resp.Header.Get("Access-Control-Allow-Origin"); got == "" {
2122
+ t.Fatal("expected Access-Control-Allow-Origin header to be set")
2123
+ }
2124
+ }
2125
+
2126
+ func TestServer_Docs_Redirect(t *testing.T) {
2127
+ req := httptest.NewRequest(http.MethodGet, "/docs", nil)
2128
+ resp, err := server.NewApp(config.Load()).Test(req, -1)
2129
+ if err != nil {
2130
+ t.Fatalf("request error: %v", err)
2131
+ }
2132
+ defer resp.Body.Close()
2133
+
2134
+ if resp.StatusCode != http.StatusFound {
2135
+ t.Fatalf("expected 302 redirect from /docs, got %d", resp.StatusCode)
2136
+ }
2137
+ if loc := resp.Header.Get("Location"); loc != "/docs/index.html" {
2138
+ t.Fatalf("expected Location=/docs/index.html, got %q", loc)
2139
+ }
2140
+ }
2141
+ `}function lr(e){return `package middleware
2142
+
2143
+ import (
2144
+ "os"
2145
+ "strconv"
2146
+ "time"
2147
+
2148
+ "github.com/gofiber/fiber/v2"
2149
+ "github.com/gofiber/fiber/v2/middleware/limiter"
2150
+
2151
+ "${e.module_path}/internal/apierr"
2152
+ )
2153
+
2154
+ // RateLimit returns a per-IP sliding-window rate limiter.
2155
+ // Configure the limit via RATE_LIMIT_RPS env var (requests per second, default 100).
2156
+ func RateLimit() fiber.Handler {
2157
+ rps := 100
2158
+ if raw := os.Getenv("RATE_LIMIT_RPS"); raw != "" {
2159
+ if n, err := strconv.Atoi(raw); err == nil && n > 0 {
2160
+ rps = n
2161
+ }
2162
+ }
2163
+ return limiter.New(limiter.Config{
2164
+ Max: rps,
2165
+ Expiration: time.Second,
2166
+ KeyGenerator: func(c *fiber.Ctx) string {
2167
+ return c.IP()
2168
+ },
2169
+ LimitReached: func(c *fiber.Ctx) error {
2170
+ return apierr.TooManyRequests(c, "rate limit exceeded")
2171
+ },
2172
+ })
2173
+ }
2174
+ `}function pr(e){return `package middleware_test
2175
+
2176
+ import (
2177
+ "net/http"
2178
+ "net/http/httptest"
2179
+ "testing"
2180
+
2181
+ "github.com/gofiber/fiber/v2"
2182
+
2183
+ "${e.module_path}/internal/middleware"
2184
+ )
2185
+
2186
+ func newRateLimitApp(t *testing.T) *fiber.App {
2187
+ t.Helper()
2188
+ app := fiber.New()
2189
+ app.Use(middleware.RateLimit())
2190
+ app.Get("/", func(c *fiber.Ctx) error { return c.SendStatus(http.StatusOK) })
2191
+ return app
2192
+ }
2193
+
2194
+ func TestRateLimit_AllowsUnderLimit(t *testing.T) {
2195
+ t.Setenv("RATE_LIMIT_RPS", "3")
2196
+ app := newRateLimitApp(t)
2197
+
2198
+ for i := 0; i < 3; i++ {
2199
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
2200
+ resp, err := app.Test(req, -1)
2201
+ if err != nil {
2202
+ t.Fatalf("request %d: %v", i+1, err)
2203
+ }
2204
+ resp.Body.Close()
2205
+ if resp.StatusCode != http.StatusOK {
2206
+ t.Fatalf("request %d: expected 200, got %d", i+1, resp.StatusCode)
2207
+ }
2208
+ }
2209
+ }
2210
+
2211
+ func TestRateLimit_Blocks_After_Limit(t *testing.T) {
2212
+ t.Setenv("RATE_LIMIT_RPS", "2")
2213
+ app := newRateLimitApp(t)
2214
+
2215
+ // Exhaust the limit.
2216
+ for i := 0; i < 2; i++ {
2217
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
2218
+ resp, _ := app.Test(req, -1)
2219
+ resp.Body.Close()
2220
+ }
2221
+
2222
+ // Next request must be rejected.
2223
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
2224
+ resp, err := app.Test(req, -1)
2225
+ if err != nil {
2226
+ t.Fatalf("over-limit request: %v", err)
2227
+ }
2228
+ defer resp.Body.Close()
2229
+
2230
+ if resp.StatusCode != http.StatusTooManyRequests {
2231
+ t.Fatalf("expected 429, got %d", resp.StatusCode)
2232
+ }
2233
+ }
2234
+
2235
+ func TestRateLimit_InvalidRPS(t *testing.T) {
2236
+ // Invalid value should fall back to default (100 rps) and allow normal requests.
2237
+ t.Setenv("RATE_LIMIT_RPS", "not-a-number")
2238
+ app := newRateLimitApp(t)
2239
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
2240
+ resp, err := app.Test(req, -1)
2241
+ if err != nil {
2242
+ t.Fatalf("request error: %v", err)
2243
+ }
2244
+ defer resp.Body.Close()
2245
+
2246
+ if resp.StatusCode != http.StatusOK {
2247
+ t.Fatalf("expected 200 with invalid RPS env, got %d", resp.StatusCode)
2248
+ }
2249
+ }
2250
+ `}function ur(e){return `# Air \u2014 live reload for Go projects
2251
+ # https://github.com/air-verse/air
2252
+ root = "."
2253
+ tmp_dir = "tmp"
2254
+
2255
+ [build]
2256
+ pre_cmd = ["$(go env GOPATH)/bin/swag init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency 2>/dev/null || true"]
2257
+ cmd = "go build -o ./tmp/server ./cmd/server"
2258
+ bin = "./tmp/server"
2259
+ include_ext = ["go", "yaml", "yml", "env"]
2260
+ exclude_dir = ["tmp", "vendor", ".git", "testdata", "docs"]
2261
+ delay = 500
2262
+ rerun_delay = 500
2263
+ send_interrupt = true
2264
+ kill_delay = "200ms"
2265
+
2266
+ [env]
2267
+ PORT = "${e.port}"
2268
+
2269
+ [misc]
2270
+ clean_on_exit = true
2271
+
2272
+ [log]
2273
+ time = false
2274
+ `}function gr(e){return `run:
2275
+ timeout: 5m
2276
+
2277
+ linters:
2278
+ enable:
2279
+ - bodyclose
2280
+ - durationcheck
2281
+ - errcheck
2282
+ - errname
2283
+ - errorlint
2284
+ - gci
2285
+ - goimports
2286
+ - gosimple
2287
+ - govet
2288
+ - ineffassign
2289
+ - misspell
2290
+ - noctx
2291
+ - nolintlint
2292
+ - prealloc
2293
+ - staticcheck
2294
+ - unconvert
2295
+ - unused
2296
+ - wrapcheck
2297
+
2298
+ linters-settings:
2299
+ gci:
2300
+ sections:
2301
+ - standard
2302
+ - default
2303
+ - prefix(${e})
2304
+ goimports:
2305
+ local-prefixes: "${e}"
2306
+ govet:
2307
+ enable:
2308
+ - shadow
2309
+ wrapcheck:
2310
+ ignorePackageGlobs:
2311
+ - "${e}/*"
2312
+
2313
+ issues:
2314
+ max-same-issues: 5
2315
+ exclude-rules:
2316
+ - path: _test.go
2317
+ linters:
2318
+ - errcheck
2319
+ - wrapcheck
2320
+ `}function mr(){return JSON.stringify({engine:"npm",runtime:"go"},null,2)}function fr(e){return `#!/usr/bin/env sh
2321
+ # RapidKit Go/Fiber project launcher \u2014 generated by RapidKit CLI
2322
+ # https://getrapidkit.com
2323
+
2324
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
2325
+ CMD="\${1:-}"
2326
+ shift 2>/dev/null || true
2327
+
2328
+ case "$CMD" in
2329
+ init)
2330
+ cd "$SCRIPT_DIR"
2331
+ echo "\u{1F439} Initializing Go/Fiber project\u2026"
2332
+ GOBIN="$(go env GOPATH)/bin"
2333
+ echo " \u2192 installing air (hot reload)\u2026"
2334
+ go install github.com/air-verse/air@latest 2>/dev/null && echo " \u2713 air" || echo " \u26A0 air install failed (run: go install github.com/air-verse/air@latest)"
2335
+ echo " \u2192 installing swag (swagger)\u2026"
2336
+ go install github.com/swaggo/swag/cmd/swag@latest 2>/dev/null && echo " \u2713 swag" || echo " \u26A0 swag install failed (run: go install github.com/swaggo/swag/cmd/swag@latest)"
2337
+ if [ ! -f ".env" ] && [ -f ".env.example" ]; then
2338
+ cp .env.example .env && echo " \u2713 .env created from .env.example"
2339
+ fi
2340
+ go mod tidy && echo " \u2713 go mod tidy"
2341
+ echo " \u2192 generating swagger docs (first build)\u2026"
2342
+ "$(go env GOPATH)/bin/swag" init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency 2>/dev/null && echo " \u2713 swagger docs generated" || echo " \u26A0 swagger docs skipped (run: rapidkit docs)"
2343
+ echo "\u2705 Ready \u2014 run: rapidkit dev"
2344
+ ;;
2345
+ dev)
2346
+ cd "$SCRIPT_DIR"
2347
+ echo "\u{1F4D6} Syncing swagger docs\u2026"
2348
+ "$(go env GOPATH)/bin/swag" init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency 2>/dev/null || true
2349
+ if [ -f "$SCRIPT_DIR/Makefile" ]; then
2350
+ exec make -C "$SCRIPT_DIR" dev "$@"
2351
+ else
2352
+ exec go run ./cmd/server "$@"
2353
+ fi
2354
+ ;;
2355
+ start)
2356
+ BIN="$SCRIPT_DIR/bin/${e.project_name}"
2357
+ if [ ! -f "$BIN" ]; then
2358
+ make -C "$SCRIPT_DIR" build
2359
+ fi
2360
+ exec "$BIN" "$@"
2361
+ ;;
2362
+ build)
2363
+ exec make -C "$SCRIPT_DIR" build "$@"
2364
+ ;;
2365
+ test)
2366
+ exec make -C "$SCRIPT_DIR" test "$@"
2367
+ ;;
2368
+ lint)
2369
+ exec make -C "$SCRIPT_DIR" lint "$@"
2370
+ ;;
2371
+ format|fmt)
2372
+ exec make -C "$SCRIPT_DIR" fmt "$@"
2373
+ ;;
2374
+ docs)
2375
+ exec make -C "$SCRIPT_DIR" docs "$@"
2376
+ ;;
2377
+ help|--help|-h)
2378
+ echo "RapidKit \u2014 Go/Fiber project: ${e.project_name}"
2379
+ echo ""
2380
+ echo "Usage: rapidkit <command>"
2381
+ echo ""
2382
+ echo " init Install tools + create .env (air, swag, go mod tidy)"
2383
+ echo " dev Hot reload dev server (make dev \u2014 requires air)"
2384
+ echo " start Run compiled binary (make build + bin)"
2385
+ echo " build Build binary (make build)"
2386
+ echo " docs Generate Swagger docs (make docs \u2014 requires swag)"
2387
+ echo " test Run tests (make test)"
2388
+ echo " lint Run linter (make lint)"
2389
+ echo " format Format code (make fmt)"
2390
+ ;;
2391
+ *)
2392
+ if [ -n "$CMD" ]; then
2393
+ echo "rapidkit: unknown command: $CMD" >&2
2394
+ fi
2395
+ echo "Available: init, dev, start, build, docs, test, lint, format" >&2
2396
+ exit 1
2397
+ ;;
2398
+ esac
2399
+ `}function hr(e){return `@echo off
2400
+ rem RapidKit Go/Fiber project launcher \u2014 Windows
2401
+ set CMD=%1
2402
+ if "%CMD%"=="" goto usage
2403
+ shift
2404
+
2405
+ if "%CMD%"=="init" (
2406
+ echo Initializing Go/Fiber project...
2407
+ go install github.com/air-verse/air@latest
2408
+ go install github.com/swaggo/swag/cmd/swag@latest
2409
+ if not exist .env if exist .env.example copy .env.example .env
2410
+ go mod tidy
2411
+ exit /b %ERRORLEVEL%
2412
+ )
2413
+ if "%CMD%"=="dev" ( make dev %* & exit /b %ERRORLEVEL% )
2414
+ if "%CMD%"=="build" ( make build %* & exit /b %ERRORLEVEL% )
2415
+ if "%CMD%"=="test" ( make test %* & exit /b %ERRORLEVEL% )
2416
+ if "%CMD%"=="lint" ( make lint %* & exit /b %ERRORLEVEL% )
2417
+ if "%CMD%"=="format" ( make fmt %* & exit /b %ERRORLEVEL% )
2418
+ if "%CMD%"=="docs" ( make docs %* & exit /b %ERRORLEVEL% )
2419
+ if "%CMD%"=="start" ( bin\\${e.project_name}.exe %* & exit /b %ERRORLEVEL% )
2420
+
2421
+ :usage
2422
+ echo Available: init, dev, start, build, docs, test, lint, format
2423
+ exit /b 1
2424
+ `}function wr(e,o){return JSON.stringify({kit_name:"gofiber.standard",runtime:"go",module_support:false,project_name:e.project_name,module_path:e.module_path,app_version:e.app_version,created_by:"rapidkit-npm",rapidkit_version:o,created_at:new Date().toISOString()},null,2)}async function Ze(e,o){let t={project_name:o.project_name,module_path:o.module_path||o.project_name,author:o.author||"RapidKit User",description:o.description||`Go/Fiber REST API \u2014 ${o.project_name}`,go_version:o.go_version||"1.24",app_version:o.app_version||"0.1.0",port:o.port||"3000",skipGit:o.skipGit??false},n=c();try{await execa("go",["version"],{timeout:3e3});}catch{console.log(g.yellow("\n\u26A0 Go not found in PATH \u2014 project will be scaffolded, but `go mod tidy` requires Go 1.21+")),console.log(g.gray(` Install: https://go.dev/dl/
2425
+ `));}let i=Qe(`Generating Go/Fiber project: ${t.project_name}\u2026`).start();try{let r=(s,d)=>Mo(h.join(e,s),d),a=h.join(e,"rapidkit"),c=h.join(e,"rapidkit.cmd");await Promise.all([r("cmd/server/main.go",Lo(t)),r("go.mod",Fo(t)),r("internal/config/config.go",Ho(t)),r("internal/server/server.go",Ko(t)),r("internal/middleware/requestid.go",Zo()),r("internal/middleware/requestid_test.go",er(t)),r("internal/apierr/apierr.go",tr()),r("internal/apierr/apierr_test.go",or(t)),r("internal/handlers/health.go",Vo()),r("internal/handlers/health_test.go",Uo(t)),r("internal/handlers/example.go",nr(t)),r("internal/handlers/example_test.go",ir(t)),r("internal/config/config_test.go",ar(t)),r("internal/middleware/cors.go",sr()),r("internal/middleware/cors_test.go",cr(t)),r("internal/middleware/ratelimit.go",lr(t)),r("internal/middleware/ratelimit_test.go",pr(t)),r("internal/server/server_test.go",dr(t)),r("docs/doc.go",rr(t)),r(".air.toml",ur(t)),r("Dockerfile",Wo()),r("docker-compose.yml",Bo(t)),r("Makefile",Jo(t)),r(".golangci.yml",gr(t.module_path)),r(".env.example",zo(t)),r(".gitignore",Yo()),r(".github/workflows/ci.yml",Qo(t)),r("README.md",Xo(t)),r(".rapidkit/project.json",wr(t,n)),r(".rapidkit/context.json",mr()),r("rapidkit",fr(t)),r("rapidkit.cmd",hr(t))]),await promises.chmod(a,493),await promises.chmod(c,493),i.succeed(g.green(`Project created at ${e}`));try{i.start("Fetching Go dependencies\u2026"),await execa("go",["mod","tidy"],{cwd:e,timeout:12e4}),i.succeed(g.gray("\u2713 go mod tidy completed"));}catch{i.warn(g.yellow("\u26A0 go mod tidy failed \u2014 run manually: go mod tidy"));}if(!t.skipGit)try{await execa("git",["init"],{cwd:e}),await execa("git",["add","-A"],{cwd:e}),await execa("git",["commit","-m","chore: initial scaffold (rapidkit gofiber.standard)"],{cwd:e}),console.log(g.gray("\u2713 git repository initialized"));}catch{console.log(g.gray("\u26A0 git init skipped (git not found or error)"));}console.log(""),console.log(g.bold("\u2705 Go/Fiber project ready!")),console.log(""),console.log(g.cyan("Next steps:")),console.log(g.white(` cd ${t.project_name}`)),console.log(g.white(" make run # start dev server")),console.log(g.white(" make test # run tests")),console.log(""),console.log(g.gray("Server will listen on port "+t.port)),console.log(g.gray(" http://localhost:"+t.port+"/api/v1/health/live")),console.log(g.gray(" http://localhost:"+t.port+"/api/v1/health/ready")),console.log(""),console.log(g.yellow("\u2139 RapidKit modules are not available for Go projects (module system uses Python/pip).")),console.log("");}catch(r){throw i.fail(g.red("Failed to generate Go/Fiber project")),r}}function At(e){return e.split(/[-_\s]+/).map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join("")}async function vr(e,o){await promises.mkdir(h.dirname(e),{recursive:true}),await promises.writeFile(e,o,"utf8");}function br(e){return `package main
2426
+
2427
+ import (
2428
+ "context"
2429
+ "errors"
2430
+ "fmt"
2431
+ "log/slog"
2432
+ "net/http"
2433
+ "os"
2434
+ "os/signal"
2435
+ "syscall"
2436
+ "time"
2437
+
2438
+ _ "${e.module_path}/docs"
2439
+ "${e.module_path}/internal/config"
2440
+ "${e.module_path}/internal/server"
2441
+ )
2442
+
2443
+ // Build-time variables \u2014 injected via -ldflags.
2444
+ var (
2445
+ version = "dev"
2446
+ commit = "none"
2447
+ date = "unknown"
2448
+ )
2449
+
2450
+ func main() {
2451
+ cfg := config.Load()
2452
+
2453
+ log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
2454
+ Level: config.ParseLogLevel(cfg.LogLevel),
2455
+ }))
2456
+ slog.SetDefault(log)
2457
+
2458
+ r := server.NewRouter(cfg)
2459
+
2460
+ srv := &http.Server{
2461
+ Addr: ":" + cfg.Port,
2462
+ Handler: r,
2463
+ ReadTimeout: 5 * time.Second,
2464
+ WriteTimeout: 10 * time.Second,
2465
+ IdleTimeout: 30 * time.Second,
2466
+ }
2467
+
2468
+ go func() {
2469
+ slog.Info("starting", "port", cfg.Port, "version", version, "commit", commit, "date", date, "env", cfg.Env)
2470
+ fmt.Printf("\\n\u{1F680} Server \u2192 http://127.0.0.1:%s\\n", cfg.Port)
2471
+ fmt.Printf("\u{1F4D6} Docs \u2192 http://127.0.0.1:%s/docs\\n\\n", cfg.Port)
2472
+ if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
2473
+ slog.Error("server error", "err", err)
2474
+ os.Exit(1)
2475
+ }
2476
+ }()
2477
+
2478
+ quit := make(chan os.Signal, 1)
2479
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
2480
+ <-quit
2481
+
2482
+ slog.Info("shutting down\u2026")
2483
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
2484
+ defer cancel()
2485
+
2486
+ if err := srv.Shutdown(ctx); err != nil {
2487
+ slog.Error("forced shutdown", "err", err)
2488
+ os.Exit(1)
2489
+ }
2490
+ slog.Info("server stopped")
2491
+ }
2492
+ `}function kr(e){return `module ${e.module_path}
2493
+
2494
+ go ${e.go_version}
2495
+
2496
+ require (
2497
+ github.com/gin-gonic/gin v1.10.0
2498
+ github.com/swaggo/gin-swagger v1.6.0
2499
+ github.com/swaggo/swag v1.16.3
2500
+ )
2501
+
2502
+ require (
2503
+ github.com/KyleBanks/depth v1.2.1 // indirect
2504
+ github.com/bytedance/sonic v1.11.6 // indirect
2505
+ github.com/bytedance/sonic/loader v0.1.1 // indirect
2506
+ github.com/cloudwego/base64x v0.1.4 // indirect
2507
+ github.com/cloudwego/iasm v0.2.0 // indirect
2508
+ github.com/gabriel-vasile/mimetype v1.4.3 // indirect
2509
+ github.com/ghodss/yaml v1.0.0 // indirect
2510
+ github.com/gin-contrib/sse v0.1.0 // indirect
2511
+ github.com/go-openapi/jsonpointer v0.21.0 // indirect
2512
+ github.com/go-openapi/jsonreference v0.21.0 // indirect
2513
+ github.com/go-openapi/spec v0.21.0 // indirect
2514
+ github.com/go-openapi/swag v0.23.0 // indirect
2515
+ github.com/go-playground/locales v0.14.1 // indirect
2516
+ github.com/go-playground/universal-translator v0.18.1 // indirect
2517
+ github.com/go-playground/validator/v10 v10.20.0 // indirect
2518
+ github.com/goccy/go-json v0.10.2 // indirect
2519
+ github.com/josharian/intern v1.0.0 // indirect
2520
+ github.com/json-iterator/go v1.1.12 // indirect
2521
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
2522
+ github.com/leodido/go-urn v1.4.0 // indirect
2523
+ github.com/mailru/easyjson v0.7.7 // indirect
2524
+ github.com/mattn/go-isatty v0.0.20 // indirect
2525
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2526
+ github.com/modern-go/reflect2 v1.0.2 // indirect
2527
+ github.com/pelletier/go-toml/v2 v2.2.2 // indirect
2528
+ github.com/swaggo/files v1.0.1 // indirect
2529
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
2530
+ github.com/ugorji/go/codec v1.2.12 // indirect
2531
+ golang.org/x/arch v0.8.0 // indirect
2532
+ golang.org/x/crypto v0.23.0 // indirect
2533
+ golang.org/x/net v0.25.0 // indirect
2534
+ golang.org/x/sys v0.20.0 // indirect
2535
+ golang.org/x/text v0.15.0 // indirect
2536
+ golang.org/x/tools v0.21.0 // indirect
2537
+ google.golang.org/protobuf v1.34.1 // indirect
2538
+ gopkg.in/yaml.v2 v2.4.0 // indirect
2539
+ gopkg.in/yaml.v3 v3.0.1 // indirect
2540
+ )
2541
+ `}function Rr(e){return `package config
2542
+
2543
+ import (
2544
+ "log/slog"
2545
+ "os"
2546
+ "strings"
2547
+ )
2548
+
2549
+ // Config holds application configuration loaded from environment variables.
2550
+ type Config struct {
2551
+ Port string
2552
+ Env string
2553
+ GinMode string
2554
+ LogLevel string
2555
+ }
2556
+
2557
+ // Load reads configuration from environment variables with sensible defaults.
2558
+ func Load() *Config {
2559
+ env := getEnv("APP_ENV", "development")
2560
+ return &Config{
2561
+ Port: getEnv("PORT", "${e.port}"),
2562
+ Env: env,
2563
+ GinMode: getEnv("GIN_MODE", "debug"),
2564
+ LogLevel: getEnv("LOG_LEVEL", defaultLogLevel(env)),
2565
+ }
2566
+ }
2567
+
2568
+ // ParseLogLevel maps a level string to the corresponding slog.Level.
2569
+ // Falls back to Info for unrecognised values.
2570
+ func ParseLogLevel(s string) slog.Level {
2571
+ switch strings.ToLower(s) {
2572
+ case "debug":
2573
+ return slog.LevelDebug
2574
+ case "warn", "warning":
2575
+ return slog.LevelWarn
2576
+ case "error":
2577
+ return slog.LevelError
2578
+ default:
2579
+ return slog.LevelInfo
2580
+ }
2581
+ }
2582
+
2583
+ func defaultLogLevel(env string) string {
2584
+ if env == "development" {
2585
+ return "debug"
2586
+ }
2587
+ return "info"
2588
+ }
2589
+
2590
+ func getEnv(key, fallback string) string {
2591
+ if v, ok := os.LookupEnv(key); ok && v != "" {
2592
+ return v
2593
+ }
2594
+ return fallback
2595
+ }
2596
+ `}function Sr(e){return `package server
2597
+
2598
+ import (
2599
+ "net/http"
2600
+
2601
+ "github.com/gin-gonic/gin"
2602
+ ginSwagger "github.com/swaggo/gin-swagger"
2603
+ swaggerFiles "github.com/swaggo/files"
2604
+
2605
+ "${e.module_path}/internal/apierr"
2606
+ "${e.module_path}/internal/config"
2607
+ "${e.module_path}/internal/handlers"
2608
+ "${e.module_path}/internal/middleware"
2609
+ )
2610
+
2611
+ // NewRouter assembles the Gin engine with all middleware and routes.
2612
+ // Call this from main \u2014 or from tests via server.NewRouter(cfg).
2613
+ func NewRouter(cfg *config.Config) *gin.Engine {
2614
+ if cfg.GinMode == "release" {
2615
+ gin.SetMode(gin.ReleaseMode)
2616
+ }
2617
+
2618
+ r := gin.New()
2619
+ r.Use(gin.Recovery())
2620
+ r.Use(middleware.CORS())
2621
+ r.Use(middleware.RequestID())
2622
+ r.Use(middleware.RateLimit())
2623
+ r.Use(middleware.Logger())
2624
+
2625
+ // Swagger UI \u2014 /docs redirects to /docs/index.html
2626
+ r.GET("/docs", func(c *gin.Context) { c.Redirect(http.StatusFound, "/docs/index.html") })
2627
+ r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
2628
+
2629
+ v1 := r.Group("/api/v1")
2630
+ {
2631
+ v1.GET("/health/live", handlers.Liveness)
2632
+ v1.GET("/health/ready", handlers.Readiness)
2633
+ v1.GET("/echo/:name", handlers.EchoParams)
2634
+ }
2635
+
2636
+ // Return JSON for unknown routes/methods instead of Gin's default text responses.
2637
+ // HandleMethodNotAllowed must be true so NoMethod handler fires for 405 cases.
2638
+ r.HandleMethodNotAllowed = true
2639
+ r.NoRoute(func(c *gin.Context) {
2640
+ apierr.NotFound(c, "route not found")
2641
+ })
2642
+ r.NoMethod(func(c *gin.Context) {
2643
+ apierr.MethodNotAllowed(c)
2644
+ })
2645
+
2646
+ return r
2647
+ }
2648
+ `}function xr(){return `package handlers
2649
+
2650
+ import (
2651
+ "net/http"
2652
+ "time"
2653
+
2654
+ "github.com/gin-gonic/gin"
2655
+ )
2656
+
2657
+ // Liveness signals the process is alive (Kubernetes livenessProbe).
2658
+ //
2659
+ // @Summary Liveness probe
2660
+ // @Description Returns 200 when the process is alive.
2661
+ // @Tags health
2662
+ // @Produce json
2663
+ // @Success 200 {object} map[string]string
2664
+ // @Router /api/v1/health/live [get]
2665
+ func Liveness(c *gin.Context) {
2666
+ c.JSON(http.StatusOK, gin.H{
2667
+ "status": "ok",
2668
+ "time": time.Now().UTC().Format(time.RFC3339),
2669
+ })
2670
+ }
2671
+
2672
+ // Readiness signals the service can accept traffic (Kubernetes readinessProbe).
2673
+ // Extend this function to check database connectivity, caches, etc.
2674
+ //
2675
+ // @Summary Readiness probe
2676
+ // @Description Returns 200 when the service is ready to accept traffic.
2677
+ // @Tags health
2678
+ // @Produce json
2679
+ // @Success 200 {object} map[string]string
2680
+ // @Router /api/v1/health/ready [get]
2681
+ func Readiness(c *gin.Context) {
2682
+ c.JSON(http.StatusOK, gin.H{
2683
+ "status": "ready",
2684
+ "time": time.Now().UTC().Format(time.RFC3339),
2685
+ })
2686
+ }
2687
+ `}function _r(e){return `package handlers_test
2688
+
2689
+ import (
2690
+ "encoding/json"
2691
+ "net/http"
2692
+ "net/http/httptest"
2693
+ "testing"
2694
+
2695
+ "github.com/gin-gonic/gin"
2696
+
2697
+ "${e.module_path}/internal/config"
2698
+ "${e.module_path}/internal/server"
2699
+ )
2700
+
2701
+ func init() { gin.SetMode(gin.TestMode) }
2702
+
2703
+ func newRouter() *gin.Engine { return server.NewRouter(config.Load()) }
2704
+
2705
+ func TestLiveness(t *testing.T) {
2706
+ w := httptest.NewRecorder()
2707
+ req, _ := http.NewRequest(http.MethodGet, "/api/v1/health/live", nil)
2708
+ newRouter().ServeHTTP(w, req)
2709
+
2710
+ if w.Code != http.StatusOK {
2711
+ t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
2712
+ }
2713
+
2714
+ var body map[string]any
2715
+ if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
2716
+ t.Fatalf("invalid JSON: %v", err)
2717
+ }
2718
+ if body["status"] != "ok" {
2719
+ t.Fatalf("expected ok, got %v", body["status"])
2720
+ }
2721
+ }
2722
+
2723
+ func TestReadiness(t *testing.T) {
2724
+ w := httptest.NewRecorder()
2725
+ req, _ := http.NewRequest(http.MethodGet, "/api/v1/health/ready", nil)
2726
+ newRouter().ServeHTTP(w, req)
2727
+
2728
+ if w.Code != http.StatusOK {
2729
+ t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
2730
+ }
2731
+ }
2732
+ `}function Cr(){return `# \u2500\u2500 Build stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2733
+ FROM golang:1.24-alpine AS builder
2734
+
2735
+ # Build-time version injection
2736
+ ARG VERSION=dev
2737
+ ARG COMMIT=none
2738
+ ARG DATE=unknown
2739
+
2740
+ WORKDIR /app
2741
+ COPY go.mod go.sum ./
2742
+ RUN go mod download
2743
+
2744
+ COPY . .
2745
+ RUN CGO_ENABLED=0 GOOS=linux go build \\
2746
+ -ldflags="-s -w -X main.version=$\${VERSION} -X main.commit=$\${COMMIT} -X main.date=$\${DATE}" \\
2747
+ -o server ./cmd/server
2748
+
2749
+ # \u2500\u2500 Runtime stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2750
+ # alpine includes busybox wget required for the HEALTHCHECK below.
2751
+ FROM alpine:3.21
2752
+
2753
+ RUN addgroup -S app && adduser -S -G app app
2754
+ COPY --from=builder /app/server /server
2755
+ USER app
2756
+
2757
+ EXPOSE 8080
2758
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
2759
+ CMD wget -qO- http://localhost:8080/api/v1/health/live || exit 1
2760
+ ENTRYPOINT ["/server"]
2761
+ `}function Ir(e){return `version: "3.9"
2762
+
2763
+ services:
2764
+ api:
2765
+ build: .
2766
+ container_name: ${e.project_name}
2767
+ ports:
2768
+ - "${e.port}:${e.port}"
2769
+ environment:
2770
+ PORT: "${e.port}"
2771
+ APP_ENV: development
2772
+ GIN_MODE: debug
2773
+ LOG_LEVEL: info
2774
+ CORS_ALLOW_ORIGINS: "*"
2775
+ RATE_LIMIT_RPS: "100"
2776
+ restart: unless-stopped
2777
+ `}function Er(e){return `.PHONY: dev run build test cover lint fmt tidy docs docker-up docker-down
2778
+
2779
+ # Build-time metadata
2780
+ VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
2781
+ COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "none")
2782
+ DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
2783
+ LDFLAGS = -ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE)"
2784
+ # Go tool binaries are installed to GOPATH/bin; include it so \`air\` and \`swag\` are found.
2785
+ GOBIN ?= $(shell go env GOPATH)/bin
2786
+
2787
+ # Hot reload \u2014 installs air on first use
2788
+ dev:
2789
+ @test -x "$(GOBIN)/air" || go install github.com/air-verse/air@latest
2790
+ GIN_MODE=debug $(GOBIN)/air
2791
+
2792
+ run:
2793
+ GIN_MODE=debug go run $(LDFLAGS) ./cmd/server
2794
+
2795
+ build:
2796
+ go build $(LDFLAGS) -o bin/${e.project_name} ./cmd/server
2797
+
2798
+ test:
2799
+ GIN_MODE=test go test ./... -v -race
2800
+
2801
+ cover:
2802
+ GIN_MODE=test go test ./... -race -coverprofile=coverage.out
2803
+ go tool cover -html=coverage.out -o coverage.html
2804
+ @echo "Coverage report: coverage.html"
2805
+
2806
+ # Generate Swagger docs \u2014 installs swag on first use
2807
+ docs:
2808
+ @test -x "$(GOBIN)/swag" || go install github.com/swaggo/swag/cmd/swag@latest
2809
+ $(GOBIN)/swag init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency
2810
+
2811
+ lint:
2812
+ @command -v golangci-lint >/dev/null 2>&1 || (echo "golangci-lint not found. Install: https://golangci-lint.run/usage/install/" && exit 1)
2813
+ golangci-lint run ./...
2814
+
2815
+ fmt:
2816
+ gofmt -w .
2817
+
2818
+ tidy:
2819
+ go mod tidy
2820
+
2821
+ docker-up:
2822
+ go mod tidy
2823
+ docker compose up --build \\
2824
+ --build-arg VERSION=$(VERSION) \\
2825
+ --build-arg COMMIT=$(COMMIT) \\
2826
+ --build-arg DATE=$(DATE) \\
2827
+ -d
2828
+
2829
+ docker-down:
2830
+ docker compose down
2831
+ `}function Pr(e){return `# Application
2832
+ PORT=${e.port}
2833
+ APP_ENV=development
2834
+ GIN_MODE=debug
2835
+ LOG_LEVEL=debug
2836
+
2837
+ # CORS \u2014 comma-separated list of allowed origins, or * to allow all
2838
+ CORS_ALLOW_ORIGINS=*
2839
+
2840
+ # Rate limiting \u2014 max requests per IP per second
2841
+ RATE_LIMIT_RPS=100
2842
+ `}function Tr(){return `# Binaries
2843
+ bin/
2844
+ *.exe
2845
+ *.exe~
2846
+ *.dll
2847
+ *.so
2848
+ *.dylib
2849
+
2850
+ # Test binary
2851
+ *.test
2852
+
2853
+ # Output of go coverage tool
2854
+ *.out
2855
+ coverage.html
2856
+
2857
+ # Go workspace
2858
+ go.work
2859
+ go.work.sum
2860
+
2861
+ # Environment
2862
+ .env
2863
+ .env.local
2864
+
2865
+ # Hot reload (air)
2866
+ tmp/
2867
+
2868
+ # Swagger \u2014 generated files (committed stub docs/doc.go; run \`make docs\` to regenerate)
2869
+ docs/swagger.json
2870
+ docs/swagger.yaml
2871
+ docs/docs.go
2872
+
2873
+ # Editor
2874
+ .idea/
2875
+ .vscode/
2876
+ *.swp
2877
+ *.swo
2878
+
2879
+ # OS
2880
+ .DS_Store
2881
+ Thumbs.db
2882
+ `}function Or(e){return `name: CI
2883
+
2884
+ on:
2885
+ push:
2886
+ branches: [main, develop]
2887
+ pull_request:
2888
+ branches: [main]
2889
+
2890
+ jobs:
2891
+ test:
2892
+ name: Test
2893
+ runs-on: ubuntu-latest
2894
+
2895
+ steps:
2896
+ - uses: actions/checkout@v4
2897
+
2898
+ - name: Set up Go
2899
+ uses: actions/setup-go@v5
2900
+ with:
2901
+ go-version: "${e.go_version}"
2902
+ cache: true
2903
+
2904
+ - name: Tidy
2905
+ run: go mod tidy
2906
+
2907
+ - name: Build
2908
+ run: go build ./...
2909
+
2910
+ - name: Test
2911
+ run: GIN_MODE=test go test ./... -race -coverprofile=coverage.out
2912
+
2913
+ - name: Upload coverage
2914
+ uses: actions/upload-artifact@v4
2915
+ with:
2916
+ name: coverage
2917
+ path: coverage.out
2918
+
2919
+ lint:
2920
+ name: Lint
2921
+ runs-on: ubuntu-latest
2922
+
2923
+ steps:
2924
+ - uses: actions/checkout@v4
2925
+
2926
+ - name: Set up Go
2927
+ uses: actions/setup-go@v5
2928
+ with:
2929
+ go-version: "${e.go_version}"
2930
+ cache: true
2931
+
2932
+ - name: golangci-lint
2933
+ uses: golangci/golangci-lint-action@v6
2934
+ with:
2935
+ version: latest
2936
+ `}function Ar(e){return `# ${At(e.project_name)}
2937
+
2938
+ > ${e.description}
2939
+
2940
+ Built with [Go](https://go.dev/) + [Gin](https://gin-gonic.com/) \xB7 Scaffolded by [RapidKit](https://getrapidkit.com)
2941
+
2942
+ ## Quick start
2943
+
2944
+ \`\`\`bash
2945
+ # Run locally (hot reload)
2946
+ make dev
2947
+
2948
+ # Run tests
2949
+ make test
2950
+
2951
+ # Build binary
2952
+ make build
2953
+
2954
+ # Generate / refresh Swagger docs
2955
+ make docs
2956
+
2957
+ # Docker
2958
+ make docker-up
2959
+ \`\`\`
2960
+
2961
+ ## Swagger / OpenAPI
2962
+
2963
+ After running \`make docs\`, the interactive UI is available at:
2964
+
2965
+ \`\`\`
2966
+ http://localhost:${e.port}/docs
2967
+ \`\`\`
2968
+
2969
+ The raw OpenAPI spec is served at \`/docs/doc.json\`.
2970
+
2971
+ ## Endpoints
2972
+
2973
+ | Method | Path | Description |
2974
+ |--------|------|--------------|
2975
+ | GET | /api/v1/health/live | Kubernetes livenessProbe |
2976
+ | GET | /api/v1/health/ready | Kubernetes readinessProbe |
2977
+ | GET | /api/v1/echo/:name | Example handler \u2014 remove in production |
2978
+ | GET | /docs/* | Swagger UI (OpenAPI docs) |
2979
+
2980
+ ## Configuration
2981
+
2982
+ All configuration is done through environment variables (see \`.env.example\`):
2983
+
2984
+ | Variable | Default | Description |
2985
+ |----------|---------|-------------|
2986
+ | \`PORT\` | \`${e.port}\` | HTTP listen port |
2987
+ | \`APP_ENV\` | \`development\` | Application environment |
2988
+ | \`GIN_MODE\` | \`debug\` | \`debug\` \\| \`release\` \\| \`test\` |
2989
+ | \`LOG_LEVEL\` | \`debug\` / \`info\` | \`debug\` \\| \`info\` \\| \`warn\` \\| \`error\` |
2990
+ | \`CORS_ALLOW_ORIGINS\` | \`*\` | Comma-separated list of allowed origins, or \`*\` |
2991
+ | \`RATE_LIMIT_RPS\` | \`100\` | Max requests per IP per second |
2992
+
2993
+ ## Project structure
2994
+
2995
+ \`\`\`
2996
+ ${e.project_name}/
2997
+ \u251C\u2500\u2500 cmd/
2998
+ \u2502 \u2514\u2500\u2500 server/
2999
+ \u2502 \u2514\u2500\u2500 main.go # Graceful shutdown + version ldflags
3000
+ \u251C\u2500\u2500 docs/ # Swagger generated files (\`make docs\`)
3001
+ \u2502 \u2514\u2500\u2500 doc.go # Package-level OpenAPI annotations
3002
+ \u251C\u2500\u2500 internal/
3003
+ \u2502 \u251C\u2500\u2500 apierr/ # Consistent JSON error envelope
3004
+ \u2502 \u2502 \u251C\u2500\u2500 apierr.go
3005
+ \u2502 \u2502 \u2514\u2500\u2500 apierr_test.go
3006
+ \u2502 \u251C\u2500\u2500 config/ # 12-factor configuration
3007
+ \u2502 \u2502 \u251C\u2500\u2500 config.go
3008
+ \u2502 \u2502 \u2514\u2500\u2500 config_test.go
3009
+ \u2502 \u251C\u2500\u2500 handlers/ # HTTP handlers + tests
3010
+ \u2502 \u2502 \u251C\u2500\u2500 health.go
3011
+ \u2502 \u2502 \u251C\u2500\u2500 health_test.go
3012
+ \u2502 \u2502 \u251C\u2500\u2500 example.go # EchoParams \u2014 replace with your own handlers
3013
+ \u2502 \u2502 \u2514\u2500\u2500 example_test.go
3014
+ \u2502 \u251C\u2500\u2500 middleware/
3015
+ \u2502 \u2502 \u251C\u2500\u2500 requestid.go # X-Request-ID + structured logger
3016
+ \u2502 \u2502 \u251C\u2500\u2500 requestid_test.go
3017
+ \u2502 \u2502 \u251C\u2500\u2500 cors.go # CORS (CORS_ALLOW_ORIGINS)
3018
+ \u2502 \u2502 \u251C\u2500\u2500 cors_test.go
3019
+ \u2502 \u2502 \u251C\u2500\u2500 ratelimit.go # Per-IP limiter (RATE_LIMIT_RPS)
3020
+ \u2502 \u2502 \u2514\u2500\u2500 ratelimit_test.go
3021
+ \u2502 \u2514\u2500\u2500 server/
3022
+ \u2502 \u251C\u2500\u2500 server.go
3023
+ \u2502 \u2514\u2500\u2500 server_test.go
3024
+ \u251C\u2500\u2500 .air.toml # Hot reload
3025
+ \u251C\u2500\u2500 .github/workflows/ci.yml # CI: test + lint
3026
+ \u251C\u2500\u2500 .golangci.yml
3027
+ \u251C\u2500\u2500 Dockerfile # Multi-stage, alpine HEALTHCHECK
3028
+ \u251C\u2500\u2500 docker-compose.yml
3029
+ \u251C\u2500\u2500 Makefile
3030
+ \u2514\u2500\u2500 README.md
3031
+ \`\`\`
3032
+
3033
+ ## Available commands
3034
+
3035
+ | Command | Description |
3036
+ |---------|-------------|
3037
+ | \`make dev\` | Hot reload via [air](https://github.com/air-verse/air) |
3038
+ | \`make run\` | Run without hot reload |
3039
+ | \`make build\` | Binary with version ldflags |
3040
+ | \`make test\` | Run tests with race detector |
3041
+ | \`make cover\` | HTML coverage report |
3042
+ | \`make docs\` | Re-generate Swagger JSON (needs \`swag\`) |
3043
+ | \`make lint\` | golangci-lint |
3044
+ | \`make fmt\` | gofmt |
3045
+ | \`make tidy\` | go mod tidy |
3046
+ | \`make docker-up\` | Build & run via Docker Compose |
3047
+ | \`make docker-down\` | Stop |
3048
+
3049
+ ## License
3050
+
3051
+ ${e.app_version} \xB7 ${e.author}
3052
+ `}function Nr(){return `package middleware
3053
+
3054
+ import (
3055
+ "crypto/rand"
3056
+ "encoding/hex"
3057
+ "log/slog"
3058
+ "time"
3059
+
3060
+ "github.com/gin-gonic/gin"
3061
+ )
3062
+
3063
+ const headerRequestID = "X-Request-ID"
3064
+
3065
+ // RequestID injects a unique identifier into every request.
3066
+ // If the caller sends an X-Request-ID header it is reused; otherwise a new one
3067
+ // is generated and written back in the response.
3068
+ func RequestID() gin.HandlerFunc {
3069
+ return func(c *gin.Context) {
3070
+ id := c.GetHeader(headerRequestID)
3071
+ if id == "" {
3072
+ id = newID()
3073
+ }
3074
+ c.Set(headerRequestID, id)
3075
+ c.Header(headerRequestID, id)
3076
+ c.Next()
3077
+ }
3078
+ }
3079
+
3080
+ // Logger emits a structured JSON log line after each request.
3081
+ func Logger() gin.HandlerFunc {
3082
+ return func(c *gin.Context) {
3083
+ start := time.Now()
3084
+ c.Next()
3085
+ slog.Info("http",
3086
+ "method", c.Request.Method,
3087
+ "path", c.Request.URL.Path,
3088
+ "status", c.Writer.Status(),
3089
+ "bytes", c.Writer.Size(),
3090
+ "latency_ms", time.Since(start).Milliseconds(),
3091
+ "ip", c.ClientIP(),
3092
+ "request_id", c.GetString(headerRequestID),
3093
+ )
3094
+ }
3095
+ }
3096
+
3097
+ func newID() string {
3098
+ b := make([]byte, 8)
3099
+ if _, err := rand.Read(b); err != nil {
3100
+ return "unknown"
3101
+ }
3102
+ return hex.EncodeToString(b)
3103
+ }
3104
+ `}function jr(e){return `package middleware_test
3105
+
3106
+ import (
3107
+ "net/http"
3108
+ "net/http/httptest"
3109
+ "testing"
3110
+
3111
+ "github.com/gin-gonic/gin"
3112
+
3113
+ "${e.module_path}/internal/middleware"
3114
+ )
3115
+
3116
+ func init() { gin.SetMode(gin.TestMode) }
3117
+
3118
+ func newTestRouter() *gin.Engine {
3119
+ r := gin.New()
3120
+ r.Use(middleware.RequestID())
3121
+ r.Use(middleware.Logger())
3122
+ r.GET("/ping", func(c *gin.Context) {
3123
+ c.String(http.StatusOK, "pong")
3124
+ })
3125
+ return r
3126
+ }
3127
+
3128
+ func TestRequestID_IsGenerated(t *testing.T) {
3129
+ w := httptest.NewRecorder()
3130
+ req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
3131
+ newTestRouter().ServeHTTP(w, req)
3132
+
3133
+ id := w.Header().Get("X-Request-ID")
3134
+ if id == "" {
3135
+ t.Fatal("expected X-Request-ID header to be set")
3136
+ }
3137
+ if len(id) != 16 { // 8 random bytes \u2192 16 hex chars
3138
+ t.Fatalf("unexpected request ID length %d, want 16", len(id))
3139
+ }
3140
+ }
3141
+
3142
+ func TestRequestID_IsReused(t *testing.T) {
3143
+ w := httptest.NewRecorder()
3144
+ req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
3145
+ req.Header.Set("X-Request-ID", "my-trace-id")
3146
+ newTestRouter().ServeHTTP(w, req)
3147
+
3148
+ id := w.Header().Get("X-Request-ID")
3149
+ if id != "my-trace-id" {
3150
+ t.Fatalf("expected X-Request-ID to be reused, got %q", id)
3151
+ }
3152
+ }
3153
+ `}function Gr(){return `// Package apierr provides a consistent JSON error envelope for all API responses.
3154
+ //
3155
+ // Every error response looks like:
3156
+ //
3157
+ // {"error": "user not found", "code": "NOT_FOUND", "request_id": "a1b2c3d4..."}
3158
+ package apierr
3159
+
3160
+ import (
3161
+ "net/http"
3162
+
3163
+ "github.com/gin-gonic/gin"
3164
+ )
3165
+
3166
+ // Response is the standard error envelope returned by all API endpoints.
3167
+ type Response struct {
3168
+ Error string \`json:"error"\`
3169
+ Code string \`json:"code"\`
3170
+ RequestID string \`json:"request_id,omitempty"\`
3171
+ }
3172
+
3173
+ func reply(c *gin.Context, status int, msg, code string) {
3174
+ c.AbortWithStatusJSON(status, Response{
3175
+ Error: msg,
3176
+ Code: code,
3177
+ RequestID: c.GetString("X-Request-ID"),
3178
+ })
3179
+ }
3180
+
3181
+ // BadRequest responds with 400 and code "BAD_REQUEST".
3182
+ func BadRequest(c *gin.Context, msg string) {
3183
+ reply(c, http.StatusBadRequest, msg, "BAD_REQUEST")
3184
+ }
3185
+
3186
+ // NotFound responds with 404 and code "NOT_FOUND".
3187
+ func NotFound(c *gin.Context, msg string) {
3188
+ reply(c, http.StatusNotFound, msg, "NOT_FOUND")
3189
+ }
3190
+
3191
+ // Unauthorized responds with 401 and code "UNAUTHORIZED".
3192
+ func Unauthorized(c *gin.Context) {
3193
+ reply(c, http.StatusUnauthorized, "authentication required", "UNAUTHORIZED")
3194
+ }
3195
+
3196
+ // Forbidden responds with 403 and code "FORBIDDEN".
3197
+ func Forbidden(c *gin.Context) {
3198
+ reply(c, http.StatusForbidden, "access denied", "FORBIDDEN")
3199
+ }
3200
+
3201
+ // MethodNotAllowed responds with 405 and code "METHOD_NOT_ALLOWED".
3202
+ func MethodNotAllowed(c *gin.Context) {
3203
+ reply(c, http.StatusMethodNotAllowed, "method not allowed", "METHOD_NOT_ALLOWED")
3204
+ }
3205
+
3206
+ // InternalError responds with 500 and code "INTERNAL_ERROR".
3207
+ // The original error is intentionally not exposed to the client.
3208
+ func InternalError(c *gin.Context, _ error) {
3209
+ reply(c, http.StatusInternalServerError, "an internal error occurred", "INTERNAL_ERROR")
3210
+ }
3211
+
3212
+ // TooManyRequests responds with 429 and code "TOO_MANY_REQUESTS".
3213
+ func TooManyRequests(c *gin.Context) {
3214
+ reply(c, http.StatusTooManyRequests, "rate limit exceeded", "TOO_MANY_REQUESTS")
3215
+ }
3216
+ `}function $r(e){return `package apierr_test
3217
+
3218
+ import (
3219
+ "encoding/json"
3220
+ "net/http"
3221
+ "net/http/httptest"
3222
+ "testing"
3223
+
3224
+ "github.com/gin-gonic/gin"
3225
+
3226
+ "${e.module_path}/internal/apierr"
3227
+ )
3228
+
3229
+ func init() { gin.SetMode(gin.TestMode) }
3230
+
3231
+ func makeRouter(fn gin.HandlerFunc) *gin.Engine {
3232
+ r := gin.New()
3233
+ r.GET("/test", fn)
3234
+ return r
3235
+ }
3236
+
3237
+ func readJSON(t *testing.T, w *httptest.ResponseRecorder) apierr.Response {
3238
+ t.Helper()
3239
+ var out apierr.Response
3240
+ if err := json.NewDecoder(w.Body).Decode(&out); err != nil {
3241
+ t.Fatalf("invalid JSON: %v", err)
3242
+ }
3243
+ return out
3244
+ }
3245
+
3246
+ func TestBadRequest(t *testing.T) {
3247
+ w := httptest.NewRecorder()
3248
+ req, _ := http.NewRequest(http.MethodGet, "/test", nil)
3249
+ makeRouter(func(c *gin.Context) { apierr.BadRequest(c, "invalid email") }).ServeHTTP(w, req)
3250
+
3251
+ if w.Code != http.StatusBadRequest {
3252
+ t.Fatalf("expected 400, got %d", w.Code)
3253
+ }
3254
+ body := readJSON(t, w)
3255
+ if body.Code != "BAD_REQUEST" {
3256
+ t.Fatalf("expected BAD_REQUEST, got %q", body.Code)
3257
+ }
3258
+ if body.Error != "invalid email" {
3259
+ t.Fatalf("unexpected error message: %q", body.Error)
3260
+ }
3261
+ }
3262
+
3263
+ func TestNotFound(t *testing.T) {
3264
+ w := httptest.NewRecorder()
3265
+ req, _ := http.NewRequest(http.MethodGet, "/test", nil)
3266
+ makeRouter(func(c *gin.Context) { apierr.NotFound(c, "user not found") }).ServeHTTP(w, req)
3267
+
3268
+ if w.Code != http.StatusNotFound {
3269
+ t.Fatalf("expected 404, got %d", w.Code)
3270
+ }
3271
+ body := readJSON(t, w)
3272
+ if body.Code != "NOT_FOUND" {
3273
+ t.Fatalf("expected NOT_FOUND, got %q", body.Code)
3274
+ }
3275
+ }
3276
+
3277
+ func TestUnauthorized(t *testing.T) {
3278
+ w := httptest.NewRecorder()
3279
+ req, _ := http.NewRequest(http.MethodGet, "/test", nil)
3280
+ makeRouter(func(c *gin.Context) { apierr.Unauthorized(c) }).ServeHTTP(w, req)
3281
+
3282
+ if w.Code != http.StatusUnauthorized {
3283
+ t.Fatalf("expected 401, got %d", w.Code)
3284
+ }
3285
+ }
3286
+
3287
+ func TestForbidden(t *testing.T) {
3288
+ w := httptest.NewRecorder()
3289
+ req, _ := http.NewRequest(http.MethodGet, "/test", nil)
3290
+ makeRouter(func(c *gin.Context) { apierr.Forbidden(c) }).ServeHTTP(w, req)
3291
+
3292
+ if w.Code != http.StatusForbidden {
3293
+ t.Fatalf("expected 403, got %d", w.Code)
3294
+ }
3295
+ body := readJSON(t, w)
3296
+ if body.Code != "FORBIDDEN" {
3297
+ t.Fatalf("expected FORBIDDEN, got %q", body.Code)
3298
+ }
3299
+ }
3300
+
3301
+ func TestMethodNotAllowed(t *testing.T) {
3302
+ w := httptest.NewRecorder()
3303
+ req, _ := http.NewRequest(http.MethodGet, "/test", nil)
3304
+ makeRouter(func(c *gin.Context) { apierr.MethodNotAllowed(c) }).ServeHTTP(w, req)
3305
+
3306
+ if w.Code != http.StatusMethodNotAllowed {
3307
+ t.Fatalf("expected 405, got %d", w.Code)
3308
+ }
3309
+ body := readJSON(t, w)
3310
+ if body.Code != "METHOD_NOT_ALLOWED" {
3311
+ t.Fatalf("expected METHOD_NOT_ALLOWED, got %q", body.Code)
3312
+ }
3313
+ }
3314
+
3315
+ func TestInternalError(t *testing.T) {
3316
+ w := httptest.NewRecorder()
3317
+ req, _ := http.NewRequest(http.MethodGet, "/test", nil)
3318
+ makeRouter(func(c *gin.Context) { apierr.InternalError(c, nil) }).ServeHTTP(w, req)
3319
+
3320
+ if w.Code != http.StatusInternalServerError {
3321
+ t.Fatalf("expected 500, got %d", w.Code)
3322
+ }
3323
+ body := readJSON(t, w)
3324
+ if body.Code != "INTERNAL_ERROR" {
3325
+ t.Fatalf("expected INTERNAL_ERROR, got %q", body.Code)
3326
+ }
3327
+ }
3328
+
3329
+ func TestTooManyRequests(t *testing.T) {
3330
+ w := httptest.NewRecorder()
3331
+ req, _ := http.NewRequest(http.MethodGet, "/test", nil)
3332
+ makeRouter(func(c *gin.Context) { apierr.TooManyRequests(c) }).ServeHTTP(w, req)
3333
+
3334
+ if w.Code != http.StatusTooManyRequests {
3335
+ t.Fatalf("expected 429, got %d", w.Code)
3336
+ }
3337
+ body := readJSON(t, w)
3338
+ if body.Code != "TOO_MANY_REQUESTS" {
3339
+ t.Fatalf("expected TOO_MANY_REQUESTS, got %q", body.Code)
3340
+ }
3341
+ }
3342
+ `}function Dr(e){return `// Package docs provides the swaggo-generated OpenAPI specification.
3343
+ //
3344
+ // Run \`make docs\` to regenerate after changing handler annotations.
3345
+ //
3346
+ // @title ${At(e.project_name)} API
3347
+ // @version ${e.app_version}
3348
+ // @description ${e.description}
3349
+ // @host localhost:${e.port}
3350
+ // @BasePath /
3351
+ // @schemes http https
3352
+ //
3353
+ // @contact.name ${e.author}
3354
+ // @license.name MIT
3355
+ package docs
3356
+ `}function qr(e){return `package handlers
3357
+
3358
+ import (
3359
+ "net/http"
3360
+
3361
+ "github.com/gin-gonic/gin"
3362
+
3363
+ "${e.module_path}/internal/apierr"
3364
+ )
3365
+
3366
+ // EchoResponse is the JSON body returned by EchoParams.
3367
+ type EchoResponse struct {
3368
+ Name string \`json:"name"\`
3369
+ RequestID string \`json:"request_id"\`
3370
+ }
3371
+
3372
+ // EchoParams is an example handler demonstrating how to:
3373
+ // - read URL path parameters
3374
+ // - use apierr for consistent JSON error responses
3375
+ // - access the request ID injected by RequestID middleware
3376
+ //
3377
+ // Replace or remove this file once you add your own business logic.
3378
+ //
3379
+ // @Summary Echo path parameter
3380
+ // @Description Returns the :name path parameter together with the request ID.
3381
+ // @Tags example
3382
+ // @Produce json
3383
+ // @Param name path string true "Name to echo"
3384
+ // @Success 200 {object} handlers.EchoResponse
3385
+ // @Failure 400 {object} apierr.Response
3386
+ // @Router /api/v1/echo/{name} [get]
3387
+ func EchoParams(c *gin.Context) {
3388
+ name := c.Param("name")
3389
+ if name == "" {
3390
+ apierr.BadRequest(c, "name parameter is required")
3391
+ return
3392
+ }
3393
+ c.JSON(http.StatusOK, EchoResponse{
3394
+ Name: name,
3395
+ RequestID: c.GetString("X-Request-ID"),
3396
+ })
3397
+ }
3398
+ `}function Mr(e){return `package handlers_test
3399
+
3400
+ import (
3401
+ "encoding/json"
3402
+ "net/http"
3403
+ "net/http/httptest"
3404
+ "testing"
3405
+
3406
+ "github.com/gin-gonic/gin"
3407
+
3408
+ "${e.module_path}/internal/handlers"
3409
+ "${e.module_path}/internal/middleware"
3410
+ )
3411
+
3412
+ func newEchoRouter() *gin.Engine {
3413
+ r := gin.New()
3414
+ r.Use(middleware.RequestID())
3415
+ r.GET("/echo/:name", handlers.EchoParams)
3416
+ return r
3417
+ }
3418
+
3419
+ func TestEchoParams_Success(t *testing.T) {
3420
+ w := httptest.NewRecorder()
3421
+ req, _ := http.NewRequest(http.MethodGet, "/echo/alice", nil)
3422
+ newEchoRouter().ServeHTTP(w, req)
3423
+
3424
+ if w.Code != http.StatusOK {
3425
+ t.Fatalf("expected 200, got %d", w.Code)
3426
+ }
3427
+
3428
+ var body map[string]any
3429
+ if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
3430
+ t.Fatalf("invalid JSON: %v", err)
3431
+ }
3432
+ if body["name"] != "alice" {
3433
+ t.Fatalf("expected name=alice, got %v", body["name"])
3434
+ }
3435
+ if body["request_id"] == nil || body["request_id"] == "" {
3436
+ t.Fatal("expected request_id to be set by RequestID middleware")
3437
+ }
3438
+ }
3439
+
3440
+ // TestEchoParams_EmptyName registers EchoParams on a param-free route so that
3441
+ // c.Param("name") returns "" and the 400 guard executes.
3442
+ func TestEchoParams_EmptyName(t *testing.T) {
3443
+ gin.SetMode(gin.TestMode)
3444
+ r := gin.New()
3445
+ r.Use(middleware.RequestID())
3446
+ r.GET("/echo-bare", handlers.EchoParams)
3447
+
3448
+ w := httptest.NewRecorder()
3449
+ req, _ := http.NewRequest(http.MethodGet, "/echo-bare", nil)
3450
+ r.ServeHTTP(w, req)
3451
+
3452
+ if w.Code != http.StatusBadRequest {
3453
+ t.Fatalf("expected 400, got %d", w.Code)
3454
+ }
3455
+ var body map[string]any
3456
+ if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
3457
+ t.Fatalf("invalid JSON: %v", err)
3458
+ }
3459
+ if body["code"] != "BAD_REQUEST" {
3460
+ t.Fatalf("expected code=BAD_REQUEST, got %v", body["code"])
3461
+ }
3462
+ }
3463
+ `}function Lr(e){return `package config_test
3464
+
3465
+ import (
3466
+ "log/slog"
3467
+ "testing"
3468
+
3469
+ "${e.module_path}/internal/config"
3470
+ )
3471
+
3472
+ func TestParseLogLevel(t *testing.T) {
3473
+ tests := []struct {
3474
+ input string
3475
+ want slog.Level
3476
+ }{
3477
+ {"debug", slog.LevelDebug},
3478
+ {"DEBUG", slog.LevelDebug},
3479
+ {"warn", slog.LevelWarn},
3480
+ {"warning", slog.LevelWarn},
3481
+ {"error", slog.LevelError},
3482
+ {"info", slog.LevelInfo},
3483
+ {"", slog.LevelInfo},
3484
+ {"unknown", slog.LevelInfo},
3485
+ }
3486
+ for _, tc := range tests {
3487
+ got := config.ParseLogLevel(tc.input)
3488
+ if got != tc.want {
3489
+ t.Errorf("ParseLogLevel(%q) = %v, want %v", tc.input, got, tc.want)
3490
+ }
3491
+ }
3492
+ }
3493
+
3494
+ func TestLoad_EnvOverride(t *testing.T) {
3495
+ t.Setenv("PORT", "9090")
3496
+ t.Setenv("APP_ENV", "production")
3497
+ t.Setenv("LOG_LEVEL", "warn")
3498
+ t.Setenv("GIN_MODE", "release")
3499
+
3500
+ cfg := config.Load()
3501
+
3502
+ if cfg.Port != "9090" {
3503
+ t.Errorf("expected Port=9090, got %q", cfg.Port)
3504
+ }
3505
+ if cfg.Env != "production" {
3506
+ t.Errorf("expected Env=production, got %q", cfg.Env)
3507
+ }
3508
+ if cfg.LogLevel != "warn" {
3509
+ t.Errorf("expected LogLevel=warn, got %q", cfg.LogLevel)
3510
+ }
3511
+ if cfg.GinMode != "release" {
3512
+ t.Errorf("expected GinMode=release, got %q", cfg.GinMode)
3513
+ }
3514
+ }
3515
+
3516
+ func TestLoad_Defaults(t *testing.T) {
3517
+ // Empty string forces getEnv() to return the built-in fallback value.
3518
+ t.Setenv("PORT", "")
3519
+ t.Setenv("APP_ENV", "")
3520
+ t.Setenv("LOG_LEVEL", "")
3521
+ t.Setenv("GIN_MODE", "")
3522
+
3523
+ cfg := config.Load()
3524
+
3525
+ if cfg.Port != "${e.port}" {
3526
+ t.Errorf("expected default Port=${e.port}, got %q", cfg.Port)
3527
+ }
3528
+ if cfg.Env != "development" {
3529
+ t.Errorf("expected default Env=development, got %q", cfg.Env)
3530
+ }
3531
+ // APP_ENV="" \u2192 fallback "development" \u2192 defaultLogLevel \u2192 "debug"
3532
+ if cfg.LogLevel != "debug" {
3533
+ t.Errorf("expected default LogLevel=debug (development env), got %q", cfg.LogLevel)
3534
+ }
3535
+ if cfg.GinMode != "debug" {
3536
+ t.Errorf("expected default GinMode=debug, got %q", cfg.GinMode)
3537
+ }
3538
+ }
3539
+ `}function Fr(){return `package middleware
3540
+
3541
+ import (
3542
+ "net/http"
3543
+ "os"
3544
+ "strings"
3545
+
3546
+ "github.com/gin-gonic/gin"
3547
+ )
3548
+
3549
+ // CORS returns a Gin middleware configured via CORS_ALLOW_ORIGINS env var.
3550
+ //
3551
+ // Set CORS_ALLOW_ORIGINS="*" for development (the default when unset).
3552
+ // In production supply a comma-separated list of allowed origins:
3553
+ //
3554
+ // CORS_ALLOW_ORIGINS=https://app.example.com,https://admin.example.com
3555
+ func CORS() gin.HandlerFunc {
3556
+ allowed := os.Getenv("CORS_ALLOW_ORIGINS")
3557
+ if allowed == "" {
3558
+ allowed = "*"
3559
+ }
3560
+ allowAll := allowed == "*"
3561
+
3562
+ return func(c *gin.Context) {
3563
+ origin := c.Request.Header.Get("Origin")
3564
+ if allowAll {
3565
+ c.Header("Access-Control-Allow-Origin", "*")
3566
+ } else if origin != "" {
3567
+ for _, o := range strings.Split(allowed, ",") {
3568
+ if strings.TrimSpace(o) == origin {
3569
+ c.Header("Access-Control-Allow-Origin", origin)
3570
+ c.Header("Vary", "Origin")
3571
+ break
3572
+ }
3573
+ }
3574
+ }
3575
+ c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
3576
+ c.Header("Access-Control-Allow-Headers", "Origin,Content-Type,Authorization,X-Request-ID")
3577
+ c.Header("Access-Control-Expose-Headers", "X-Request-ID")
3578
+ c.Header("Access-Control-Max-Age", "600")
3579
+
3580
+ if c.Request.Method == http.MethodOptions {
3581
+ c.AbortWithStatus(http.StatusNoContent)
3582
+ return
3583
+ }
3584
+ c.Next()
3585
+ }
3586
+ }
3587
+ `}function Hr(e){return `package middleware_test
3588
+
3589
+ import (
3590
+ "net/http"
3591
+ "net/http/httptest"
3592
+ "testing"
3593
+
3594
+ "github.com/gin-gonic/gin"
3595
+
3596
+ "${e.module_path}/internal/middleware"
3597
+ )
3598
+
3599
+ func newCORSRouter(t *testing.T) *gin.Engine {
3600
+ t.Helper()
3601
+ gin.SetMode(gin.TestMode)
3602
+ r := gin.New()
3603
+ r.Use(middleware.CORS())
3604
+ r.GET("/ping", func(c *gin.Context) { c.Status(http.StatusOK) })
3605
+ return r
3606
+ }
3607
+
3608
+ func TestCORS_Wildcard(t *testing.T) {
3609
+ t.Setenv("CORS_ALLOW_ORIGINS", "*")
3610
+ w := httptest.NewRecorder()
3611
+ req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
3612
+ req.Header.Set("Origin", "https://example.com")
3613
+ newCORSRouter(t).ServeHTTP(w, req)
3614
+
3615
+ if got := w.Header().Get("Access-Control-Allow-Origin"); got != "*" {
3616
+ t.Fatalf("expected ACAO=*, got %q", got)
3617
+ }
3618
+ }
3619
+
3620
+ func TestCORS_Preflight(t *testing.T) {
3621
+ t.Setenv("CORS_ALLOW_ORIGINS", "*")
3622
+ gin.SetMode(gin.TestMode)
3623
+ r := gin.New()
3624
+ r.Use(middleware.CORS())
3625
+
3626
+ w := httptest.NewRecorder()
3627
+ req, _ := http.NewRequest(http.MethodOptions, "/ping", nil)
3628
+ req.Header.Set("Origin", "https://example.com")
3629
+ req.Header.Set("Access-Control-Request-Method", "POST")
3630
+ r.ServeHTTP(w, req)
3631
+
3632
+ if w.Code != http.StatusNoContent {
3633
+ t.Fatalf("expected 204 preflight, got %d", w.Code)
3634
+ }
3635
+ }
3636
+
3637
+ func TestCORS_SpecificOrigin_Allowed(t *testing.T) {
3638
+ t.Setenv("CORS_ALLOW_ORIGINS", "https://app.example.com")
3639
+ w := httptest.NewRecorder()
3640
+ req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
3641
+ req.Header.Set("Origin", "https://app.example.com")
3642
+ newCORSRouter(t).ServeHTTP(w, req)
3643
+
3644
+ if got := w.Header().Get("Access-Control-Allow-Origin"); got != "https://app.example.com" {
3645
+ t.Fatalf("expected ACAO=https://app.example.com, got %q", got)
3646
+ }
3647
+ }
3648
+
3649
+ func TestCORS_SpecificOrigin_Denied(t *testing.T) {
3650
+ t.Setenv("CORS_ALLOW_ORIGINS", "https://app.example.com")
3651
+ w := httptest.NewRecorder()
3652
+ req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
3653
+ req.Header.Set("Origin", "https://evil.com")
3654
+ newCORSRouter(t).ServeHTTP(w, req)
3655
+
3656
+ if got := w.Header().Get("Access-Control-Allow-Origin"); got != "" {
3657
+ t.Fatalf("expected no ACAO header for denied origin, got %q", got)
3658
+ }
3659
+ }
3660
+
3661
+ func TestCORS_Default_Origin(t *testing.T) {
3662
+ // When CORS_ALLOW_ORIGINS is unset the middleware must default to "*".
3663
+ t.Setenv("CORS_ALLOW_ORIGINS", "")
3664
+ w := httptest.NewRecorder()
3665
+ req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
3666
+ req.Header.Set("Origin", "https://anywhere.com")
3667
+ newCORSRouter(t).ServeHTTP(w, req)
3668
+
3669
+ if got := w.Header().Get("Access-Control-Allow-Origin"); got == "" {
3670
+ t.Fatal("expected CORS header when CORS_ALLOW_ORIGINS defaults to *")
3671
+ }
3672
+ }
3673
+ `}function Kr(e){return `package server_test
3674
+
3675
+ import (
3676
+ "encoding/json"
3677
+ "net/http"
3678
+ "net/http/httptest"
3679
+ "testing"
3680
+
3681
+ "github.com/gin-gonic/gin"
3682
+
3683
+ "${e.module_path}/internal/config"
3684
+ "${e.module_path}/internal/server"
3685
+ )
3686
+
3687
+ func init() { gin.SetMode(gin.TestMode) }
3688
+
3689
+ type serverAPIError struct {
3690
+ Code string \`json:"code"\`
3691
+ Message string \`json:"message"\`
3692
+ }
3693
+
3694
+ func TestServer_NotFound_JSON(t *testing.T) {
3695
+ w := httptest.NewRecorder()
3696
+ req, _ := http.NewRequest(http.MethodGet, "/no-such-route", nil)
3697
+ server.NewRouter(config.Load()).ServeHTTP(w, req)
3698
+
3699
+ if w.Code != http.StatusNotFound {
3700
+ t.Fatalf("expected 404, got %d", w.Code)
3701
+ }
3702
+ var body serverAPIError
3703
+ if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
3704
+ t.Fatalf("expected JSON error body: %v", err)
3705
+ }
3706
+ if body.Code != "NOT_FOUND" {
3707
+ t.Fatalf("expected code=NOT_FOUND, got %q", body.Code)
3708
+ }
3709
+ }
3710
+
3711
+ func TestServer_MethodNotAllowed_JSON(t *testing.T) {
3712
+ w := httptest.NewRecorder()
3713
+ req, _ := http.NewRequest(http.MethodPost, "/api/v1/health/live", nil)
3714
+ server.NewRouter(config.Load()).ServeHTTP(w, req)
3715
+
3716
+ if w.Code != http.StatusMethodNotAllowed {
3717
+ t.Fatalf("expected 405, got %d", w.Code)
3718
+ }
3719
+ var body serverAPIError
3720
+ if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
3721
+ t.Fatalf("expected JSON error body: %v", err)
3722
+ }
3723
+ if body.Code != "METHOD_NOT_ALLOWED" {
3724
+ t.Fatalf("expected code=METHOD_NOT_ALLOWED, got %q", body.Code)
3725
+ }
3726
+ }
3727
+
3728
+ func TestServer_CORS_Header(t *testing.T) {
3729
+ t.Setenv("CORS_ALLOW_ORIGINS", "*")
3730
+ w := httptest.NewRecorder()
3731
+ req, _ := http.NewRequest(http.MethodGet, "/api/v1/health/live", nil)
3732
+ req.Header.Set("Origin", "https://example.com")
3733
+ server.NewRouter(config.Load()).ServeHTTP(w, req)
3734
+
3735
+ if got := w.Header().Get("Access-Control-Allow-Origin"); got == "" {
3736
+ t.Fatal("expected Access-Control-Allow-Origin header to be set")
3737
+ }
3738
+ }
3739
+
3740
+ func TestServer_HealthLive(t *testing.T) {
3741
+ w := httptest.NewRecorder()
3742
+ req, _ := http.NewRequest(http.MethodGet, "/api/v1/health/live", nil)
3743
+ server.NewRouter(config.Load()).ServeHTTP(w, req)
3744
+
3745
+ if w.Code != http.StatusOK {
3746
+ t.Fatalf("expected 200, got %d", w.Code)
3747
+ }
3748
+ var body map[string]any
3749
+ if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
3750
+ t.Fatalf("invalid JSON: %v", err)
3751
+ }
3752
+ if body["status"] != "ok" {
3753
+ t.Fatalf("expected status=ok, got %v", body["status"])
3754
+ }
3755
+ }
3756
+
3757
+ func TestServer_Docs_Redirect(t *testing.T) {
3758
+ w := httptest.NewRecorder()
3759
+ req, _ := http.NewRequest(http.MethodGet, "/docs", nil)
3760
+ server.NewRouter(config.Load()).ServeHTTP(w, req)
3761
+
3762
+ if w.Code != http.StatusFound {
3763
+ t.Fatalf("expected 302 redirect from /docs, got %d", w.Code)
3764
+ }
3765
+ if loc := w.Header().Get("Location"); loc != "/docs/index.html" {
3766
+ t.Fatalf("expected Location=/docs/index.html, got %q", loc)
3767
+ }
3768
+ }
3769
+
3770
+ func TestServer_ReleaseMode(t *testing.T) {
3771
+ // Covers the gin.SetMode(gin.ReleaseMode) branch in NewRouter.
3772
+ // Restore test mode after so other tests are not affected.
3773
+ t.Cleanup(func() { gin.SetMode(gin.TestMode) })
3774
+ t.Setenv("GIN_MODE", "release")
3775
+ w := httptest.NewRecorder()
3776
+ req, _ := http.NewRequest(http.MethodGet, "/api/v1/health/live", nil)
3777
+ server.NewRouter(config.Load()).ServeHTTP(w, req)
3778
+
3779
+ if w.Code != http.StatusOK {
3780
+ t.Fatalf("expected 200 in release mode, got %d", w.Code)
3781
+ }
3782
+ }
3783
+ `}function Vr(e){return `package middleware
3784
+
3785
+ import (
3786
+ "os"
3787
+ "strconv"
3788
+ "sync"
3789
+ "time"
3790
+
3791
+ "github.com/gin-gonic/gin"
3792
+
3793
+ "${e.module_path}/internal/apierr"
3794
+ )
3795
+
3796
+ // ipBucket tracks the per-IP fixed-window request counter.
3797
+ type ipBucket struct {
3798
+ mu sync.Mutex
3799
+ count int
3800
+ windowStart time.Time
3801
+ }
3802
+
3803
+ // RateLimit returns a per-IP fixed-window rate limiter.
3804
+ // Configure the limit via RATE_LIMIT_RPS env var (requests per second, default 100).
3805
+ func RateLimit() gin.HandlerFunc {
3806
+ rps := 100
3807
+ if raw := os.Getenv("RATE_LIMIT_RPS"); raw != "" {
3808
+ if n, err := strconv.Atoi(raw); err == nil && n > 0 {
3809
+ rps = n
3810
+ }
3811
+ }
3812
+ var buckets sync.Map
3813
+ return func(c *gin.Context) {
3814
+ ip := c.ClientIP()
3815
+ now := time.Now()
3816
+ v, _ := buckets.LoadOrStore(ip, &ipBucket{windowStart: now})
3817
+ b := v.(*ipBucket)
3818
+ b.mu.Lock()
3819
+ if now.Sub(b.windowStart) >= time.Second {
3820
+ b.count = 0
3821
+ b.windowStart = now
3822
+ }
3823
+ b.count++
3824
+ count := b.count
3825
+ b.mu.Unlock()
3826
+ if count > rps {
3827
+ apierr.TooManyRequests(c)
3828
+ c.Header("Retry-After", "1")
3829
+ return
3830
+ }
3831
+ c.Next()
3832
+ }
3833
+ }
3834
+ `}function Ur(e){return `package middleware_test
3835
+
3836
+ import (
3837
+ "net/http"
3838
+ "net/http/httptest"
3839
+ "testing"
3840
+
3841
+ "github.com/gin-gonic/gin"
3842
+
3843
+ "${e.module_path}/internal/middleware"
3844
+ )
3845
+
3846
+ func newRateLimitRouter(t *testing.T) *gin.Engine {
3847
+ t.Helper()
3848
+ gin.SetMode(gin.TestMode)
3849
+ r := gin.New()
3850
+ r.Use(middleware.RateLimit())
3851
+ r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
3852
+ return r
3853
+ }
3854
+
3855
+ func TestRateLimit_AllowsUnderLimit(t *testing.T) {
3856
+ t.Setenv("RATE_LIMIT_RPS", "3")
3857
+ r := newRateLimitRouter(t)
3858
+
3859
+ for i := 0; i < 3; i++ {
3860
+ w := httptest.NewRecorder()
3861
+ req, _ := http.NewRequest(http.MethodGet, "/", nil)
3862
+ r.ServeHTTP(w, req)
3863
+ if w.Code != http.StatusOK {
3864
+ t.Fatalf("request %d: expected 200, got %d", i+1, w.Code)
3865
+ }
3866
+ }
3867
+ }
3868
+
3869
+ func TestRateLimit_Blocks_After_Limit(t *testing.T) {
3870
+ t.Setenv("RATE_LIMIT_RPS", "2")
3871
+ r := newRateLimitRouter(t)
3872
+
3873
+ // Exhaust the limit.
3874
+ for i := 0; i < 2; i++ {
3875
+ w := httptest.NewRecorder()
3876
+ req, _ := http.NewRequest(http.MethodGet, "/", nil)
3877
+ r.ServeHTTP(w, req)
3878
+ }
3879
+
3880
+ // Next request must be rejected.
3881
+ w := httptest.NewRecorder()
3882
+ req, _ := http.NewRequest(http.MethodGet, "/", nil)
3883
+ r.ServeHTTP(w, req)
3884
+
3885
+ if w.Code != http.StatusTooManyRequests {
3886
+ t.Fatalf("expected 429 after limit, got %d", w.Code)
3887
+ }
3888
+ }
3889
+
3890
+ func TestRateLimit_InvalidRPS(t *testing.T) {
3891
+ // When RATE_LIMIT_RPS is not a valid positive integer, the middleware
3892
+ // must fall back to the default limit (100 rps) and allow normal requests.
3893
+ t.Setenv("RATE_LIMIT_RPS", "not-a-number")
3894
+ r := newRateLimitRouter(t)
3895
+ w := httptest.NewRecorder()
3896
+ req, _ := http.NewRequest(http.MethodGet, "/", nil)
3897
+ r.ServeHTTP(w, req)
3898
+
3899
+ if w.Code != http.StatusOK {
3900
+ t.Fatalf("expected 200 with invalid RPS env, got %d", w.Code)
3901
+ }
3902
+ }
3903
+ `}function Wr(e){return `# Air \u2014 live reload for Go projects
3904
+ # https://github.com/air-verse/air
3905
+ root = "."
3906
+ tmp_dir = "tmp"
3907
+
3908
+ [build]
3909
+ pre_cmd = ["$(go env GOPATH)/bin/swag init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency 2>/dev/null || true"]
3910
+ cmd = "go build -o ./tmp/server ./cmd/server"
3911
+ bin = "./tmp/server"
3912
+ include_ext = ["go", "yaml", "yml", "env"]
3913
+ exclude_dir = ["tmp", "vendor", ".git", "testdata", "docs"]
3914
+ delay = 500
3915
+ rerun_delay = 500
3916
+ send_interrupt = true
3917
+ kill_delay = "200ms"
3918
+
3919
+ [env]
3920
+ GIN_MODE = "debug"
3921
+ PORT = "${e.port}"
3922
+
3923
+ [misc]
3924
+ clean_on_exit = true
3925
+
3926
+ [log]
3927
+ time = false
3928
+ `}function Br(e){return `run:
3929
+ timeout: 5m
3930
+
3931
+ linters:
3932
+ enable:
3933
+ - bodyclose
3934
+ - durationcheck
3935
+ - errcheck
3936
+ - errname
3937
+ - errorlint
3938
+ - gci
3939
+ - goimports
3940
+ - gosimple
3941
+ - govet
3942
+ - ineffassign
3943
+ - misspell
3944
+ - noctx
3945
+ - nolintlint
3946
+ - prealloc
3947
+ - staticcheck
3948
+ - unconvert
3949
+ - unused
3950
+ - wrapcheck
3951
+
3952
+ linters-settings:
3953
+ gci:
3954
+ sections:
3955
+ - standard
3956
+ - default
3957
+ - prefix(${e})
3958
+ goimports:
3959
+ local-prefixes: "${e}"
3960
+ govet:
3961
+ enable:
3962
+ - shadow
3963
+ wrapcheck:
3964
+ ignorePackageGlobs:
3965
+ - "${e}/*"
3966
+
3967
+ issues:
3968
+ max-same-issues: 5
3969
+ exclude-rules:
3970
+ - path: _test.go
3971
+ linters:
3972
+ - errcheck
3973
+ - wrapcheck
3974
+ `}function Jr(){return JSON.stringify({engine:"npm",runtime:"go"},null,2)}function zr(e,o){return JSON.stringify({kit_name:"gogin.standard",runtime:"go",module_support:false,project_name:e.project_name,module_path:e.module_path,app_version:e.app_version,created_by:"rapidkit-npm",rapidkit_version:o,created_at:new Date().toISOString()},null,2)}function Yr(e){return `#!/usr/bin/env sh
3975
+ # RapidKit Go/Gin project launcher \u2014 generated by RapidKit CLI
3976
+ # https://getrapidkit.com
3977
+
3978
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
3979
+ CMD="\${1:-}"
3980
+ shift 2>/dev/null || true
3981
+
3982
+ case "$CMD" in
3983
+ init)
3984
+ cd "$SCRIPT_DIR"
3985
+ echo "\u{1F439} Initializing Go/Gin project\u2026"
3986
+ GOBIN="$(go env GOPATH)/bin"
3987
+ echo " \u2192 installing air (hot reload)\u2026"
3988
+ go install github.com/air-verse/air@latest 2>/dev/null && echo " \u2713 air" || echo " \u26A0 air install failed (run: go install github.com/air-verse/air@latest)"
3989
+ echo " \u2192 installing swag (swagger)\u2026"
3990
+ go install github.com/swaggo/swag/cmd/swag@latest 2>/dev/null && echo " \u2713 swag" || echo " \u26A0 swag install failed (run: go install github.com/swaggo/swag/cmd/swag@latest)"
3991
+ if [ ! -f ".env" ] && [ -f ".env.example" ]; then
3992
+ cp .env.example .env && echo " \u2713 .env created from .env.example"
3993
+ fi
3994
+ go mod tidy && echo " \u2713 go mod tidy"
3995
+ echo " \u2192 generating swagger docs (first build)\u2026"
3996
+ "$(go env GOPATH)/bin/swag" init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency 2>/dev/null && echo " \u2713 swagger docs generated" || echo " \u26A0 swagger docs skipped (run: rapidkit docs)"
3997
+ echo "\u2705 Ready \u2014 run: rapidkit dev"
3998
+ ;;
3999
+ dev)
4000
+ cd "$SCRIPT_DIR"
4001
+ echo "\u{1F4D6} Syncing swagger docs\u2026"
4002
+ "$(go env GOPATH)/bin/swag" init -g main.go -d cmd/server,internal/handlers,internal/apierr -o docs --parseDependency 2>/dev/null || true
4003
+ if [ -f "$SCRIPT_DIR/Makefile" ]; then
4004
+ exec make -C "$SCRIPT_DIR" dev "$@"
4005
+ else
4006
+ cd "$SCRIPT_DIR" && GIN_MODE=debug exec go run ./cmd/server "$@"
4007
+ fi
4008
+ ;;
4009
+ start)
4010
+ BIN="$SCRIPT_DIR/bin/${e.project_name}"
4011
+ if [ ! -f "$BIN" ]; then
4012
+ make -C "$SCRIPT_DIR" build
4013
+ fi
4014
+ exec "$BIN" "$@"
4015
+ ;;
4016
+ build)
4017
+ exec make -C "$SCRIPT_DIR" build "$@"
4018
+ ;;
4019
+ test)
4020
+ exec make -C "$SCRIPT_DIR" test "$@"
4021
+ ;;
4022
+ lint)
4023
+ exec make -C "$SCRIPT_DIR" lint "$@"
4024
+ ;;
4025
+ format|fmt)
4026
+ exec make -C "$SCRIPT_DIR" fmt "$@"
4027
+ ;;
4028
+ docs)
4029
+ exec make -C "$SCRIPT_DIR" docs "$@"
4030
+ ;;
4031
+ help|--help|-h)
4032
+ echo "RapidKit \u2014 Go/Gin project: ${e.project_name}"
4033
+ echo ""
4034
+ echo "Usage: rapidkit <command>"
4035
+ echo ""
4036
+ echo " init Install tools + create .env (air, swag, go mod tidy)"
4037
+ echo " dev Hot reload dev server (make dev \u2014 requires air)"
4038
+ echo " start Run compiled binary (make build + bin)"
4039
+ echo " build Build binary (make build)"
4040
+ echo " docs Generate Swagger docs (make docs \u2014 requires swag)"
4041
+ echo " test Run tests (make test)"
4042
+ echo " lint Run linter (make lint)"
4043
+ echo " format Format code (make fmt)"
4044
+ ;;
4045
+ *)
4046
+ if [ -n "$CMD" ]; then
4047
+ echo "rapidkit: unknown command: $CMD" >&2
4048
+ fi
4049
+ echo "Available: init, dev, start, build, docs, test, lint, format" >&2
4050
+ exit 1
4051
+ ;;
4052
+ esac
4053
+ `}function Qr(e){return `@echo off
4054
+ rem RapidKit Go/Gin project launcher \u2014 Windows
4055
+ set CMD=%1
4056
+ if "%CMD%"=="" goto usage
4057
+ shift
4058
+
4059
+ if "%CMD%"=="init" (
4060
+ echo Initializing Go/Gin project...
4061
+ go install github.com/air-verse/air@latest
4062
+ go install github.com/swaggo/swag/cmd/swag@latest
4063
+ if not exist .env if exist .env.example copy .env.example .env
4064
+ go mod tidy
4065
+ exit /b %ERRORLEVEL%
4066
+ )
4067
+ if "%CMD%"=="dev" ( make dev %* & exit /b %ERRORLEVEL% )
4068
+ if "%CMD%"=="build" ( make build %* & exit /b %ERRORLEVEL% )
4069
+ if "%CMD%"=="test" ( make test %* & exit /b %ERRORLEVEL% )
4070
+ if "%CMD%"=="lint" ( make lint %* & exit /b %ERRORLEVEL% )
4071
+ if "%CMD%"=="format" ( make fmt %* & exit /b %ERRORLEVEL% )
4072
+ if "%CMD%"=="docs" ( make docs %* & exit /b %ERRORLEVEL% )
4073
+ if "%CMD%"=="start" ( bin\\${e.project_name}.exe %* & exit /b %ERRORLEVEL% )
4074
+
4075
+ :usage
4076
+ echo Available: init, dev, start, build, docs, test, lint, format
4077
+ exit /b 1
4078
+ `}async function et(e,o){let t={project_name:o.project_name,module_path:o.module_path||o.project_name,author:o.author||"RapidKit User",description:o.description||`Go/Gin REST API \u2014 ${o.project_name}`,go_version:o.go_version||"1.24",app_version:o.app_version||"0.1.0",port:o.port||"8080",skipGit:o.skipGit??false},n=c();try{await execa("go",["version"],{timeout:3e3});}catch{console.log(g.yellow("\n\u26A0 Go not found in PATH \u2014 project will be scaffolded, but `go mod tidy` requires Go 1.21+")),console.log(g.gray(` Install: https://go.dev/dl/
4079
+ `));}let i=Qe(`Generating Go/Gin project: ${t.project_name}\u2026`).start();try{let r=(s,d)=>vr(h.join(e,s),d),a=h.join(e,"rapidkit"),c=h.join(e,"rapidkit.cmd");await Promise.all([r("cmd/server/main.go",br(t)),r("go.mod",kr(t)),r("internal/config/config.go",Rr(t)),r("internal/server/server.go",Sr(t)),r("internal/middleware/requestid.go",Nr()),r("internal/middleware/requestid_test.go",jr(t)),r("internal/apierr/apierr.go",Gr()),r("internal/apierr/apierr_test.go",$r(t)),r("internal/handlers/health.go",xr()),r("internal/handlers/health_test.go",_r(t)),r("internal/handlers/example.go",qr(t)),r("internal/handlers/example_test.go",Mr(t)),r("internal/config/config_test.go",Lr(t)),r("internal/middleware/cors.go",Fr()),r("internal/middleware/cors_test.go",Hr(t)),r("internal/middleware/ratelimit.go",Vr(t)),r("internal/middleware/ratelimit_test.go",Ur(t)),r("internal/server/server_test.go",Kr(t)),r("docs/doc.go",Dr(t)),r(".air.toml",Wr(t)),r("Dockerfile",Cr()),r("docker-compose.yml",Ir(t)),r("Makefile",Er(t)),r(".golangci.yml",Br(t.module_path)),r(".env.example",Pr(t)),r(".gitignore",Tr()),r(".github/workflows/ci.yml",Or(t)),r("README.md",Ar(t)),r(".rapidkit/project.json",zr(t,n)),r(".rapidkit/context.json",Jr()),r("rapidkit",Yr(t)),r("rapidkit.cmd",Qr(t))]),await promises.chmod(a,493),await promises.chmod(c,493),i.succeed(g.green(`Project created at ${e}`));try{i.start("Fetching Go dependencies\u2026"),await execa("go",["mod","tidy"],{cwd:e,timeout:12e4}),i.succeed(g.gray("\u2713 go mod tidy completed"));}catch{i.warn(g.yellow("\u26A0 go mod tidy failed \u2014 run manually: go mod tidy"));}if(!t.skipGit)try{await execa("git",["init"],{cwd:e}),await execa("git",["add","-A"],{cwd:e}),await execa("git",["commit","-m","chore: initial scaffold (rapidkit gogin.standard)"],{cwd:e}),console.log(g.gray("\u2713 git repository initialized"));}catch{console.log(g.gray("\u26A0 git init skipped (git not found or error)"));}console.log(""),console.log(g.bold("\u2705 Go/Gin project ready!")),console.log(""),console.log(g.cyan("Next steps:")),console.log(g.white(` cd ${t.project_name}`)),console.log(g.white(" make run # start dev server")),console.log(g.white(" make test # run tests")),console.log(""),console.log(g.gray("Server will listen on port "+t.port)),console.log(g.gray(" http://localhost:"+t.port+"/api/v1/health/live")),console.log(g.gray(" http://localhost:"+t.port+"/api/v1/health/ready")),console.log(""),console.log(g.yellow("\u2139 RapidKit modules are not available for Go projects (module system uses Python/pip).")),console.log("");}catch(r){throw i.fail(g.red("Failed to generate Go/Gin project")),r}}async function jt(){let e=process.platform==="win32"?["python","python3"]:["python3","python"];for(let o of e)try{let{stdout:t}=await execa(o,["--version"],{timeout:3e3}),n=t.match(/Python (\d+\.\d+\.\d+)/);if(n){let i=n[1],[r,a]=i.split(".").map(Number);return r<3||r===3&&a<10?{status:"warn",message:`Python ${i} (requires 3.10+)`,details:`${o} found but version is below minimum requirement`}:{status:"ok",message:`Python ${i}`,details:`Using ${o}`}}}catch{continue}return {status:"error",message:"Python not found",details:"Install Python 3.10+ and ensure it's in PATH"}}async function Gt(){try{let{stdout:e}=await execa("poetry",["--version"],{timeout:3e3}),o=e.match(/Poetry .*version ([\d.]+)/);return o?{status:"ok",message:`Poetry ${o[1]}`,details:"Available for dependency management"}:{status:"warn",message:"Poetry version unknown"}}catch{return {status:"warn",message:"Poetry not installed",details:"Optional: Install for better dependency management"}}}async function $t(){try{let{stdout:e}=await execa("pipx",["--version"],{timeout:3e3});return {status:"ok",message:`pipx ${e.trim()}`,details:"Available for global tool installation"}}catch{return {status:"warn",message:"pipx not installed",details:"Optional: Install for isolated Python tools"}}}async function Dt(){try{let{stdout:e}=await execa("go",["version"],{timeout:3e3}),o=e.match(/go version go(\d+\.\d+(?:\.\d+)?)/);return o?{status:"ok",message:`Go ${o[1]}`,details:"Available for Go/Fiber and Go/Gin projects"}:{status:"ok",message:"Go (version unknown)",details:"go found in PATH"}}catch{return {status:"warn",message:"Go not installed",details:"Optional: Required only for gofiber.standard / gogin.standard projects \u2014 https://go.dev/dl/"}}}async function qt(){let e=process.env.HOME||process.env.USERPROFILE||"",o=[],t=[{location:"Global (pipx)",path:h.join(e,".local","bin","rapidkit")},{location:"Global (pipx)",path:h.join(e,"AppData","Roaming","Python","Scripts","rapidkit.exe")},{location:"Global (pyenv)",path:h.join(e,".pyenv","shims","rapidkit")},{location:"Global (system)",path:"/usr/local/bin/rapidkit"},{location:"Global (system)",path:"/usr/bin/rapidkit"}],n=[{location:"Workspace (.venv)",path:h.join(process.cwd(),".venv","bin","rapidkit")},{location:"Workspace (.venv)",path:h.join(process.cwd(),".venv","Scripts","rapidkit.exe")}];for(let{location:r,path:a}of [...t,...n])try{if(await b__default.pathExists(a)){let{stdout:c,exitCode:s}=await execa(a,["--version"],{timeout:3e3,reject:false});if(s===0&&(c.includes("RapidKit Version")||c.includes("RapidKit"))){let d=c.match(/v?([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);d&&o.push({location:r,path:a,version:d[1]});}}}catch{continue}if(o.length>0)return {status:"ok",message:`RapidKit Core ${o[0].version}`,paths:o.map(a=>({location:a.location,path:a.path,version:a.version}))};try{let{stdout:r,exitCode:a}=await execa("rapidkit",["--version"],{timeout:3e3,reject:false});if(a===0&&(r.includes("RapidKit Version")||r.includes("RapidKit"))){let c=r.match(/v?([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);if(c)return {status:"ok",message:`RapidKit Core ${c[1]}`,details:"Available via PATH"}}}catch{}try{let{stdout:r,exitCode:a}=await execa("poetry",["run","rapidkit","--version"],{timeout:3e3,reject:false});if(a===0&&(r.includes("RapidKit Version")||r.includes("RapidKit"))){let c=r.match(/v?([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);if(c)return {status:"ok",message:`RapidKit Core ${c[1]}`,details:"Available via Poetry"}}}catch{}let i=process.platform==="win32"?["python","python3"]:["python3","python"];for(let r of i)try{let{stdout:a,exitCode:c}=await execa(r,["-c","import rapidkit_core; print(rapidkit_core.__version__)"],{timeout:3e3,reject:false});if(c===0&&a&&!a.includes("Traceback")&&!a.includes("ModuleNotFoundError")){let s=a.trim();if(s)return {status:"ok",message:`RapidKit Core ${s}`,details:`Available in ${r} environment`}}}catch{continue}return {status:"error",message:"RapidKit Core not installed",details:"Install with: pipx install rapidkit-core"}}async function Ae(e,o){let t=h.join(e,"Dockerfile");o.hasDocker=await b__default.pathExists(t);let n=h.join(e,"tests"),i=h.join(e,"test"),r=await b__default.pathExists(n)||await b__default.pathExists(i),a=false;if(o.framework==="Go/Fiber"||o.framework==="Go/Gin")try{let c=[{dir:e,depth:0}],s=4,d=new Set([".git",".venv","node_modules","dist","build","vendor"]);for(;c.length>0&&!a;){let l=c.shift();if(!l)break;let u=[];try{u=await b__default.readdir(l.dir);}catch{continue}for(let p of u){let k=h.join(l.dir,p),R;try{R=await b__default.stat(k);}catch{continue}if(R.isFile()&&p.endsWith("_test.go")){a=true;break}R.isDirectory()&&l.depth<s&&!d.has(p)&&!p.startsWith(".")&&c.push({dir:k,depth:l.depth+1});}}}catch{}if(o.hasTests=r||a,o.framework==="NestJS"){let c=h.join(e,".eslintrc.js"),s=h.join(e,".eslintrc.json");o.hasCodeQuality=await b__default.pathExists(c)||await b__default.pathExists(s);}else if(o.framework==="Go/Fiber"||o.framework==="Go/Gin"){let c=h.join(e,".golangci.yml"),s=h.join(e,".golangci.yaml"),d=h.join(e,"Makefile"),l=await b__default.pathExists(d)&&(await b__default.readFile(d,"utf8")).includes("golangci-lint");o.hasCodeQuality=await b__default.pathExists(c)||await b__default.pathExists(s)||l;}else if(o.framework==="FastAPI"){let c=h.join(e,"ruff.toml"),s=h.join(e,"pyproject.toml");if(await b__default.pathExists(s))try{let d=await b__default.readFile(s,"utf8");o.hasCodeQuality=d.includes("[tool.ruff]")||await b__default.pathExists(c);}catch{o.hasCodeQuality=await b__default.pathExists(c);}}try{if(o.framework==="NestJS"){let{stdout:c}=await execa("npm",["audit","--json"],{cwd:e,reject:false});if(c)try{let d=JSON.parse(c).metadata?.vulnerabilities;d&&(o.vulnerabilities=(d.high||0)+(d.critical||0)+(d.moderate||0));}catch{}}else if(o.framework==="FastAPI"){let c=h.join(e,".venv"),s=process.platform==="win32"?h.join(c,"Scripts","python.exe"):h.join(c,"bin","python");if(await b__default.pathExists(s))try{let{stdout:d}=await execa(s,["-m","pip","list","--format=json"],{timeout:5e3,reject:false});if(d){JSON.parse(d);o.vulnerabilities=0;}}catch{}}}catch{}}async function Zr(e){let t={name:h.basename(e),path:e,venvActive:false,depsInstalled:false,coreInstalled:false,issues:[],fixCommands:[]},n=h.join(e,".rapidkit");if(!await b__default.pathExists(n))return t.issues.push("Not a valid RapidKit project (missing .rapidkit directory)"),t;try{let u=h.join(e,"registry.json");if(await b__default.pathExists(u)){let p=await b__default.readJson(u);p.installed_modules&&(t.stats={modules:p.installed_modules.length});}}catch{}let i=null;try{let u=h.join(n,"project.json");if(await b__default.pathExists(u)){i=await b__default.readJson(u);let p=i?.kit_name||i?.kit;p&&(t.kit=p);}}catch{}try{let u=h.join(e,".git");if(await b__default.pathExists(u)){let{stdout:p}=await execa("git",["log","-1","--format=%cr"],{cwd:e,reject:false});p&&(t.lastModified=p.trim());}else {let p=await b__default.stat(e),R=Date.now()-p.mtime.getTime(),w=Math.floor(R/(1e3*60*60*24));t.lastModified=w===0?"today":`${w} day${w>1?"s":""} ago`;}}catch{}let r=h.join(e,"package.json"),a=h.join(e,"pyproject.toml"),c=h.join(e,"go.mod");if(await b__default.pathExists(c)||i?.runtime==="go"||typeof i?.kit_name=="string"&&(i.kit_name.startsWith("gofiber")||i.kit_name.startsWith("gogin"))){let u=i?.kit_name??"";t.framework=u.startsWith("gogin")?"Go/Gin":"Go/Fiber",t.isGoProject=true,t.venvActive=true,t.coreInstalled=false;try{await execa("go",["version"],{timeout:3e3});}catch{t.issues.push("Go toolchain not found \u2014 install from https://go.dev/dl/"),t.fixCommands?.push("https://go.dev/dl/");}let p=h.join(e,"go.sum");return await b__default.pathExists(p)?t.depsInstalled=true:(t.depsInstalled=false,t.issues.push("Go dependencies not downloaded (go.sum missing)"),t.fixCommands?.push(`cd ${e} && go mod tidy`)),await Ae(e,t),t}let d=await b__default.pathExists(r),l=await b__default.pathExists(a);if(d){t.framework="NestJS",t.venvActive=true;let u=h.join(e,"node_modules");if(await b__default.pathExists(u))try{let w=(await b__default.readdir(u)).filter(x=>!x.startsWith(".")&&!x.startsWith("_"));t.depsInstalled=w.length>0;}catch{t.depsInstalled=false;}t.depsInstalled||(t.issues.push("Dependencies not installed (node_modules empty or missing)"),t.fixCommands?.push(`cd ${e} && rapidkit init`)),t.coreInstalled=false;let p=h.join(e,".env");if(t.hasEnvFile=await b__default.pathExists(p),!t.hasEnvFile){let R=h.join(e,".env.example");await b__default.pathExists(R)&&(t.issues.push("Environment file missing (found .env.example)"),t.fixCommands?.push(`cd ${e} && cp .env.example .env`));}let k=h.join(e,"src");if(t.modulesHealthy=true,t.missingModules=[],await b__default.pathExists(k))try{let R=await b__default.readdir(k);t.modulesHealthy=R.length>0;}catch{t.modulesHealthy=false;}return await Ae(e,t),t}if(l){t.framework="FastAPI";let u=h.join(e,".venv");if(await b__default.pathExists(u)){t.venvActive=true;let w=process.platform==="win32"?h.join(u,"Scripts","python.exe"):h.join(u,"bin","python");if(await b__default.pathExists(w)){try{let{stdout:x}=await execa(w,["-c","import rapidkit_core; print(rapidkit_core.__version__)"],{timeout:2e3});t.coreInstalled=true,t.coreVersion=x.trim();}catch{t.coreInstalled=false;}try{await execa(w,["-c","import fastapi"],{timeout:2e3}),t.depsInstalled=true;}catch{try{let x=h.join(u,"lib");if(await b__default.pathExists(x)){let G=(await b__default.readdir(x)).find(V=>V.startsWith("python"));if(G){let V=h.join(x,G,"site-packages");if(await b__default.pathExists(V)){let We=(await b__default.readdir(V)).filter(Be=>!Be.startsWith("_")&&!Be.includes("dist-info")&&!["pip","setuptools","wheel","pkg_resources"].includes(Be));t.depsInstalled=We.length>0;}}}t.depsInstalled||(t.issues.push("Dependencies not installed"),t.fixCommands?.push(`cd ${e} && rapidkit init`));}catch{t.issues.push("Could not verify dependency installation");}}}else t.issues.push("Virtual environment exists but Python executable not found");}else t.issues.push("Virtual environment not created"),t.fixCommands?.push(`cd ${e} && rapidkit init`);let p=h.join(e,".env");if(t.hasEnvFile=await b__default.pathExists(p),!t.hasEnvFile){let w=h.join(e,".env.example");await b__default.pathExists(w)&&(t.issues.push("Environment file missing (found .env.example)"),t.fixCommands?.push(`cd ${e} && cp .env.example .env`));}let k=h.join(e,"src"),R=h.join(e,"modules");if(t.modulesHealthy=true,t.missingModules=[],await b__default.pathExists(k)){let w=h.join(k,"__init__.py");await b__default.pathExists(w)||(t.modulesHealthy=false,t.missingModules.push("src/__init__.py"));}if(await b__default.pathExists(R))try{let w=await Mt(R);for(let x of w){let O=h.join(R,x,"__init__.py");await b__default.pathExists(O)||(t.modulesHealthy=false,t.missingModules.push(`modules/${x}/__init__.py`));}}catch{}return !t.modulesHealthy&&t.missingModules.length>0&&t.issues.push(`Missing module init files: ${t.missingModules.join(", ")}`),await Ae(e,t),t}return t.issues.push("Unknown project type (no package.json or pyproject.toml)"),await Ae(e,t),t}async function Mt(e){try{return (await b__default.readdir(e,{withFileTypes:true})).filter(t=>t.isDirectory()).map(t=>t.name)}catch{try{let o=await b__default.readdir(e),t=[];for(let n of o)try{(await b__default.stat(h.join(e,n))).isDirectory()&&t.push(n);}catch{continue}return t}catch{return []}}}async function tt(e){let o=h.join(e,".rapidkit");if(!await b__default.pathExists(o))return false;let t=["project.json","context.json","file-hashes.json"];for(let n of t)if(await b__default.pathExists(h.join(o,n)))return true;return false}function Lt(e,o){if(o.has(e))return true;let t=e.toLowerCase();return !!(t==="dist"||t.startsWith("dist-")||t.startsWith("dist_")||t==="build"||t.startsWith("build-")||t.startsWith("build_"))}async function en(e,o,t){let n=new Set,i=[{dir:e,depth:0}];for(;i.length>0;){let r=i.shift();if(!r)break;try{let a=await b__default.readdir(r.dir);for(let c of a){if(Lt(c,t))continue;let s=h.join(r.dir,c),d;try{d=await b__default.stat(s);}catch{continue}if(d.isDirectory()){if(await tt(s)){n.add(s);continue}r.depth<o&&i.push({dir:s,depth:r.depth+1});}}}catch{continue}}return Array.from(n)}async function tn(e){let o=e,t=h.parse(o).root;for(;o!==t;){let n=[h.join(o,".rapidkit-workspace"),h.join(o,".rapidkit","workspace-marker.json"),h.join(o,".rapidkit","config.json")];for(let i of n)if(await b__default.pathExists(i))return o;o=h.dirname(o);}return null}function on(e,o){let t=0,n=0,i=0;return e.forEach(a=>{a.status==="ok"?t++:a.status==="warn"?n++:a.status==="error"&&i++;}),o.forEach(a=>{(a.isGoProject?a.issues.length===0&&a.depsInstalled:a.issues.length===0&&a.venvActive&&a.depsInstalled)?t++:a.issues.length>0&&n++;}),{total:t+n+i,passed:t,warnings:n,errors:i}}async function rn(e){let o=h.basename(e);try{let i=h.join(e,".rapidkit-workspace");await b__default.pathExists(i)&&(o=(await b__default.readJSON(i)).name||o);}catch{try{let i=h.join(e,".rapidkit","config.json");o=(await b__default.readJSON(i)).workspace_name||o;}catch{}}let t={workspacePath:e,workspaceName:o,python:await jt(),poetry:await Gt(),pipx:await $t(),go:await Dt(),rapidkitCore:await qt(),projects:[]};try{let i=new Set([".git",".venv","node_modules",".rapidkit","dist","build","coverage","__pycache__"]),r=new Set;await tt(e)&&r.add(e);let a$1=async(c,s)=>{if(s<0)return;let d=await Mt(c);for(let l of d){if(Lt(l,i))continue;let u=h.join(c,l);if(await tt(u)){r.add(u);continue}s>0&&await a$1(u,s-1);}};if(await a$1(e,1),a.debug(`Workspace scan (shallow) found ${r.size} project(s)`),r.size===0){let c=await en(e,3,i);c.forEach(s=>r.add(s)),a.debug(`Workspace scan (deep fallback) found ${c.length} project(s)`);}r.size>0&&a.debug(`Workspace projects detected: ${Array.from(r).join(", ")}`);for(let c of r){let s=await Zr(c);t.projects.push(s);}}catch(i){a.debug(`Failed to scan workspace projects: ${i}`);}let n=[t.python,t.poetry,t.pipx,t.go,t.rapidkitCore];if(t.healthScore=on(n,t.projects),t.rapidkitCore.status==="ok"){let i=t.rapidkitCore.message.match(/([\d.]+(?:rc\d+)?(?:a\d+)?(?:b\d+)?)/);i&&(t.coreVersion=i[1]);}return t}function B(e,o){let t=e.status==="ok"?"\u2705":e.status==="warn"?"\u26A0\uFE0F":"\u274C",n=e.status==="ok"?g.green:e.status==="warn"?g.yellow:g.red;console.log(`${t} ${g.bold(o)}: ${n(e.message)}`),e.paths&&e.paths.length>0?e.paths.forEach(i=>{let r=i.version?g.cyan(` -> ${i.version}`):"";console.log(` ${g.cyan("\u2022")} ${g.gray(i.location)}: ${g.dim(i.path)}${r}`);}):e.details&&console.log(` ${g.gray(e.details)}`);}function nn(e){let o=e.issues.length>0,t=o?"\u26A0\uFE0F":"\u2705",n=o?g.yellow:g.green;if(console.log(`
4080
+ ${t} ${g.bold("Project")}: ${n(e.name)}`),e.framework){let s=e.framework==="FastAPI"?"\u{1F40D}":e.framework==="NestJS"?"\u{1F985}":e.framework==="Go/Fiber"||e.framework==="Go/Gin"?"\u{1F439}":"\u{1F4E6}";console.log(` ${s} Framework: ${g.cyan(e.framework)}${e.kit?g.gray(` (${e.kit})`):""}`);}console.log(` ${g.gray(`Path: ${e.path}`)}`);let i=e.isGoProject===true,r=!i&&e.venvActive&&!e.coreInstalled;if(!i&&!r&&(e.venvActive?console.log(` \u2705 Virtual environment: ${g.green("Active")}`):console.log(` \u274C Virtual environment: ${g.red("Not found")}`),e.coreInstalled?console.log(` ${g.dim("\u2139")} RapidKit Core: ${g.gray(e.coreVersion||"In venv")} ${g.dim("(optional)")}`):console.log(` ${g.dim("\u2139")} RapidKit Core: ${g.gray("Using global installation")} ${g.dim("(recommended)")}`)),e.depsInstalled?console.log(` \u2705 Dependencies: ${g.green("Installed")}`):console.log(` \u26A0\uFE0F Dependencies: ${g.yellow("Not installed")}`),e.hasEnvFile!==void 0&&(e.hasEnvFile?console.log(` \u2705 Environment: ${g.green(".env configured")}`):console.log(` \u26A0\uFE0F Environment: ${g.yellow(".env missing")}`)),e.modulesHealthy!==void 0&&(e.modulesHealthy?console.log(` \u2705 Modules: ${g.green("Healthy")}`):e.missingModules&&e.missingModules.length>0&&console.log(` \u26A0\uFE0F Modules: ${g.yellow(`Missing ${e.missingModules.length} init file(s)`)}`)),e.stats){let s=[];e.stats.modules!==void 0&&s.push(`${e.stats.modules} module${e.stats.modules!==1?"s":""}`),s.length>0&&console.log(` \u{1F4CA} Stats: ${g.cyan(s.join(" \u2022 "))}`);}e.lastModified&&console.log(` \u{1F552} Last Modified: ${g.gray(e.lastModified)}`);let c=[];if(e.hasTests!==void 0&&c.push(e.hasTests?"\u2705 Tests":g.dim("\u2298 No tests")),e.hasDocker!==void 0&&c.push(e.hasDocker?"\u2705 Docker":g.dim("\u2298 No Docker")),e.hasCodeQuality!==void 0){let s=e.framework==="NestJS"?"ESLint":e.framework==="Go/Fiber"||e.framework==="Go/Gin"?"golangci-lint":"Ruff";c.push(e.hasCodeQuality?`\u2705 ${s}`:g.dim(`\u2298 No ${s}`));}c.length>0&&console.log(` ${c.join(" \u2022 ")}`),e.vulnerabilities!==void 0&&e.vulnerabilities>0&&console.log(` \u26A0\uFE0F Security: ${g.yellow(`${e.vulnerabilities} vulnerability(ies) found`)}`),e.issues.length>0&&(console.log(` ${g.bold("Issues:")}`),e.issues.forEach(s=>{console.log(` \u2022 ${g.yellow(s)}`);}),e.fixCommands&&e.fixCommands.length>0&&(console.log(`
4081
+ ${g.bold.cyan("\u{1F527} Quick Fix:")}`),e.fixCommands.forEach(s=>{console.log(` ${g.cyan("$")} ${g.white(s)}`);})));}async function Nt(e,o=false){let t=e.filter(i=>i.fixCommands&&i.fixCommands.length>0);if(t.length===0){console.log(g.green(`
4082
+ \u2705 No fixes needed - all projects are healthy!`));return}console.log(g.bold.cyan(`
859
4083
  \u{1F527} Available Fixes:
860
- `));for(let i of t)console.log(u.bold(`Project: ${u.yellow(i.name)}`)),i.fixCommands.forEach((r,s)=>{console.log(` ${s+1}. ${u.cyan(r)}`);}),console.log();if(!o){console.log(u.gray("\u{1F4A1} Run with --fix flag to apply fixes automatically"));return}let{confirm:n}=await ue.prompt([{type:"confirm",name:"confirm",message:`Apply ${t.reduce((i,r)=>i+r.fixCommands.length,0)} fix(es)?`,default:false}]);if(!n){console.log(u.yellow(`
861
- \u26A0\uFE0F Fixes cancelled by user`));return}console.log(u.bold.cyan(`
4084
+ `));for(let i of t)console.log(g.bold(`Project: ${g.yellow(i.name)}`)),i.fixCommands.forEach((r,a)=>{console.log(` ${a+1}. ${g.cyan(r)}`);}),console.log();if(!o){console.log(g.gray("\u{1F4A1} Run with --fix flag to apply fixes automatically"));return}let{confirm:n}=await te.prompt([{type:"confirm",name:"confirm",message:`Apply ${t.reduce((i,r)=>i+r.fixCommands.length,0)} fix(es)?`,default:false}]);if(!n){console.log(g.yellow(`
4085
+ \u26A0\uFE0F Fixes cancelled by user`));return}console.log(g.bold.cyan(`
862
4086
  \u{1F680} Applying fixes...
863
- `));for(let i of t){console.log(u.bold(`Fixing ${u.cyan(i.name)}...`));for(let r of i.fixCommands)try{console.log(u.gray(` $ ${r}`)),await execa(r,{shell:true,stdio:"inherit"}),console.log(u.green(` \u2705 Success
864
- `));}catch(s){console.log(u.red(` \u274C Failed: ${s instanceof Error?s.message:String(s)}
865
- `));}}console.log(u.bold.green(`
866
- \u2705 Fix process completed!`));}async function kt(e={}){if(e.json||console.log(u.bold.cyan(`
4087
+ `));for(let i of t){console.log(g.bold(`Fixing ${g.cyan(i.name)}...`));for(let r of i.fixCommands)try{console.log(g.gray(` $ ${r}`)),await execa(r,{shell:true,stdio:"inherit"}),console.log(g.green(` \u2705 Success
4088
+ `));}catch(a){console.log(g.red(` \u274C Failed: ${a instanceof Error?a.message:String(a)}
4089
+ `));}}console.log(g.bold.green(`
4090
+ \u2705 Fix process completed!`));}async function Ft(e={}){if(e.json||console.log(g.bold.cyan(`
867
4091
  \u{1FA7A} RapidKit Health Check
868
- `)),e.workspace){let o=await ko(process.cwd());o||(a.error("No RapidKit workspace found in current directory or parents"),a.info('Run this command from within a workspace, or use "rapidkit doctor" for system check'),process.exit(1)),e.json||(console.log(u.bold(`Workspace: ${u.cyan(w.basename(o))}`)),console.log(u.gray(`Path: ${o}`)));let t=await bo(o);if(e.json){let r={workspace:{name:w.basename(o),path:o},healthScore:t.healthScore,system:{python:t.python,poetry:t.poetry,pipx:t.pipx,rapidkitCore:t.rapidkitCore,versions:{core:t.coreVersion,npm:t.npmVersion}},projects:t.projects.map(s=>({name:s.name,path:s.path,venvActive:s.venvActive,depsInstalled:s.depsInstalled,coreInstalled:s.coreInstalled,coreVersion:s.coreVersion,issues:s.issues,fixCommands:s.fixCommands})),summary:{totalProjects:t.projects.length,totalIssues:t.projects.reduce((s,c)=>s+c.issues.length,0),hasSystemErrors:[t.python,t.rapidkitCore].some(s=>s.status==="error")}};console.log(JSON.stringify(r,null,2));return}if(t.healthScore){let r=t.healthScore,s=Math.round(r.passed/r.total*100),c=s>=80?u.green:s>=50?u.yellow:u.red,a="\u2588".repeat(Math.floor(s/5))+"\u2591".repeat(20-Math.floor(s/5));console.log(u.bold(`
869
- \u{1F4CA} Health Score:`)),console.log(` ${c(`${s}%`)} ${u.gray(a)}`),console.log(` ${u.green(`\u2705 ${r.passed} passed`)} ${u.gray("|")} ${u.yellow(`\u26A0\uFE0F ${r.warnings} warnings`)} ${u.gray("|")} ${u.red(`\u274C ${r.errors} errors`)}`);}if(console.log(u.bold(`
4092
+ `)),e.workspace){let o=await tn(process.cwd());o||(a.error("No RapidKit workspace found in current directory or parents"),a.info('Run this command from within a workspace, or use "rapidkit doctor" for system check'),process.exit(1)),e.json||(console.log(g.bold(`Workspace: ${g.cyan(h.basename(o))}`)),console.log(g.gray(`Path: ${o}`)));let t=await rn(o);if(e.json){let r={workspace:{name:h.basename(o),path:o},healthScore:t.healthScore,system:{python:t.python,poetry:t.poetry,pipx:t.pipx,rapidkitCore:t.rapidkitCore,versions:{core:t.coreVersion,npm:t.npmVersion}},projects:t.projects.map(a=>({name:a.name,path:a.path,venvActive:a.venvActive,depsInstalled:a.depsInstalled,coreInstalled:a.coreInstalled,coreVersion:a.coreVersion,issues:a.issues,fixCommands:a.fixCommands})),summary:{totalProjects:t.projects.length,totalIssues:t.projects.reduce((a,c)=>a+c.issues.length,0),hasSystemErrors:[t.python,t.rapidkitCore].some(a=>a.status==="error")}};console.log(JSON.stringify(r,null,2));return}if(t.healthScore){let r=t.healthScore,a=Math.round(r.passed/r.total*100),c=a>=80?g.green:a>=50?g.yellow:g.red,s="\u2588".repeat(Math.floor(a/5))+"\u2591".repeat(20-Math.floor(a/5));console.log(g.bold(`
4093
+ \u{1F4CA} Health Score:`)),console.log(` ${c(`${a}%`)} ${g.gray(s)}`),console.log(` ${g.green(`\u2705 ${r.passed} passed`)} ${g.gray("|")} ${g.yellow(`\u26A0\uFE0F ${r.warnings} warnings`)} ${g.gray("|")} ${g.red(`\u274C ${r.errors} errors`)}`);}if(console.log(g.bold(`
870
4094
 
871
4095
  System Tools:
872
- `)),q(t.python,"Python"),q(t.poetry,"Poetry"),q(t.pipx,"pipx"),q(t.rapidkitCore,"RapidKit Core"),t.coreVersion&&t.npmVersion){let r=t.coreVersion.split(".")[1],s=t.npmVersion.split(".")[1];r!==s&&(console.log(u.yellow(`
873
- \u26A0\uFE0F Version mismatch: Core ${t.coreVersion} / CLI ${t.npmVersion}`)),console.log(u.gray(" Consider updating to matching versions for best compatibility")));}t.projects.length>0?(console.log(u.bold(`
874
- \u{1F4E6} Projects (${t.projects.length}):`)),t.projects.forEach(r=>xo(r))):(console.log(u.bold(`
875
- \u{1F4E6} Projects:`)),console.log(u.gray(" No RapidKit projects found in workspace")));let n=t.projects.reduce((r,s)=>r+s.issues.length,0),i=[t.python,t.rapidkitCore].some(r=>r.status==="error");i||n>0?(console.log(u.bold.yellow(`
876
- \u26A0\uFE0F Found ${n} project issue(s)`)),i&&console.log(u.bold.red("\u274C System requirements not met")),e.fix?await ut(t.projects,true):n>0&&await ut(t.projects,false)):console.log(u.bold.green(`
877
- \u2705 All checks passed! Workspace is healthy.`));}else {console.log(u.bold(`System Tools:
878
- `));let o=await mt(),t=await gt(),n=await ft(),i=await ht();q(o,"Python"),q(t,"Poetry"),q(n,"pipx"),q(i,"RapidKit Core"),[o,i].some(s=>s.status==="error")?(console.log(u.bold.red(`
879
- \u274C Some required tools are missing`)),console.log(u.gray(`
880
- Tip: Run "rapidkit doctor --workspace" from within a workspace for detailed project checks`))):(console.log(u.bold.green(`
881
- \u2705 All required tools are installed!`)),console.log(u.gray(`
882
- Tip: Run "rapidkit doctor --workspace" from within a workspace for detailed project checks`)));}console.log("");}var We=w.join(zt__default.homedir(),".rapidkit"),xe=w.join(We,"config.json");function oe(){try{if(!S.existsSync(xe))return {};let e=S.readFileSync(xe,"utf-8");return JSON.parse(e)}catch{return {}}}function Pe(e){let t={...oe(),...e};S.existsSync(We)||S.mkdirSync(We,{recursive:true}),S.writeFileSync(xe,JSON.stringify(t,null,2),"utf-8");}function le(){return process.env.OPENAI_API_KEY||oe().openaiApiKey||null}function Le(){return oe().aiEnabled!==false}function Ge(){return xe}async function bt(){return (await import('inquirer')).default}function xt(e){let o=e.command("config").description("Configure RapidKit settings");o.command("set-api-key").description("Set OpenAI API key for AI features").option("--key <key>","API key (or enter interactively)").action(async t=>{let n=t.key;n?n.startsWith("sk-")||(console.log(u.red(`
4096
+ `)),B(t.python,"Python"),B(t.poetry,"Poetry"),B(t.pipx,"pipx"),B(t.go,"Go"),B(t.rapidkitCore,"RapidKit Core"),t.coreVersion&&t.npmVersion){let r=t.coreVersion.split(".")[1],a=t.npmVersion.split(".")[1];r!==a&&(console.log(g.yellow(`
4097
+ \u26A0\uFE0F Version mismatch: Core ${t.coreVersion} / CLI ${t.npmVersion}`)),console.log(g.gray(" Consider updating to matching versions for best compatibility")));}t.projects.length>0?(console.log(g.bold(`
4098
+ \u{1F4E6} Projects (${t.projects.length}):`)),t.projects.forEach(r=>nn(r))):(console.log(g.bold(`
4099
+ \u{1F4E6} Projects:`)),console.log(g.gray(" No RapidKit projects found in workspace")));let n=t.projects.reduce((r,a)=>r+a.issues.length,0),i=[t.python,t.rapidkitCore].some(r=>r.status==="error");i||n>0?(console.log(g.bold.yellow(`
4100
+ \u26A0\uFE0F Found ${n} project issue(s)`)),i&&console.log(g.bold.red("\u274C System requirements not met")),e.fix?await Nt(t.projects,true):n>0&&await Nt(t.projects,false)):console.log(g.bold.green(`
4101
+ \u2705 All checks passed! Workspace is healthy.`));}else {console.log(g.bold(`System Tools:
4102
+ `));let o=await jt(),t=await Gt(),n=await $t(),i=await Dt(),r=await qt();B(o,"Python"),B(t,"Poetry"),B(n,"pipx"),B(i,"Go"),B(r,"RapidKit Core"),[o,r].some(c=>c.status==="error")?(console.log(g.bold.red(`
4103
+ \u274C Some required tools are missing`)),console.log(g.gray(`
4104
+ Tip: Run "rapidkit doctor --workspace" from within a workspace for detailed project checks`))):(console.log(g.bold.green(`
4105
+ \u2705 All required tools are installed!`)),console.log(g.gray(`
4106
+ Tip: Run "rapidkit doctor --workspace" from within a workspace for detailed project checks`)));}console.log("");}var ot=h.join(wo__default.homedir(),".rapidkit"),Ne=h.join(ot,"config.json");function ae(){try{if(!P.existsSync(Ne))return {};let e=P.readFileSync(Ne,"utf-8");return JSON.parse(e)}catch{return {}}}function je(e){let t={...ae(),...e};P.existsSync(ot)||P.mkdirSync(ot,{recursive:true}),P.writeFileSync(Ne,JSON.stringify(t,null,2),"utf-8");}function fe(){return process.env.OPENAI_API_KEY||ae().openaiApiKey||null}function rt(){return ae().aiEnabled!==false}function nt(){return Ne}async function Kt(){return (await import('inquirer')).default}function Vt(e){let o=e.command("config").description("Configure RapidKit settings");o.command("set-api-key").description("Set OpenAI API key for AI features").option("--key <key>","API key (or enter interactively)").action(async t=>{let n=t.key;n?n.startsWith("sk-")||(console.log(g.red(`
883
4107
  \u274C Invalid API key format (should start with sk-)
884
- `)),process.exit(1)):n=(await(await bt()).prompt([{type:"password",name:"apiKey",message:"Enter your OpenAI API key:",validate:s=>s?s.startsWith("sk-")?s.length<20?"API key seems too short":true:"Invalid API key format (should start with sk-)":"API key is required"}])).apiKey,Pe({openaiApiKey:n}),console.log(u.green(`
4108
+ `)),process.exit(1)):n=(await(await Kt()).prompt([{type:"password",name:"apiKey",message:"Enter your OpenAI API key:",validate:a=>a?a.startsWith("sk-")?a.length<20?"API key seems too short":true:"Invalid API key format (should start with sk-)":"API key is required"}])).apiKey,je({openaiApiKey:n}),console.log(g.green(`
885
4109
  \u2705 OpenAI API key saved successfully!
886
- `)),console.log(u.gray(`Stored in: ${Ge()}`)),console.log(u.cyan(`
887
- \u{1F389} You can now use AI features:`)),console.log(u.white(' rapidkit ai recommend "I need user authentication"')),console.log(u.gray(`
888
- \u{1F4A1} To generate module embeddings (one-time):`)),console.log(u.white(" cd rapidkit-npm")),console.log(u.white(` npx tsx src/ai/generate-embeddings.ts
889
- `));}),o.command("show").description("Show current configuration").action(()=>{let t=oe();if(console.log(u.bold(`
4110
+ `)),console.log(g.gray(`Stored in: ${nt()}`)),console.log(g.cyan(`
4111
+ \u{1F389} You can now use AI features:`)),console.log(g.white(' rapidkit ai recommend "I need user authentication"')),console.log(g.gray(`
4112
+ \u{1F4A1} To generate module embeddings (one-time):`)),console.log(g.white(" cd rapidkit-npm")),console.log(g.white(` npx tsx src/ai/generate-embeddings.ts
4113
+ `));}),o.command("show").description("Show current configuration").action(()=>{let t=ae();if(console.log(g.bold(`
890
4114
  \u2699\uFE0F RapidKit Configuration
891
- `)),t.openaiApiKey){let n=t.openaiApiKey.substring(0,8)+"..."+t.openaiApiKey.slice(-4);console.log(u.cyan("OpenAI API Key:"),u.white(n));}else console.log(u.cyan("OpenAI API Key:"),u.red("Not set")),console.log(u.gray(" Set with: rapidkit config set-api-key"));console.log(u.cyan("AI Features:"),t.aiEnabled!==false?u.green("Enabled"):u.red("Disabled")),console.log(u.gray(`
892
- \u{1F4C1} Config file: ${Ge()}
893
- `));}),o.command("remove-api-key").description("Remove stored OpenAI API key").action(async()=>{if(!oe().openaiApiKey){console.log(u.yellow(`
4115
+ `)),t.openaiApiKey){let n=t.openaiApiKey.substring(0,8)+"..."+t.openaiApiKey.slice(-4);console.log(g.cyan("OpenAI API Key:"),g.white(n));}else console.log(g.cyan("OpenAI API Key:"),g.red("Not set")),console.log(g.gray(" Set with: rapidkit config set-api-key"));console.log(g.cyan("AI Features:"),t.aiEnabled!==false?g.green("Enabled"):g.red("Disabled")),console.log(g.gray(`
4116
+ \u{1F4C1} Config file: ${nt()}
4117
+ `));}),o.command("remove-api-key").description("Remove stored OpenAI API key").action(async()=>{if(!ae().openaiApiKey){console.log(g.yellow(`
894
4118
  \u26A0\uFE0F No API key is currently stored
895
- `));return}(await(await bt()).prompt([{type:"confirm",name:"confirm",message:"Are you sure you want to remove your OpenAI API key?",default:false}])).confirm?(Pe({openaiApiKey:void 0}),console.log(u.green(`
4119
+ `));return}(await(await Kt()).prompt([{type:"confirm",name:"confirm",message:"Are you sure you want to remove your OpenAI API key?",default:false}])).confirm?(je({openaiApiKey:void 0}),console.log(g.green(`
896
4120
  \u2705 API key removed successfully
897
- `))):console.log(u.gray(`
4121
+ `))):console.log(g.gray(`
898
4122
  Cancelled
899
- `));}),o.command("ai <action>").description("Enable or disable AI features (enable|disable)").action(t=>{t!=="enable"&&t!=="disable"&&(console.log(u.red(`
900
- \u274C Invalid action: ${t}`)),console.log(u.gray(`Use: rapidkit config ai enable|disable
901
- `)),process.exit(1));let n=t==="enable";Pe({aiEnabled:n}),console.log(u.green(`
4123
+ `));}),o.command("ai <action>").description("Enable or disable AI features (enable|disable)").action(t=>{t!=="enable"&&t!=="disable"&&(console.log(g.red(`
4124
+ \u274C Invalid action: ${t}`)),console.log(g.gray(`Use: rapidkit config ai enable|disable
4125
+ `)),process.exit(1));let n=t==="enable";je({aiEnabled:n}),console.log(g.green(`
902
4126
  \u2705 AI features ${n?"enabled":"disabled"}
903
- `));});}var je=null,Ie=false,Ve=null;async function jo(){return Ve||(Ve=(await import('openai')).default),Ve}function Pt(){Ie=true;}function jt(e){let t=new Array(1536),n=0;for(let r=0;r<e.length;r++)n=(n<<5)-n+e.charCodeAt(r),n=n&n;for(let r=0;r<1536;r++)n=n*1664525+1013904223&4294967295,t[r]=n/4294967295*2-1;let i=Math.sqrt(t.reduce((r,s)=>r+s*s,0));return t.map(r=>r/i)}async function Ce(e){let o=await jo();je=new o({apiKey:e});}function It(){if(!je)throw new Error("OpenAI client not initialized. Call initOpenAI() first with your API key.");return je}async function Ct(e){return Ie?jt(e):(await It().embeddings.create({model:"text-embedding-3-small",input:e,encoding_format:"float"})).data[0].embedding}async function Rt(e){return Ie?e.map(jt):(await It().embeddings.create({model:"text-embedding-3-small",input:e,encoding_format:"float"})).data.map(n=>n.embedding)}function $t(){return je!==null}function At(){return Ie}var Ro=promisify(exec),Et=[{id:"authentication-core",name:"Authentication Core",category:"auth",description:"Complete authentication system with password hashing, JWT tokens, OAuth 2.0, and secure session management",longDescription:"Production-ready authentication with bcrypt password hashing, JWT access/refresh tokens, OAuth 2.0 providers (Google, GitHub, etc), rate limiting, and security best practices.",keywords:["auth","login","password","jwt","oauth","token","authentication","security","signin","signup"],framework:"both",dependencies:[],useCases:["User login and logout","Password reset flow","OAuth social login (Google, GitHub)","JWT authentication","Secure session management","Token refresh","Rate limiting"]},{id:"users-core",name:"Users Core",category:"auth",description:"User management system with profiles, roles, permissions, and user CRUD operations",longDescription:"Complete user management with user profiles, role-based access control (RBAC), permissions, user search, soft delete, and audit trails.",keywords:["user","profile","role","permission","rbac","management","admin","accounts"],framework:"both",dependencies:["authentication-core"],useCases:["User registration","User profile management","Role management (admin, user, etc)","Permission system","User administration dashboard","Soft delete users"]},{id:"session-management",name:"Session Management",category:"auth",description:"Secure session handling with Redis storage, session rotation, and device tracking",longDescription:"Advanced session management with Redis-backed storage, automatic session rotation, device fingerprinting, IP tracking, and session revocation.",keywords:["session","redis","cookie","storage","device","tracking"],framework:"both",dependencies:["authentication-core","redis-cache"],useCases:["User session management","Remember me functionality","Device tracking","Session security","Logout from all devices","Session expiration"]},{id:"db-postgres",name:"PostgreSQL",category:"database",description:"PostgreSQL integration with async SQLAlchemy, migrations, connection pooling, and query optimization",longDescription:"Production-ready PostgreSQL with async SQLAlchemy 2.0, Alembic migrations, connection pooling, query optimization, JSON support, and full-text search.",keywords:["postgres","postgresql","database","sql","sqlalchemy","migration","orm","relational"],framework:"both",dependencies:[],useCases:["Relational database","Complex SQL queries","Database transactions","Data integrity","Production-grade database","ACID compliance"]},{id:"db-mongodb",name:"MongoDB",category:"database",description:"MongoDB integration with Motor async driver, schema validation, and aggregation pipelines",longDescription:"Async MongoDB with Motor driver, Pydantic schema validation, aggregation pipelines, indexes, and Atlas integration.",keywords:["mongodb","mongo","nosql","document","database","motor"],framework:"both",dependencies:[],useCases:["Document storage","Flexible schema","Real-time data","JSON documents","Unstructured data","Analytics"]},{id:"stripe-payment",name:"Stripe Payment",category:"payment",description:"Stripe integration with payment intents, subscriptions, webhooks, and customer portal",longDescription:"Complete Stripe integration with Payment Intents API, subscription management, automatic webhooks, customer portal, refunds, and SCA compliance.",keywords:["stripe","payment","subscription","billing","checkout","webhook","credit card"],framework:"both",dependencies:[],useCases:["Accept credit card payments","Subscription billing","One-time payments","Checkout flow","Payment webhooks","Refunds and disputes"]},{id:"email",name:"Email",category:"communication",description:"Email sending with templates, SMTP/SendGrid/AWS SES support, and queue management",longDescription:"Production email system with Jinja2 templates, multiple providers (SMTP, SendGrid, AWS SES), queue management, retry logic, and bounce handling.",keywords:["email","mail","smtp","sendgrid","ses","template","notification"],framework:"both",dependencies:[],useCases:["Welcome emails","Password reset emails","Notifications","Marketing emails","Transactional emails","Email templates"]},{id:"sms",name:"SMS",category:"communication",description:"SMS sending with Twilio, verification codes, and delivery tracking",longDescription:"SMS integration with Twilio, verification codes, two-factor authentication, delivery tracking, and international support.",keywords:["sms","twilio","text","message","2fa","verification","otp"],framework:"both",dependencies:[],useCases:["2FA verification codes","SMS notifications","Phone verification","OTP generation","SMS alerts"]},{id:"redis-cache",name:"Redis Cache",category:"infrastructure",description:"Redis caching with decorators, TTL management, and cache invalidation patterns",longDescription:"Redis integration with async client, caching decorators, TTL management, cache invalidation, pub/sub, and rate limiting.",keywords:["redis","cache","memory","performance","speed","pubsub"],framework:"both",dependencies:[],useCases:["API response caching","Session storage","Rate limiting","Real-time features","Performance optimization","Pub/sub messaging"]},{id:"celery",name:"Celery",category:"infrastructure",description:"Background task processing with Celery, periodic tasks, and monitoring",longDescription:"Celery task queue with Redis/RabbitMQ backend, periodic tasks (cron), task monitoring, retry logic, and failure handling.",keywords:["celery","task","background","queue","async","worker","job","cron"],framework:"fastapi",dependencies:["redis-cache"],useCases:["Background email sending","Data processing","Report generation","Scheduled tasks","Long-running jobs"]},{id:"storage",name:"Storage",category:"infrastructure",description:"File storage with S3, local filesystem, and image processing",longDescription:"Unified storage interface for AWS S3, local files, image resizing, format conversion, CDN integration, and presigned URLs.",keywords:["storage","s3","file","upload","image","cdn","aws"],framework:"both",dependencies:[],useCases:["File uploads","Image storage","Document management","Profile pictures","Media files","CDN integration"]}],ne=null,_t=0,$o=300*1e3;function Ao(e){return {id:e.name||e.id||e.module_id||"",name:e.display_name||e.name||"",category:_o(e.category||"infrastructure"),description:e.description||e.summary||"",longDescription:e.long_description||e.description||"",keywords:e.keywords||e.tags||[],framework:Eo(e.framework),dependencies:e.dependencies||[],useCases:e.use_cases||e.useCases||[]}}function _o(e){return {auth:"auth",authentication:"auth",database:"database",payment:"payment",billing:"payment",communication:"communication",infrastructure:"infrastructure",security:"security",analytics:"analytics"}[e.toLowerCase()]||"infrastructure"}function Eo(e){if(!e)return "both";if(typeof e=="string"){if(e.toLowerCase().includes("fastapi"))return "fastapi";if(e.toLowerCase().includes("nest"))return "nestjs"}return "both"}async function So(){try{let{stdout:e}=await Ro("rapidkit modules list --json-schema 1",{timeout:1e4,maxBuffer:10485760}),o=e.match(/\{[\s\S]*\}/),t=o?o[0]:e,n=JSON.parse(t),i=[];return Array.isArray(n)?i=n:n.modules&&Array.isArray(n.modules)?i=n.modules:n.data&&Array.isArray(n.data)&&(i=n.data),i.map(Ao).filter(r=>r.id&&r.name)}catch(e){return e.code==="ENOENT"?console.warn("\u26A0\uFE0F RapidKit Python Core not found in PATH"):e.killed?console.warn("\u26A0\uFE0F Python Core command timed out"):console.warn("\u26A0\uFE0F Failed to fetch modules from Python Core:",e.message),console.warn(" Using fallback module catalog (11 modules)"),Et}}async function Re(){let e=Date.now();return ne&&e-_t<$o||(ne=await So(),_t=e,ne.length===0&&(console.warn("\u26A0\uFE0F No modules found, using fallback catalog"),ne=Et)),ne}var Do=fileURLToPath(import.meta.url),Mt=w.dirname(Do),de=null;function Ko(){if(de)return de;let e=[w.join(Mt,"../../data/modules-embeddings.json"),w.join(Mt,"../data/modules-embeddings.json"),w.join(process.cwd(),"data/modules-embeddings.json")],o=null;for(let i of e)if(S.existsSync(i)){o=i;break}if(!o)throw new Error("embeddings file not found");let t=S.readFileSync(o,"utf-8"),n=JSON.parse(t);return Array.isArray(n)?de={model:"mock-or-text-embedding-3-small",dimension:n[0]?.embedding?.length||1536,generated_at:new Date().toISOString(),modules:n}:de=n,de}function Fo(e,o){if(e.length!==o.length)throw new Error("Vectors must have the same length");let t=0,n=0,i=0;for(let s=0;s<e.length;s++)t+=e[s]*o[s],n+=e[s]*e[s],i+=o[s]*o[s];let r=Math.sqrt(n)*Math.sqrt(i);return r===0?0:t/r}function Oo(e,o){let t=o.toLowerCase(),n=e.keywords.filter(i=>t.includes(i)||i.includes(t));return n.length>0?`Matches: ${n.slice(0,3).join(", ")}`:`Relevant for: ${e.useCases[0]}`}async function Dt(e,o=5){let t=Ko(),n=await Re(),i=await Ct(e),r=t.modules.map(s=>{let c=n.find(l=>l.id===s.id);if(!c)return null;let a=Fo(i,s.embedding);return {module:c,score:a,reason:Oo(c,e)}}).filter(s=>s!==null);return r.sort((s,c)=>c.score-s.score),r.slice(0,o)}var Wo=fileURLToPath(import.meta.url),Kt=w.dirname(Wo);async function Ft(){return (await import('inquirer')).default}function Lo(){return [w.join(Kt,"../../data/modules-embeddings.json"),w.join(Kt,"../data/modules-embeddings.json"),w.join(process.cwd(),"data/modules-embeddings.json")]}function Ot(){let e=Lo();for(let o of e)if(S.existsSync(o))try{let t=JSON.parse(S.readFileSync(o,"utf-8")),n=Array.isArray(t)?t:t.modules||[];return {exists:true,path:o,moduleCount:n.length,generatedAt:t.generated_at||null}}catch{continue}return {exists:false,path:null,moduleCount:0,generatedAt:null}}async function Ae(e=true,o){try{if(!$t()&&!At())return console.log(u.red(`
904
- \u274C OpenAI not initialized`)),console.log(u.yellow("Please set your API key:")),console.log(u.white(" rapidkit config set-api-key")),console.log(u.gray(` OR set: export OPENAI_API_KEY="sk-..."
905
- `)),false;console.log(u.blue(`
4127
+ `));});}var Ge=null,$e=false,it=null;async function sn(){return it||(it=(await import('openai')).default),it}function Ut(){$e=true;}function Wt(e){let t=new Array(1536),n=0;for(let r=0;r<e.length;r++)n=(n<<5)-n+e.charCodeAt(r),n=n&n;for(let r=0;r<1536;r++)n=n*1664525+1013904223&4294967295,t[r]=n/4294967295*2-1;let i=Math.sqrt(t.reduce((r,a)=>r+a*a,0));return t.map(r=>r/i)}async function De(e){let o=await sn();Ge=new o({apiKey:e});}function Bt(){if(!Ge)throw new Error("OpenAI client not initialized. Call initOpenAI() first with your API key.");return Ge}async function Jt(e){return $e?Wt(e):(await Bt().embeddings.create({model:"text-embedding-3-small",input:e,encoding_format:"float"})).data[0].embedding}async function zt(e){return $e?e.map(Wt):(await Bt().embeddings.create({model:"text-embedding-3-small",input:e,encoding_format:"float"})).data.map(n=>n.embedding)}function Yt(){return Ge!==null}function Qt(){return $e}var ln=promisify(exec),Zt=[{id:"authentication-core",name:"Authentication Core",category:"auth",description:"Complete authentication system with password hashing, JWT tokens, OAuth 2.0, and secure session management",longDescription:"Production-ready authentication with bcrypt password hashing, JWT access/refresh tokens, OAuth 2.0 providers (Google, GitHub, etc), rate limiting, and security best practices.",keywords:["auth","login","password","jwt","oauth","token","authentication","security","signin","signup"],framework:"both",dependencies:[],useCases:["User login and logout","Password reset flow","OAuth social login (Google, GitHub)","JWT authentication","Secure session management","Token refresh","Rate limiting"]},{id:"users-core",name:"Users Core",category:"auth",description:"User management system with profiles, roles, permissions, and user CRUD operations",longDescription:"Complete user management with user profiles, role-based access control (RBAC), permissions, user search, soft delete, and audit trails.",keywords:["user","profile","role","permission","rbac","management","admin","accounts"],framework:"both",dependencies:["authentication-core"],useCases:["User registration","User profile management","Role management (admin, user, etc)","Permission system","User administration dashboard","Soft delete users"]},{id:"session-management",name:"Session Management",category:"auth",description:"Secure session handling with Redis storage, session rotation, and device tracking",longDescription:"Advanced session management with Redis-backed storage, automatic session rotation, device fingerprinting, IP tracking, and session revocation.",keywords:["session","redis","cookie","storage","device","tracking"],framework:"both",dependencies:["authentication-core","redis-cache"],useCases:["User session management","Remember me functionality","Device tracking","Session security","Logout from all devices","Session expiration"]},{id:"db-postgres",name:"PostgreSQL",category:"database",description:"PostgreSQL integration with async SQLAlchemy, migrations, connection pooling, and query optimization",longDescription:"Production-ready PostgreSQL with async SQLAlchemy 2.0, Alembic migrations, connection pooling, query optimization, JSON support, and full-text search.",keywords:["postgres","postgresql","database","sql","sqlalchemy","migration","orm","relational"],framework:"both",dependencies:[],useCases:["Relational database","Complex SQL queries","Database transactions","Data integrity","Production-grade database","ACID compliance"]},{id:"db-mongodb",name:"MongoDB",category:"database",description:"MongoDB integration with Motor async driver, schema validation, and aggregation pipelines",longDescription:"Async MongoDB with Motor driver, Pydantic schema validation, aggregation pipelines, indexes, and Atlas integration.",keywords:["mongodb","mongo","nosql","document","database","motor"],framework:"both",dependencies:[],useCases:["Document storage","Flexible schema","Real-time data","JSON documents","Unstructured data","Analytics"]},{id:"stripe-payment",name:"Stripe Payment",category:"payment",description:"Stripe integration with payment intents, subscriptions, webhooks, and customer portal",longDescription:"Complete Stripe integration with Payment Intents API, subscription management, automatic webhooks, customer portal, refunds, and SCA compliance.",keywords:["stripe","payment","subscription","billing","checkout","webhook","credit card"],framework:"both",dependencies:[],useCases:["Accept credit card payments","Subscription billing","One-time payments","Checkout flow","Payment webhooks","Refunds and disputes"]},{id:"email",name:"Email",category:"communication",description:"Email sending with templates, SMTP/SendGrid/AWS SES support, and queue management",longDescription:"Production email system with Jinja2 templates, multiple providers (SMTP, SendGrid, AWS SES), queue management, retry logic, and bounce handling.",keywords:["email","mail","smtp","sendgrid","ses","template","notification"],framework:"both",dependencies:[],useCases:["Welcome emails","Password reset emails","Notifications","Marketing emails","Transactional emails","Email templates"]},{id:"sms",name:"SMS",category:"communication",description:"SMS sending with Twilio, verification codes, and delivery tracking",longDescription:"SMS integration with Twilio, verification codes, two-factor authentication, delivery tracking, and international support.",keywords:["sms","twilio","text","message","2fa","verification","otp"],framework:"both",dependencies:[],useCases:["2FA verification codes","SMS notifications","Phone verification","OTP generation","SMS alerts"]},{id:"redis-cache",name:"Redis Cache",category:"infrastructure",description:"Redis caching with decorators, TTL management, and cache invalidation patterns",longDescription:"Redis integration with async client, caching decorators, TTL management, cache invalidation, pub/sub, and rate limiting.",keywords:["redis","cache","memory","performance","speed","pubsub"],framework:"both",dependencies:[],useCases:["API response caching","Session storage","Rate limiting","Real-time features","Performance optimization","Pub/sub messaging"]},{id:"celery",name:"Celery",category:"infrastructure",description:"Background task processing with Celery, periodic tasks, and monitoring",longDescription:"Celery task queue with Redis/RabbitMQ backend, periodic tasks (cron), task monitoring, retry logic, and failure handling.",keywords:["celery","task","background","queue","async","worker","job","cron"],framework:"fastapi",dependencies:["redis-cache"],useCases:["Background email sending","Data processing","Report generation","Scheduled tasks","Long-running jobs"]},{id:"storage",name:"Storage",category:"infrastructure",description:"File storage with S3, local filesystem, and image processing",longDescription:"Unified storage interface for AWS S3, local files, image resizing, format conversion, CDN integration, and presigned URLs.",keywords:["storage","s3","file","upload","image","cdn","aws"],framework:"both",dependencies:[],useCases:["File uploads","Image storage","Document management","Profile pictures","Media files","CDN integration"]}],se=null,Xt=0,pn=300*1e3;function un(e){return {id:e.name||e.id||e.module_id||"",name:e.display_name||e.name||"",category:gn(e.category||"infrastructure"),description:e.description||e.summary||"",longDescription:e.long_description||e.description||"",keywords:e.keywords||e.tags||[],framework:mn(e.framework),dependencies:e.dependencies||[],useCases:e.use_cases||e.useCases||[]}}function gn(e){return {auth:"auth",authentication:"auth",database:"database",payment:"payment",billing:"payment",communication:"communication",infrastructure:"infrastructure",security:"security",analytics:"analytics"}[e.toLowerCase()]||"infrastructure"}function mn(e){if(!e)return "both";if(typeof e=="string"){if(e.toLowerCase().includes("fastapi"))return "fastapi";if(e.toLowerCase().includes("nest"))return "nestjs"}return "both"}async function fn(){try{let{stdout:e}=await ln("rapidkit modules list --json-schema 1",{timeout:1e4,maxBuffer:10485760}),o=e.match(/\{[\s\S]*\}/),t=o?o[0]:e,n=JSON.parse(t),i=[];return Array.isArray(n)?i=n:n.modules&&Array.isArray(n.modules)?i=n.modules:n.data&&Array.isArray(n.data)&&(i=n.data),i.map(un).filter(r=>r.id&&r.name)}catch(e){return e.code==="ENOENT"?console.warn("\u26A0\uFE0F RapidKit Python Core not found in PATH"):e.killed?console.warn("\u26A0\uFE0F Python Core command timed out"):console.warn("\u26A0\uFE0F Failed to fetch modules from Python Core:",e.message),console.warn(" Using fallback module catalog (11 modules)"),Zt}}async function qe(){let e=Date.now();return se&&e-Xt<pn||(se=await fn(),Xt=e,se.length===0&&(console.warn("\u26A0\uFE0F No modules found, using fallback catalog"),se=Zt)),se}var wn=fileURLToPath(import.meta.url),to=h.dirname(wn),he=null;function yn(){if(he)return he;let e=[h.join(to,"../../data/modules-embeddings.json"),h.join(to,"../data/modules-embeddings.json"),h.join(process.cwd(),"data/modules-embeddings.json")],o=null;for(let i of e)if(P.existsSync(i)){o=i;break}if(!o)throw new Error("embeddings file not found");let t=P.readFileSync(o,"utf-8"),n=JSON.parse(t);return Array.isArray(n)?he={model:"mock-or-text-embedding-3-small",dimension:n[0]?.embedding?.length||1536,generated_at:new Date().toISOString(),modules:n}:he=n,he}function vn(e,o){if(e.length!==o.length)throw new Error("Vectors must have the same length");let t=0,n=0,i=0;for(let a=0;a<e.length;a++)t+=e[a]*o[a],n+=e[a]*e[a],i+=o[a]*o[a];let r=Math.sqrt(n)*Math.sqrt(i);return r===0?0:t/r}function bn(e,o){let t=o.toLowerCase(),n=e.keywords.filter(i=>t.includes(i)||i.includes(t));return n.length>0?`Matches: ${n.slice(0,3).join(", ")}`:`Relevant for: ${e.useCases[0]}`}async function oo(e,o=5){let t=yn(),n=await qe(),i=await Jt(e),r=t.modules.map(a=>{let c=n.find(d=>d.id===a.id);if(!c)return null;let s=vn(i,a.embedding);return {module:c,score:s,reason:bn(c,e)}}).filter(a=>a!==null);return r.sort((a,c)=>c.score-a.score),r.slice(0,o)}var Sn=fileURLToPath(import.meta.url),ro=h.dirname(Sn);async function no(){return (await import('inquirer')).default}function xn(){return [h.join(ro,"../../data/modules-embeddings.json"),h.join(ro,"../data/modules-embeddings.json"),h.join(process.cwd(),"data/modules-embeddings.json")]}function io(){let e=xn();for(let o of e)if(P.existsSync(o))try{let t=JSON.parse(P.readFileSync(o,"utf-8")),n=Array.isArray(t)?t:t.modules||[];return {exists:true,path:o,moduleCount:n.length,generatedAt:t.generated_at||null}}catch{continue}return {exists:false,path:null,moduleCount:0,generatedAt:null}}async function Le(e=true,o){try{if(!Yt()&&!Qt())return console.log(g.red(`
4128
+ \u274C OpenAI not initialized`)),console.log(g.yellow("Please set your API key:")),console.log(g.white(" rapidkit config set-api-key")),console.log(g.gray(` OR set: export OPENAI_API_KEY="sk-..."
4129
+ `)),false;console.log(g.blue(`
906
4130
  \u{1F916} Generating AI embeddings for RapidKit modules...
907
- `)),console.log(u.gray("\u{1F4E1} Fetching modules from RapidKit..."));let t=await Re();console.log(u.green(`\u2713 Found ${t.length} modules
908
- `));let n=t.length*50/1e6*.02;if(console.log(u.cyan(`\u{1F4B0} Estimated cost: ~$${n.toFixed(3)}`)),console.log(u.gray(` (Based on ${t.length} modules at $0.02/1M tokens)
909
- `)),e){let s=await Ft(),{confirm:c}=await s.prompt([{type:"confirm",name:"confirm",message:"Generate embeddings now?",default:true}]);if(!c)return console.log(u.yellow(`
4131
+ `)),console.log(g.gray("\u{1F4E1} Fetching modules from RapidKit..."));let t=await qe();console.log(g.green(`\u2713 Found ${t.length} modules
4132
+ `));let n=t.length*50/1e6*.02;if(console.log(g.cyan(`\u{1F4B0} Estimated cost: ~$${n.toFixed(3)}`)),console.log(g.gray(` (Based on ${t.length} modules at $0.02/1M tokens)
4133
+ `)),e){let a=await no(),{confirm:c}=await a.prompt([{type:"confirm",name:"confirm",message:"Generate embeddings now?",default:true}]);if(!c)return console.log(g.yellow(`
910
4134
  \u26A0\uFE0F Embeddings generation cancelled
911
- `)),false}let i=t.map(s=>`${s.name}. ${s.description}. ${s.longDescription}. Keywords: ${s.keywords.join(", ")}. Use cases: ${s.useCases.join(", ")}.`),r=Fe(`Generating embeddings for ${t.length} modules...`).start();try{let s=await Rt(i);r.succeed(`Generated embeddings for ${t.length} modules`);let c={model:"text-embedding-3-small",dimension:s[0].length,generated_at:new Date().toISOString(),modules:t.map((d,m)=>({id:d.id,name:d.name,embedding:s[m]}))},a=o||w.join(process.cwd(),"data","modules-embeddings.json"),l=w.dirname(a);return S.existsSync(l)||S.mkdirSync(l,{recursive:true}),S.writeFileSync(a,JSON.stringify(c,null,2)),console.log(u.green(`
912
- \u2705 Embeddings generated successfully!`)),console.log(u.gray(`\u{1F4C1} Saved to: ${a}`)),console.log(u.gray(`\u{1F4CA} Size: ${t.length} modules, ${s[0].length} dimensions
913
- `)),true}catch(s){return r.fail("Failed to generate embeddings"),s.message?.includes("429")?(console.log(u.red(`
914
- \u274C OpenAI API quota exceeded`)),console.log(u.yellow(`Please check your billing: https://platform.openai.com/account/billing
915
- `))):s.message?.includes("401")?(console.log(u.red(`
916
- \u274C Invalid API key`)),console.log(u.yellow("Please set a valid API key:")),console.log(u.white(` rapidkit config set-api-key
917
- `))):console.log(u.red(`
918
- \u274C Error: ${s.message}
919
- `)),false}}catch(t){return console.log(u.red(`
4135
+ `)),false}let i=t.map(a=>`${a.name}. ${a.description}. ${a.longDescription}. Keywords: ${a.keywords.join(", ")}. Use cases: ${a.useCases.join(", ")}.`),r=Qe(`Generating embeddings for ${t.length} modules...`).start();try{let a=await zt(i);r.succeed(`Generated embeddings for ${t.length} modules`);let c={model:"text-embedding-3-small",dimension:a[0].length,generated_at:new Date().toISOString(),modules:t.map((l,u)=>({id:l.id,name:l.name,embedding:a[u]}))},s=o||h.join(process.cwd(),"data","modules-embeddings.json"),d=h.dirname(s);return P.existsSync(d)||P.mkdirSync(d,{recursive:true}),P.writeFileSync(s,JSON.stringify(c,null,2)),console.log(g.green(`
4136
+ \u2705 Embeddings generated successfully!`)),console.log(g.gray(`\u{1F4C1} Saved to: ${s}`)),console.log(g.gray(`\u{1F4CA} Size: ${t.length} modules, ${a[0].length} dimensions
4137
+ `)),true}catch(a){return r.fail("Failed to generate embeddings"),a.message?.includes("429")?(console.log(g.red(`
4138
+ \u274C OpenAI API quota exceeded`)),console.log(g.yellow(`Please check your billing: https://platform.openai.com/account/billing
4139
+ `))):a.message?.includes("401")?(console.log(g.red(`
4140
+ \u274C Invalid API key`)),console.log(g.yellow("Please set a valid API key:")),console.log(g.white(` rapidkit config set-api-key
4141
+ `))):console.log(g.red(`
4142
+ \u274C Error: ${a.message}
4143
+ `)),false}}catch(t){return console.log(g.red(`
920
4144
  \u274C Failed to generate embeddings: ${t.message}
921
- `)),false}}async function Tt(e=true){if(Ot().exists)return true;if(console.log(u.yellow(`
922
- \u26A0\uFE0F Module embeddings not found`)),console.log(u.gray(`AI recommendations require embeddings to be generated.
923
- `)),!e)return console.log(u.red("\u274C Cannot generate embeddings in non-interactive mode")),console.log(u.white(`Run: rapidkit ai generate-embeddings
924
- `)),false;let t=await Ft(),{action:n}=await t.prompt([{type:"list",name:"action",message:"What would you like to do?",choices:[{name:"\u{1F680} Generate embeddings now (requires OpenAI API key)",value:"generate"},{name:"\u{1F4DD} Show me how to generate them manually",value:"manual"},{name:"\u274C Cancel",value:"cancel"}]}]);return n==="generate"?await Ae(true):(n==="manual"&&(console.log(u.cyan(`
4145
+ `)),false}}async function ao(e=true){if(io().exists)return true;if(console.log(g.yellow(`
4146
+ \u26A0\uFE0F Module embeddings not found`)),console.log(g.gray(`AI recommendations require embeddings to be generated.
4147
+ `)),!e)return console.log(g.red("\u274C Cannot generate embeddings in non-interactive mode")),console.log(g.white(`Run: rapidkit ai generate-embeddings
4148
+ `)),false;let t=await no(),{action:n}=await t.prompt([{type:"list",name:"action",message:"What would you like to do?",choices:[{name:"\u{1F680} Generate embeddings now (requires OpenAI API key)",value:"generate"},{name:"\u{1F4DD} Show me how to generate them manually",value:"manual"},{name:"\u274C Cancel",value:"cancel"}]}]);return n==="generate"?await Le(true):(n==="manual"&&(console.log(g.cyan(`
925
4149
  \u{1F4DD} To generate embeddings manually:
926
- `)),console.log(u.white("1. Get OpenAI API key from: https://platform.openai.com/api-keys")),console.log(u.white("2. Set the API key:")),console.log(u.gray(" rapidkit config set-api-key")),console.log(u.gray(` OR: export OPENAI_API_KEY="sk-..."
927
- `)),console.log(u.white("3. Generate embeddings:")),console.log(u.gray(` rapidkit ai generate-embeddings
928
- `)),console.log(u.cyan(`\u{1F4B0} Cost: ~$0.50 one-time
929
- `))),false)}async function Nt(){let e=Ot();return e.exists?(console.log(u.blue(`
930
- \u{1F504} Updating embeddings...`)),console.log(u.gray(`Current: ${e.moduleCount} modules`)),console.log(u.gray(`Generated: ${e.generatedAt||"unknown"}
931
- `)),await Ae(true,e.path)):(console.log(u.yellow(`
932
- \u26A0\uFE0F No existing embeddings found`)),console.log(u.gray(`Use: rapidkit ai generate-embeddings
933
- `)),false)}async function Wt(){return (await import('inquirer')).default}function Lt(e){let o=e.command("ai").description("AI-powered features");o.command("recommend").description("Get AI-powered module recommendations").argument("[query]",'What do you want to build? (e.g., "user authentication with email")').option("-n, --number <count>","Number of recommendations","5").option("--json","Output as JSON").action(async(t,n)=>{try{Le()||(console.log(u.yellow(`
934
- \u26A0\uFE0F AI features are disabled`)),console.log(u.gray(`Enable with: rapidkit config ai enable
935
- `)),process.exit(1));let i=le();i?await Ce(i):(console.log(u.yellow(`
4150
+ `)),console.log(g.white("1. Get OpenAI API key from: https://platform.openai.com/api-keys")),console.log(g.white("2. Set the API key:")),console.log(g.gray(" rapidkit config set-api-key")),console.log(g.gray(` OR: export OPENAI_API_KEY="sk-..."
4151
+ `)),console.log(g.white("3. Generate embeddings:")),console.log(g.gray(` rapidkit ai generate-embeddings
4152
+ `)),console.log(g.cyan(`\u{1F4B0} Cost: ~$0.50 one-time
4153
+ `))),false)}async function so(){let e=io();return e.exists?(console.log(g.blue(`
4154
+ \u{1F504} Updating embeddings...`)),console.log(g.gray(`Current: ${e.moduleCount} modules`)),console.log(g.gray(`Generated: ${e.generatedAt||"unknown"}
4155
+ `)),await Le(true,e.path)):(console.log(g.yellow(`
4156
+ \u26A0\uFE0F No existing embeddings found`)),console.log(g.gray(`Use: rapidkit ai generate-embeddings
4157
+ `)),false)}async function co(){return (await import('inquirer')).default}function lo(e){let o=e.command("ai").description("AI-powered features");o.command("recommend").description("Get AI-powered module recommendations").argument("[query]",'What do you want to build? (e.g., "user authentication with email")').option("-n, --number <count>","Number of recommendations","5").option("--json","Output as JSON").action(async(t,n)=>{try{rt()||(console.log(g.yellow(`
4158
+ \u26A0\uFE0F AI features are disabled`)),console.log(g.gray(`Enable with: rapidkit config ai enable
4159
+ `)),process.exit(1));let i=fe();i?await De(i):(console.log(g.yellow(`
936
4160
  \u26A0\uFE0F OpenAI API key not configured - using MOCK MODE for testing
937
- `)),console.log(u.gray("\u{1F4DD} Note: Mock embeddings provide approximate results for testing.")),console.log(u.gray(` For production, configure your OpenAI API key:
938
- `)),console.log(u.white(" 1. Get your key from: https://platform.openai.com/api-keys")),console.log(u.white(" 2. Configure it: rapidkit config set-api-key")),console.log(u.gray(` OR set: export OPENAI_API_KEY="sk-proj-..."
939
- `)),Pt());let r=t;r||(r=(await(await Wt()).prompt([{type:"input",name:"query",message:"\u{1F916} What do you want to build?",validate:P=>P.length===0?"Please enter a description":P.length<3?"Please be more specific (at least 3 characters)":true}])).query),n.json||console.log(u.blue(`
4161
+ `)),console.log(g.gray("\u{1F4DD} Note: Mock embeddings provide approximate results for testing.")),console.log(g.gray(` For production, configure your OpenAI API key:
4162
+ `)),console.log(g.white(" 1. Get your key from: https://platform.openai.com/api-keys")),console.log(g.white(" 2. Configure it: rapidkit config set-api-key")),console.log(g.gray(` OR set: export OPENAI_API_KEY="sk-proj-..."
4163
+ `)),Ut());let r=t;r||(r=(await(await co()).prompt([{type:"input",name:"query",message:"\u{1F916} What do you want to build?",validate:R=>R.length===0?"Please enter a description":R.length<3?"Please be more specific (at least 3 characters)":true}])).query),n.json||console.log(g.blue(`
940
4164
  \u{1F916} Analyzing your request...
941
- `)),await Tt(!n.json)||(console.log(u.yellow(`
4165
+ `)),await ao(!n.json)||(console.log(g.yellow(`
942
4166
  \u26A0\uFE0F Cannot proceed without embeddings
943
- `)),process.exit(1));let c=parseInt(n.number,10),a=await Dt(r,c);if(a.length===0||a[0].score<.3)if(console.log(u.yellow(`
4167
+ `)),process.exit(1));let c=parseInt(n.number,10),s=await oo(r,c);if(s.length===0||s[0].score<.3)if(console.log(g.yellow(`
944
4168
  \u26A0\uFE0F No matching modules found in RapidKit registry.
945
- `)),console.log(u.cyan(`\u{1F4A1} Options:
946
- `)),console.log(u.white("1. Create custom module:")),console.log(u.gray(" rapidkit modules scaffold <name> --category <category>")),console.log(u.gray(` Example: rapidkit modules scaffold blockchain-integration --category integrations
947
- `)),console.log(u.white("2. Search with different keywords")),console.log(u.gray(` Try more general terms (e.g., "storage" instead of "blockchain")
948
- `)),console.log(u.white("3. Request feature:")),console.log(u.gray(` https://github.com/getrapidkit/rapidkit/issues
949
- `)),a.length>0)console.log(u.yellow(`\u26A0\uFE0F Low confidence matches found:
950
- `));else return;if(n.json){console.log(JSON.stringify({query:r,recommendations:a},null,2));return}console.log(u.green.bold(`\u{1F4E6} Recommended Modules:
951
- `)),a.forEach((p,y)=>{let P=(p.score*100).toFixed(1),v=p.score>.8?" \u2B50":"";console.log(u.bold(`${y+1}. ${p.module.name}${v}`)),console.log(u.gray(` ${p.module.description}`)),console.log(u.cyan(` Match: ${P}%`)+u.gray(` - ${p.reason}`)),console.log(u.yellow(` Category: ${p.module.category}`)),p.module.dependencies.length>0&&console.log(u.magenta(` Requires: ${p.module.dependencies.join(", ")}`)),console.log();});let l=a.slice(0,3).map(p=>p.module.id);console.log(u.cyan("\u{1F4A1} Quick install (top 3):")),console.log(u.white(` rapidkit add module ${l.join(" ")}
952
- `));let d=await Wt(),{shouldInstall:m}=await d.prompt([{type:"confirm",name:"shouldInstall",message:"Would you like to install these modules now?",default:false}]);if(m){let{selectedModules:p}=await d.prompt([{type:"checkbox",name:"selectedModules",message:"Select modules to install:",choices:a.map(y=>({name:`${y.module.name} - ${y.module.description}`,value:y.module.id,checked:y.score>.7}))}]);p.length>0?(console.log(u.blue(`
4169
+ `)),console.log(g.cyan(`\u{1F4A1} Options:
4170
+ `)),console.log(g.white("1. Create custom module:")),console.log(g.gray(" rapidkit modules scaffold <name> --category <category>")),console.log(g.gray(` Example: rapidkit modules scaffold blockchain-integration --category integrations
4171
+ `)),console.log(g.white("2. Search with different keywords")),console.log(g.gray(` Try more general terms (e.g., "storage" instead of "blockchain")
4172
+ `)),console.log(g.white("3. Request feature:")),console.log(g.gray(` https://github.com/getrapidkit/rapidkit/issues
4173
+ `)),s.length>0)console.log(g.yellow(`\u26A0\uFE0F Low confidence matches found:
4174
+ `));else return;if(n.json){console.log(JSON.stringify({query:r,recommendations:s},null,2));return}console.log(g.green.bold(`\u{1F4E6} Recommended Modules:
4175
+ `)),s.forEach((p,k)=>{let R=(p.score*100).toFixed(1),w=p.score>.8?" \u2B50":"";console.log(g.bold(`${k+1}. ${p.module.name}${w}`)),console.log(g.gray(` ${p.module.description}`)),console.log(g.cyan(` Match: ${R}%`)+g.gray(` - ${p.reason}`)),console.log(g.yellow(` Category: ${p.module.category}`)),p.module.dependencies.length>0&&console.log(g.magenta(` Requires: ${p.module.dependencies.join(", ")}`)),console.log();});let d=s.slice(0,3).map(p=>p.module.id);console.log(g.cyan("\u{1F4A1} Quick install (top 3):")),console.log(g.white(` rapidkit add module ${d.join(" ")}
4176
+ `));let l=await co(),{shouldInstall:u}=await l.prompt([{type:"confirm",name:"shouldInstall",message:"Would you like to install these modules now?",default:false}]);if(u){let{selectedModules:p}=await l.prompt([{type:"checkbox",name:"selectedModules",message:"Select modules to install:",choices:s.map(k=>({name:`${k.module.name} - ${k.module.description}`,value:k.module.id,checked:k.score>.7}))}]);p.length>0?(console.log(g.blue(`
953
4177
  \u{1F4E6} Installing ${p.length} modules...
954
- `)),console.log(u.gray(`Command: rapidkit add module ${p.join(" ")}`)),console.log(u.yellow(`
955
- \u26A0\uFE0F Note: Module installation not yet implemented`)),console.log(u.gray(`Coming soon in next version!
956
- `))):console.log(u.gray(`
4178
+ `)),console.log(g.gray(`Command: rapidkit add module ${p.join(" ")}`)),console.log(g.yellow(`
4179
+ \u26A0\uFE0F Note: Module installation not yet implemented`)),console.log(g.gray(`Coming soon in next version!
4180
+ `))):console.log(g.gray(`
957
4181
  No modules selected
958
4182
  `));}}catch(i){a.error(`
959
- \u274C Error:`,i.message),i.code==="invalid_api_key"?(console.log(u.yellow(`
960
- \u{1F4A1} Your API key may be invalid or expired`)),console.log(u.cyan(` Update it: rapidkit config set-api-key
961
- `))):i.message.includes("embeddings file not found")&&(console.log(u.yellow(`
962
- \u{1F4A1} Module embeddings not generated yet`)),console.log(u.cyan(" Generate them (one-time):")),console.log(u.white(" cd rapidkit-npm")),console.log(u.white(' export OPENAI_API_KEY="sk-proj-..."')),console.log(u.white(` npx tsx src/ai/generate-embeddings.ts
963
- `))),process.exit(1);}}),o.command("info").description("Show AI features information").action(()=>{let t=le(),n=Le();console.log(u.bold(`
4183
+ \u274C Error:`,i.message),i.code==="invalid_api_key"?(console.log(g.yellow(`
4184
+ \u{1F4A1} Your API key may be invalid or expired`)),console.log(g.cyan(` Update it: rapidkit config set-api-key
4185
+ `))):i.message.includes("embeddings file not found")&&(console.log(g.yellow(`
4186
+ \u{1F4A1} Module embeddings not generated yet`)),console.log(g.cyan(" Generate them (one-time):")),console.log(g.white(" cd rapidkit-npm")),console.log(g.white(' export OPENAI_API_KEY="sk-proj-..."')),console.log(g.white(` npx tsx src/ai/generate-embeddings.ts
4187
+ `))),process.exit(1);}}),o.command("info").description("Show AI features information").action(()=>{let t=fe(),n=rt();console.log(g.bold(`
964
4188
  \u{1F916} RapidKit AI Features
965
- `)),console.log(u.cyan("Status:"),n?u.green("Enabled"):u.red("Disabled")),console.log(u.cyan("API Key:"),t?u.green("Configured \u2713"):u.red("Not configured \u2717")),console.log(u.bold(`
4189
+ `)),console.log(g.cyan("Status:"),n?g.green("Enabled"):g.red("Disabled")),console.log(g.cyan("API Key:"),t?g.green("Configured \u2713"):g.red("Not configured \u2717")),console.log(g.bold(`
966
4190
  \u{1F4E6} Available Features:
967
- `)),console.log(u.white("\u2022 Module Recommender")+u.gray(" - AI-powered module suggestions")),console.log(u.gray(' Usage: rapidkit ai recommend "I need authentication"')),console.log(u.bold(`
4191
+ `)),console.log(g.white("\u2022 Module Recommender")+g.gray(" - AI-powered module suggestions")),console.log(g.gray(' Usage: rapidkit ai recommend "I need authentication"')),console.log(g.bold(`
968
4192
  \u{1F4B0} Pricing:
969
- `)),console.log(u.white("\u2022 Per query: ~$0.0002")+u.gray(" (practically free)")),console.log(u.white("\u2022 100 queries: ~$0.02")+u.gray(" (2 cents)")),console.log(u.white("\u2022 1000 queries: ~$0.20")+u.gray(" (20 cents)")),console.log(u.bold(`
4193
+ `)),console.log(g.white("\u2022 Per query: ~$0.0002")+g.gray(" (practically free)")),console.log(g.white("\u2022 100 queries: ~$0.02")+g.gray(" (2 cents)")),console.log(g.white("\u2022 1000 queries: ~$0.20")+g.gray(" (20 cents)")),console.log(g.bold(`
970
4194
  \u{1F680} Getting Started:
971
- `)),t?(console.log(u.green("\u2713 You're all set!")),console.log(u.white(' Try: rapidkit ai recommend "user authentication"'))):(console.log(u.white("1. Get OpenAI API key: https://platform.openai.com/api-keys")),console.log(u.white("2. Configure: rapidkit config set-api-key")),console.log(u.white('3. Try: rapidkit ai recommend "user authentication"'))),console.log();}),o.command("generate-embeddings").description("Generate AI embeddings for all modules (one-time setup)").option("--force","Force regeneration even if embeddings exist").action(async()=>{try{let t=le();t||(console.log(u.red(`
4195
+ `)),t?(console.log(g.green("\u2713 You're all set!")),console.log(g.white(' Try: rapidkit ai recommend "user authentication"'))):(console.log(g.white("1. Get OpenAI API key: https://platform.openai.com/api-keys")),console.log(g.white("2. Configure: rapidkit config set-api-key")),console.log(g.white('3. Try: rapidkit ai recommend "user authentication"'))),console.log();}),o.command("generate-embeddings").description("Generate AI embeddings for all modules (one-time setup)").option("--force","Force regeneration even if embeddings exist").action(async()=>{try{let t=fe();t||(console.log(g.red(`
972
4196
  \u274C OpenAI API key not configured
973
- `)),console.log(u.cyan(`To generate embeddings, you need an OpenAI API key:
974
- `)),console.log(u.white("1. Get your key from: https://platform.openai.com/api-keys")),console.log(u.white("2. Configure it: rapidkit config set-api-key")),console.log(u.gray(`
975
- OR set environment variable:`)),console.log(u.white(` export OPENAI_API_KEY="sk-proj-..."
976
- `)),process.exit(1)),Ce(t);let n=await Ae(true);n&&(console.log(u.green("\u2705 Ready to use AI recommendations!")),console.log(u.cyan(`Try: rapidkit ai recommend "authentication"
977
- `))),process.exit(n?0:1);}catch(t){a.error("Failed to generate embeddings:",t.message),process.exit(1);}}),o.command("update-embeddings").description("Update existing embeddings with latest modules").action(async()=>{try{let t=le();t||(console.log(u.red(`
4197
+ `)),console.log(g.cyan(`To generate embeddings, you need an OpenAI API key:
4198
+ `)),console.log(g.white("1. Get your key from: https://platform.openai.com/api-keys")),console.log(g.white("2. Configure it: rapidkit config set-api-key")),console.log(g.gray(`
4199
+ OR set environment variable:`)),console.log(g.white(` export OPENAI_API_KEY="sk-proj-..."
4200
+ `)),process.exit(1)),De(t);let n=await Le(true);n&&(console.log(g.green("\u2705 Ready to use AI recommendations!")),console.log(g.cyan(`Try: rapidkit ai recommend "authentication"
4201
+ `))),process.exit(n?0:1);}catch(t){a.error("Failed to generate embeddings:",t.message),process.exit(1);}}),o.command("update-embeddings").description("Update existing embeddings with latest modules").action(async()=>{try{let t=fe();t||(console.log(g.red(`
978
4202
  \u274C OpenAI API key not configured
979
- `)),console.log(u.white(`Set your API key: rapidkit config set-api-key
980
- `)),process.exit(1)),Ce(t);let n=await Nt();process.exit(n?0:1);}catch(t){a.error("Failed to update embeddings:",t.message),process.exit(1);}});}function Ue(e){if(!e||typeof e!="object")return null;let o=e.code;return o==="PYTHON_NOT_FOUND"||o==="BRIDGE_VENV_BOOTSTRAP_FAILED"?o:null}function Vo(e){let o=e.trim().toLowerCase();return o?o.startsWith("fastapi")?"fastapi":o.startsWith("nestjs")?"nestjs":null:null}function Ht(e,o){let t=e.indexOf(o);if(t>=0&&t+1<e.length)return e[t+1];let n=e.find(i=>i.startsWith(`${o}=`));if(n)return n.slice(o.length+1)}async function He(e,o){if(e.includes("--json"))return process.stderr.write("RapidKit (npm) offline fallback does not support --json for `create` commands.\nInstall Python 3.10+ and retry the same command.\n"),1;if(e[0]!=="create")return 1;if(e[1]!=="project")return process.stderr.write(`RapidKit (npm) could not run the Python core engine for \`create\`.
4203
+ `)),console.log(g.white(`Set your API key: rapidkit config set-api-key
4204
+ `)),process.exit(1)),De(t);let n=await so();process.exit(n?0:1);}catch(t){a.error("Failed to update embeddings:",t.message),process.exit(1);}});}function at(e){if(!e||typeof e!="object")return null;let o=e.code;return o==="PYTHON_NOT_FOUND"||o==="BRIDGE_VENV_BOOTSTRAP_FAILED"?o:null}function Cn(e){let o=e.trim().toLowerCase();return o?o.startsWith("fastapi")?"fastapi":o.startsWith("nestjs")?"nestjs":null:null}function lt(e){let o=e.trim().toLowerCase();return o.startsWith("gofiber")||o==="go"||o==="go.standard"||o==="fiber"}function Fe(e){let o=e.trim().toLowerCase();return o.startsWith("gogin")||o==="gin"}function He(e){let o=e;for(;;){let t=h.join(o,".rapidkit","project.json");if(P.existsSync(t))try{return JSON.parse(P.readFileSync(t,"utf8"))}catch{return null}let n=h.dirname(o);if(n===o)break;o=n;}return null}function Ue(e,o){let t=e.indexOf(o);if(t>=0&&t+1<e.length)return e[t+1];let n=e.find(i=>i.startsWith(`${o}=`));if(n)return n.slice(o.length+1)}async function uo(e){if(e[0]!=="create"||e[1]!=="project")return 1;let o=e[2],t=e[3];if(!o||!t)return process.stderr.write(`Usage: rapidkit create project gofiber.standard <name> [--output <dir>]
4205
+ `),1;let n=Ue(e,"--output")||process.cwd(),i=h.resolve(n,t),r=e.includes("--skip-git")||e.includes("--no-git");try{let{default:a}=await import('fs-extra');if(await a.ensureDir(h.dirname(i)),await a.pathExists(i))return process.stderr.write(`\u274C Directory "${i}" already exists
4206
+ `),1;await a.ensureDir(i),await Ze(i,{project_name:t,module_path:t,skipGit:r});let c=oe(process.cwd());if(c){let{syncWorkspaceProjects:s}=await import('./workspace-LZZGJRGV.js');await s(c,true);}return 0}catch(a){return process.stderr.write(`RapidKit Go/Fiber generator failed: ${a?.message??a}
4207
+ `),1}}async function go(e){if(e[0]!=="create"||e[1]!=="project")return 1;let o=e[2],t=e[3];if(!o||!t)return process.stderr.write(`Usage: rapidkit create project gogin.standard <name> [--output <dir>]
4208
+ `),1;let n=Ue(e,"--output")||process.cwd(),i=h.resolve(n,t),r=e.includes("--skip-git")||e.includes("--no-git");try{let{default:a}=await import('fs-extra');if(await a.ensureDir(h.dirname(i)),await a.pathExists(i))return process.stderr.write(`\u274C Directory "${i}" already exists
4209
+ `),1;await a.ensureDir(i),await et(i,{project_name:t,module_path:t,skipGit:r});let c=oe(process.cwd());if(c){let{syncWorkspaceProjects:s}=await import('./workspace-LZZGJRGV.js');await s(c,true);}return 0}catch(a){return process.stderr.write(`RapidKit Go/Gin generator failed: ${a?.message??a}
4210
+ `),1}}async function st(e,o){if(e.includes("--json"))return process.stderr.write("RapidKit (npm) offline fallback does not support --json for `create` commands.\nInstall Python 3.10+ and retry the same command.\n"),1;if(e[0]!=="create")return 1;if(e[1]!=="project")return process.stderr.write(`RapidKit (npm) could not run the Python core engine for \`create\`.
981
4211
  Reason: ${o}.
982
4212
  Install Python 3.10+ to use the interactive wizard and full kit catalog.
983
4213
  `),1;let i=e[2],r=e[3];if(!i||!r)return process.stderr.write(`Usage: rapidkit create project <kit> <name> [--output <dir>]
984
4214
  Tip: offline fallback supports only fastapi* and nestjs* kits.
985
- `),1;let s=Vo(i);if(!s)return process.stderr.write(`RapidKit (npm) could not run the Python core engine to create this kit.
4215
+ `),1;let a=Cn(i);if(!a)return process.stderr.write(`RapidKit (npm) could not run the Python core engine to create this kit.
986
4216
  Reason: ${o}.
987
4217
  Requested kit: ${i}
988
4218
  Offline fallback only supports: fastapi.standard, nestjs.standard (and their shorthands).
989
4219
  Install Python 3.10+ to access all kits.
990
- `),1;let c=Ht(e,"--output")||process.cwd(),a=w.resolve(c,r),l=e.includes("--skip-git")||e.includes("--no-git"),d=e.includes("--skip-install");try{if(await k.ensureDir(w.dirname(a)),await k.pathExists(a))return process.stderr.write(`\u274C Directory "${a}" already exists
991
- `),1;let m="pip",p=me(process.cwd());if(p)try{let{readWorkspaceMarker:y}=await import('./workspace-marker-IOPQ42A7.js'),P=await y(p);P?.metadata?.npm?.installMethod&&(m=P.metadata.npm.installMethod,console.log(`[DEBUG] Detected workspace engine: ${m}`));}catch(y){console.log("[DEBUG] Failed to read workspace marker:",y);}else console.log("[DEBUG] No workspace found, using default engine: pip");if(await k.ensureDir(a),await pt(a,{project_name:r,template:s,kit_name:i,skipGit:l,skipInstall:d,engine:m}),p){let{syncWorkspaceProjects:y}=await import('./workspace-LZZGJRGV.js');await y(p,true);}return 0}catch(m){return process.stderr.write(`RapidKit (npm) offline fallback failed: ${m?.message??m}
992
- `),1}}async function Uo(e){let o=new Set(["--yes","-y","--skip-git","--skip-install","--debug","--dry-run","--no-update-check","--create-workspace","--no-workspace"]);if(e[0]==="create"&&e[1]==="workspace")try{let t=e.includes("--yes")||e.includes("-y"),n=e.includes("--skip-git")||e.includes("--no-git"),i=e[2]&&!e[2].startsWith("-")?e[2]:void 0,r=Ht(e,"--install-method"),s=r==="poetry"||r==="venv"||r==="pipx"?r:void 0,c=i||(t?"my-workspace":(await ue.prompt([{type:"input",name:"workspaceName",message:"Workspace name:",default:"my-workspace"}])).workspaceName);if(!c||!c.trim())return process.stderr.write(`Workspace name is required.
993
- `),1;try{Me(c);}catch(m){if(m instanceof F)return process.stderr.write(`${m.message}
994
- `),1;throw m}let a=w.resolve(process.cwd(),c);if(await k.pathExists(a))return process.stderr.write(`\u274C Directory "${c}" already exists
995
- `),1;let l=await z(),d=l.author||process.env.USER||"RapidKit User";if(!t){let m=await ue.prompt([{type:"input",name:"author",message:"Author name:",default:d}]);m.author?.trim()&&(d=m.author.trim());}return await ve(c,{skipGit:n,yes:t,userConfig:{...l,author:d},installMethod:s}),0}catch(t){return process.stderr.write(`RapidKit (npm) failed to create workspace: ${t?.message??t}
996
- `),1}try{if(e[0]==="create"&&e[1]==="project"){let t=e.includes("--create-workspace"),n=e.includes("--no-workspace"),i=e.includes("--yes")||e.includes("-y"),r=e.includes("--skip-git")||e.includes("--no-git");if(!!!_e(process.cwd())){if(t)await Y(process.cwd(),{skipGit:r,yes:i,userConfig:await z()});else if(!n)if(i)await Y(process.cwd(),{skipGit:r,yes:true,userConfig:await z()});else {let{createWs:a}=await ue.prompt([{type:"confirm",name:"createWs",message:"This project will be created outside a RapidKit workspace. Create and register a workspace here?",default:true}]);a&&await Y(process.cwd(),{skipGit:r,yes:false,userConfig:await z()});}}let c=e.filter(a=>{let l=a.split("=")[0];return !o.has(a)&&!o.has(l)});try{await c$1();let a$1=await d(c,{cwd:process.cwd()});if(a$1===0){let l=me(process.cwd());if(l){try{let m=e[3];if(m){let p=e.indexOf("--output"),y=p>=0?e[p+1]:".",P=w.resolve(process.cwd(),y,m),v=w.join(l,".python-version"),C=w.join(P,".python-version");if(S.existsSync(v)&&S.existsSync(P)){let E=S.readFileSync(v,"utf-8");S.writeFileSync(C,E.trim()+`
997
- `),a.debug(`Synced Python version ${E.trim()} from workspace to ${m}`);}}}catch(m){a.debug("Could not sync Python version from workspace:",m);}let{syncWorkspaceProjects:d}=await import('./workspace-LZZGJRGV.js');await d(l,true);}}return a$1}catch(a){let l=Ue(a);return l?await He(c,l):(process.stderr.write(`RapidKit (npm) failed to run the Python core engine: ${a?.message??a}
998
- `),1)}}if(e[0]==="create"&&e[1]!=="project")try{await c$1();let t=await d(e,{cwd:process.cwd()});if(t===0){let n=me(process.cwd());if(n){let{syncWorkspaceProjects:i}=await import('./workspace-LZZGJRGV.js');await i(n,true);}}return t}catch(t){let n=Ue(t);return n?await He(e,n):(process.stderr.write(`RapidKit (npm) failed to run the Python core engine: ${t?.message??t}
999
- `),1)}return await c$1(),await d(e,{cwd:process.cwd()})}catch(t){let n=Ue(t);return n?await He(e,n):(process.stderr.write(`RapidKit (npm) failed to run the Python core engine: ${t?.message??t}
1000
- `),1)}}var Gt=["dev","start","build","test","lint","format","create","help","--help","-h"];function qt(e){let o=e;for(;;){let t=w.join(o,".rapidkit","context.json");if(S.existsSync(t))return t;let n=w.dirname(o);if(n===o)break;o=n;}return null}function _e(e){let o=e;for(;;){let t=w.join(o,".rapidkit-workspace");if(S.existsSync(t))return t;let n=w.dirname(o);if(n===o)break;o=n;}return null}function me(e){let o=e;for(;;){let t=w.join(o,".rapidkit-workspace");if(S.existsSync(t))return o;let n=w.dirname(o);if(n===o)break;o=n;}return null}async function Vt(e,o,t){return await new Promise(n=>{let i=spawn(e,o,{stdio:"inherit",cwd:t,shell:process.platform==="win32"});i.on("close",r=>n(r??1)),i.on("error",()=>n(1));})}async function Ut(e){let o="poetry";try{let{readWorkspaceMarker:t}=await import('./workspace-marker-IOPQ42A7.js'),i=(await t(e))?.metadata?.npm?.installMethod;(i==="poetry"||i==="venv"||i==="pipx"||i==="pip")&&(o=i);}catch{}if(o==="poetry")return await Vt("poetry",["install","--no-root"],e);if(o==="venv"){let t=w.join(e,".venv",process.platform==="win32"?"Scripts":"bin",process.platform==="win32"?"python.exe":"python");return await k.pathExists(t)?await Vt(t,["-m","pip","install","--upgrade","rapidkit-core"],e):(process.stderr.write(`Workspace virtualenv not found (.venv).
1001
- `),1)}return 0}async function Ho(e){let o=await S.promises.readdir(e,{withFileTypes:true}),t=[];for(let n of o){if(!n.isDirectory()||n.name.startsWith("."))continue;let i=w.join(e,n.name),r=w.join(i,".rapidkit","context.json"),s=w.join(i,".rapidkit","project.json");(await k.pathExists(r)||await k.pathExists(s))&&t.push(i);}return t}function qo(e){let o="my-workspace",t=1;for(;;){let n=t===1?o:`${o}-${t}`,i=w.join(e,n);if(!S.existsSync(i))return {name:n,targetPath:i};t+=1;}}async function Jo(e){let o=process.cwd();if(e.length>1)return await d(e,{cwd:o});let t=me(o),n=qt(o),i=n?w.dirname(w.dirname(n)):null;if(i&&i!==t)return await d(["init"],{cwd:i});if(t&&o===t){let r=await Ut(t);if(r!==0)return r;let s=await Ho(t);for(let c of s){let a=await d(["init"],{cwd:c});if(a!==0)return a}return 0}if(!t){let r=await z(),{name:s}=qo(o);await ve(s,{yes:true,userConfig:r});let c=w.join(o,s);return await Ut(c)}return await d(e,{cwd:o})}async function zo(){let e=process.cwd(),o=process.argv.slice(2);if(o[0]==="create")return false;try{let a=o[0],l=!a||a==="--help"||a==="-h"||a==="help";if(_e(e)&&l){let m=await d(a?["--help"]:[],{cwd:e});process.exit(m);}}catch{}try{let a=o[0],l=a==="shell"&&o[1]==="activate",d$1=a==="create",m=await tt(e,{cwd:e,timeoutMs:1200});if(m.ok&&m.data?.isRapidkitProject&&m.data.engine==="python"&&!l&&!d$1){let p=await d(process.argv.slice(2),{cwd:e});process.exit(p);}}catch{}let t=qt(e),n=process.platform==="win32",i=n?[w.join(e,"rapidkit.cmd"),w.join(e,"rapidkit"),w.join(e,".rapidkit","rapidkit.cmd"),w.join(e,".rapidkit","rapidkit")]:[w.join(e,"rapidkit"),w.join(e,".rapidkit","rapidkit")],r=null;for(let a of i)if(await k.pathExists(a)){r=a;break}let s=o[0],c=s==="create";if(r&&s&&Gt.includes(s)&&!c){a.debug(`Delegating to local CLI: ${r} ${o.join(" ")}`);let a$1=spawn(r,o,{stdio:"inherit",cwd:e,shell:n});return a$1.on("close",l=>{process.exit(l??0);}),a$1.on("error",l=>{a.error(`Failed to run local rapidkit: ${l.message}`),process.exit(1);}),true}if(t&&await k.pathExists(t))try{if((await k.readJson(t)).engine==="pip"){let l=o[0],m=process.platform==="win32"?[w.join(e,"rapidkit.cmd"),w.join(e,"rapidkit"),w.join(e,".rapidkit","rapidkit.cmd"),w.join(e,".rapidkit","rapidkit")]:[w.join(e,"rapidkit"),w.join(e,".rapidkit","rapidkit")],p=null;for(let P of m)if(await k.pathExists(P)){p=P;break}if(p&&l&&Gt.includes(l)){a.debug(`Delegating to local CLI (early detection): ${p} ${o.join(" ")}`);let P=spawn(p,o,{stdio:"inherit",cwd:e});return P.on("close",v=>process.exit(v??0)),P.on("error",v=>{a.error(`Failed to run local rapidkit: ${v.message}`),process.exit(1);}),true}if(l==="shell"&&o[1]==="activate"){let P=`# RapidKit: activation snippet - eval "$(rapidkit shell activate)"
4220
+ `),1;let c=Ue(e,"--output")||process.cwd(),s=h.resolve(c,r),d=e.includes("--skip-git")||e.includes("--no-git"),l=e.includes("--skip-install");try{if(await b.ensureDir(h.dirname(s)),await b.pathExists(s))return process.stderr.write(`\u274C Directory "${s}" already exists
4221
+ `),1;let u="pip",p=oe(process.cwd());if(p)try{let{readWorkspaceMarker:k}=await import('./workspace-marker-IOPQ42A7.js'),R=await k(p);R?.metadata?.npm?.installMethod&&(u=R.metadata.npm.installMethod,console.log(`[DEBUG] Detected workspace engine: ${u}`));}catch(k){console.log("[DEBUG] Failed to read workspace marker:",k);}else console.log("[DEBUG] No workspace found, using default engine: pip");if(await b.ensureDir(s),await Tt(s,{project_name:r,template:a,kit_name:i,skipGit:d,skipInstall:l,engine:u}),p){let{syncWorkspaceProjects:k}=await import('./workspace-LZZGJRGV.js');await k(p,true);}return 0}catch(u){return process.stderr.write(`RapidKit (npm) offline fallback failed: ${u?.message??u}
4222
+ `),1}}async function In(e){let o=new Set(["--yes","-y","--skip-git","--skip-install","--debug","--dry-run","--no-update-check","--create-workspace","--no-workspace"]);if(e[0]==="create"&&e[1]==="workspace")try{let t=e.includes("--yes")||e.includes("-y"),n=e.includes("--skip-git")||e.includes("--no-git"),i=e[2]&&!e[2].startsWith("-")?e[2]:void 0,r=Ue(e,"--install-method"),a=r==="poetry"||r==="venv"||r==="pipx"?r:void 0,c=i||(t?"my-workspace":(await te.prompt([{type:"input",name:"workspaceName",message:"Workspace name:",default:"my-workspace"}])).workspaceName);if(!c||!c.trim())return process.stderr.write(`Workspace name is required.
4223
+ `),1;try{Je(c);}catch(u){if(u instanceof F)return process.stderr.write(`${u.message}
4224
+ `),1;throw u}let s=h.resolve(process.cwd(),c);if(await b.pathExists(s))return process.stderr.write(`\u274C Directory "${c}" already exists
4225
+ `),1;let d=await Q(),l=d.author||process.env.USER||"RapidKit User";if(!t){let u=await te.prompt([{type:"input",name:"author",message:"Author name:",default:l}]);u.author?.trim()&&(l=u.author.trim());}return await Ce(c,{skipGit:n,yes:t,userConfig:{...d,author:l},installMethod:a}),0}catch(t){return process.stderr.write(`RapidKit (npm) failed to create workspace: ${t?.message??t}
4226
+ `),1}try{if(e[0]==="create"&&e[1]==="project"){if(!e[2]||e[2].startsWith("-")){console.log(g.bold(`
4227
+ \u{1F680} RapidKit
4228
+ `));let{kitChoice:s}=await te.prompt([{type:"rawlist",name:"kitChoice",message:"Select a kit to scaffold:",choices:[{name:"fastapi \u2014 FastAPI Standard Kit",value:"fastapi.standard"},{name:"fastapi \u2014 FastAPI DDD Kit",value:"fastapi.ddd"},{name:"nestjs \u2014 NestJS Standard Kit",value:"nestjs.standard"},{name:"go/fiber \u2014 Go Fiber Standard Kit",value:"gofiber.standard"},{name:"go/gin \u2014 Go Gin Standard Kit",value:"gogin.standard"}]}]);if(lt(s)||Fe(s)){let{projectName:d}=await te.prompt([{type:"input",name:"projectName",message:"Project name:",validate:u=>u.trim().length>0||"Project name is required"}]),l=e.slice(2).filter(u=>u.startsWith("-"));return Fe(s)?await go(["create","project",s,d.trim(),...l]):await uo(["create","project",s,d.trim(),...l])}e.splice(2,0,s);}if(lt(e[2]||""))return await uo(e);if(Fe(e[2]||""))return await go(e);let t=e.includes("--create-workspace"),n=e.includes("--no-workspace"),i=e.includes("--yes")||e.includes("-y"),r=e.includes("--skip-git")||e.includes("--no-git");if(!!!Ke(process.cwd())){if(t)await Z(process.cwd(),{skipGit:r,yes:i,userConfig:await Q()});else if(!n)if(i)await Z(process.cwd(),{skipGit:r,yes:true,userConfig:await Q()});else {let{createWs:s}=await te.prompt([{type:"confirm",name:"createWs",message:"This project will be created outside a RapidKit workspace. Create and register a workspace here?",default:true}]);s&&await Z(process.cwd(),{skipGit:r,yes:false,userConfig:await Q()});}}let c=e.filter(s=>{let d=s.split("=")[0];return !o.has(s)&&!o.has(d)});try{await c$1();let s=await d(c,{cwd:process.cwd()});if(s===0){let d=oe(process.cwd());if(d){try{let u=e[3];if(u){let p=e.indexOf("--output"),k=p>=0?e[p+1]:".",R=h.resolve(process.cwd(),k,u),w=h.join(d,".python-version"),x=h.join(R,".python-version");if(P.existsSync(w)&&P.existsSync(R)){let O=P.readFileSync(w,"utf-8");P.writeFileSync(x,O.trim()+`
4229
+ `),a.debug(`Synced Python version ${O.trim()} from workspace to ${u}`);}}}catch(u){a.debug("Could not sync Python version from workspace:",u);}let{syncWorkspaceProjects:l}=await import('./workspace-LZZGJRGV.js');await l(d,true);}}return s}catch(s){let d=at(s);return d?await st(c,d):(process.stderr.write(`RapidKit (npm) failed to run the Python core engine: ${s?.message??s}
4230
+ `),1)}}if(e[0]==="create"&&e[1]!=="project")try{await c$1();let t=await d(e,{cwd:process.cwd()});if(t===0){let n=oe(process.cwd());if(n){let{syncWorkspaceProjects:i}=await import('./workspace-LZZGJRGV.js');await i(n,true);}}return t}catch(t){let n=at(t);return n?await st(e,n):(process.stderr.write(`RapidKit (npm) failed to run the Python core engine: ${t?.message??t}
4231
+ `),1)}return await c$1(),await d(e,{cwd:process.cwd()})}catch(t){let n=at(t);return n?await st(e,n):(process.stderr.write(`RapidKit (npm) failed to run the Python core engine: ${t?.message??t}
4232
+ `),1)}}var mo=["init","dev","start","build","test","docs","lint","format","create","help","--help","-h"];function ho(e){let o=e;for(;;){let t=h.join(o,".rapidkit","context.json");if(P.existsSync(t))return t;let n=h.dirname(o);if(n===o)break;o=n;}return null}function Ke(e){let o=e;for(;;){let t=h.join(o,".rapidkit-workspace");if(P.existsSync(t))return t;let n=h.dirname(o);if(n===o)break;o=n;}return null}function oe(e){let o=e;for(;;){let t=h.join(o,".rapidkit-workspace");if(P.existsSync(t))return o;let n=h.dirname(o);if(n===o)break;o=n;}return null}async function ye(e,o,t){return await new Promise(n=>{let i=spawn(e,o,{stdio:"inherit",cwd:t,shell:process.platform==="win32"});i.on("close",r=>n(r??1)),i.on("error",()=>n(1));})}async function fo(e){let o="poetry";try{let{readWorkspaceMarker:t}=await import('./workspace-marker-IOPQ42A7.js'),i=(await t(e))?.metadata?.npm?.installMethod;(i==="poetry"||i==="venv"||i==="pipx"||i==="pip")&&(o=i);}catch{}if(o==="poetry")return await ye("poetry",["install","--no-root"],e);if(o==="venv"){let t=h.join(e,".venv",process.platform==="win32"?"Scripts":"bin",process.platform==="win32"?"python.exe":"python");return await b.pathExists(t)?await ye(t,["-m","pip","install","--upgrade","rapidkit-core"],e):(process.stderr.write(`Workspace virtualenv not found (.venv).
4233
+ `),1)}return 0}async function En(e){let o=await P.promises.readdir(e,{withFileTypes:true}),t=[];for(let n of o){if(!n.isDirectory()||n.name.startsWith("."))continue;let i=h.join(e,n.name),r=h.join(i,".rapidkit","context.json"),a=h.join(i,".rapidkit","project.json");(await b.pathExists(r)||await b.pathExists(a))&&t.push(i);}return t}function Pn(e){let o="my-workspace",t=1;for(;;){let n=t===1?o:`${o}-${t}`,i=h.join(e,n);if(!P.existsSync(i))return {name:n,targetPath:i};t+=1;}}async function ct(e){return console.log(g.cyan(`
4234
+ \u{1F439} Go project detected: ${h.basename(e)}`)),console.log(g.gray(`Running go mod tidy\u2026
4235
+ `)),await ye("go",["mod","tidy"],e)}async function Tn(e){let o=h.join(e,"Makefile");return P.existsSync(o)?(console.log(g.cyan(`
4236
+ \u{1F439} Starting Go/Fiber dev server (make run)\u2026
4237
+ `)),await ye("make",["run"],e)):(console.log(g.cyan(`
4238
+ \u{1F439} Starting Go/Fiber dev server (go run ./main.go)\u2026
4239
+ `)),await ye("go",["run","./main.go"],e))}async function On(e){let o=process.cwd();if(e.length>1){let c=h.resolve(o,e[1]),s=h.join(c,"go.mod");return P.existsSync(s)?await ct(c):await d(e,{cwd:o})}let t=He(o),n=h.join(o,"go.mod");if(t?.runtime==="go"||P.existsSync(n))return await ct(o);let i=oe(o),r=ho(o),a=r?h.dirname(h.dirname(r)):null;if(a&&a!==i)return await d(["init"],{cwd:a});if(i&&o===i){let c=await fo(i);if(c!==0)return c;let s=await En(i);for(let d$1 of s){let l=He(d$1),u=h.join(d$1,"go.mod");if(l?.runtime==="go"||P.existsSync(u)){let p=await ct(d$1);if(p!==0)return p}else {let p=await d(["init"],{cwd:d$1});if(p!==0)return p}}return 0}if(!i){let c=await Q(),{name:s}=Pn(o);await Ce(s,{yes:true,userConfig:c});let d=h.join(o,s);return await fo(d)}return await d(e,{cwd:o})}async function An(){let e=process.cwd(),o=process.argv.slice(2);if(o[0]==="create")return false;try{let s=o[0],d$1=!s||s==="--help"||s==="-h"||s==="help";if(Ke(e)&&d$1){let u=await d(s?["--help"]:[],{cwd:e});process.exit(u);}}catch{}try{let s=o[0],d$1=s==="shell"&&o[1]==="activate",l=s==="create",u=await bt(e,{cwd:e,timeoutMs:1200});if(u.ok&&u.data?.isRapidkitProject&&u.data.engine==="python"&&!d$1&&!l){let p=await d(process.argv.slice(2),{cwd:e});process.exit(p);}}catch{}let t=ho(e),n=process.platform==="win32",i=n?[h.join(e,"rapidkit.cmd"),h.join(e,"rapidkit"),h.join(e,".rapidkit","rapidkit.cmd"),h.join(e,".rapidkit","rapidkit")]:[h.join(e,"rapidkit"),h.join(e,".rapidkit","rapidkit")],r=null;for(let s of i)if(await b.pathExists(s)){r=s;break}let a$1=o[0],c=a$1==="create";if(r&&a$1&&mo.includes(a$1)&&!c){a.debug(`Delegating to local CLI: ${r} ${o.join(" ")}`);let s=spawn(r,o,{stdio:"inherit",cwd:e,shell:n});return s.on("close",d=>{process.exit(d??0);}),s.on("error",d=>{a.error(`Failed to run local rapidkit: ${d.message}`),process.exit(1);}),true}if(t&&await b.pathExists(t))try{if((await b.readJson(t)).engine==="pip"){let d$1=o[0],u=process.platform==="win32"?[h.join(e,"rapidkit.cmd"),h.join(e,"rapidkit"),h.join(e,".rapidkit","rapidkit.cmd"),h.join(e,".rapidkit","rapidkit")]:[h.join(e,"rapidkit"),h.join(e,".rapidkit","rapidkit")],p=null;for(let R of u)if(await b.pathExists(R)){p=R;break}if(p&&d$1&&mo.includes(d$1)){a.debug(`Delegating to local CLI (early detection): ${p} ${o.join(" ")}`);let R=spawn(p,o,{stdio:"inherit",cwd:e});return R.on("close",w=>process.exit(w??0)),R.on("error",w=>{a.error(`Failed to run local rapidkit: ${w.message}`),process.exit(1);}),true}if(d$1==="shell"&&o[1]==="activate"){let R=`# RapidKit: activation snippet - eval "$(rapidkit shell activate)"
1002
4240
  VENV='.venv'
1003
4241
  if [ -f "$VENV/bin/activate" ]; then
1004
4242
  . "$VENV/bin/activate"
@@ -1007,45 +4245,45 @@ elif [ -f "$VENV/bin/activate.fish" ]; then
1007
4245
  fi
1008
4246
  export RAPIDKIT_PROJECT_ROOT="$(pwd)"
1009
4247
  export PATH="$(pwd)/.rapidkit:$(pwd):$PATH"
1010
- `;console.log(u.green.bold(`
4248
+ `;console.log(g.green.bold(`
1011
4249
  \u2705 Activation snippet \u2014 run the following to activate this project in your current shell:
1012
- `)),console.log(P),console.log(u.gray(`
4250
+ `)),console.log(R),console.log(g.gray(`
1013
4251
  \u{1F4A1} After activation you can run: rapidkit dev
1014
- `)),process.exit(0);}let y=await d(o,{cwd:e});process.exit(y);}}catch{}return false}var J=null,Ee=false,V=new Command;async function Yo(e){if(e.length===0)return false;let o=e[0],t=e[1];if(o==="shell"&&t==="activate"||o==="workspace"||o==="doctor"||o==="ai"||o==="config")return false;if(e.includes("--tui"))return true;if(o==="--help"||o==="-h"||o==="help"||o==="--version"||o==="-V"||e.includes("--template")||e.includes("-t"))return false;let n=new Set(["--yes","-y","--skip-git","--skip-install","--debug","--dry-run","--no-update-check","--create-workspace","--no-workspace"]);if(e.some(r=>n.has(r)))return false;let i=await g();return i?i.has(o):!!(a$1.has(o)||e.length>1)}V.name("rapidkit").description("Create RapidKit workspaces and projects").version(c());V.addHelpText("beforeAll",`RapidKit
4252
+ `)),process.exit(0);}let k=await d(o,{cwd:e});process.exit(k);}}catch{}return false}var Y=null,Ve=false,J=new Command;async function jn(e){if(e.length===0)return false;let o=e[0],t=e[1];if(o==="shell"&&t==="activate"||o==="workspace"||o==="doctor"||o==="ai"||o==="config")return false;if(e.includes("--tui"))return true;if(o==="--help"||o==="-h"||o==="help"||o==="--version"||o==="-V"||e.includes("--template")||e.includes("-t"))return false;let n=new Set(["--yes","-y","--skip-git","--skip-install","--debug","--dry-run","--no-update-check","--create-workspace","--no-workspace"]);if(e.some(r=>n.has(r)))return false;let i=await h$1();return i?i.has(o):!!(a$1.has(o)||e.length>1)}J.name("rapidkit").description("Create RapidKit workspaces and projects").version(c());J.addHelpText("beforeAll",`RapidKit
1015
4253
 
1016
4254
  Global CLI
1017
4255
  Create RapidKit workspaces and projects
1018
4256
 
1019
4257
  Global Engine Commands
1020
4258
  Access engine-level commands when inside a RapidKit workspace or via the core bridge
1021
- `);V.addHelpText("afterAll",`
4259
+ `);J.addHelpText("afterAll",`
1022
4260
  Project Commands
1023
4261
  rapidkit create
1024
4262
  rapidkit init
1025
4263
  rapidkit dev
1026
4264
 
1027
4265
  Use "rapidkit help <command>" for more information.
1028
- `);V.argument("[name]","Name of the workspace or project directory").addOption(new Option("-t, --template <template>","Legacy: create a project with template (fastapi, nestjs) instead of a workspace").hideHelp()).option("-y, --yes","Skip prompts and use defaults").addOption(new Option("--skip-git","Skip git initialization").hideHelp()).addOption(new Option("--skip-install","Legacy: skip installing dependencies (template mode)").hideHelp()).option("--debug","Enable debug logging").addOption(new Option("--dry-run","Show what would be created without creating it").hideHelp()).addOption(new Option("--install-method <method>","Installation method: poetry, venv, or pipx").choices(["poetry","venv","pipx"]).hideHelp()).addOption(new Option("--create-workspace","When creating a project outside a workspace: create and register a workspace in the current directory").hideHelp()).addOption(new Option("--no-workspace","When creating a project outside a workspace: do not create a workspace").hideHelp()).option("--no-update-check","Skip checking for updates").action(async(e,o)=>{try{o.debug&&(a.setDebug(true),a.debug("Debug mode enabled"));let t=await z();a.debug("User config loaded",t);let n=await Ze();a.debug("RapidKit config loaded",n);let i=et(t,n,{author:o.author,skipGit:o.skipGit});a.debug("Merged config",i),o.updateCheck!==false&&await b(),console.log(u.blue.bold(`
4266
+ `);J.argument("[name]","Name of the workspace or project directory").addOption(new Option("-t, --template <template>","Legacy: create a project with template (fastapi, nestjs) instead of a workspace").hideHelp()).option("-y, --yes","Skip prompts and use defaults").addOption(new Option("--skip-git","Skip git initialization").hideHelp()).addOption(new Option("--skip-install","Legacy: skip installing dependencies (template mode)").hideHelp()).option("--debug","Enable debug logging").addOption(new Option("--dry-run","Show what would be created without creating it").hideHelp()).addOption(new Option("--install-method <method>","Installation method: poetry, venv, or pipx").choices(["poetry","venv","pipx"]).hideHelp()).addOption(new Option("--create-workspace","When creating a project outside a workspace: create and register a workspace in the current directory").hideHelp()).addOption(new Option("--no-workspace","When creating a project outside a workspace: do not create a workspace").hideHelp()).option("--no-update-check","Skip checking for updates").action(async(e$1,o)=>{try{o.debug&&(a.setDebug(true),a.debug("Debug mode enabled"));let t=await Q();a.debug("User config loaded",t);let n=await yt();a.debug("RapidKit config loaded",n);let i=vt(t,n,{author:o.author,skipGit:o.skipGit});a.debug("Merged config",i),o.updateCheck!==false&&await b$1(),console.log(g.blue.bold(`
1029
4267
  \u{1F680} Welcome to RapidKit!
1030
- `)),e||(Qo(),process.exit(0));try{Me(e);}catch(c){throw c instanceof F&&(a.error(`
4268
+ `)),e$1||(Gn(),process.exit(0));try{Je(e$1);}catch(c){throw c instanceof F&&(a.error(`
1031
4269
  \u274C ${c.message}`),c.details&&a.warn(`\u{1F4A1} ${c.details}
1032
- `),process.exit(1)),c}let r=w.resolve(process.cwd(),e);J=r,await k.pathExists(r)&&(a.error(`
1033
- \u274C Directory "${e}" already exists`),console.log(u.cyan(`
4270
+ `),process.exit(1)),c}let r=h.resolve(process.cwd(),e$1);Y=r,await b.pathExists(r)&&(a.error(`
4271
+ \u274C Directory "${e$1}" already exists`),console.log(g.cyan(`
1034
4272
  \u{1F4A1} Choose a different name or delete the existing directory.
1035
- `)),process.exit(1));let s=!!o.template;if(o.dryRun){console.log(u.cyan(`
4273
+ `)),process.exit(1));let a$1=!!o.template;if(o.dryRun){console.log(g.cyan(`
1036
4274
  \u{1F50D} Dry-run mode - showing what would be created:
1037
- `)),console.log(u.white("\u{1F4C2} Path:"),r),console.log(u.white("\u{1F4E6} Type:"),s?`Project (${o.template})`:"Workspace"),console.log();return}if(!o.yes&&!s?await ue.prompt([{type:"input",name:"author",message:"Author name:",default:process.env.USER||"RapidKit User"}]):o.yes&&console.log(u.gray(`Using default values (--yes flag)
1038
- `)),s){let c=String(o.template||"").trim(),a$1=c.toLowerCase(),l=a$1==="fastapi"?"fastapi.standard":a$1==="nestjs"?"nestjs.standard":c;if(!!!_e(process.cwd())){if(o.createWorkspace)await Y(process.cwd(),{skipGit:o.skipGit,yes:o.yes,userConfig:t});else if(!o.noWorkspace)if(o.yes)await Y(process.cwd(),{skipGit:o.skipGit,yes:true,userConfig:t});else {let{createWs:P}=await ue.prompt([{type:"confirm",name:"createWs",message:"This project will be created outside a RapidKit workspace. Create and register a workspace here?",default:true}]);P&&await Y(process.cwd(),{skipGit:o.skipGit,yes:false,userConfig:t});}}let m=["create","project",l,e,"--output",process.cwd(),"--install-essentials"],p=await d(m,{cwd:process.cwd()});p!==0&&process.exit(p);let y=_e(process.cwd());if(y){let P=w.dirname(y),v=w.join(P,".python-version"),C=w.join(r,".python-version");try{if(await k.pathExists(v)){let E=S.readFileSync(v,"utf-8");S.writeFileSync(C,E.trim()+`
1039
- `),a.debug(`Synced Python version ${E.trim()} from workspace to project`);}}catch(E){a.debug("Could not sync Python version from workspace:",E);}}if(!o.skipInstall){let P=await d(["init",r],{cwd:process.cwd()});if(P!==0&&process.exit(P),y){let v=w.dirname(y),C=w.join(v,".python-version"),E=w.join(r,".python-version");try{if(await k.pathExists(C)){let M=S.readFileSync(C,"utf-8");S.writeFileSync(E,M.trim()+`
1040
- `),a.debug(`Re-synced Python version ${M.trim()} after init`);}}catch(M){a.debug("Could not re-sync Python version after init:",M);}}}}else await ve(e,{skipGit:o.skipGit,dryRun:o.dryRun,yes:o.yes,userConfig:i,installMethod:o.installMethod});}catch(t){t instanceof F?(a.error(`
4275
+ `)),console.log(g.white("\u{1F4C2} Path:"),r),console.log(g.white("\u{1F4E6} Type:"),a$1?`Project (${o.template})`:"Workspace"),console.log();return}if(!o.yes&&!a$1?await te.prompt([{type:"input",name:"author",message:"Author name:",default:process.env.USER||"RapidKit User"}]):o.yes&&console.log(g.gray(`Using default values (--yes flag)
4276
+ `)),a$1){let c=String(o.template||"").trim(),s=c.toLowerCase(),d$1=s==="fastapi"?"fastapi.standard":s==="nestjs"?"nestjs.standard":s==="go"||s==="fiber"?"gofiber.standard":s==="gin"?"gogin.standard":c;if(lt(d$1)){let R=h.resolve(process.cwd(),e$1);await Ze(R,{project_name:e$1,module_path:e$1,skipGit:o.skipGit});return}if(Fe(d$1)){let R=h.resolve(process.cwd(),e$1);await et(R,{project_name:e$1,module_path:e$1,skipGit:o.skipGit});return}if(!!!Ke(process.cwd())){if(o.createWorkspace)await Z(process.cwd(),{skipGit:o.skipGit,yes:o.yes,userConfig:t});else if(!o.noWorkspace)if(o.yes)await Z(process.cwd(),{skipGit:o.skipGit,yes:true,userConfig:t});else {let{createWs:R}=await te.prompt([{type:"confirm",name:"createWs",message:"This project will be created outside a RapidKit workspace. Create and register a workspace here?",default:true}]);R&&await Z(process.cwd(),{skipGit:o.skipGit,yes:false,userConfig:t});}}let u=["create","project",d$1,e$1,"--output",process.cwd(),"--install-essentials"],p=await e(u,{cwd:process.cwd()});p!==0&&process.exit(p);let k=Ke(process.cwd());if(k){let R=h.dirname(k),w=h.join(R,".python-version"),x=h.join(r,".python-version");try{if(await b.pathExists(w)){let O=P.readFileSync(w,"utf-8");P.writeFileSync(x,O.trim()+`
4277
+ `),a.debug(`Synced Python version ${O.trim()} from workspace to project`);}}catch(O){a.debug("Could not sync Python version from workspace:",O);}}if(!o.skipInstall){let R=await d(["init",r],{cwd:process.cwd()});if(R!==0&&process.exit(R),k){let w=h.dirname(k),x=h.join(w,".python-version"),O=h.join(r,".python-version");try{if(await b.pathExists(x)){let G=P.readFileSync(x,"utf-8");P.writeFileSync(O,G.trim()+`
4278
+ `),a.debug(`Re-synced Python version ${G.trim()} after init`);}}catch(G){a.debug("Could not re-sync Python version after init:",G);}}}}else await Ce(e$1,{skipGit:o.skipGit,dryRun:o.dryRun,yes:o.yes,userConfig:i,installMethod:o.installMethod});}catch(t){t instanceof F?(a.error(`
1041
4279
  \u274C ${t.message}`),t.details&&a.warn(`\u{1F4A1} ${t.details}`),a.debug("Error code:",t.code)):(a.error(`
1042
- \u274C An unexpected error occurred:`),console.error(t)),process.exit(1);}finally{J=null;}});Lt(V);xt(V);V.command("shell <action>").description("Shell helpers (activate virtualenv in current shell)").action(async e=>{e!=="activate"&&(console.log(u.red(`Unknown shell command: ${e}`)),process.exit(1));let o=process.cwd();function t(a){let l=a;for(;;){let d=w.join(l,".rapidkit","context.json");if(S.existsSync(d))return d;let m=w.dirname(l);if(m===l)break;l=m;}return null}let n=t(o);function i(a){let l=a;for(;;){let d=w.join(l,".venv"),m=w.join(l,".rapidkit","activate");if(S.existsSync(m)||S.existsSync(d))return {venv:d,activateFile:m};let p=w.dirname(l);if(p===l)break;l=p;}return null}let r=i(o);!n&&!r&&(console.log(u.yellow("No RapidKit project found in this directory")),process.exit(1));let s;r&&S.existsSync(r.activateFile)?s=r.activateFile:r&&S.existsSync(r.venv)?s=process.platform==="win32"?w.join(r.venv,"Scripts","activate"):w.join(r.venv,"bin","activate"):(console.log(u.yellow("No virtual environment found")),process.exit(1));let c=process.platform==="win32";console.log(c?`call "${s}"`:`. "${s}"`);});V.command("doctor").description("\u{1FA7A} Check RapidKit environment health").option("--workspace","Check entire workspace (including all projects)").option("--json","Output results in JSON format (for CI/CD pipelines)").option("--fix","Automatically fix common issues (with confirmation)").action(async e=>{await kt(e);});V.command("workspace <action>").description("Manage RapidKit workspaces (list, sync)").action(async e=>{if(e==="list"){let{listWorkspaces:o}=await import('./workspace-LZZGJRGV.js');await o();}else if(e==="sync"){let o=me(process.cwd());o||(console.log(u.red("\u274C Not inside a RapidKit workspace")),console.log(u.gray("\u{1F4A1} Run this command from within a workspace directory")),process.exit(1));let{syncWorkspaceProjects:t}=await import('./workspace-LZZGJRGV.js');console.log(u.cyan(`\u{1F4C2} Scanning workspace: ${w.basename(o)}`)),await t(o);}else console.log(u.red(`Unknown workspace action: ${e}`)),console.log(u.gray("Available: list, sync")),process.exit(1);});function Qo(){console.log(u.white(`Usage:
1043
- `)),console.log(u.cyan(" npx rapidkit <workspace-name> [options]")),console.log(u.cyan(` npx rapidkit create <...>
1044
- `)),console.log(u.bold("Recommended workflow:")),console.log(u.cyan(" npx rapidkit my-workspace")),console.log(u.cyan(" cd my-workspace")),console.log(u.cyan(" npx rapidkit create project fastapi.standard my-api --output .")),console.log(u.cyan(" cd my-api")),console.log(u.cyan(` npx rapidkit init && npx rapidkit dev
1045
- `)),console.log(u.bold("Options (workspace creation):")),console.log(u.gray(" -y, --yes Skip prompts and use defaults")),console.log(u.gray(" --skip-git Skip git initialization")),console.log(u.gray(" --debug Enable debug logging")),console.log(u.gray(" --dry-run Show what would be created")),console.log(u.gray(" --create-workspace When creating a project outside a workspace: create and register a workspace in the current directory")),console.log(u.gray(" --no-workspace When creating a project outside a workspace: do not create a workspace")),console.log(u.gray(` --no-update-check Skip checking for updates
1046
- `)),console.log(u.gray(`Tip: set RAPIDKIT_SHOW_LEGACY=1 to show legacy template flags in help.
1047
- `));}process.on("SIGINT",async()=>{if(!Ee){if(Ee=true,console.log(u.yellow(`
1048
-
1049
- \u26A0\uFE0F Interrupted by user`)),J&&await k.pathExists(J)){console.log(u.gray("Cleaning up partial installation..."));try{await k.remove(J),console.log(u.green("\u2713 Cleanup complete"));}catch(e){a.debug("Cleanup failed:",e);}}process.exit(130);}});process.on("SIGTERM",async()=>{if(!Ee){if(Ee=true,a.debug("Received SIGTERM"),J&&await k.pathExists(J))try{await k.remove(J);}catch(e){a.debug("Cleanup failed:",e);}process.exit(143);}});zo().then(async e=>{if(!e){let o=process.argv.slice(2);if(process.env.RAPIDKIT_NPM_DEBUG_ARGS==="1"&&process.stderr.write(`[rapidkit-npm] argv=${JSON.stringify(o)}
1050
- `),o[0]==="create"){let n=await Uo(o);process.exit(n);}if(o[0]==="init"){let n=await Jo(o);process.exit(n);}let t=await Yo(o);if(process.env.RAPIDKIT_NPM_DEBUG_ARGS==="1"&&process.stderr.write(`[rapidkit-npm] shouldForwardToCore=${t}
1051
- `),t){let n=await d(o,{cwd:process.cwd()});process.exit(n);}V.parse();}});export{Uo as handleCreateOrFallback};
4280
+ \u274C An unexpected error occurred:`),console.error(t)),process.exit(1);}finally{Y=null;}});lo(J);Vt(J);J.command("shell <action>").description("Shell helpers (activate virtualenv in current shell)").action(async e=>{e!=="activate"&&(console.log(g.red(`Unknown shell command: ${e}`)),process.exit(1));let o=process.cwd();function t(s){let d=s;for(;;){let l=h.join(d,".rapidkit","context.json");if(P.existsSync(l))return l;let u=h.dirname(d);if(u===d)break;d=u;}return null}let n=t(o);function i(s){let d=s;for(;;){let l=h.join(d,".venv"),u=h.join(d,".rapidkit","activate");if(P.existsSync(u)||P.existsSync(l))return {venv:l,activateFile:u};let p=h.dirname(d);if(p===d)break;d=p;}return null}let r=i(o);!n&&!r&&(console.log(g.yellow("No RapidKit project found in this directory")),process.exit(1));let a;r&&P.existsSync(r.activateFile)?a=r.activateFile:r&&P.existsSync(r.venv)?a=process.platform==="win32"?h.join(r.venv,"Scripts","activate"):h.join(r.venv,"bin","activate"):(console.log(g.yellow("No virtual environment found")),process.exit(1));let c=process.platform==="win32";console.log(c?`call "${a}"`:`. "${a}"`);});J.command("doctor").description("\u{1FA7A} Check RapidKit environment health").option("--workspace","Check entire workspace (including all projects)").option("--json","Output results in JSON format (for CI/CD pipelines)").option("--fix","Automatically fix common issues (with confirmation)").action(async e=>{await Ft(e);});J.command("workspace <action>").description("Manage RapidKit workspaces (list, sync)").action(async e=>{if(e==="list"){let{listWorkspaces:o}=await import('./workspace-LZZGJRGV.js');await o();}else if(e==="sync"){let o=oe(process.cwd());o||(console.log(g.red("\u274C Not inside a RapidKit workspace")),console.log(g.gray("\u{1F4A1} Run this command from within a workspace directory")),process.exit(1));let{syncWorkspaceProjects:t}=await import('./workspace-LZZGJRGV.js');console.log(g.cyan(`\u{1F4C2} Scanning workspace: ${h.basename(o)}`)),await t(o);}else console.log(g.red(`Unknown workspace action: ${e}`)),console.log(g.gray("Available: list, sync")),process.exit(1);});function Gn(){console.log(g.white(`Usage:
4281
+ `)),console.log(g.cyan(" npx rapidkit <workspace-name> [options]")),console.log(g.cyan(` npx rapidkit create <...>
4282
+ `)),console.log(g.bold("Recommended workflow:")),console.log(g.cyan(" npx rapidkit my-workspace")),console.log(g.cyan(" cd my-workspace")),console.log(g.cyan(" npx rapidkit create project fastapi.standard my-api --output .")),console.log(g.cyan(" cd my-api")),console.log(g.cyan(` npx rapidkit init && npx rapidkit dev
4283
+ `)),console.log(g.bold("Options (workspace creation):")),console.log(g.gray(" -y, --yes Skip prompts and use defaults")),console.log(g.gray(" --skip-git Skip git initialization")),console.log(g.gray(" --debug Enable debug logging")),console.log(g.gray(" --dry-run Show what would be created")),console.log(g.gray(" --create-workspace When creating a project outside a workspace: create and register a workspace in the current directory")),console.log(g.gray(" --no-workspace When creating a project outside a workspace: do not create a workspace")),console.log(g.gray(` --no-update-check Skip checking for updates
4284
+ `)),console.log(g.gray(`Tip: set RAPIDKIT_SHOW_LEGACY=1 to show legacy template flags in help.
4285
+ `));}process.on("SIGINT",async()=>{if(!Ve){if(Ve=true,console.log(g.yellow(`
4286
+
4287
+ \u26A0\uFE0F Interrupted by user`)),Y&&await b.pathExists(Y)){console.log(g.gray("Cleaning up partial installation..."));try{await b.remove(Y),console.log(g.green("\u2713 Cleanup complete"));}catch(e){a.debug("Cleanup failed:",e);}}process.exit(130);}});process.on("SIGTERM",async()=>{if(!Ve){if(Ve=true,a.debug("Received SIGTERM"),Y&&await b.pathExists(Y))try{await b.remove(Y);}catch(e){a.debug("Cleanup failed:",e);}process.exit(143);}});var $n=process.env.VITEST==="true"||process.env.VITEST==="1"||process.env.NODE_ENV==="test",Dn=(()=>{let e=process.argv[1];if(!e)return false;try{return P.realpathSync(e)===P.realpathSync(fileURLToPath(import.meta.url))}catch{return h.resolve(e)===h.resolve(fileURLToPath(import.meta.url))}})(),qn=!$n||Dn;qn&&An().then(async e=>{if(!e){let o=process.argv.slice(2);if(process.env.RAPIDKIT_NPM_DEBUG_ARGS==="1"&&process.stderr.write(`[rapidkit-npm] argv=${JSON.stringify(o)}
4288
+ `),o[0]==="create"){let n=await In(o);process.exit(n);}if(o[0]==="init"){let n=await On(o);process.exit(n);}if(o[0]==="dev"){let n=He(process.cwd()),i=h.join(process.cwd(),"go.mod");if(n?.runtime==="go"||P.existsSync(i)){let r=await Tn(process.cwd());process.exit(r);}}if(o[0]==="add"||o[0]==="module"&&o[1]==="add"){let n=He(process.cwd());(n?.runtime==="go"||n?.module_support===false)&&(console.error(g.red("\u274C RapidKit modules are not available for Go projects.")),console.error(g.gray(" The module system requires Python and is only supported for FastAPI and NestJS projects.")),process.exit(1));}let t=await jn(o);if(process.env.RAPIDKIT_NPM_DEBUG_ARGS==="1"&&process.stderr.write(`[rapidkit-npm] shouldForwardToCore=${t}
4289
+ `),t){let n=await d(o,{cwd:process.cwd()});process.exit(n);}J.parse();}});export{In as handleCreateOrFallback};