create-cen-app
Version:
create an client-engineering-style app
82 lines (68 loc) • 33.1 kB
JavaScript
import*as h from"@clack/prompts";import ee from"chalk";import{execSync as ne}from"child_process";import{execa as W}from"execa";import he from"fs-extra";import te from"path";var mt=e=>{try{return ne("git --version",{cwd:e}),!0}catch{return!1}},dt=e=>he.existsSync(te.join(e,".git")),ut=async e=>{try{return await W("git",["rev-parse","--is-inside-work-tree"],{cwd:e,stdout:"ignore"}),!0}catch{return!1}},gt=()=>{let t=ne("git --version").toString().trim().split(" ")[2],n=t?.split(".")[0],s=t?.split(".")[1];return{major:Number(n),minor:Number(s)}},ft=()=>ne("git config --global init.defaultBranch || echo main").toString().trim(),ve=async e=>{if(h.log.info("Initializing Git..."),!mt(e)){h.log.warn("Git is not installed. Skipping Git initialization.");return}h.log.info("Creating a new git repo...");let t=dt(e),n=await ut(e),s=te.parse(e).name;if(n&&t){let o=await h.confirm({message:`${ee.redBright.bold("Warning:")} Git is already initialized in "${s}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,initialValue:!1});if(h.isCancel(o)){h.cancel("Operation cancelled. Skipping Git initialization.");return}if(!o){h.log.info("Skipping Git initialization.");return}he.removeSync(te.join(e,".git"))}else if(n&&!t){let o=await h.confirm({message:`${ee.redBright.bold("Warning:")} "${s}" is already in a git worktree. Would you still like to initialize a new git repository in this directory?`,initialValue:!1});if(h.isCancel(o)){h.cancel("Operation cancelled. Skipping Git initialization.");return}if(!o){h.log.info("Skipping Git initialization.");return}}try{let o=ft(),{major:a,minor:i}=gt();a<2||a==2&&i<28?(await W("git",["init"],{cwd:e}),await W("git",["symbolic-ref","HEAD",`refs/heads/${o}`],{cwd:e})):await W("git",["init",`--initial-branch=${o}`],{cwd:e}),await W("git",["add","."],{cwd:e}),h.log.success(`Successfully initialized and staged ${ee.green.bold("git")}
`)}catch{h.log.error(`Failed: could not initialize git. Update git to the latest version!
`)}};import*as L from"@clack/prompts";import{execa as oe}from"execa";var S=()=>{let e=process.env.npm_config_user_agent;return e?e.startsWith("yarn")?"yarn":e.startsWith("pnpm")?"pnpm":"npm":"npm"};var yt=async(e,t)=>{let n=L.spinner();switch(e){case"npm":n.start("Installing dependencies with npm..."),await oe(e,["install"],{cwd:t,stderr:"inherit"}),n.stop();break;case"pnpm":n.start("Running pnpm install...");let s=oe(e,["install"],{cwd:t,stdout:"pipe"});await new Promise((a,i)=>{s.stdout?.on("data",r=>{let l=r.toString();l.includes("Progress")&&n.message(l.includes("|")?l.split(" | ")[1]??"":l)}),s.on("error",r=>i(r)),s.on("close",()=>a())}),n.stop();break;case"yarn":n.start("Running yarn...");let o=oe(e,[],{cwd:t,stdout:"pipe"});await new Promise((a,i)=>{o.stdout?.on("data",r=>{n.message(r.toString())}),o.on("error",r=>i(r)),o.on("close",()=>a())}),n.stop();break}},G=async({frontendDir:e})=>{L.log.info("Installing frontend dependencies...");let t=S();await yt(t,e),L.log.success(`Successfully installed dependencies!
`)};import kt from"fs-extra";import wt from"path";import be from"path";import{fileURLToPath as ht}from"url";var vt=ht(import.meta.url),bt=be.dirname(vt),f=be.join(bt,"../"),ke=` ___ ___ ___ __ _____ ___ ___ ___ _ _ __ ___ __
/ __| _ \\ __| / \\_ _| __| / __|___| \\ | | / \\ | _ \\ _ \\
| (__| / _| / /\\ \\| | | _| | (__| _|| \\ | / /\\ \\| _/ _/
\\___|_|_\\___|_/\u203E\u203E\\_\\_| |___| \\___|___|_| \\_| /_/\u203E\u203E\\_\\_| |_|`,J="my-cen-app",we="Client Engineering",se="create-cen-app",ae="git@github.ibm.com:technology-garage-dach/full-stack-cen-template.git";var F=()=>{let e=wt.join(f,"package.json");return kt.readJSONSync(e).version??"1.0.0"};import*as C from"@clack/prompts";import{execSync as xt}from"child_process";import Pt from"https";var xe=e=>{let t=F();t.includes("beta")?(C.log.warn(" You are using a beta version of create-cen-app."),C.log.warn(" Please report any bugs you encounter.")):t.includes("next")?(C.log.warn(" You are running create-cen-app with the @next tag which is no longer maintained."),C.log.warn(" Please run the CLI with @latest instead.")):t!==e&&(C.log.warn(" You are using an outdated version of create-cen-app."),C.log.warn(` Your version: ${t}. Latest version in the npm registry: ${e}`),C.log.warn(" Please run the CLI with @latest to get the latest updates.")),console.log("")};function It(){return new Promise((e,t)=>{Pt.get("https://registry.npmjs.org/-/package/create-cen-app/dist-tags",n=>{if(n.statusCode===200){let s="";n.on("data",o=>s+=o),n.on("end",()=>{e(JSON.parse(s).latest)})}else t()}).on("error",()=>{t()})})}var Pe=()=>It().catch(()=>{try{return xt("npm view create-cen-app version").toString().trim()}catch{return null}});import*as Q from"@clack/prompts";import rt from"fs-extra";import lt from"path";import*as c from"@clack/prompts";import{isCancel as x}from"@clack/prompts";import _ from"chalk";import{Command as Et}from"commander";import _t from"fs-extra";import ie from"path";import Se from"fs-extra";import _e from"path";import St from"sort-package-json";var Ie={"next-auth":"^4.22.1","@next-auth/prisma-adapter":"^1.0.5",prisma:"^4.14.0","@prisma/client":"^4.14.0",tailwindcss:"^3.4.1",autoprefixer:"^10.4.14",postcss:"^8.4.21",prettier:"^3.1.0","prettier-plugin-tailwindcss":"^0.5.7","@trpc/client":"^10.45.0","@trpc/server":"^10.45.0","@trpc/react-query":"^10.45.0","@trpc/next":"^10.45.0","@tanstack/react-query":"^4.29.7",superjson:"1.12.2",recoil:"^0.7.7","@carbon/icons-react":"^11.34.1","@carbon/react":"^1.48.1","http-proxy":"^1.18.1","@types/http-proxy":"^1.17.11"};var k=e=>{let{dependencies:t,devMode:n,frontendDir:s}=e,o=Se.readJSONSync(_e.join(s,"package.json"));t.forEach(i=>{let r=Ie[i];n&&o.devDependencies?o.devDependencies[i]=r:o.dependencies&&(o.dependencies[i]=r)});let a=St(o);Se.writeJSONSync(_e.join(s,"package.json"),a,{spaces:2})};var Ae=({frontendDir:e,packages:t})=>{k({frontendDir:e,dependencies:["@carbon/react","@carbon/icons-react"],devMode:!1});let n=ie.join(f,"template/extras");if(t?.recoil.inUse){let s=ie.join(n,"src/atoms/useTheme.ts"),o=ie.join(e,"src/atoms/useTheme.ts");_t.copySync(s,o)}};import At from"fs-extra";import re from"path";var Ce=({frontendDir:e,packages:t})=>{k({frontendDir:e,dependencies:["recoil"],devMode:!1});let n=re.join(f,"template/extras");if(!t?.carbon.inUse){let s=re.join(n,"src/atoms/useCounter.ts"),o=re.join(e,"src/atoms/useCounter.ts");At.copySync(s,o)}};import le from"fs-extra";import z from"path";var De=({frontendDir:e,packages:t})=>{let n=Ct(),s="";if(s!==""){let i=z.join(f,"template/extras/src/env",s),r=z.join(e,"src/env.mjs");le.copySync(i,r)}let o=z.join(e,".env"),a=z.join(e,".env.example");le.writeFileSync(o,n,"utf-8"),le.writeFileSync(a,Dt+n,"utf-8")},Ct=()=>`
# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.
# Example:
# SERVERVAR="foo"
# NEXT_PUBLIC_CLIENTVAR="bar" (Client env vars must start with NEXT_PUBLIC_)
# keep in mind that NEXT_PUBLIC_ vars have to be available at build time
# to use runtime env vars pass them to the client via getServerSideProps
# You need this in your .env when using Recoil with next.js
RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false
# use this when running the backend locally
API_URL="http://127.0.0.1:4000/api"
`.trim().concat(`
`),Dt=`
# Since the ".env" file is gitignored, you can use the ".env.example" file to
# build a new ".env" file when you clone the repo. Keep this file up-to-date
# when you add new variables to \`.env\`.
# This file will be committed to version control, so make sure not to have any
# secrets in it. If you are cloning this repo, create a copy of this file named
# ".env" and populate it with your secrets.
`.trim().concat(`
`);import M from"fs-extra";import P from"path";var je=({frontendDir:e,packages:t})=>{k({frontendDir:e,dependencies:["tailwindcss","postcss","autoprefixer","prettier","prettier-plugin-tailwindcss"],devMode:!0});let n=P.join(f,"template/extras"),s=P.join(e,"tailwind.config.ts"),o=P.join(n,"config/tailwind.config.ts");t?.carbon.inUse&&(o=P.join(n,"config/with-tw-carbon/tailwind.config.ts"));let a=P.join(n,"config/postcss.config.cjs"),i=P.join(e,"postcss.config.cjs"),r=P.join(n,"config/prettier.config.cjs"),l=P.join(e,"prettier.config.cjs"),m=P.join(n,"src/styles/tailwind.css"),d=P.join(e,"src/styles/tailwind.css");M.copySync(o,s),M.copySync(a,i),M.copySync(m,d),M.copySync(r,l);let u=P.join(e,"src/pages/index.module.css");M.unlinkSync(u);let b=P.join(e,"src/components/layout/Layout.module.css");M.unlinkSync(b)};import R from"fs-extra";import w from"path";var Te=({frontendDir:e,packages:t})=>{k({frontendDir:e,dependencies:["@tanstack/react-query","superjson","@trpc/server","@trpc/client","@trpc/next","@trpc/react-query"],devMode:!1});let n=w.join(f,"template/extras"),s=w.join(n,"src/pages/api/trpc/[trpc].ts"),o=w.join(e,"src/pages/api/trpc/[trpc].ts"),a=w.join(n,"src/utils/api.ts"),i=w.join(e,"src/utils/api.ts"),r="base.ts",l=w.join(n,"src/server/api/trpc",r),m=w.join(e,"src/server/api/trpc.ts"),d=w.join(n,"src/server/api/root.ts"),u=w.join(e,"src/server/api/root.ts"),b=w.join(n,"config/with-trpc.next.config.mjs"),I=w.join(e,"next.config.mjs"),A="base.ts",T=w.join(n,"src/server/api/routers/example",A),O=w.join(e,"src/server/api/routers/example.ts");R.copySync(s,o),R.copySync(a,i),R.copySync(l,m),R.copySync(d,u),R.copySync(T,O),R.copySync(b,I)};var $e=["tailwind","trpc","envVariables","recoil","carbon"];var Ve=e=>({tailwind:{inUse:e.includes("tailwind"),installer:je},trpc:{inUse:e.includes("trpc"),installer:Te},envVariables:{inUse:!0,installer:De},recoil:{inUse:e.includes("recoil"),installer:Ce},carbon:{inUse:e.includes("carbon"),installer:Ae}}),Ne=[{name:'"no Backend" (default Next.js API Routes)',value:"default",short:"default"},{name:"tRPC (front-to-back typsafe Next.js API Routes)",value:"trpc",short:"tRPC"},{name:"FastAPI",value:"fastapi",short:"FastAPI"}],K=[{value:"full-stack-cen-template",name:"full-stack-cen-template",description:"Feature-rich React/Vite + FastAPI template with auth, database and Docker"},{value:"create-cen-app",name:"original create-cen-app (deprecated)",description:"Lightweight Next.js template with a choice of FastAPI, tRPC or default Next.js API Routes"}],Ee=[{value:"main",name:"main",description:"Carbon UI + built-in auth"},{value:"oauth-proxy",name:"oauth-proxy",description:"Carbon UI + auth via oauth proxy & AppID"},{value:"backend-only",name:"backend-only",description:"API-only with API key auth"},{value:"main-custom-ui",name:"main-custom-ui",description:"shadcn UI + built-in auth"},{value:"oauth-proxy-custom-ui",name:"oauth-proxy-custom-ui",description:"shadcn UI + auth via oauth proxy & AppID"}];import{execa as ce}from"execa";var Y=async()=>{try{let e=await jt(),t=[];for(let s of e){let o=await Tt(s);t.push(...o)}if(t.length===0)return null;let n=[];for(let s of t){let o=await $t(s);o&&(s.startsWith("/usr/bin")?n.push({path:s,version:o,owner:"system"}):n.push({path:s,version:o,owner:"user"}))}return n=n.filter((s,o,a)=>o===a.findIndex(i=>i.path===s.path)),n=n.sort((s,o)=>s.version&&o.version?Vt(s.version,o.version):0),n}catch{return null}};async function jt(){try{let{stdout:e}=await ce("compgen",["-c","|","grep","-E","'^python[0-9.]*$'"],{shell:"bash"});return e.trim().split(`
`)}catch{return[]}}async function Tt(e){try{let{stdout:t}=await ce("type",["-a",e],{shell:"bash"}),n=t.trim(),s=new RegExp(`(?<=${e} is ).+`,"gm");return n.match(s)||[]}catch{return[]}}async function $t(e){try{let{stdout:t}=await ce(e,["--version"]);return t.trim()}catch{return null}}function Vt(e,t){let n=e.split(".").map(Number),s=t.split(".").map(Number);for(let o=0;o<Math.max(n.length,s.length);o++){let a=n[o]||0,i=s[o]||0;if(a<i)return 1;if(a>i)return-1}return 0}var Oe=e=>(e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e);var Nt=/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,Fe=e=>{let t=Oe(e),n=t.split("/"),s=n.findIndex(a=>a.startsWith("@")),o=n[n.length-1];if(n.findIndex(a=>a.startsWith("@"))!==-1&&(o=n.slice(s).join("/")),!(t==="."||Nt.test(o??"")))return"App name must consist of only lowercase alphanumeric characters, '-', and '_'"};var pe={appName:J,displayName:we,flavour:"main",packages:["tailwind","envVariables","carbon","recoil"],backend:"default",pythonVersion:{path:"/usr/bin/python3",owner:"system"},template:K[0],flags:{noGit:!1,noInstall:!1,noVenv:!1,default:!1,proxy:!1}},Me=async()=>{let e=pe,t=new Et().name(se);t.description("A CLI for creating web applications with the t3 stack").argument("[dir]","The name of the application, as well as the name of the directory to create").option("--noGit","Explicitly tell the CLI to not initialize a new git repo in the project",!1).option("--noInstall","Explicitly tell the CLI to not run the package manager's install command",!1).option("-y, --default","Bypass the CLI and use all default options to bootstrap a new cen-app",!1).version(F(),"-v, --version","Display the version number").addHelpText("afterAll",`
The t3 stack was inspired by ${_.hex("#E8DCFF").bold("@t3dotgg")} and has been used to build awesome fullstack applications like ${_.hex("#E24A8D").underline("https://ping.gg")}
`).parse(process.argv),process.env.npm_config_user_agent?.startsWith("yarn/3")&&c.log.warn("WARNING: Yarn 3 is currently not supported and may cause crashes. Please use pnpm, npm, or Yarn Classic instead.");let n=t.args[0];n&&(e.appName=n),e.flags=t.opts();try{if(process.env.TERM_PROGRAM?.toLowerCase().includes("mintty"))throw c.log.warn("WARNING: MinTTY detected which is non-interactive. Please use another terminal like Windows Terminal, or provide CLI arguments directly."),Object.assign(new Error("Non-interactive environment"),{isTTYError:!0});if(!e.flags.default){let s=await c.select({message:"Which template would you like to use?",options:K.map(o=>({label:_.bold(o.name),hint:o.description,value:o})),initialValue:K[0]});if(x(s)&&(c.cancel("Operation cancelled"),process.exit(0)),e.template=s,!n){let o=await c.text({message:"What will your project be called?",defaultValue:pe.appName,placeholder:pe.appName,validate:Fe});x(o)&&(c.cancel("Operation cancelled"),process.exit(0)),o&&(e.appName=o)}if(e.template.value==="create-cen-app"){let o=await c.select({message:"Will you be using TypeScript or JavaScript?",options:[{label:"TypeScript",value:"typescript"},{label:"JavaScript",value:"javascript"}],initialValue:"typescript"});x(o)&&(c.cancel("Operation cancelled"),process.exit(0)),o==="javascript"&&c.note(_.redBright("Wrong answer, using TypeScript instead"));let a=await c.multiselect({message:"Which packages would you like to enable? (Space to select. A to toggle all)",options:$e.filter(l=>l!=="envVariables"&&l!=="trpc").map(l=>({label:l,value:l}))});x(a)&&(c.cancel("Operation cancelled"),process.exit(0)),e.packages=a;let i=await c.select({message:"Which backend would you like to use?",options:Ne.map(l=>({label:l.name,value:l.value})),initialValue:"default"});x(i)&&(c.cancel("Operation cancelled"),process.exit(0)),e.backend=i;let r=i==="fastapi"?!0:await c.confirm({message:"Would you like us to setup a proxy on /api for you?",initialValue:!0});if(x(r)&&(c.cancel("Operation cancelled"),process.exit(0)),e.flags.proxy=r,!e.flags.noInstall){let l=S(),m=l==="yarn"?l:`${l} install`,d=await c.confirm({message:`Would you like us to run ${_.magenta(`'${m}'`)}?`,initialValue:!0});x(d)&&(c.cancel("Operation cancelled"),process.exit(0)),e.flags.noInstall=!d}if(!e.flags.noGit){let l=await c.confirm({message:"Initialize a new git repository?",initialValue:!0});x(l)&&(c.cancel("Operation cancelled"),process.exit(0)),e.flags.noGit=!l}e.backend==="fastapi"&&await Ot(e)}if(e.template.value==="full-stack-cen-template"){let o=await c.select({message:"Which flavour would you like to use?",options:Ee.map(a=>({label:a.name,value:a.value,hint:a.description})),initialValue:"default"});if(x(o)&&(c.cancel("Operation cancelled"),process.exit(0)),e.flavour=o,!e.flags.noInstall){let a=S(),i=a==="yarn"?a:`${a} install`,r=await c.confirm({message:o==="backend-only"?`Would you like us to run ${_.magenta("'uv sync'")}?`:o==="go"||o==="java"?`Would you like us to run ${_.magenta(`'${i}'`)}?`:`Would you like us to run ${_.magenta(`'${i}'`)} and ${_.magenta("'uv sync'")}?`,initialValue:!0});x(r)&&(c.cancel("Operation cancelled"),process.exit(0)),e.flags.noInstall=!r}e.flags.noGit=!0}}}catch(s){if(s instanceof Error&&"isTTYError"in s){c.log.warn(`
${se} needs an interactive terminal to provide options`);let o=await c.confirm({message:"Continue scaffolding a default CEN app?",initialValue:!0});x(o)&&(c.cancel("Operation cancelled"),process.exit(0)),o||(c.log.info("Exiting..."),process.exit(0)),c.log.info(`Bootstrapping a default CEN app in ./${e.appName}`)}else throw s}return e},Ot=async e=>{if(!e.flags.noVenv){let t=await c.confirm({message:`Would you like us to setup your ${_.magenta("python environment (venv)")}? ${_.yellow("experimental")}`,initialValue:!0});if(x(t)&&(c.cancel("Operation cancelled"),process.exit(0)),e.flags.noVenv=!t,!e.flags.noVenv){let n=await Y();if(n?.some(o=>{let a=o.version?.match(/3\.(\d+)/);return a?Number(a[1])>=10:!1})||(c.log.warn("This backend requires Python 3.10 or higher but we couldn't find it on your system. Please install and follow the Backend README to setup your environment."),e.flags.noVenv=!0),!e.flags.noVenv&&n?.[0]){let o=await c.select({message:"Which python version would you like to use? We found these Versions on your system:",options:n.map(a=>({label:`${a.version} - ${a.path}`,value:a}))});x(o)&&(c.cancel("Operation cancelled"),process.exit(0)),e.pythonVersion=o}}}};import*as p from"@clack/prompts";import g from"chalk";import{execa as U}from"execa";import me from"fs-extra";var Re=async({projectDir:e,projectName:t,template:n,noInstall:s,flavour:o})=>{if(me.existsSync(e))if(me.readdirSync(e).length===0)t!=="."&&p.log.info(`${g.cyan.bold(t)} exists but is empty, continuing...
`);else{let l=await p.select({message:`${g.redBright.bold("Warning:")} ${g.cyan.bold(t)} already exists and isn't empty. How would you like to proceed?`,options:[{label:"Abort installation (recommended)",value:"abort"},{label:"Clear the directory and continue installation",value:"clear"},{label:"Continue installation and overwrite conflicting files",value:"overwrite"}]});(p.isCancel(l)||l==="abort")&&(p.log.error("Aborting installation..."),process.exit(1));let d=await p.confirm({message:`Are you sure you want to ${l==="clear"?"clear the directory":"overwrite conflicting files"}?`,initialValue:!1});(p.isCancel(d)||!d)&&(p.log.error("Aborting installation..."),process.exit(1)),l==="clear"&&(p.log.info(`
Emptying ${g.cyan.bold(t)} and creating CEN app..
`),me.emptyDirSync(e))}let a=s,i=[],r=[];if(n==="full-stack-cen-template"&&(p.log.info(`Checking for dependencies...
`),o!=="go"&&o!=="java"&&(await Ft()?p.log.success(`${g.green("uv is installed")}`):(r.push("uv"),p.log.error(g.red("\u274C uv is not installed")),p.log.message(g.cyan.bold("Install uv: https://docs.astral.sh/uv/getting-started/installation/")))),await Mt()?(p.log.success(`${g.green("Docker CLI is installed")}`),await Rt()?p.log.success(`${g.green("Docker Compose is installed")}`):(i.push("Docker Compose"),p.log.warn(g.yellow("Docker Compose is not available")),p.log.message(g.cyan.bold(`Install Docker Compose with Homebrew: brew install docker-compose
`)),p.log.message(g.cyan.bold("Install Docker Runtime (colima): https://github.com/abiosoft/colima/ ")))):(i.push("Docker CLI"),p.log.warn(g.yellow("Docker CLI is not installed")),p.log.message(g.cyan.bold(`Install Docker with Homebrew: brew install docker
`)),p.log.message(g.cyan.bold("Install Docker Runtime (colima): https://github.com/abiosoft/colima/ "))),o!=="go"&&o!=="java"&&(await Wt()?p.log.success(`${g.green("Python 3.10, 3.11, or 3.12 is installed")}`):(r.push("Python 3.10+"),p.log.error(g.red("\u274C You need Python 3.10, 3.11, or 3.12 installed to use this template")),p.log.message(g.cyan.bold("Install Python: https://www.python.org/downloads/")))),o!=="backend-only"&&(await Lt()?p.log.success(`${g.green("Node.js 20.x or higher is installed")}`):(r.push("Node.js 20+"),p.log.error(g.red("\u274C You need Node.js 20.x or higher to use this template")),p.log.message(g.cyan.bold("Install Node.js: https://nodejs.org/")))),i.length>0||r.length>0)){let m=`Missing dependencies: ${[...r,...i].join(", ")}`;p.log.warn(g.yellow(m));let d=await p.confirm({message:"Would you like to continue anyway?",initialValue:r.length===0});(p.isCancel(d)||!d)&&(p.log.error(g.red("Aborting installation...")),process.exit(1)),r.length>0&&(a=!0)}return{noInstall:a,missingDependencies:[...r,...i]}},Ft=async()=>{try{let{stdout:e}=await U("uv",["--version"]);return e!==""}catch{return!1}},Mt=async()=>{try{let{stdout:e}=await U("docker",["--version"]);return e!==""}catch{return!1}},Rt=async()=>{let e=!1;try{let{stdout:t}=await U("docker",["compose","version"]);e=t!==""}catch{e=!1}if(!e)try{let{stdout:t}=await U("docker-compose",["version"]);e=t!==""}catch{e=!1}return e},Wt=async()=>{let e=["3.10","3.11","3.12"],t=await Y();return t?t.map(s=>s.version?.match(/(\d+\.\d+\.\d+)$/)?.[1]).filter(s=>s!==void 0).some(s=>e.some(o=>s.startsWith(o))):!1},Lt=async()=>{try{let{stdout:e}=await U("node",["--version"]),t=e?.slice(1).split(".")[0];return t?parseInt(t)>=20:!1}catch{return!1}};import*as tt from"@clack/prompts";import nn from"chalk";import ye from"path";import*as de from"@clack/prompts";import Ut from"chalk";var We=e=>{let{packages:t}=e;de.log.info("Adding boilerplate...");for(let[n,s]of Object.entries(t))s.inUse&&(s.installer(e),de.log.success(`Successfully setup boilerplate for ${Ut.green.bold(n)}`))};import*as X from"@clack/prompts";import Ke from"chalk";import q from"fs-extra";import V from"path";import Bt from"fs-extra";import Le from"path";var Ue=({frontendDir:e})=>{k({frontendDir:e,dependencies:["http-proxy"],devMode:!1}),k({frontendDir:e,dependencies:["@types/http-proxy"],devMode:!0});let t=Le.join(f,"template/extras/src/pages/api/proxy"),n=Le.join(e,"src/pages/api");Bt.copySync(t,n)};var Be=({packages:e,backend:t})=>{let n=[];return t==="trpc"&&n.push("trpc"),t==="fastapi"&&n.push("extBackend"),e.tailwind.inUse&&n.push("tailwind"),e.recoil.inUse&&n.push("recoil"),e.carbon.inUse&&n.push("carbon"),n};import Ge from"fs-extra";import Gt from"prettier";var Je=(e,t)=>{let n=Ge.readFileSync(e,"utf-8"),s=Gt.format(n,{printWidth:100,tabWidth:2,parser:t});Ge.writeFileSync(e,s)};function Jt(e){let t=e.trim();return t.startsWith("//")?t.substring(2).trim():t.startsWith("{/*")&&t.endsWith("*/}")?t.substring(3,t.length-3).trim():t.startsWith("/*")&&t.endsWith("*/")?t.substring(2,t.length-2).trim():""}var zt=e=>{let t=Jt(e);if(!t)return null;let n=t.trim().replace(/ +(?= )/g,"");return n.startsWith("$with:")?{conditionString:n.substring(7).trim(),position:"start"}:n.startsWith("$end:")?{conditionString:n.substring(5).trim(),position:"end"}:null},Kt=(e,t)=>{let{conditionString:n,position:s}=e,o=[n];n.includes("&&")&&(o=n.split("&&"));for(let a=0;a<o.length;a++){let i=o[a].trim();if(!Yt(i,t))return{mode:"delete",position:s,conditionString:n}}return{mode:"keep",position:s,conditionString:n}},Yt=(e,t)=>{let n=e.split("||");for(let s=0;s<n.length;s++)if(Xt(n[s],t))return!0;return!1},qt=(e,t)=>t.includes(e),Xt=(e,t)=>{let n="!",s=e.trim();if(s.startsWith(n)){let o=s.substring(1).trim();return!qt(o,t)}return t.includes(s)},ze=(e,t)=>{let n=zt(e);return n?Kt(n,t):null};import $ from"fs-extra";import Ht from"path";var Qt=({templatePath:e,resultPath:t,usedDependencies:n})=>{let o=$.readFileSync(e,"utf-8").split(`
`),a=[],i=!1,r=null;for(let m=0;m<o.length;m++){let d=o[m].trim(),u=ze(d,n);if(u){if(u.position==="start"&&r)continue;u.mode==="delete"&&u.position==="start"&&(i=!0,r=u.conditionString),u.mode==="delete"&&u.position==="end"&&r===u.conditionString&&(i=!1,r=null)}!i&&!u&&a.push(d)}$.existsSync(t)||$.ensureFileSync(t),$.writeFileSync(t,a.join(`
`));let l=t.endsWith(".ts")||t.endsWith(".tsx")?"typescript":"scss";Je(t,l)},ue=({templateDir:e,resultDir:t,usedDependencies:n})=>{let s=$.readdirSync(e);for(let o of s){let a=Ht.join(e,o);if($.statSync(a).isDirectory())ue({templateDir:a,resultDir:`${t}/${o}`,usedDependencies:n});else if(o.includes(".tmpl")){let i=`${e}/${o}`,r=`${t}/${o.replace(".tmpl","")}`;Qt({templatePath:i,resultPath:r,usedDependencies:n})}else{let i=`${e}/${o}`,r=`${t}/${o}`;$.copySync(i,r)}}};var Ye=async({template:e,projectName:t,projectDir:n,frontendDir:s,backendDir:o,pkgManager:a,noInstall:i,packages:r,proxy:l,backend:m})=>{let d=V.join(f,"template/base"),u=X.spinner();u.start(`Scaffolding in: ${n}...
`);let b=Be({packages:r,backend:m});ue({templateDir:d,resultDir:s,usedDependencies:b}),q.renameSync(V.join(s,"_gitignore"),V.join(s,".gitignore")),q.renameSync(V.join(s,"_eslintrc.cjs"),V.join(s,".eslintrc.cjs")),l&&Ue({frontendDir:s}),m!=="default"&&m!=="trpc"&&k({frontendDir:s,dependencies:["@tanstack/react-query"],devMode:!1});let I=q.createReadStream(V.join(d,"public/favicon.ico"),{encoding:"binary"}),A=q.createWriteStream(V.join(s,"public/favicon.ico"),{encoding:"binary"});I.pipe(A);let T=t==="."?"App":Ke.cyan.bold(t);u.stop(),X.log.success(`${T} ${Ke.green("scaffolded successfully!")}
`)};import*as D from"@clack/prompts";import Zt from"chalk";import{execa as qe}from"execa";import ge from"fs-extra";import H from"path";var Xe=async({backendDir:e,noVenv:t,pythonVersion:n})=>{let s=e,o=H.join(f,"template/extras/backends/fastapi");ge.copySync(o,s),ge.renameSync(H.join(s,"_gitignore"),H.join(s,".gitignore"));let a=H.join(e,".env"),i="EXAMPLE=XXXX";if(ge.writeFileSync(a,i,"utf-8"),t){D.log.info("Skipping FastAPI environment setup");return}D.log.info("Preparing FastAPI environment...");let r=D.spinner();r.start("Creating virtual environment..."),await qe(n.path,["-m","venv",".venv"],{cwd:s,stdio:"inherit"}),r.stop(),D.log.success(`Successfully created virtual environment for ${Zt.green.bold("FastAPI")}`);let l=D.spinner();l.start("Installing python requirements..."),await qe(".venv/bin/pip",["install","-r","requirements.txt"],{cwd:s,stdio:"inherit"}),l.stop(),D.log.success("Successfully installed python requirements")};import*as v from"@clack/prompts";import B from"chalk";import{execa as N}from"execa";import en from"fs-extra";import He from"path";var Qe=async({projectName:e,flavour:t,backendDir:n,frontendDir:s,projectDir:o,noInstall:a})=>{let i=v.spinner();i.start("Cloning full-stack-cen-template repository..."),await N("git",["clone","-b",t,ae,o],{cwd:f,stdio:"inherit"}),i.stop(),v.log.success(`Successfully cloned ${B.green.bold("full-stack-cen-template")} repository
`),v.log.info("Setting up git...");try{await N("git",["branch","-m","main"],{cwd:o}),v.log.success(`Successfully renamed branch to ${B.green.bold("main")}`)}catch(l){v.log.error(`Failed to rename branch to main: ${l}`)}try{await N("git",["remote","remove","origin"],{cwd:o}),v.log.success(`Successfully removed git remote ${B.green.bold("origin")}`)}catch(l){v.log.error(`Failed to remove git remote origin: ${l}`)}try{await N("git",["remote","add","upstream",ae],{cwd:o}),v.log.success(`Successfully added ${B.green.bold("upstream")} remote`)}catch(l){v.log.error(`Failed to add upstream remote: ${l}`)}v.log.success(`Successfully setup git
`);let r=He.join(o,".env");if(en.copySync(He.join(o,".env.example"),r),a){v.log.info("Skipping dependencies installation");return}if(t==="backend-only"||t==="main"||t==="oauth-proxy"||t==="main-custom-ui"||t==="oauth-proxy-custom-ui"){v.log.info("Preparing Python environment...");let l=["3.12","3.11","3.10"],m=null;for(let d of l)if(await N(`python${d}`,["--version"]).then(()=>!0).catch(()=>!1)){m=d;break}if(!m){v.log.error("Python 3.10 or higher is not installed. Please install Python 3.10+ and follow the Backend README.");return}i.start(`Creating virtual environment with Python ${m}...`);try{await N("uv",["venv","--python",`python${m}`],{cwd:n,stdio:"inherit"}),await N("uv",["sync"],{cwd:n,stdio:"inherit"}),i.stop(),v.log.success(`Successfully installed ${B.green.bold("python")} requirements
`)}catch{i.stop(),v.log.error("Failed to setup Python environment. Please check the Backend README for manual setup.")}}t!=="backend-only"&&await G({frontendDir:s})};var fe=e=>e.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&");import E from"fs";import tn from"path";function Ze(e,t,n){if(E.statSync(e).isDirectory())E.readdirSync(e).forEach(o=>{let a=tn.join(e,o);if(E.statSync(a).isDirectory())Ze(a,t,n);else{let r=E.readFileSync(a,"utf8").replace(new RegExp(fe(t),"g"),n);E.writeFileSync(a,r,"utf8")}});else{let o=E.readFileSync(e,"utf8").replace(new RegExp(fe(t),"g"),n);E.writeFileSync(e,o,"utf8")}}var et=Ze;var nt=async({projectName:e,displayName:t,packages:n,backend:s,pythonVersion:o,noInstall:a,noVenv:i,proxy:r,template:l,flavour:m})=>{let d=S(),u=ye.resolve(process.cwd(),e),b="",I="";l==="create-cen-app"&&(s==="default"||s==="trpc")?b=u:(b=ye.resolve(u,"frontend"),I=ye.resolve(u,"backend")),a||tt.log.info(`
Using: ${nn.cyan.bold(d)}
`);let{noInstall:A,missingDependencies:T}=await Re({projectDir:u,projectName:e,template:l,noInstall:a,flavour:m});return a=A,l==="create-cen-app"&&(await Ye({projectDir:u,projectName:e,frontendDir:b,backendDir:I,pkgManager:d,noInstall:a,proxy:r,packages:n,backend:s,template:l}),We({frontendDir:b,pkgManager:d,packages:n,noInstall:a}),s==="fastapi"&&await Xe({backendDir:I,noVenv:i,pythonVersion:o}),t&&et(b,"[project-name]",t)),l==="full-stack-cen-template"&&await Qe({backendDir:I,frontendDir:b,projectDir:u,projectName:e,noInstall:a,flavour:m}),{frontendDir:b,backendDir:I,projectDir:u,missingDependencies:T}};import*as j from"@clack/prompts";import y from"chalk";var ot=({projectName:e=J,packages:t,backend:n,frontendDir:s,noInstall:o,noVenv:a,template:i,flavour:r,missingDependencies:l})=>{j.log.info(`${y.bold.green("All done!")} \u{1F389}
`),i==="create-cen-app"&&on({projectName:e,noVenv:a,frontendDir:s??"",noInstall:o??!0,backend:n}),i==="full-stack-cen-template"&&sn({projectName:e,flavour:r,missingDependencies:l}),j.log.message(""),j.outro(`${y.bold.green("Have fun building!")} \u{1F680}`)},on=({projectName:e,noVenv:t,frontendDir:n,noInstall:s,backend:o})=>{let a=S(),i=o!=="default"&&o!=="trpc",r=`${y.bold.cyan("Next steps:")}
`;i?t?r+=` ${y.cyan("--setup venv and install dependencies--")}
`:r+=` ${y.cyan("cd")} ${e}/backend
${y.cyan("./run")}
${y.yellow("In another terminal window:")}
${y.cyan("cd")} ${n}
`:e!=="."&&(r+=` ${y.cyan("cd")} ${e}
`),s&&(r+=` ${y.cyan(a)}${a!=="yarn"?" install":""}
`),r+=` ${y.cyan(a==="npm"?"npm run":a)} dev
`,j.log.info(r)},sn=({projectName:e,flavour:t,missingDependencies:n})=>{let s=`${y.bold.cyan("Note:")}
`;s+=`${y.cyan(`The base template has been registered as upstream, so that you can pull updates later on.
Add your own remote as origin and pull updates from upstream:`)}
${y.cyan("git remote add origin <your-remote-url>")}
${y.cyan("git pull --no-commit upstream "+t)}`,j.log.info(s);let o=`${y.bold.cyan("Next steps:")}
`;n.length>0&&(o+=` ${y.yellow("Install missing dependencies:")} ${n.join(", ")}
`),(t==="oauth-proxy"||t==="oauth-proxy-custom-ui")&&(o+=` ${y.cyan("Get an AppID Instance (see development.md) and put the credentials in .env")}
`),o+=` ${y.cyan("cd")} ${e}
`,o+=` ${y.cyan("docker compose watch")}
`,j.log.info(o)};import st from"path";var at=e=>{let t=e.split("/"),n=t[t.length-1];if(n==="."){let a=st.resolve(process.cwd());n=st.basename(a)}let s=t.findIndex(a=>a.startsWith("@"));t.findIndex(a=>a.startsWith("@"))!==-1&&(n=t.slice(s).join("/"));let o=t.filter(a=>!a.startsWith("@")).join("/");return[n,o]};import an from"gradient-string";var rn={blue:"#add7ff",cyan:"#89ddff",green:"#5de4c7",magenta:"#fae4fc",red:"#d0679d",yellow:"#fffac2"},it=()=>{let e=an(Object.values(rn)),t=S();(t==="yarn"||t==="pnpm")&&console.log(""),console.log(e.multiline(ke))};var ln=async()=>{let e=await Pe();it(),e&&xe(e);let{appName:t,packages:n,displayName:s,backend:o,pythonVersion:a,flavour:i,template:r,flags:{noGit:l,noInstall:m,noVenv:d,proxy:u}}=await Me(),b=Ve(n),[I,A]=at(t),{projectDir:T,frontendDir:O,backendDir:ct,missingDependencies:pt}=await nt({projectName:A,packages:b,backend:o,pythonVersion:a,noInstall:m,noVenv:d,proxy:u,template:r.value,flavour:i,displayName:s});if(i!=="backend-only"){let Z=rt.readJSONSync(lt.join(O,"package.json"));Z.name=I,Z.ct3aMetadata={initVersion:F()},rt.writeJSONSync(lt.join(O,"package.json"),Z,{spaces:2})}!m&&r.value==="create-cen-app"&&await G({frontendDir:O}),l||await ve(T),ot({projectName:A,packages:b,template:r.value,noInstall:m,frontendDir:O,backendDir:ct,backend:o,noVenv:d,missingDependencies:pt,flavour:i}),process.exit(0)};ln().catch(e=>{Q.log.error("Aborting installation..."),e instanceof Error?Q.log.error(e.message):(Q.log.error("An unknown error has occurred. Please open an issue on github with the below:"),console.log(e)),process.exit(1)});
//# sourceMappingURL=index.js.map