create-instant-app 1.0.9 → 1.0.10

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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{Command as Fe,Option as l}from"commander";import*as B from"@clack/prompts";import{spawn as Ce}from"child_process";import{execa as Oe}from"execa";async function $(){return new Promise(e=>{let t=process.env.SHELL||"/bin/bash",n=Ce(t,["-i","-c","which claude"],{stdio:["ignore","pipe","ignore"]}),r="";n.stdout.on("data",a=>{r+=a.toString()}),n.on("close",a=>{if(a===0){let o=r.trim();o.includes("aliased to ")?e(o.split("aliased to ")[1]||null):e(o)}else e(null)})})}var K=async(e,t)=>{let n=await $();if(!n)throw new Error("Claude not found in path");await Oe(n,[e],{stdio:"inherit",cwd:t})};import{version as Re}from"@instantdb/version";import H from"path";var Y=e=>(e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e),je=/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,q=e=>{let n=Y(e).split("/"),r=n[n.length-1];if(r==="."){let i=H.resolve(process.cwd());r=H.basename(i)}let a=n.findIndex(i=>i.startsWith("@"));n.findIndex(i=>i.startsWith("@"))!==-1&&(r=n.slice(a).join("/"));let o=n.filter(i=>!i.startsWith("@")).join("/");return[r,o]},E=e=>e.trim(),D=e=>{let t=Y(e),n=t.split("/"),r=n.findIndex(o=>o.startsWith("@")),a=n[n.length-1];if(n.findIndex(o=>o.startsWith("@"))!==-1&&(a=n.slice(r).join("/")),!(t==="."||je.test(a??"")))return"App name must consist of only lowercase alphanumeric characters, '-', and '_'"};import{renderUnwrap as v,UI as f}from"instant-cli/ui";var C={base:"next-js-app-dir",appName:"Awesome Todos",ruleFiles:null,createRepo:!0,prompt:null,app:null,token:null,yes:!1},X=e=>e.base||e.vanilla&&"vite-vanilla"||e.next&&"next-js-app-dir"||e.expo&&"expo"||e.sv&&"sveltekit"||null,Z=e=>e.cursor&&"cursor"||e.claude&&"claude"||e.codex&&"codex"||e.gemini&&"gemini"||e.rules&&"codex"||null,Q=async()=>{let e=C,t=new Fe().name("Create Instant App").description("A CLI for creating web/mobile applications with InstantDB").argument("[dir]","The name of the application, as well as the name of the directory to create").addOption(new l("-b --base <template>","The base template to scaffold from").choices(["next-js-app-dir","vite-vanilla","expo","bun-react","tanstack-start","tanstack-start-with-tanstack-query","solidjs-vite","sveltekit","vercel-ai-sdk","ai-chat"])).addOption(new l("-g --git","Create a git repo in the new project").default(!0)).addOption(new l("--expo","Use the Expo starter template").default(!1)).addOption(new l("--next","Use the NextJS starter template").default(!1)).addOption(new l("--vanilla","Use the vanilla JS starter template").default(!1)).addOption(new l("--sv","Use the SvelteKit starter template").default(!1)).addOption(new l("--no-git","Don't create a git repo in the new project")).addOption(new l("--cursor","Include a Cursor rules file in the scaffold")).addOption(new l("--claude","Include a CLAUDE.md file in the scaffold")).addOption(new l("--codex","Include an AGENTS.md file in the scaffold")).addOption(new l("--gemini","Include a GEMINI.md file in the scaffold")).addOption(new l("--rules","Include an AGENTS.md file in the scaffold")).addOption(new l("--ai","Create a new InstantDB app based off of a prompt. (requires Claude Code)")).addOption(new l("-a --app <app-id>","Link to an existing InstantDB app by ID (requires login or --token)")).addOption(new l("-t --token <token>","Auth token override (use with --app when not logged in)")).addOption(new l("-y --yes","Use all defaults (requires project name as first argument)").default(!1)).version(Re).parse(process.argv),n=t.args[0]&&E(t.args[0]);if(n){let o=D(n);if(o)throw new Error("Invalid app name: "+o);e.appName=n}let r=t.opts();if(r.yes){if(r.ai)throw new Error("--yes is not supported with --ai");if(!n)throw new Error(`When using --yes, you must specify a project name as the first argument.
3
- Usage: npx create-instant-app my-app --yes`);return{...C,appName:n,base:X(r)??C.base,ruleFiles:Z(r)??"claude",createRepo:r.git??C.createRepo,app:r.app??null,token:r.token??null,yes:!0}}if(r.ai&&!await $())throw new Error("--ai only works with Claude Code, but we couldn't find it in your machine. Install it first, and run it again : ). Alternatively you can scaffold out a project without --ai");return{...await B.group({appName:async()=>{if(n)return n.trim();let o=await v(new f.TextInput({prompt:"What will your project/folder be called?",placeholder:"awesome-todos",defaultValue:"awesome-todos",validate:s=>D(E(s)),modifyOutput:f.ciaModifier()}));return E(o)},prompt:async()=>r.ai?await v(new f.TextInput({prompt:"What would you like to create?",placeholder:"Create an app that...",modifyOutput:f.modifiers.piped([f.ciaModifier()])})):null,base:async({results:o})=>{let i=X(r);return i||(o.prompt?v(new f.Select({promptText:"What framework would you like to use?",options:[{value:"next-js-app-dir",label:"Next.js"},{value:"expo",label:"Expo: React Native"}],defaultValue:"next-js-app-dir",modifyOutput:f.modifiers.piped([f.ciaModifier()])})):v(new f.Select({promptText:"What framework would you like to use?",options:[{value:"next-js-app-dir",label:"Web: Next.js"},{value:"expo",label:"Mobile: Expo"},{value:"vite-vanilla",label:"Vite: Vanilla TS",secondary:!0},{value:"tanstack-start",label:"Tanstack Start",secondary:!0},{value:"bun-react",label:"Bun + React",secondary:!0},{value:"solidjs-vite",label:"Vite: SolidJS",secondary:!0},{value:"sveltekit",label:"SvelteKit",secondary:!0},{value:"vercel-ai-sdk",label:"Vercel AI SDK App Builder + SSR",secondary:!0},{value:"ai-chat",label:"Vercel AI SDK Chat App",secondary:!0}],defaultValue:"next-js-app-dir",modifyOutput:f.modifiers.piped([f.ciaModifier()])})))},ruleFiles:async({results:o})=>{if(o.prompt)return"claude";let i=Z(r);return i||v(new f.Select({promptText:"Which AI tool would you like to add rule files for?",options:[{value:"claude",label:"Claude"},{value:"cursor",label:"Cursor"},{value:"codex",label:"Codex"},{value:"gemini",label:"Gemini"},{value:"zed",label:"Zed"},{value:"windsurf",label:"Windsurf"},{value:null,label:"None"}],defaultValue:"claude",modifyOutput:f.ciaModifier()}))},createRepo:async()=>r.git!==void 0?r.git:!0},{onCancel(){process.exit(1)}}),app:r.app??null,token:r.token??null,yes:!1}};import ve from"path";import Ae from"fs-extra";import{log as Ne,outro as _t}from"@clack/prompts";import{intro as Me}from"@clack/prompts";import ee from"chalk";var $e=` _ _ _
2
+ import{Command as Fe,Option as l}from"commander";import*as U from"@clack/prompts";import{spawn as Ce}from"child_process";import{execa as Oe}from"execa";async function $(){return new Promise(e=>{let t=process.env.SHELL||"/bin/bash",n=Ce(t,["-i","-c","which claude"],{stdio:["ignore","pipe","ignore"]}),r="";n.stdout.on("data",a=>{r+=a.toString()}),n.on("close",a=>{if(a===0){let o=r.trim();o.includes("aliased to ")?e(o.split("aliased to ")[1]||null):e(o)}else e(null)})})}var K=async(e,t)=>{let n=await $();if(!n)throw new Error("Claude not found in path");await Oe(n,[e],{stdio:"inherit",cwd:t})};import{version as Re}from"@instantdb/version";import H from"path";var Y=e=>(e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e),je=/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,q=e=>{let n=Y(e).split("/"),r=n[n.length-1];if(r==="."){let i=H.resolve(process.cwd());r=H.basename(i)}let a=n.findIndex(i=>i.startsWith("@"));n.findIndex(i=>i.startsWith("@"))!==-1&&(r=n.slice(a).join("/"));let o=n.filter(i=>!i.startsWith("@")).join("/");return[r,o]},E=e=>e.trim(),D=e=>{let t=Y(e),n=t.split("/"),r=n.findIndex(o=>o.startsWith("@")),a=n[n.length-1];if(n.findIndex(o=>o.startsWith("@"))!==-1&&(a=n.slice(r).join("/")),!(t==="."||je.test(a??"")))return"App name must consist of only lowercase alphanumeric characters, '-', and '_'"};import{renderUnwrap as P,UI as f}from"instant-cli/ui";var C={base:"next-js-app-dir",appName:"Awesome Todos",ruleFiles:null,createRepo:!0,prompt:null,app:null,token:null,yes:!1},X=e=>e.base||e.viteReact&&"vite-react"||e.vanilla&&"vite-vanilla"||e.next&&"next-js-app-dir"||e.expo&&"expo"||e.sv&&"sveltekit"||null,Z=e=>e.cursor&&"cursor"||e.claude&&"claude"||e.codex&&"codex"||e.gemini&&"gemini"||e.rules&&"codex"||null,Q=async()=>{let e=C,t=new Fe().name("Create Instant App").description("A CLI for creating web/mobile applications with InstantDB").argument("[dir]","The name of the application, as well as the name of the directory to create").addOption(new l("-b --base <template>","The base template to scaffold from").choices(["next-js-app-dir","vite-react","vite-vanilla","expo","bun-react","tanstack-start","tanstack-start-with-tanstack-query","solidjs-vite","sveltekit","vercel-ai-sdk","ai-chat"])).addOption(new l("-g --git","Create a git repo in the new project").default(!0)).addOption(new l("--expo","Use the Expo starter template").default(!1)).addOption(new l("--next","Use the NextJS starter template").default(!1)).addOption(new l("--vanilla","Use the vanilla JS starter template").default(!1)).addOption(new l("--vite-react","Use the Vite + React starter template").default(!1)).addOption(new l("--sv","Use the SvelteKit starter template").default(!1)).addOption(new l("--no-git","Don't create a git repo in the new project")).addOption(new l("--cursor","Include a Cursor rules file in the scaffold")).addOption(new l("--claude","Include a CLAUDE.md file in the scaffold")).addOption(new l("--codex","Include an AGENTS.md file in the scaffold")).addOption(new l("--gemini","Include a GEMINI.md file in the scaffold")).addOption(new l("--rules","Include an AGENTS.md file in the scaffold")).addOption(new l("--ai","Create a new InstantDB app based off of a prompt. (requires Claude Code)")).addOption(new l("-a --app <app-id>","Link to an existing InstantDB app by ID (requires login or --token)")).addOption(new l("-t --token <token>","Auth token override (use with --app when not logged in)")).addOption(new l("-y --yes","Use all defaults (requires project name as first argument)").default(!1)).version(Re).parse(process.argv),n=t.args[0]&&E(t.args[0]);if(n){let o=D(n);if(o)throw new Error("Invalid app name: "+o);e.appName=n}let r=t.opts();if(r.yes){if(r.ai)throw new Error("--yes is not supported with --ai");if(!n)throw new Error(`When using --yes, you must specify a project name as the first argument.
3
+ Usage: npx create-instant-app my-app --yes`);return{...C,appName:n,base:X(r)??C.base,ruleFiles:Z(r)??"claude",createRepo:r.git??C.createRepo,app:r.app??null,token:r.token??null,yes:!0}}if(r.ai&&!await $())throw new Error("--ai only works with Claude Code, but we couldn't find it in your machine. Install it first, and run it again : ). Alternatively you can scaffold out a project without --ai");return{...await U.group({appName:async()=>{if(n)return n.trim();let o=await P(new f.TextInput({prompt:"What will your project/folder be called?",placeholder:"awesome-todos",defaultValue:"awesome-todos",validate:s=>D(E(s)),modifyOutput:f.ciaModifier()}));return E(o)},prompt:async()=>r.ai?await P(new f.TextInput({prompt:"What would you like to create?",placeholder:"Create an app that...",modifyOutput:f.modifiers.piped([f.ciaModifier()])})):null,base:async({results:o})=>{let i=X(r);return i||(o.prompt?P(new f.Select({promptText:"What framework would you like to use?",options:[{value:"next-js-app-dir",label:"Web: Next.js"},{value:"vite-react",label:"Web: Vite React"},{value:"expo",label:"Mobile: Expo"}],defaultValue:"next-js-app-dir",modifyOutput:f.modifiers.piped([f.ciaModifier()])})):P(new f.Select({promptText:"What framework would you like to use?",options:[{value:"next-js-app-dir",label:"Web: Next.js"},{value:"expo",label:"Mobile: Expo"},{value:"vite-react",label:"Vite: React",secondary:!0},{value:"vite-vanilla",label:"Vite: Vanilla TS",secondary:!0},{value:"tanstack-start",label:"Tanstack Start",secondary:!0},{value:"bun-react",label:"Bun + React",secondary:!0},{value:"solidjs-vite",label:"Vite: SolidJS",secondary:!0},{value:"sveltekit",label:"SvelteKit",secondary:!0},{value:"vercel-ai-sdk",label:"Vercel AI SDK App Builder + SSR",secondary:!0},{value:"ai-chat",label:"Vercel AI SDK Chat App",secondary:!0}],defaultValue:"next-js-app-dir",modifyOutput:f.modifiers.piped([f.ciaModifier()])})))},ruleFiles:async({results:o})=>{if(o.prompt)return"claude";let i=Z(r);return i||P(new f.Select({promptText:"Which AI tool would you like to add rule files for?",options:[{value:"claude",label:"Claude"},{value:"cursor",label:"Cursor"},{value:"codex",label:"Codex"},{value:"gemini",label:"Gemini"},{value:"zed",label:"Zed"},{value:"windsurf",label:"Windsurf"},{value:null,label:"None"}],defaultValue:"claude",modifyOutput:f.ciaModifier()}))},createRepo:async()=>r.git!==void 0?r.git:!0},{onCancel(){process.exit(1)}}),app:r.app??null,token:r.token??null,yes:!1}};import Pe from"path";import Ae from"fs-extra";import{log as Ne,outro as xt}from"@clack/prompts";import{intro as Me}from"@clack/prompts";import ee from"chalk";var $e=` _ _ _
4
4
  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 (_) | | | |
5
5
  \u2588 \u2588\u2588\u2588\u2588 _ _ __ ___| |_ __ _ _ _ _| |_
6
6
  \u2588 \u2588\u2588\u2588\u2588 | | '_ \\/ __| __/ _\\\`| '_ \\| __|
@@ -8,27 +8,27 @@ Usage: npx create-instant-app my-app --yes`);return{...C,appName:n,base:X(r)??C.
8
8
  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 |_|_| |_|___/\\__\\__,_|_| |_|\\__|`,te=()=>{Me(`
9
9
  `+$e.split(`
10
10
  `).map(e=>`${ee.gray("\u2502")}${ee.hex("#EA580D").bold(" "+e)}`).join(`
11
- `))};import d from"path";import Ge from"tiged";import c from"fs-extra";import ne from"path";import{fileURLToPath as De}from"url";var Be=De(import.meta.url),Ue=ne.dirname(Be),w=ne.join(Ue,"../");import _ from"chalk";var k=e=>{if(e==="bun-react")return"bun";let t=process.env.npm_config_user_agent;return t?t.startsWith("yarn")?"yarn":t.startsWith("pnpm")?"pnpm":t.startsWith("bun")?"bun":"npm":"npm"};import{renderUnwrap as re,UI as y}from"instant-cli/ui";import Le from"slugify";import Ve from"ignore";var oe=async(e,t)=>{let n=d.resolve(process.cwd(),t);if(c.existsSync(n))if(c.readdirSync(n).length===0)y.log(`${_.cyan.bold(e.appName)} exists but is empty, continuing...`,y.ciaModifier(null));else{let o=await re(new y.Select({promptText:_.redBright(`${_.bold("Warning:")} ${_.bold(e.appName)} already exists and isn't empty. How would you like to proceed?`),options:[{label:"Abort installation",value:"abort"},{label:"Clear the directory and continue installation",value:"clear"}],defaultValue:"abort",modifyOutput:y.ciaModifier()}));o==="abort"&&(y.log("Aborting installation..."),process.exit(1)),o==="clear"&&c.emptyDirSync(n)}let r=Je({projectDir:n,baseTemplateName:e.base}),a=e.appName==="."?"App":_.hex("#EA570B").bold(e.appName);if(await re(new y.Spinner({promise:r,workingText:"Scaffolding project files...",doneText:`Successfully scaffolded ${a}!`,errorText:"Error scaffolding project files",modifyOutput:y.ciaModifier(null)})),c.pathExistsSync(d.join(n,"pnpm-lock.yaml"))&&c.removeSync(d.join(n,"pnpm-lock.yaml")),c.pathExistsSync(d.join(n,"bun.lock"))&&c.removeSync(d.join(n,"bun.lock")),k(e.base)==="pnpm"&&e.base==="expo"&&await c.appendFile(d.join(n,".npmrc"),`node-linker=hoisted
12
- enable-pre-post-scripts=true`),e.base==="expo"){let o=Le.default(t);U(d.join(n,"app.json"),'"name": "expo-template"',`"name": "${t}"`),U(d.join(n,"app.json"),'"slug": "expo-template"',`"slug": "${o}"`),U(d.join(n,"app/_layout.tsx"),'"My Instant App"',`"${t}"`)}return n},U=(e,t,n)=>{let a=c.readFileSync(e,"utf8").replaceAll(t,n);c.writeFileSync(e,a)},We=async({projectDir:e,baseTemplateName:t})=>{let n=`instantdb/instant/examples/${t}`;await Ge(n,{mode:"tar",disableCache:!0}).clone(e)};async function ze(e,t){let n=Ve();n.add(".git");try{let r=d.join(e,".gitignore"),a=await c.readFile(r,"utf8");n.add(a)}catch{}await c.copy(e,t,{filter:r=>{let a=d.relative(e,r);return a===""?!0:!n.ignores(a)}})}var Je=async({projectDir:e,baseTemplateName:t})=>{let n=d.join(w,`template/base/${t}`),r=!!process.env.INSTANT_CLI_DEV&&!!process.env.INSTANT_REPO_FOLDER,a=c.pathExistsSync(n),o=r?"dev":a?"bundled-template":"tiged";if(o==="bundled-template"){c.copySync(n,e);let i=d.join(e,"_gitignore"),s=d.join(e,".gitignore");c.pathExistsSync(i)&&c.renameSync(i,s);return}if(o==="dev"){let i=process.env.INSTANT_REPO_FOLDER;if(!i)throw new Error("INSTANT_REPO_FOLDER is required when using repo-examples scaffolding.");let s=d.join(i,"examples",t);await ze(s,e);return}process.env.INSTANT_CLI_DEV&&!process.env.INSTANT_REPO_FOLDER&&y.log(_.bold.yellowBright(`WARNING: INSTANT_CLI_DEV is TRUE but no INSTANT_REPO_FOLDER is set.
13
- Using git to clone from main...`),y.ciaModifier(null)),await We({projectDir:e,baseTemplateName:t})};import{execa as Ke}from"execa";import{renderUnwrap as He,UI as O}from"instant-cli/ui";var ae=async(e,t)=>{let n=Ke(e,["install"],{cwd:t}),r=await He(new O.Spinner({promise:n,workingText:`Installing dependencies with ${e}...`,doneText:"Successfully installed dependencies!",modifyOutput:O.ciaModifier(null)}));r.exitCode!==0&&(O.log(r.stderr,O.ciaModifier(null)),process.exit(1))};import It from"chalk";import T from"fs-extra";import u from"path";var ie=({projectDir:e,ruleFilesToAdd:t})=>{if(t!==null)switch(t){case"cursor":T.ensureDirSync(u.join(e,".cursor/rules")),T.copyFileSync(u.join(w,"template/rules/cursor-rules.md"),u.join(e,".cursor/rules/instant.mdc"));break;case"claude":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"CLAUDE.md"));break;case"codex":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"AGENTS.md"));break;case"gemini":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"GEMINI.md"));break;case"zed":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"AGENTS.md"));break;case"windsurf":T.ensureDirSync(u.join(e,".windsurf/rules")),T.copyFileSync(u.join(w,"template/rules/windsurf-rules.md"),u.join(e,".windsurf/rules/instant.md"));break}};import{execSync as L}from"child_process";import G from"path";import*as pe from"@clack/prompts";import j from"chalk";import{execa as S}from"execa";import le from"fs-extra";import{renderUnwrap as se,UI as I}from"instant-cli/ui";var Ye=e=>{try{return L("git --version",{cwd:e}),!0}catch{return!1}},qe=e=>le.existsSync(G.join(e,".git")),Xe=async e=>{try{return await S("git",["rev-parse","--is-inside-work-tree"],{cwd:e,stdout:"ignore"}),!0}catch{return!1}},Ze=()=>{let t=L("git --version").toString().trim().split(" ")[2],n=t?.split(".")[0],r=t?.split(".")[1];return{major:Number(n),minor:Number(r)}},Qe=()=>L("git config --global init.defaultBranch || echo main").toString().trim(),ce=async e=>{if(!Ye(e)){pe.log.warn("Git is not installed. Skipping Git initialization.");return}let t=qe(e),n=await Xe(e),r=G.parse(e).name;if(n&&t){if(!await se(new I.Confirmation({promptText:`${j.redBright.bold("Warning:")} Git is already initialized in "${r}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,defaultValue:!1,modifyOutput:I.ciaModifier()})))return;le.removeSync(G.join(e,".git"))}else if(n&&!t&&!await se(new I.Confirmation({promptText:`${j.redBright.bold("Warning:")} "${r}" is already in a git worktree.
11
+ `))};import d from"path";import Ge from"tiged";import c from"fs-extra";import ne from"path";import{fileURLToPath as De}from"url";var Ue=De(import.meta.url),Be=ne.dirname(Ue),w=ne.join(Be,"../");import x from"chalk";var k=e=>{if(e==="bun-react")return"bun";let t=process.env.npm_config_user_agent;return t?t.startsWith("yarn")?"yarn":t.startsWith("pnpm")?"pnpm":t.startsWith("bun")?"bun":"npm":"npm"};import{renderUnwrap as re,UI as y}from"instant-cli/ui";import Ve from"slugify";import Le from"ignore";var oe=async(e,t)=>{let n=d.resolve(process.cwd(),t);if(c.existsSync(n))if(c.readdirSync(n).length===0)y.log(`${x.cyan.bold(e.appName)} exists but is empty, continuing...`,y.ciaModifier(null));else{let o=await re(new y.Select({promptText:x.redBright(`${x.bold("Warning:")} ${x.bold(e.appName)} already exists and isn't empty. How would you like to proceed?`),options:[{label:"Abort installation",value:"abort"},{label:"Clear the directory and continue installation",value:"clear"}],defaultValue:"abort",modifyOutput:y.ciaModifier()}));o==="abort"&&(y.log("Aborting installation..."),process.exit(1)),o==="clear"&&c.emptyDirSync(n)}let r=Je({projectDir:n,baseTemplateName:e.base}),a=e.appName==="."?"App":x.hex("#EA570B").bold(e.appName);if(await re(new y.Spinner({promise:r,workingText:"Scaffolding project files...",doneText:`Successfully scaffolded ${a}!`,errorText:"Error scaffolding project files",modifyOutput:y.ciaModifier(null)})),c.pathExistsSync(d.join(n,"pnpm-lock.yaml"))&&c.removeSync(d.join(n,"pnpm-lock.yaml")),c.pathExistsSync(d.join(n,"bun.lock"))&&c.removeSync(d.join(n,"bun.lock")),k(e.base)==="pnpm"&&e.base==="expo"&&await c.appendFile(d.join(n,".npmrc"),`node-linker=hoisted
12
+ enable-pre-post-scripts=true`),e.base==="expo"){let o=Ve.default(t);B(d.join(n,"app.json"),'"name": "expo-template"',`"name": "${t}"`),B(d.join(n,"app.json"),'"slug": "expo-template"',`"slug": "${o}"`),B(d.join(n,"app/_layout.tsx"),'"My Instant App"',`"${t}"`)}return n},B=(e,t,n)=>{let a=c.readFileSync(e,"utf8").replaceAll(t,n);c.writeFileSync(e,a)},We=async({projectDir:e,baseTemplateName:t})=>{let n=`instantdb/instant/examples/${t}`;await Ge(n,{mode:"tar",disableCache:!0}).clone(e)};async function ze(e,t){let n=Le();n.add(".git");try{let r=d.join(e,".gitignore"),a=await c.readFile(r,"utf8");n.add(a)}catch{}await c.copy(e,t,{filter:r=>{let a=d.relative(e,r);return a===""?!0:!n.ignores(a)}})}var Je=async({projectDir:e,baseTemplateName:t})=>{let n=d.join(w,`template/base/${t}`),r=!!process.env.INSTANT_CLI_DEV&&!!process.env.INSTANT_REPO_FOLDER,a=c.pathExistsSync(n),o=r?"dev":a?"bundled-template":"tiged";if(o==="bundled-template"){c.copySync(n,e);let i=d.join(e,"_gitignore"),s=d.join(e,".gitignore");c.pathExistsSync(i)&&c.renameSync(i,s);return}if(o==="dev"){let i=process.env.INSTANT_REPO_FOLDER;if(!i)throw new Error("INSTANT_REPO_FOLDER is required when using repo-examples scaffolding.");let s=d.join(i,"examples",t);await ze(s,e);return}process.env.INSTANT_CLI_DEV&&!process.env.INSTANT_REPO_FOLDER&&y.log(x.bold.yellowBright(`WARNING: INSTANT_CLI_DEV is TRUE but no INSTANT_REPO_FOLDER is set.
13
+ Using git to clone from main...`),y.ciaModifier(null)),await We({projectDir:e,baseTemplateName:t})};import{execa as Ke}from"execa";import{renderUnwrap as He,UI as O}from"instant-cli/ui";var ae=async(e,t)=>{let n=Ke(e,["install"],{cwd:t}),r=await He(new O.Spinner({promise:n,workingText:`Installing dependencies with ${e}...`,doneText:"Successfully installed dependencies!",modifyOutput:O.ciaModifier(null)}));r.exitCode!==0&&(O.log(r.stderr,O.ciaModifier(null)),process.exit(1))};import It from"chalk";import T from"fs-extra";import u from"path";var ie=({projectDir:e,ruleFilesToAdd:t})=>{if(t!==null)switch(t){case"cursor":T.ensureDirSync(u.join(e,".cursor/rules")),T.copyFileSync(u.join(w,"template/rules/cursor-rules.md"),u.join(e,".cursor/rules/instant.mdc"));break;case"claude":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"CLAUDE.md"));break;case"codex":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"AGENTS.md"));break;case"gemini":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"GEMINI.md"));break;case"zed":T.copyFileSync(u.join(w,"template/rules/AGENTS.md"),u.join(e,"AGENTS.md"));break;case"windsurf":T.ensureDirSync(u.join(e,".windsurf/rules")),T.copyFileSync(u.join(w,"template/rules/windsurf-rules.md"),u.join(e,".windsurf/rules/instant.md"));break}};import{execSync as V}from"child_process";import G from"path";import*as pe from"@clack/prompts";import j from"chalk";import{execa as v}from"execa";import le from"fs-extra";import{renderUnwrap as se,UI as I}from"instant-cli/ui";var Ye=e=>{try{return V("git --version",{cwd:e}),!0}catch{return!1}},qe=e=>le.existsSync(G.join(e,".git")),Xe=async e=>{try{return await v("git",["rev-parse","--is-inside-work-tree"],{cwd:e,stdout:"ignore"}),!0}catch{return!1}},Ze=()=>{let t=V("git --version").toString().trim().split(" ")[2],n=t?.split(".")[0],r=t?.split(".")[1];return{major:Number(n),minor:Number(r)}},Qe=()=>V("git config --global init.defaultBranch || echo main").toString().trim(),ce=async e=>{if(!Ye(e)){pe.log.warn("Git is not installed. Skipping Git initialization.");return}let t=qe(e),n=await Xe(e),r=G.parse(e).name;if(n&&t){if(!await se(new I.Confirmation({promptText:`${j.redBright.bold("Warning:")} Git is already initialized in "${r}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,defaultValue:!1,modifyOutput:I.ciaModifier()})))return;le.removeSync(G.join(e,".git"))}else if(n&&!t&&!await se(new I.Confirmation({promptText:`${j.redBright.bold("Warning:")} "${r}" is already in a git worktree.
14
14
  Would you still like to initialize a new git repository in this directory?
15
- `,defaultValue:!1,modifyOutput:I.ciaModifier()})))return;try{let a=Qe(),{major:o,minor:i}=Ze();o<2||o==2&&i<28?(await S("git",["init"],{cwd:e}),await S("git",["symbolic-ref","HEAD",`refs/heads/${a}`],{cwd:e})):await S("git",["init",`--initial-branch=${a}`],{cwd:e}),await S("git",["add","."],{cwd:e}),await S("git",["commit","-m","Initial commit (create-instant-app)"],{cwd:e}),I.log(j.dim(`${j.green("\u2713")} Git repository initialized successfully.`),I.ciaModifier(null))}catch{}};import tt from"env-paths";import{mkdir as nt,readFile as rt,writeFile as ot}from"node:fs/promises";import at from"open";import{join as it}from"node:path";import{randomUUID as me}from"node:crypto";import{version as et}from"@instantdb/version";var de=!!process.env.INSTANT_CLI_DEV,ue=de?"http://localhost:3000":"https://instantdb.com",V=process.env.INSTANT_CLI_API_URI||(de?"http://localhost:8888":"https://api.instantdb.com");async function b({path:e,body:t,method:n="GET",authToken:r,metadata:a}){let i={"Content-Type":"application/json","X-Instant-Source":"create-instant-app","X-Instant-Version":et,"X-Instant-Command":"create"};r&&(i.Authorization=`Bearer ${r}`),a&&(i["X-Instant-Metadata"]=JSON.stringify(a));let s=await fetch(`${V}${e}`,{method:n??"GET",headers:i,body:t?JSON.stringify(t):void 0,signal:AbortSignal.timeout(3e5)}),p;try{p=await s.json()}catch{p=null}if(!s.ok){let g=p.message||p.hint?.errors?.[0]?.message||"There was an error";throw new Error(g)}return p}import{renderUnwrap as R,UI as m}from"instant-cli/ui";function F(e){return e.replace(/[-_]/g," ").replace(/\b\w/g,t=>t.toUpperCase())}var st=!!process.env.INSTANT_CLI_DEV,pt=!!process.env.INSTANT_CLI_FORCE_EPHEMERAL;function ge(){let e=`instantdb-${st?"dev":"prod"}`,{config:t}=tt(e);return{authConfigFilePath:it(t,"a"),appConfigDirPath:t}}var fe=async(e,t,n,r)=>{let a=me(),o=me(),i={id:a,title:e,admin_token:o,org_id:n};return r?.rules&&(i.rules=r.rules),r?.schema&&(i.schema=r.schema),await b({method:"POST",authToken:t,path:"/dash/apps",body:i,metadata:r}),{appID:a,adminToken:o,source:"created"}},W=(e,t,n)=>{b({method:"POST",path:`/dash/apps/${e}/track-import`,authToken:t,metadata:n}).catch(()=>{})},he=async e=>await b({method:"GET",path:"/dash",authToken:e}),we=async(e,t)=>await b({method:"GET",path:`/dash/orgs/${t}`,authToken:e});var lt=async(e,t)=>{try{let n=await he(e),r=n.apps.find(a=>a.id===t);if(r)return{appId:r.id,adminToken:r.admin_token};for(let a of n.orgs){let{apps:o}=await we(e,a.id),i=o.find(s=>s.id===t);if(i)return{appId:i.id,adminToken:i.admin_token}}return null}catch{return null}},ct=async(e,t)=>{try{return await b({method:"GET",path:`/dash/apps/${e}/schema/pull`,authToken:t}),!0}catch{return!1}},dt=async()=>pt?null:process.env.INSTANT_CLI_AUTH_TOKEN?process.env.INSTANT_CLI_AUTH_TOKEN:await rt(ge().authConfigFilePath,"utf-8").catch(()=>null),z=async(e,t)=>{let n=await b({authToken:null,method:"POST",path:"/dash/apps/ephemeral",body:{title:e,rules:{$users:{view:"true"},$files:{allow:{view:"true",create:"true",delete:"true"}}}},metadata:t});return{appId:n.app.id,adminToken:n.app["admin-token"]}},ye=async(e,t,n)=>{let r=await dt();if(t?.yes&&!t?.app)if(r){let{appID:s,adminToken:p}=await fe(F(e),r,void 0,n);return{appId:s,adminToken:p,approach:"create"}}else{let{appId:s,adminToken:p}=await z(F(e),n);return{appId:s,adminToken:p,approach:"ephemeral"}}if(t?.app){if(t.token){if(!await ct(t.app,t.token))throw new Error("Invalid app ID and token combination. Please verify both the app ID and token are correct.");return m.log(`Linking to app: ${t.app}`,m.ciaModifier(null)),W(t.app,t.token,n),{appId:t.app,adminToken:t.token,approach:"import"}}if(r){let s=await lt(r,t.app);if(!s)throw new Error(`You don't have access to app "${t.app}". Please check the app ID or use --token to provide a token.`);return m.log(`Linking to app: ${t.app}`,m.ciaModifier(null)),W(s.appId,r,n),{appId:s.appId,adminToken:s.adminToken,approach:"import"}}throw new Error(`You must be logged in or provide --token when using --app. Either run 'npx instant-cli login' first, or use: --app ${t.app} --token <token>`)}if(!r){let s=await R(new m.Select({promptText:"You are not logged in.",options:[{label:"Login to your Instant account",value:"login"},{label:"Create a temporary app",value:"ephemeral"},{label:"Create an app later",value:"skip"}],modifyOutput:m.ciaModifier()}));if(s==="login"){let p=await b({authToken:null,method:"POST",path:"/dash/cli/auth/register"}).catch(Ee=>{throw new Error("Failed to register",{cause:Ee})}),{secret:g,ticket:h}=p;at(`${ue}/dash?ticket=${h}`);let M=mt({secret:g}),J=await R(new m.Spinner({promise:M,workingText:"Waiting for login in browser",disappearWhenDone:!0,modifyOutput:m.ciaModifier(null)}));await ft(J.token),r=J.token}if(s==="skip")return m.log("Skipping app link step",m.ciaModifier(null)),null;if(s==="ephemeral"){let p=await R(new m.TextInput({defaultValue:"my-cool-app",prompt:"Enter a name for your temporary app:",placeholder:"my-cool-app",modifyOutput:m.ciaModifier()}));return{...await z(p,n),approach:"ephemeral"}}}if(!r)return null;let a=await he(r).catch(s=>{throw new Error("Failed to fetch dashboard",{cause:s})}),o=a.orgs.filter(s=>s.role!=="app-member");a.orgs=o;let i=await R(new m.AppSelector({startingMenuIndex:0,defaultAppName:F(e),allowCreate:!0,allowEphemeral:!0,api:{getDash(){return a},createEphemeralApp(s){return z(s,n)},getAppsForOrg:async s=>{let{apps:p}=await we(r,s);return{apps:p}},createApp:async(s,p)=>{let{appID:g,adminToken:h}=await fe(s,r,p,n);return{appId:g,adminToken:h}}},modifyOutput:m.ciaModifier()}));return i?.approach==="import"&&W(i.appId,r,n),i};function ut(e){return new Promise(t=>setTimeout(t,e))}async function mt({secret:e}){for(let t=1;t<=120;t++){await ut(1e3);let n=await fetch(`${V}/dash/cli/auth/check`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({secret:e})});if(!(!n.ok&&(await n.json()).hint.errors?.[0]?.issue==="waiting-for-user")&&n.ok)return n.json()}throw new Error("Timed out waiting for login")}async function ft(e){let t=ge();return await nt(t.appConfigDirPath,{recursive:!0}),ot(t.authConfigFilePath,e,"utf-8")}import gt from"fs-extra";import ht from"path";var wt={"next-js-app-dir":"NEXT_PUBLIC_INSTANT_APP_ID","vite-vanilla":"VITE_INSTANT_APP_ID",expo:"EXPO_PUBLIC_INSTANT_APP_ID","tanstack-start":"VITE_INSTANT_APP_ID","bun-react":"BUN_PUBLIC_INSTANT_APP_ID","solidjs-vite":"VITE_INSTANT_APP_ID",sveltekit:"VITE_INSTANT_APP_ID","tanstack-start-with-tanstack-query":"VITE_INSTANT_APP_ID","vercel-ai-sdk":"NEXT_PUBLIC_INSTANT_APP_ID","ai-chat":"NEXT_PUBLIC_INSTANT_APP_ID"},Te=(e,t,n,r)=>{let a=ht.join(t,".env"),i=`${wt[e.base]}=${n}
16
- INSTANT_APP_ADMIN_TOKEN=${r}`;gt.writeFileSync(a,i)};import{stdin as x,stdout as be}from"process";import{setRawModeWindowsFriendly as ke}from"instant-cli/ui";async function yt(){return new Promise(e=>{if(!be.isTTY||!x.isTTY){e(null);return}let t=x.isRaw;t||ke(x,!0);let n=setTimeout(()=>{a(),e(null)},100),r="",a=()=>{clearTimeout(n),x.removeListener("data",o),!t&&x.isTTY&&ke(x,!1)},o=i=>{let s=i.toString();r+=s;let p=r.match(/\x1b\]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)(?:\x1b\\|\x07)/);if(p){a();let[,g,h,M]=p;e(`rgb:${g}/${h}/${M}`)}};x.on("data",o),be.write("\x1B]11;?\x1B\\")})}function Tt(e){let t=e.match(/rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)/);if(!t)return null;let[,n,r,a]=t,o=Math.round(parseInt(n,16)/257),i=Math.round(parseInt(r,16)/257),s=Math.round(parseInt(a,16)/257);return{r:o,g:i,b:s}}function bt(e){return(.299*e.r+.587*e.g+.114*e.b)/255>.5}async function xe(){try{let e=await yt();if(!e)return"unknown";let t=Tt(e);return t?bt(t)?"light":"dark":"unknown"}catch(e){return console.error("Error detecting terminal theme:",e),"unknown"}}import A from"chalk";var P=(e,t)=>e==="light"?A.bgYellowBright(t):A.bgBlackBright(t),_e=(e,t=console.log,n=!1)=>{let r=(process.stdout.columns||80)-4,a=e.split(`
17
- `),o=!0;a.forEach(i=>{if(i.length===0){o?(t(""),o=!1):console.log(A.gray("\u2502"));return}for(let s=0;s<i.length;s+=r){let p=i.slice(s,s+r);s+r<i.length&&p.length===r&&(p=p+"-"),o?(t(p),o=!1):console.log(A.gray("\u2502 ")+p)}}),n&&console.log(A.gray("\u2502"))};var N="\x1B[?25h";import{execa as St}from"execa";import Ie from"fs-extra";import{permsTypescriptFileToCode as kt,schemaTypescriptFileToInstantSchema as xt}from"@instantdb/platform";function Se(e){for(let t of["src/instant.perms.ts","instant.perms.ts"])try{let n=Ie.readFileSync(`${e}/${t}`,"utf8");return{code:kt(n,t)}}catch{}return null}function Pe(e){for(let t of["src/instant.schema.ts","instant.schema.ts"])try{let n=Ie.readFileSync(`${e}/${t}`,"utf8");return xt(n,t)}catch{}return null}var Pt=async()=>{process.argv.some(h=>["-h","--help","--version","-V"].includes(h))||te();let e=await xe(),t=await Q(),[n,r]=q(t.appName),a=k(t.base),o=await oe(t,r);ie({projectDir:o,ruleFilesToAdd:t.ruleFiles});let i={template:t.base,aiTool:t.ruleFiles??"none",usedAiPrompt:!!t.prompt,rules:Se(o),schema:Pe(o)},s=await ye(r==="."?n:r,t,i);s&&Te(t,o,s.appId,s.adminToken);let p=Ae.readJSONSync(ve.join(o,"package.json"));if(p.name=n,a!=="bun"){let{stdout:h}=await St(a,["-v"],{cwd:o});p.packageManager=`${a}@${h.trim()}`}Ae.writeJSONSync(ve.join(o,"package.json"),p,{spaces:2}),await ae(k(t.base),o),t.createRepo&&await ce(o),t.prompt&&(await K(t.prompt,o),process.stdout.write(N)),_t("Done!");let g=t.base==="expo"?"start":"dev";s?(console.log(`
15
+ `,defaultValue:!1,modifyOutput:I.ciaModifier()})))return;try{let a=Qe(),{major:o,minor:i}=Ze();o<2||o==2&&i<28?(await v("git",["init"],{cwd:e}),await v("git",["symbolic-ref","HEAD",`refs/heads/${a}`],{cwd:e})):await v("git",["init",`--initial-branch=${a}`],{cwd:e}),await v("git",["add","."],{cwd:e}),await v("git",["commit","-m","Initial commit (create-instant-app)"],{cwd:e}),I.log(j.dim(`${j.green("\u2713")} Git repository initialized successfully.`),I.ciaModifier(null))}catch{}};import tt from"env-paths";import{mkdir as nt,readFile as rt,writeFile as ot}from"node:fs/promises";import at from"open";import{join as it}from"node:path";import{randomUUID as me}from"node:crypto";import{version as et}from"@instantdb/version";var de=!!process.env.INSTANT_CLI_DEV,ue=de?"http://localhost:3000":"https://instantdb.com",L=process.env.INSTANT_CLI_API_URI||(de?"http://localhost:8888":"https://api.instantdb.com");async function b({path:e,body:t,method:n="GET",authToken:r,metadata:a}){let i={"Content-Type":"application/json","X-Instant-Source":"create-instant-app","X-Instant-Version":et,"X-Instant-Command":"create"};r&&(i.Authorization=`Bearer ${r}`),a&&(i["X-Instant-Metadata"]=JSON.stringify(a));let s=await fetch(`${L}${e}`,{method:n??"GET",headers:i,body:t?JSON.stringify(t):void 0,signal:AbortSignal.timeout(3e5)}),p;try{p=await s.json()}catch{p=null}if(!s.ok){let g=p.message||p.hint?.errors?.[0]?.message||"There was an error";throw new Error(g)}return p}import{renderUnwrap as R,UI as m}from"instant-cli/ui";function F(e){return e.replace(/[-_]/g," ").replace(/\b\w/g,t=>t.toUpperCase())}var st=!!process.env.INSTANT_CLI_DEV,pt=!!process.env.INSTANT_CLI_FORCE_EPHEMERAL;function ge(){let e=`instantdb-${st?"dev":"prod"}`,{config:t}=tt(e);return{authConfigFilePath:it(t,"a"),appConfigDirPath:t}}var fe=async(e,t,n,r)=>{let a=me(),o=me(),i={id:a,title:e,admin_token:o,org_id:n};return r?.rules&&(i.rules=r.rules),r?.schema&&(i.schema=r.schema),await b({method:"POST",authToken:t,path:"/dash/apps",body:i,metadata:r}),{appID:a,adminToken:o,source:"created"}},W=(e,t,n)=>{b({method:"POST",path:`/dash/apps/${e}/track-import`,authToken:t,metadata:n}).catch(()=>{})},he=async e=>await b({method:"GET",path:"/dash",authToken:e}),we=async(e,t)=>await b({method:"GET",path:`/dash/orgs/${t}`,authToken:e});var lt=async(e,t)=>{try{let n=await he(e),r=n.apps.find(a=>a.id===t);if(r)return{appId:r.id,adminToken:r.admin_token};for(let a of n.orgs){let{apps:o}=await we(e,a.id),i=o.find(s=>s.id===t);if(i)return{appId:i.id,adminToken:i.admin_token}}return null}catch{return null}},ct=async(e,t)=>{try{return await b({method:"GET",path:`/dash/apps/${e}/schema/pull`,authToken:t}),!0}catch{return!1}},dt=async()=>pt?null:process.env.INSTANT_CLI_AUTH_TOKEN?process.env.INSTANT_CLI_AUTH_TOKEN:await rt(ge().authConfigFilePath,"utf-8").catch(()=>null),z=async(e,t)=>{let n=await b({authToken:null,method:"POST",path:"/dash/apps/ephemeral",body:{title:e,rules:{$users:{view:"true"},$files:{allow:{view:"true",create:"true",delete:"true"}}}},metadata:t});return{appId:n.app.id,adminToken:n.app["admin-token"]}},ye=async(e,t,n)=>{let r=await dt();if(t?.yes&&!t?.app)if(r){let{appID:s,adminToken:p}=await fe(F(e),r,void 0,n);return{appId:s,adminToken:p,approach:"create"}}else{let{appId:s,adminToken:p}=await z(F(e),n);return{appId:s,adminToken:p,approach:"ephemeral"}}if(t?.app){if(t.token){if(!await ct(t.app,t.token))throw new Error("Invalid app ID and token combination. Please verify both the app ID and token are correct.");return m.log(`Linking to app: ${t.app}`,m.ciaModifier(null)),W(t.app,t.token,n),{appId:t.app,adminToken:t.token,approach:"import"}}if(r){let s=await lt(r,t.app);if(!s)throw new Error(`You don't have access to app "${t.app}". Please check the app ID or use --token to provide a token.`);return m.log(`Linking to app: ${t.app}`,m.ciaModifier(null)),W(s.appId,r,n),{appId:s.appId,adminToken:s.adminToken,approach:"import"}}throw new Error(`You must be logged in or provide --token when using --app. Either run 'npx instant-cli login' first, or use: --app ${t.app} --token <token>`)}if(!r){let s=await R(new m.Select({promptText:"You are not logged in.",options:[{label:"Login to your Instant account",value:"login"},{label:"Create a temporary app",value:"ephemeral"},{label:"Create an app later",value:"skip"}],modifyOutput:m.ciaModifier()}));if(s==="login"){let p=await b({authToken:null,method:"POST",path:"/dash/cli/auth/register"}).catch(Ee=>{throw new Error("Failed to register",{cause:Ee})}),{secret:g,ticket:h}=p;at(`${ue}/dash?ticket=${h}`);let M=mt({secret:g}),J=await R(new m.Spinner({promise:M,workingText:"Waiting for login in browser",disappearWhenDone:!0,modifyOutput:m.ciaModifier(null)}));await ft(J.token),r=J.token}if(s==="skip")return m.log("Skipping app link step",m.ciaModifier(null)),null;if(s==="ephemeral"){let p=await R(new m.TextInput({defaultValue:"my-cool-app",prompt:"Enter a name for your temporary app:",placeholder:"my-cool-app",modifyOutput:m.ciaModifier()}));return{...await z(p,n),approach:"ephemeral"}}}if(!r)return null;let a=await he(r).catch(s=>{throw new Error("Failed to fetch dashboard",{cause:s})}),o=a.orgs.filter(s=>s.role!=="app-member");a.orgs=o;let i=await R(new m.AppSelector({startingMenuIndex:0,defaultAppName:F(e),allowCreate:!0,allowEphemeral:!0,api:{getDash(){return a},createEphemeralApp(s){return z(s,n)},getAppsForOrg:async s=>{let{apps:p}=await we(r,s);return{apps:p}},createApp:async(s,p)=>{let{appID:g,adminToken:h}=await fe(s,r,p,n);return{appId:g,adminToken:h}}},modifyOutput:m.ciaModifier()}));return i?.approach==="import"&&W(i.appId,r,n),i};function ut(e){return new Promise(t=>setTimeout(t,e))}async function mt({secret:e}){for(let t=1;t<=120;t++){await ut(1e3);let n=await fetch(`${L}/dash/cli/auth/check`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({secret:e})});if(!(!n.ok&&(await n.json()).hint.errors?.[0]?.issue==="waiting-for-user")&&n.ok)return n.json()}throw new Error("Timed out waiting for login")}async function ft(e){let t=ge();return await nt(t.appConfigDirPath,{recursive:!0}),ot(t.authConfigFilePath,e,"utf-8")}import gt from"fs-extra";import ht from"path";var wt={"next-js-app-dir":"NEXT_PUBLIC_INSTANT_APP_ID","vite-react":"VITE_INSTANT_APP_ID","vite-vanilla":"VITE_INSTANT_APP_ID",expo:"EXPO_PUBLIC_INSTANT_APP_ID","tanstack-start":"VITE_INSTANT_APP_ID","bun-react":"BUN_PUBLIC_INSTANT_APP_ID","solidjs-vite":"VITE_INSTANT_APP_ID",sveltekit:"VITE_INSTANT_APP_ID","tanstack-start-with-tanstack-query":"VITE_INSTANT_APP_ID","vercel-ai-sdk":"NEXT_PUBLIC_INSTANT_APP_ID","ai-chat":"NEXT_PUBLIC_INSTANT_APP_ID"},Te=(e,t,n,r)=>{let a=ht.join(t,".env"),i=`${wt[e.base]}=${n}
16
+ INSTANT_APP_ADMIN_TOKEN=${r}`;gt.writeFileSync(a,i)};import{stdin as _,stdout as be}from"process";import{setRawModeWindowsFriendly as ke}from"instant-cli/ui";async function yt(){return new Promise(e=>{if(!be.isTTY||!_.isTTY){e(null);return}let t=_.isRaw;t||ke(_,!0);let n=setTimeout(()=>{a(),e(null)},100),r="",a=()=>{clearTimeout(n),_.removeListener("data",o),!t&&_.isTTY&&ke(_,!1)},o=i=>{let s=i.toString();r+=s;let p=r.match(/\x1b\]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)(?:\x1b\\|\x07)/);if(p){a();let[,g,h,M]=p;e(`rgb:${g}/${h}/${M}`)}};_.on("data",o),be.write("\x1B]11;?\x1B\\")})}function Tt(e){let t=e.match(/rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)/);if(!t)return null;let[,n,r,a]=t,o=Math.round(parseInt(n,16)/257),i=Math.round(parseInt(r,16)/257),s=Math.round(parseInt(a,16)/257);return{r:o,g:i,b:s}}function bt(e){return(.299*e.r+.587*e.g+.114*e.b)/255>.5}async function _e(){try{let e=await yt();if(!e)return"unknown";let t=Tt(e);return t?bt(t)?"light":"dark":"unknown"}catch(e){return console.error("Error detecting terminal theme:",e),"unknown"}}import A from"chalk";var S=(e,t)=>e==="light"?A.bgYellowBright(t):A.bgBlackBright(t),xe=(e,t=console.log,n=!1)=>{let r=(process.stdout.columns||80)-4,a=e.split(`
17
+ `),o=!0;a.forEach(i=>{if(i.length===0){o?(t(""),o=!1):console.log(A.gray("\u2502"));return}for(let s=0;s<i.length;s+=r){let p=i.slice(s,s+r);s+r<i.length&&p.length===r&&(p=p+"-"),o?(t(p),o=!1):console.log(A.gray("\u2502 ")+p)}}),n&&console.log(A.gray("\u2502"))};var N="\x1B[?25h";import{execa as vt}from"execa";import Ie from"fs-extra";import{permsTypescriptFileToCode as kt,schemaTypescriptFileToInstantSchema as _t}from"@instantdb/platform";function ve(e){for(let t of["src/instant.perms.ts","instant.perms.ts"])try{let n=Ie.readFileSync(`${e}/${t}`,"utf8");return{code:kt(n,t)}}catch{}return null}function Se(e){for(let t of["src/instant.schema.ts","instant.schema.ts"])try{let n=Ie.readFileSync(`${e}/${t}`,"utf8");return _t(n,t)}catch{}return null}var St=async()=>{process.argv.some(h=>["-h","--help","--version","-V"].includes(h))||te();let e=await _e(),t=await Q(),[n,r]=q(t.appName),a=k(t.base),o=await oe(t,r);ie({projectDir:o,ruleFilesToAdd:t.ruleFiles});let i={template:t.base,aiTool:t.ruleFiles??"none",usedAiPrompt:!!t.prompt,rules:ve(o),schema:Se(o)},s=await ye(r==="."?n:r,t,i);s&&Te(t,o,s.appId,s.adminToken);let p=Ae.readJSONSync(Pe.join(o,"package.json"));if(p.name=n,a!=="bun"){let{stdout:h}=await vt(a,["-v"],{cwd:o});p.packageManager=`${a}@${h.trim()}`}Ae.writeJSONSync(Pe.join(o,"package.json"),p,{spaces:2}),await ae(k(t.base),o),t.createRepo&&await ce(o),t.prompt&&(await K(t.prompt,o),process.stdout.write(N)),xt("Done!");let g=t.base==="expo"?"start":"dev";s?(console.log(`
18
18
  \u{1F389} Success! Your project is ready to go!
19
19
 
20
20
  To get started:
21
- 1. ${P(e,"cd "+r)}
22
- 2. ${P(e,k(t.base)+" run "+g)}
21
+ 1. ${S(e,"cd "+r)}
22
+ 2. ${S(e,k(t.base)+" run "+g)}
23
23
  `),s.approach==="ephemeral"&&console.log(`
24
24
  An ephemeral app has been created and added to your .env file.
25
- It will expire in two weeks. For a permanent app, sign in and use ${P(e,"npx instant-cli claim")}
25
+ It will expire in two weeks. For a permanent app, sign in and use ${S(e,"npx instant-cli claim")}
26
26
  `)):console.log(`
27
27
  \u{1F389} Success! Your project is ready to go!
28
28
 
29
29
  To get started:
30
- 1. ${P(e,"cd "+r)}
30
+ 1. ${S(e,"cd "+r)}
31
31
  2. Create a new app on ${It.underline("www.instantdb.com")}
32
32
  3. Add your APP_ID to the .env file
33
- 4. ${P(e,k(t.base)+" run "+g)}
34
- `),process.stdout.write(N),process.exit(0)};Pt().catch(e=>{Ne.error("Aborting installation..."),_e(e.message,Ne.error),process.stdout.write(N),process.exit(1)});process.on("SIGINT",()=>{process.stdout.write(N),process.exit(0)});
33
+ 4. ${S(e,k(t.base)+" run "+g)}
34
+ `),process.stdout.write(N),process.exit(0)};St().catch(e=>{Ne.error("Aborting installation..."),xe(e.message,Ne.error),process.stdout.write(N),process.exit(1)});process.on("SIGINT",()=>{process.stdout.write(N),process.exit(0)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-instant-app",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Scaffold a new web/mobile app with InstantDB",
5
5
  "homepage": "https://github.com/instantdb/instant/tree/main/client/packages/create-instant-app",
6
6
  "repository": {
@@ -35,9 +35,9 @@
35
35
  "slugify": "^1.6.6",
36
36
  "sort-package-json": "^2.10.0",
37
37
  "tiged": "^2.12.7",
38
- "@instantdb/platform": "1.0.9",
39
- "@instantdb/version": "1.0.9",
40
- "instant-cli": "1.0.9"
38
+ "@instantdb/version": "1.0.10",
39
+ "instant-cli": "1.0.10",
40
+ "@instantdb/platform": "1.0.10"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@anthropic-ai/sdk": "^0.60.0",
@@ -0,0 +1,16 @@
1
+ # Welcome to your InstantDB Vite + React app 👋
2
+
3
+ [InstantDB Docs](https://www.instantdb.com/docs)
4
+
5
+ This is a Vite + React project scaffolded with create-instant-app.
6
+
7
+ To run the development server:
8
+ `npm run dev`
9
+
10
+ To push schema changes:
11
+ `npx instant-cli push`
12
+
13
+ To pull schema changes:
14
+ `npx instant-cli pull`
15
+
16
+ Got any feedback or questions? Join our [Discord](https://discord.gg/hgVf9R6SBm)
@@ -0,0 +1,26 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
25
+
26
+ .env
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs.flat.recommended,
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Create Instant App</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "vite-react-template",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@instantdb/react": "latest",
14
+ "@instantdb/admin": "latest",
15
+ "react": "^19.2.4",
16
+ "react-dom": "^19.2.4"
17
+ },
18
+ "devDependencies": {
19
+ "@eslint/js": "^9.39.4",
20
+ "@tailwindcss/vite": "^4.0.6",
21
+ "@types/node": "^24.12.2",
22
+ "@types/react": "^19.2.14",
23
+ "@types/react-dom": "^19.2.3",
24
+ "@vitejs/plugin-react": "^6.0.1",
25
+ "eslint": "^9.39.4",
26
+ "eslint-plugin-react-hooks": "^7.0.1",
27
+ "eslint-plugin-react-refresh": "^0.5.2",
28
+ "globals": "^17.4.0",
29
+ "tailwindcss": "^4.0.6",
30
+ "typescript": "~6.0.2",
31
+ "typescript-eslint": "^8.58.0",
32
+ "vite": "^8.0.4"
33
+ },
34
+ "packageManager": "pnpm@10.2.0"
35
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
@@ -0,0 +1,167 @@
1
+ import { db } from "./lib/db";
2
+ import { type AppSchema } from "./instant.schema";
3
+ import { id, type InstaQLEntity } from "@instantdb/react";
4
+
5
+ type Todo = InstaQLEntity<AppSchema, "todos">;
6
+
7
+ const room = db.room("todos");
8
+
9
+ function App() {
10
+ // Read Data
11
+ const { isLoading, error, data } = db.useQuery({ todos: {} });
12
+ const { peers } = db.rooms.usePresence(room);
13
+ const numUsers = 1 + Object.keys(peers).length;
14
+ if (isLoading) {
15
+ return null;
16
+ }
17
+ if (error) {
18
+ return <div className="text-red-500 p-4">Error: {error.message}</div>;
19
+ }
20
+ const { todos } = data;
21
+ return (
22
+ <div className="font-mono min-h-screen flex justify-center items-center flex-col space-y-4">
23
+ <div className="text-xs text-gray-500">
24
+ Number of users online: {numUsers}
25
+ </div>
26
+ <h2 className="tracking-wide text-5xl text-gray-300">todos</h2>
27
+ <div className="border border-gray-300 max-w-xs w-full">
28
+ <TodoForm todos={todos} />
29
+ <TodoList todos={todos} />
30
+ <ActionBar todos={todos} />
31
+ </div>
32
+ <div className="text-xs text-center">
33
+ Open another tab to see todos update in realtime!
34
+ </div>
35
+ </div>
36
+ );
37
+ }
38
+
39
+ // Write Data
40
+ // ---------
41
+ function addTodo(text: string) {
42
+ db.transact(
43
+ db.tx.todos[id()].update({
44
+ text,
45
+ done: false,
46
+ createdAt: Date.now(),
47
+ }),
48
+ );
49
+ }
50
+
51
+ function deleteTodo(todo: Todo) {
52
+ db.transact(db.tx.todos[todo.id].delete());
53
+ }
54
+
55
+ function toggleDone(todo: Todo) {
56
+ db.transact(db.tx.todos[todo.id].update({ done: !todo.done }));
57
+ }
58
+
59
+ function deleteCompleted(todos: Todo[]) {
60
+ const completed = todos.filter((todo) => todo.done);
61
+ const txs = completed.map((todo) => db.tx.todos[todo.id].delete());
62
+ db.transact(txs);
63
+ }
64
+
65
+ function toggleAll(todos: Todo[]) {
66
+ const newVal = !todos.every((todo) => todo.done);
67
+ db.transact(
68
+ todos.map((todo) => db.tx.todos[todo.id].update({ done: newVal })),
69
+ );
70
+ }
71
+
72
+ // Components
73
+ // ----------
74
+ function ChevronDownIcon() {
75
+ return (
76
+ <svg viewBox="0 0 20 20">
77
+ <path
78
+ d="M5 8 L10 13 L15 8"
79
+ stroke="currentColor"
80
+ fill="none"
81
+ strokeWidth="2"
82
+ />
83
+ </svg>
84
+ );
85
+ }
86
+
87
+ function TodoForm({ todos }: { todos: Todo[] }) {
88
+ return (
89
+ <div className="flex items-center h-10 border-b border-gray-300">
90
+ <button
91
+ className="h-full px-2 border-r border-gray-300 flex items-center justify-center"
92
+ onClick={() => toggleAll(todos)}
93
+ >
94
+ <div className="w-5 h-5">
95
+ <ChevronDownIcon />
96
+ </div>
97
+ </button>
98
+ <form
99
+ className="flex-1 h-full"
100
+ onSubmit={(e) => {
101
+ e.preventDefault();
102
+ const input = e.currentTarget.input as HTMLInputElement;
103
+ addTodo(input.value);
104
+ input.value = "";
105
+ }}
106
+ >
107
+ <input
108
+ className="w-full h-full px-2 outline-none bg-transparent"
109
+ autoFocus
110
+ placeholder="What needs to be done?"
111
+ type="text"
112
+ name="input"
113
+ />
114
+ </form>
115
+ </div>
116
+ );
117
+ }
118
+
119
+ function TodoList({ todos }: { todos: Todo[] }) {
120
+ return (
121
+ <div className="divide-y divide-gray-300">
122
+ {todos.map((todo) => (
123
+ <div key={todo.id} className="flex items-center h-10">
124
+ <div className="h-full px-2 flex items-center justify-center">
125
+ <div className="w-5 h-5 flex items-center justify-center">
126
+ <input
127
+ type="checkbox"
128
+ className="cursor-pointer"
129
+ checked={todo.done}
130
+ onChange={() => toggleDone(todo)}
131
+ />
132
+ </div>
133
+ </div>
134
+ <div className="flex-1 px-2 overflow-hidden flex items-center">
135
+ {todo.done ? (
136
+ <span className="line-through">{todo.text}</span>
137
+ ) : (
138
+ <span>{todo.text}</span>
139
+ )}
140
+ </div>
141
+ <button
142
+ className="h-full px-2 flex items-center justify-center text-gray-300 hover:text-gray-500"
143
+ onClick={() => deleteTodo(todo)}
144
+ >
145
+ X
146
+ </button>
147
+ </div>
148
+ ))}
149
+ </div>
150
+ );
151
+ }
152
+
153
+ function ActionBar({ todos }: { todos: Todo[] }) {
154
+ return (
155
+ <div className="flex justify-between items-center h-10 px-2 text-xs border-t border-gray-300">
156
+ <div>Remaining todos: {todos.filter((todo) => !todo.done).length}</div>
157
+ <button
158
+ className=" text-gray-300 hover:text-gray-500"
159
+ onClick={() => deleteCompleted(todos)}
160
+ >
161
+ Delete Completed
162
+ </button>
163
+ </div>
164
+ );
165
+ }
166
+
167
+ export default App;
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,24 @@
1
+ // Docs: https://www.instantdb.com/docs/permissions
2
+
3
+ import type { InstantRules } from "@instantdb/react";
4
+
5
+ const rules = {
6
+ /**
7
+ * Welcome to Instant's permission system!
8
+ * Right now your rules are empty. To start filling them in, check out the docs:
9
+ * https://www.instantdb.com/docs/permissions
10
+ *
11
+ * Here's an example to give you a feel:
12
+ * posts: {
13
+ * allow: {
14
+ * view: "true",
15
+ * create: "isOwner",
16
+ * update: "isOwner",
17
+ * delete: "isOwner",
18
+ * },
19
+ * bind: ["isOwner", "auth.id != null && auth.id == data.ownerId"],
20
+ * },
21
+ */
22
+ } satisfies InstantRules;
23
+
24
+ export default rules;
@@ -0,0 +1,51 @@
1
+ // Docs: https://www.instantdb.com/docs/modeling-data
2
+
3
+ import { i } from "@instantdb/react";
4
+
5
+ const _schema = i.schema({
6
+ entities: {
7
+ $files: i.entity({
8
+ path: i.string().unique().indexed(),
9
+ url: i.string(),
10
+ }),
11
+ $users: i.entity({
12
+ email: i.string().unique().indexed().optional(),
13
+ imageURL: i.string().optional(),
14
+ type: i.string().optional(),
15
+ }),
16
+ todos: i.entity({
17
+ text: i.string(),
18
+ done: i.boolean(),
19
+ createdAt: i.number(),
20
+ }),
21
+ },
22
+ links: {
23
+ $usersLinkedPrimaryUser: {
24
+ forward: {
25
+ on: "$users",
26
+ has: "one",
27
+ label: "linkedPrimaryUser",
28
+ onDelete: "cascade",
29
+ },
30
+ reverse: {
31
+ on: "$users",
32
+ has: "many",
33
+ label: "linkedGuestUsers",
34
+ },
35
+ },
36
+ },
37
+ rooms: {
38
+ todos: {
39
+ presence: i.entity({}),
40
+ },
41
+ },
42
+ });
43
+
44
+ // This helps TypeScript display nicer intellisense
45
+ type _AppSchema = typeof _schema;
46
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
47
+ interface AppSchema extends _AppSchema {}
48
+ const schema: AppSchema = _schema;
49
+
50
+ export type { AppSchema };
51
+ export default schema;
@@ -0,0 +1,8 @@
1
+ import { init } from "@instantdb/react";
2
+ import schema from "../instant.schema";
3
+
4
+ export const db = init({
5
+ appId: import.meta.env.VITE_INSTANT_APP_ID,
6
+ schema,
7
+ useDateObjects: true,
8
+ });
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
6
+ "module": "esnext",
7
+ "types": ["vite/client"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Linting */
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true
23
+ },
24
+ "include": ["src"]
25
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "module": "esnext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["vite.config.ts"]
24
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react(), tailwindcss()],
8
+ })