create-tauri-ui 1.0.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -4
- package/dist/index.mjs +89 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,12 @@ bunx tauri icon app-icon.png
|
|
|
33
33
|
## CLI reference
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
Usage:
|
|
36
|
+
Usage:
|
|
37
|
+
create-tauri-ui [target-dir] [options] scaffold a new project
|
|
38
|
+
create-tauri-ui <add|update|remove> <battery> manage batteries in an existing project
|
|
39
|
+
create-tauri-ui list list batteries + install status
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
Scaffold options:
|
|
39
42
|
-t, --template <name> vite | next | start | react-router | astro
|
|
40
43
|
--identifier <value> Tauri app identifier (e.g. com.example.myapp)
|
|
41
44
|
--preset <value> shadcn preset (default: b0)
|
|
@@ -47,12 +50,43 @@ Options:
|
|
|
47
50
|
--no-invoke-example skip the Rust invoke example
|
|
48
51
|
--workflow include the GitHub release workflow
|
|
49
52
|
--no-workflow skip the GitHub release workflow
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
|
|
54
|
+
Manage options:
|
|
55
|
+
--dir <path> project directory (default: current working dir)
|
|
56
|
+
--target-os <list> comma-separated platforms for workflow
|
|
57
|
+
(windows-latest,macos-latest,ubuntu-latest)
|
|
58
|
+
-f, --force overwrite an existing target directory / battery
|
|
59
|
+
-y, --yes accept defaults / skip confirmations
|
|
52
60
|
-v, --version display version
|
|
53
61
|
-h, --help display help
|
|
62
|
+
|
|
63
|
+
Batteries: debug-panel, workflow
|
|
54
64
|
```
|
|
55
65
|
|
|
66
|
+
## Managing batteries in an existing project
|
|
67
|
+
|
|
68
|
+
Run inside a scaffolded project to install, update, or remove a battery after the fact.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# see which batteries are installed
|
|
72
|
+
bunx create-tauri-ui@latest list
|
|
73
|
+
|
|
74
|
+
# upgrade the debug panel to the latest template
|
|
75
|
+
bunx create-tauri-ui@latest update debug-panel
|
|
76
|
+
|
|
77
|
+
# add the release workflow later
|
|
78
|
+
bunx create-tauri-ui@latest add workflow --target-os macos-latest,ubuntu-latest
|
|
79
|
+
|
|
80
|
+
# remove it
|
|
81
|
+
bunx create-tauri-ui@latest remove workflow
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`update` re-applies the battery using the latest CLI templates. Patches are idempotent — existing mounts, imports, and plugin registrations are preserved. Commit first, then run `update` and inspect the diff.
|
|
85
|
+
|
|
86
|
+
`remove` deletes the battery's owned files and best-effort reverts any wiring. For batteries with external dependencies (like the debug panel's `tauri-plugin-log` + `@tauri-apps/plugin-log`), the CLI prints a manual cleanup checklist.
|
|
87
|
+
|
|
88
|
+
The template is auto-detected from `package.json` and project structure — no manifest file is written to your repo.
|
|
89
|
+
|
|
56
90
|
## Examples
|
|
57
91
|
|
|
58
92
|
**Vite app with all defaults:**
|
package/dist/index.mjs
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import s from"node:fs";import
|
|
2
|
-
`);s.writeFileSync(e,t(r),"utf-8")}function
|
|
3
|
-
`)}async function
|
|
1
|
+
import s from"node:fs";import i from"node:path";import h from"node:process";import{intro as ae,outro as H,cancel as W,note as I,log as m,confirm as y,isCancel as ie,multiselect as oe,text as O,select as Ie,spinner as Oe}from"@clack/prompts";import f from"picocolors";import se from"node:os";import{execFile as Re}from"node:child_process";import{fileURLToPath as K}from"node:url";const R=new Set;let le=!1,ce=!1;class z extends Error{constructor(t,r,n,a){super(t),this.stdout=r,this.stderr=n,this.code=a}}class D extends Error{constructor(t,r,n){super(r),this.tool=t,this.stderr=n}}class u extends Error{constructor(t,r){super(r),this.file=t}}function ze(e){return e?.trim().replace(/\/+$/g,"")}function ue(e,t){s.statSync(e).isDirectory()?de(e,t):(s.mkdirSync(i.dirname(t),{recursive:!0}),s.copyFileSync(e,t))}function Y(e){return/^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(e)}function pe(e){return e.trim().toLowerCase().replace(/\s+/g,"-").replace(/^[._]/,"").replace(/[^a-z\d\-~]+/g,"-")}function de(e,t){s.mkdirSync(t,{recursive:!0});for(const r of s.readdirSync(e)){const n=i.resolve(e,r),a=i.resolve(t,r);ue(n,a)}}function Fe(e){const t=s.readdirSync(e);return t.length===0||t.length===1&&t[0]===".git"}function c(e,t){const r=s.readFileSync(e,"utf-8").replace(/\r\n/g,`
|
|
2
|
+
`);s.writeFileSync(e,t(r),"utf-8")}function v(e,t){const r=JSON.parse(s.readFileSync(e,"utf-8")),n=t(r);s.writeFileSync(e,JSON.stringify(n,null,2)+`
|
|
3
|
+
`)}async function F(e,t,r){return await new Promise((n,a)=>{Re(e,t,{cwd:r?.cwd,env:{...process.env,...r?.env},maxBuffer:1024*1024*10},(o,l,p)=>{if(o){a(new z(`Command failed: ${e} ${t.join(" ")}`,l,p,typeof o.code=="number"?o.code:null));return}n(l.trim())})})}function me(e){return s.mkdtempSync(i.join(se.tmpdir(),e))}function J(e){R.add(e)}function k(e){R.delete(e)}function $(e){s.existsSync(e)&&s.rmSync(e,{recursive:!0,force:!0})}function Ue(){const e=[...R].sort((t,r)=>r.length-t.length);for(const t of e)try{$(t)}catch{}finally{R.delete(t)}}function Me(){if(le)return;le=!0;const e=(t,r)=>{ce&&process.exit(r),ce=!0,Ue(),process.exit(r)};process.once("SIGINT",()=>e("SIGINT",130)),process.once("SIGTERM",()=>e("SIGTERM",143))}function Ve(e){return e.replace(/^@[^/]+\//,"").replace(/[^a-zA-Z0-9]+/g,"_").replace(/^_+|_+$/g,"")}const Be={name:"astro",async apply(e,t){c(i.join(e,"astro.config.mjs"),r=>{if(r.includes("server: {"))return r;const n=r.lastIndexOf(`
|
|
4
4
|
})`);if(n===-1)throw new u("astro.config.mjs","Could not find the Astro config closing brace.");return`${r.slice(0,n)}
|
|
5
5
|
server: {
|
|
6
6
|
port: 1420,
|
|
7
|
-
},${r.slice(n)}`})},tauriConfig(){return{frontendDist:"../dist",devUrl:"http://localhost:1420",beforeDevCommand:"bun run dev",beforeBuildCommand:"bun run build"}}},
|
|
7
|
+
},${r.slice(n)}`})},tauriConfig(){return{frontendDist:"../dist",devUrl:"http://localhost:1420",beforeDevCommand:"bun run dev",beforeBuildCommand:"bun run build"}}},He={name:"next",async apply(e,t){c(i.join(e,"next.config.mjs"),r=>{if(r.includes('output: "export"'))return r;if(!r.includes("const nextConfig = {}"))throw new u("next.config.mjs","Could not find the default Next.js config shape.");return r.replace("const nextConfig = {}",`const nextConfig = {
|
|
8
8
|
output: "export",
|
|
9
9
|
images: {
|
|
10
10
|
unoptimized: true,
|
|
11
11
|
},
|
|
12
|
-
}`)}),
|
|
12
|
+
}`)}),v(i.join(e,"package.json"),r=>(r.scripts?.dev&&(r.scripts.dev=r.scripts.dev.replace("next dev --turbopack","next dev --turbopack -p 1420")),r))},tauriConfig(){return{frontendDist:"../out",devUrl:"http://localhost:1420",beforeDevCommand:"bun run dev",beforeBuildCommand:"bun run build"}}};function We(e){if(e.includes("strictPort: true"))return e;const t=e.lastIndexOf(`
|
|
13
13
|
})`);if(t===-1)throw new u("vite.config.ts","Could not find the React Router Vite config closing brace.");return`${e.slice(0,t)}
|
|
14
14
|
server: {
|
|
15
15
|
port: 1420,
|
|
16
16
|
strictPort: true,
|
|
17
|
-
},${e.slice(t)}`}const
|
|
17
|
+
},${e.slice(t)}`}const Ke={name:"react-router",async apply(e,t){c(i.join(e,"react-router.config.ts"),r=>{if(r.includes("ssr: false"))return r;if(!r.includes("ssr: true"))throw new u("react-router.config.ts","Could not find the SSR flag in the generated React Router config.");return r.replace("ssr: true","ssr: false")}),c(i.join(e,"vite.config.ts"),We)},tauriConfig(){return{frontendDist:"../build/client",devUrl:"http://localhost:1420",beforeDevCommand:"bun run dev",beforeBuildCommand:"bun run build"}}};function Ye(e){if(e.includes("strictPort: true"))return e;const t=e.lastIndexOf(`
|
|
18
18
|
})`);if(t===-1)throw new u("vite.config.ts","Could not find the TanStack Start config closing brace.");return`${e.slice(0,t)}
|
|
19
19
|
server: {
|
|
20
20
|
port: 1420,
|
|
21
21
|
strictPort: true,
|
|
22
|
-
},${e.slice(t)}`}const
|
|
22
|
+
},${e.slice(t)}`}const Je={name:"start",async apply(e,t){c(i.join(e,"vite.config.ts"),r=>{let n=Ye(r);if(!n.includes("tanstackStart({ spa: { enabled: true } })")){if(!n.includes("tanstackStart(),"))throw new u("vite.config.ts","Could not find tanstackStart() in the generated Vite config.");n=n.replace("tanstackStart(),","tanstackStart({ spa: { enabled: true } }),")}return n}),v(i.join(e,"package.json"),r=>(r.scripts?.dev&&(r.scripts.dev=r.scripts.dev.replace("--port 3000","--port 1420")),r))},tauriConfig(){return{frontendDist:"../.output/public",devUrl:"http://localhost:1420",beforeDevCommand:"bun run dev",beforeBuildCommand:"bun run build"}}};function Xe(e){if(e.includes("strictPort: true"))return e;const t=e.lastIndexOf(`
|
|
23
23
|
})`);if(t===-1)throw new u("vite.config.ts","Could not find the Vite config closing brace.");return`${e.slice(0,t)}
|
|
24
24
|
server: {
|
|
25
25
|
port: 1420,
|
|
26
26
|
strictPort: true,
|
|
27
|
-
},${e.slice(t)}`}const
|
|
27
|
+
},${e.slice(t)}`}const qe={name:"vite",async apply(e,t){c(i.join(e,"vite.config.ts"),Xe)},tauriConfig(){return{frontendDist:"../dist",devUrl:"http://localhost:1420",beforeDevCommand:"bun run dev",beforeBuildCommand:"bun run build"}}},Ze={vite:qe,next:He,start:Je,"react-router":Ke,astro:Be};function Qe(e){return Ze[e]}async function fe(e,t){try{await F("bunx",["--bun","shadcn@latest",...t],{cwd:e})}catch(r){throw r instanceof z?new D("shadcn","shadcn CLI failed while updating the frontend scaffold.",r.stderr||r.stdout):r}}const et="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 96'%3E%3Crect width='96' height='96' rx='24' fill='%23181f2a'/%3E%3Ccircle cx='48' cy='34' r='18' fill='%23f8fafc'/%3E%3Cpath d='M18 82c6-14 18-22 30-22s24 8 30 22' fill='%23f8fafc'/%3E%3C/svg%3E";function tt(e){const t=i.join(e,"app/layout.tsx");s.existsSync(t)&&c(t,r=>{let n=r;if(n.includes('import { TooltipProvider } from "@/components/ui/tooltip"')||(n=n.replace(`import { ThemeProvider } from "@/components/theme-provider"
|
|
28
28
|
`,`import { ThemeProvider } from "@/components/theme-provider"
|
|
29
29
|
import { TooltipProvider } from "@/components/ui/tooltip"
|
|
30
|
-
`)),n.includes("<TooltipProvider>"))return n;const
|
|
30
|
+
`)),n.includes("<TooltipProvider>"))return n;const a=n.replace(/<ThemeProvider>([\s\S]*?)<\/ThemeProvider>/,"<ThemeProvider><TooltipProvider>$1</TooltipProvider></ThemeProvider>");if(a===n)throw new u(t,"Could not wrap the Next.js layout with TooltipProvider.");return a})}function rt(e,t){const r=t.template==="next"?i.join(e,"components/app-sidebar.tsx"):t.template==="react-router"?i.join(e,"app/components/app-sidebar.tsx"):i.join(e,"src/components/app-sidebar.tsx");s.existsSync(r)&&c(r,n=>n.replace('avatar: "/avatars/shadcn.jpg"',`avatar: "${et}"`))}function P(e,t,r,n){return`import type { CSSProperties } from "react"
|
|
31
31
|
import { AppSidebar } from "${t}components/app-sidebar"
|
|
32
32
|
import { ChartAreaInteractive } from "${t}components/chart-area-interactive"
|
|
33
33
|
import { DataTable } from "${t}components/data-table"
|
|
@@ -74,11 +74,11 @@ ${n?` <div className="px-4 lg:px-6">
|
|
|
74
74
|
</TooltipProvider>
|
|
75
75
|
)
|
|
76
76
|
}
|
|
77
|
-
`}function
|
|
78
|
-
${
|
|
77
|
+
`}function nt(e){return`import { createFileRoute } from "@tanstack/react-router"
|
|
78
|
+
${P("DashboardPage","@/","@/app/dashboard/data.json",e)}export const Route = createFileRoute("/")({
|
|
79
79
|
component: DashboardPage,
|
|
80
80
|
})
|
|
81
|
-
`}function
|
|
81
|
+
`}function at(e){return P("DashboardShell","@/","@/app/dashboard/data.json",e).replace("export default function DashboardShell()","export function DashboardShell()")}function it(e,t){try{s.renameSync(e,t);return}catch(r){if(!(r instanceof Error)||!("code"in r)||r.code!=="EXDEV")throw r}s.cpSync(e,t,{recursive:!0,force:!0}),s.rmSync(e,{recursive:!0,force:!0})}async function ot(e){s.mkdirSync(i.dirname(e.targetDir),{recursive:!0});const t=me("create-tauri-ui-frontend-"),r=i.join(t,e.projectName);J(t);try{if(await F("bunx",["--bun","shadcn@latest","init","--name",e.projectName,"--template",e.template,"--preset",e.preset,"--yes"],{cwd:t}),!s.existsSync(r))throw new Error("shadcn CLI did not produce the expected project directory.");s.rmSync(i.join(r,"node_modules"),{recursive:!0,force:!0}),it(r,e.targetDir)}catch(n){if(n instanceof z){k(t);try{$(t)}catch{}throw new D("shadcn","shadcn CLI failed while creating the frontend scaffold.",n.stderr||n.stdout)}k(t);try{$(t)}catch{}throw n}finally{k(t);try{$(t)}catch{}}return e.targetDir}async function st(e){const t=me("create-tauri-ui-"),r="tauri-native";J(t);try{await F("bunx",["create-tauri-app",r,"--template","vanilla-ts","--manager","bun","--identifier",e.identifier,"--yes"],{cwd:t})}catch(n){if(n instanceof z){k(t);try{$(t)}catch{}throw new D("cta","create-tauri-app failed while creating the native scaffold.",n.stderr||n.stdout)}k(t);try{$(t)}catch{}throw n}return{tempDir:t,projectDir:i.join(t,r)}}async function lt(e,t){await fe(e,["add","dashboard-01","--yes"]),rt(e,t);const r=t.template==="next"?i.join(e,"components/site-header.tsx"):t.template==="react-router"?i.join(e,"app/components/site-header.tsx"):i.join(e,"src/components/site-header.tsx");switch(s.existsSync(r)&&c(r,n=>n.includes("<Button")?n:n.replace(/import \{ Button \} from ["'][@~]\/components\/ui\/button["']\n/,"")),t.template){case"next":tt(e),s.writeFileSync(i.join(e,"app/page.tsx"),P("Page","@/","@/app/dashboard/data.json",t.includeInvokeExample));return;case"vite":s.writeFileSync(i.join(e,"src/App.tsx"),P("App","@/","@/app/dashboard/data.json",t.includeInvokeExample));return;case"start":s.writeFileSync(i.join(e,"src/routes/index.tsx"),nt(t.includeInvokeExample));return;case"react-router":s.writeFileSync(i.join(e,"app/routes/home.tsx"),P("Home","~/","~/dashboard/data.json",t.includeInvokeExample));return;case"astro":s.writeFileSync(i.join(e,"src/components/dashboard-shell.tsx"),at(t.includeInvokeExample)),s.writeFileSync(i.join(e,"src/pages/index.astro"),`---
|
|
82
82
|
import Layout from "@/layouts/main.astro"
|
|
83
83
|
import { DashboardShell } from "@/components/dashboard-shell"
|
|
84
84
|
---
|
|
@@ -86,10 +86,10 @@ import { DashboardShell } from "@/components/dashboard-shell"
|
|
|
86
86
|
<Layout>
|
|
87
87
|
<DashboardShell client:load />
|
|
88
88
|
</Layout>
|
|
89
|
-
`);return;default:throw new u(e,`No starter UI implementation exists for template "${t.template}".`)}}async function
|
|
90
|
-
`,
|
|
91
|
-
`,
|
|
92
|
-
`,
|
|
89
|
+
`);return;default:throw new u(e,`No starter UI implementation exists for template "${t.template}".`)}}async function ge(e,t){await fe(e,["add",t,"--yes"])}const U="use tauri_plugin_log::{Target, TargetKind};",he=` log::info!("greet command executed for {}", name);
|
|
90
|
+
`,we=` log::info!("opening external link in system browser: {}", url);
|
|
91
|
+
`,ve=` log::info!("main webview finished loading");
|
|
92
|
+
`,ct=` .plugin(
|
|
93
93
|
tauri_plugin_log::Builder::new()
|
|
94
94
|
.targets([
|
|
95
95
|
Target::new(TargetKind::Stdout),
|
|
@@ -98,33 +98,33 @@ import { DashboardShell } from "@/components/dashboard-shell"
|
|
|
98
98
|
])
|
|
99
99
|
.build(),
|
|
100
100
|
)
|
|
101
|
-
`;function
|
|
101
|
+
`;function ut(){const e=i.dirname(K(import.meta.url)),t=[i.resolve(e,"../assets/debug-panel"),i.resolve(e,"../../assets/debug-panel")];return t.find(r=>s.existsSync(r))??t[0]}const pt=ut();function dt(e){return e==="react-router"?"~/":"@/"}function xe(e,t){switch(t.template){case"next":return i.join(e,"components");case"vite":case"start":case"astro":return i.join(e,"src/components");case"react-router":return i.join(e,"app/components")}}function mt(e,t){switch(t.template){case"next":return i.join(e,"lib");case"vite":case"start":case"astro":return i.join(e,"src/lib");case"react-router":return i.join(e,"app/lib")}}function ft(e,t){return i.join(xe(e,t),"ui")}function _(e){switch(e.template){case"next":return"@/components/debug-panel";case"vite":return"./components/debug-panel.tsx";case"start":return"../components/debug-panel";case"react-router":return"./components/debug-panel";case"astro":return"@/components/debug-panel"}}async function gt(e,t){for(const r of["button","badge","dropdown-menu","tabs","tooltip"]){const n=i.join(ft(e,t),`${r}.tsx`);s.existsSync(n)||await ge(e,r)}}function X(e){return s.readFileSync(i.join(pt,e),"utf-8")}function ht(e,t){const r=i.join(xe(e,t),"debug-panel.tsx"),n=mt(e,t);s.mkdirSync(i.dirname(r),{recursive:!0}),s.mkdirSync(n,{recursive:!0}),s.writeFileSync(r,X("debug-panel.tsx.tmpl").split("__ALIAS_PREFIX__").join(dt(t.template)),"utf-8"),s.writeFileSync(i.join(n,"debug-events.ts"),X("debug-events.ts.tmpl"),"utf-8"),s.writeFileSync(i.join(n,"tauri.ts"),X("tauri.ts.tmpl"),"utf-8")}function wt(e){v(i.join(e,"package.json"),t=>(t.dependencies=t.dependencies||{},t.dependencies["@tauri-apps/plugin-log"]||(t.dependencies["@tauri-apps/plugin-log"]="^2"),t))}function be(e,t){if(e.includes(t))return e;const r=/\[dependencies\]\r?\n/;if(!r.test(e))throw new Error(`Could not find [dependencies] while inserting ${t}.`);return e.replace(r,`[dependencies]
|
|
102
102
|
${t}
|
|
103
|
-
`)}function
|
|
104
|
-
`)?
|
|
103
|
+
`)}function vt(e){const t=i.join(e,"src-tauri/Cargo.toml"),r=i.join(e,"src-tauri/src/lib.rs");c(t,n=>{let a=n;return a=be(a,'tauri-plugin-log = "2"'),a=be(a,'log = "0.4"'),a}),v(i.join(e,"src-tauri/capabilities/default.json"),n=>(n.permissions=Array.isArray(n.permissions)?n.permissions:[],n.permissions.includes("log:default")||n.permissions.push("log:default"),n)),c(r,n=>{let a=n;if(a.includes(U)||(a.includes(`use tauri_plugin_opener::OpenerExt;
|
|
104
|
+
`)?a=a.replace(`use tauri_plugin_opener::OpenerExt;
|
|
105
105
|
`,`use tauri_plugin_opener::OpenerExt;
|
|
106
|
-
${
|
|
107
|
-
`):
|
|
108
|
-
`)?
|
|
106
|
+
${U}
|
|
107
|
+
`):a.includes(`use tauri::webview::PageLoadEvent;
|
|
108
|
+
`)?a=a.replace(`use tauri::webview::PageLoadEvent;
|
|
109
109
|
`,`use tauri::webview::PageLoadEvent;
|
|
110
|
-
${
|
|
111
|
-
`):
|
|
112
|
-
${
|
|
110
|
+
${U}
|
|
111
|
+
`):a=`${U}
|
|
112
|
+
${a}`),a.includes("tauri_plugin_log::Builder::new()"))return a;const o=a.replace(` tauri::Builder::default()
|
|
113
113
|
`,` tauri::Builder::default()
|
|
114
|
-
${
|
|
115
|
-
`)&&(
|
|
114
|
+
${ct}`);if(o===a)throw new u(r,"Could not register the Tauri log plugin.");return a=o,!a.includes(he.trim())&&a.includes(` let message = format!("Hello, {}! You've been greeted from Rust!", name);
|
|
115
|
+
`)&&(a=a.replace(` let message = format!("Hello, {}! You've been greeted from Rust!", name);
|
|
116
116
|
`,` let message = format!("Hello, {}! You've been greeted from Rust!", name);
|
|
117
|
-
${
|
|
118
|
-
`)&&(
|
|
119
|
-
`,`${
|
|
120
|
-
`)),!
|
|
121
|
-
`)&&(
|
|
122
|
-
`,`${
|
|
123
|
-
`)),
|
|
117
|
+
${he}`)),!a.includes(we.trim())&&a.includes(` let _ = webview.opener().open_url(url.as_str(), None::<&str>);
|
|
118
|
+
`)&&(a=a.replace(` let _ = webview.opener().open_url(url.as_str(), None::<&str>);
|
|
119
|
+
`,`${we} let _ = webview.opener().open_url(url.as_str(), None::<&str>);
|
|
120
|
+
`)),!a.includes(ve.trim())&&a.includes(` let _ = webview.window().show();
|
|
121
|
+
`)&&(a=a.replace(` let _ = webview.window().show();
|
|
122
|
+
`,`${ve} let _ = webview.window().show();
|
|
123
|
+
`)),a})}function xt(e,t){const r=i.join(e,"src/main.tsx"),n=_(t);c(r,a=>{let o=a;if(o.includes(`import { DebugPanel } from "${n}"`)||(o=o.replace(/import { ExternalLinkGuard } from "\.\/components\/external-link-guard\.tsx"\r?\n/,`import { ExternalLinkGuard } from "./components/external-link-guard.tsx"
|
|
124
124
|
import { DebugPanel } from "${n}"
|
|
125
125
|
`)),o.includes("<DebugPanel />"))return o;const l=o.replace(/<ExternalLinkGuard \/>\r?\n(\s*)<main(?:\s+data-ui-scroll-container)?><App \/><\/main>/,`<ExternalLinkGuard />
|
|
126
126
|
$1{import.meta.env.DEV ? <DebugPanel /> : null}
|
|
127
|
-
$1<main data-ui-scroll-container><App /></main>`);if(l===o)throw new u(r,"Could not mount DebugPanel in the Vite entrypoint.");return l})}function
|
|
127
|
+
$1<main data-ui-scroll-container><App /></main>`);if(l===o)throw new u(r,"Could not mount DebugPanel in the Vite entrypoint.");return l})}function bt(e,t){const r=i.join(e,"app/layout.tsx"),n=_(t);c(r,a=>{let o=a;if(o.includes(`import { DebugPanel } from "${n}"`)||(o.includes(`import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
128
128
|
`)?o=o.replace(`import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
129
129
|
`,`import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
130
130
|
import { DebugPanel } from "${n}"
|
|
@@ -132,19 +132,19 @@ import { DebugPanel } from "${n}"
|
|
|
132
132
|
`)&&(o=o.replace(`import { ThemeProvider } from "@/components/theme-provider"
|
|
133
133
|
`,`import { ThemeProvider } from "@/components/theme-provider"
|
|
134
134
|
import { DebugPanel } from "${n}"
|
|
135
|
-
`))),o.includes("<DebugPanel />"))return o;const l=o.replace(/<ExternalLinkGuard \/>\{children\}/,'<ExternalLinkGuard />{process.env.NODE_ENV === "development" ? <DebugPanel /> : null}{children}');if(l===o)throw new u(r,"Could not mount DebugPanel in the Next.js layout.");return l})}function
|
|
135
|
+
`))),o.includes("<DebugPanel />"))return o;const l=o.replace(/<ExternalLinkGuard \/>\{children\}/,'<ExternalLinkGuard />{process.env.NODE_ENV === "development" ? <DebugPanel /> : null}{children}');if(l===o)throw new u(r,"Could not mount DebugPanel in the Next.js layout.");return l})}function yt(e,t){const r=i.join(e,"src/routes/__root.tsx"),n=_(t);c(r,a=>{let o=a;if(o.includes(`import { DebugPanel } from "${n}"`)||(o=o.replace(`import { ExternalLinkGuard } from "../components/external-link-guard"
|
|
136
136
|
`,`import { ExternalLinkGuard } from "../components/external-link-guard"
|
|
137
137
|
import { DebugPanel } from "${n}"
|
|
138
|
-
`)),o.includes("<DebugPanel />"))return o;const l=o.replace(/<main(?:\s+data-ui-scroll-container)?><ExternalLinkGuard \/>\{children\}<\/main>/,"<main data-ui-scroll-container><ExternalLinkGuard />{import.meta.env.DEV ? <DebugPanel /> : null}{children}</main>");if(l===o)throw new u(r,"Could not mount DebugPanel in the TanStack Start root route.");return l})}function
|
|
138
|
+
`)),o.includes("<DebugPanel />"))return o;const l=o.replace(/<main(?:\s+data-ui-scroll-container)?><ExternalLinkGuard \/>\{children\}<\/main>/,"<main data-ui-scroll-container><ExternalLinkGuard />{import.meta.env.DEV ? <DebugPanel /> : null}{children}</main>");if(l===o)throw new u(r,"Could not mount DebugPanel in the TanStack Start root route.");return l})}function kt(e,t){const r=i.join(e,"app/root.tsx"),n=_(t);c(r,a=>{let o=a;if(o.includes(`import { DebugPanel } from "${n}"`)||(o=o.replace(`import { ExternalLinkGuard } from "./components/external-link-guard"
|
|
139
139
|
`,`import { ExternalLinkGuard } from "./components/external-link-guard"
|
|
140
140
|
import { DebugPanel } from "${n}"
|
|
141
141
|
`)),o.includes("<DebugPanel />"))return o;const l=o.replace(`<ExternalLinkGuard />
|
|
142
142
|
{children}`,`<ExternalLinkGuard />
|
|
143
143
|
{import.meta.env.DEV ? <DebugPanel /> : null}
|
|
144
|
-
{children}`);if(l===o)throw new u(r,"Could not mount DebugPanel in the React Router root layout.");return l})}function
|
|
144
|
+
{children}`);if(l===o)throw new u(r,"Could not mount DebugPanel in the React Router root layout.");return l})}function $t(e,t){const r=i.join(e,"src/layouts/main.astro"),n=_(t);c(r,a=>{let o=a;if(o.includes(`import { DebugPanel } from "${n}"`)||(o=o.replace(`import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
145
145
|
`,`import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
146
146
|
import { DebugPanel } from "${n}"
|
|
147
|
-
`)),o.includes("<DebugPanel client:load />"))return o;const l=o.replace(/<main(?:\s+data-ui-scroll-container)?><ExternalLinkGuard client:load \/><slot \/><\/main>/,"<main data-ui-scroll-container><ExternalLinkGuard client:load />{import.meta.env.DEV ? <DebugPanel client:load /> : null}<slot /></main>");if(l===o)throw new u(r,"Could not mount DebugPanel in the Astro layout.");return l})}async function
|
|
147
|
+
`)),o.includes("<DebugPanel client:load />"))return o;const l=o.replace(/<main(?:\s+data-ui-scroll-container)?><ExternalLinkGuard client:load \/><slot \/><\/main>/,"<main data-ui-scroll-container><ExternalLinkGuard client:load />{import.meta.env.DEV ? <DebugPanel client:load /> : null}<slot /></main>");if(l===o)throw new u(r,"Could not mount DebugPanel in the Astro layout.");return l})}async function ye(e,t){switch(await gt(e,t),ht(e,t),wt(e),vt(e),t.template){case"vite":xt(e,t);return;case"next":bt(e,t);return;case"start":yt(e,t);return;case"react-router":kt(e,t);return;case"astro":$t(e,t);return}}const q="use tauri_plugin_opener::OpenerExt;",jt=`fn external_navigation_plugin<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
|
148
148
|
tauri::plugin::Builder::<R>::new("external-navigation")
|
|
149
149
|
.on_navigation(|webview, url| {
|
|
150
150
|
let is_internal_host = matches!(
|
|
@@ -170,7 +170,7 @@ import { DebugPanel } from "${n}"
|
|
|
170
170
|
.build()
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
`,
|
|
173
|
+
`,Et=`"use client"
|
|
174
174
|
|
|
175
175
|
import { useEffect } from "react"
|
|
176
176
|
|
|
@@ -271,49 +271,49 @@ export function ExternalLinkGuard() {
|
|
|
271
271
|
|
|
272
272
|
return null
|
|
273
273
|
}
|
|
274
|
-
`;function
|
|
274
|
+
`;function C(e){s.mkdirSync(i.dirname(e),{recursive:!0}),s.writeFileSync(e,Et,"utf-8")}function St(e){const t=i.join(e,"src-tauri/src/lib.rs");c(t,r=>{let n=r;if(n.includes(q)||(n.includes(`use tauri::webview::PageLoadEvent;
|
|
275
275
|
`)?n=n.replace(`use tauri::webview::PageLoadEvent;
|
|
276
276
|
`,`use tauri::webview::PageLoadEvent;
|
|
277
|
-
${
|
|
278
|
-
`):n=`${
|
|
279
|
-
${n}`),!n.includes("fn external_navigation_plugin<R: tauri::Runtime>()")){if(!n.includes("#[cfg_attr(mobile, tauri::mobile_entry_point)]"))throw new u(t,"Could not find the Tauri entry point while inserting the external navigation plugin.");n=n.replace("#[cfg_attr(mobile, tauri::mobile_entry_point)]",`${
|
|
277
|
+
${q}
|
|
278
|
+
`):n=`${q}
|
|
279
|
+
${n}`),!n.includes("fn external_navigation_plugin<R: tauri::Runtime>()")){if(!n.includes("#[cfg_attr(mobile, tauri::mobile_entry_point)]"))throw new u(t,"Could not find the Tauri entry point while inserting the external navigation plugin.");n=n.replace("#[cfg_attr(mobile, tauri::mobile_entry_point)]",`${jt}#[cfg_attr(mobile, tauri::mobile_entry_point)]`)}if(!n.includes(".plugin(external_navigation_plugin())")){if(!n.includes(".plugin(tauri_plugin_opener::init())"))throw new u(t,"Could not find the opener plugin while inserting the external navigation guard.");n=n.replace(` .plugin(tauri_plugin_opener::init())
|
|
280
280
|
`,` .plugin(tauri_plugin_opener::init())
|
|
281
281
|
.plugin(external_navigation_plugin())
|
|
282
|
-
`)}return n})}function
|
|
282
|
+
`)}return n})}function Dt(e){const t=i.join(e,"src/components/external-link-guard.tsx"),r=i.join(e,"src/main.tsx");C(t),c(r,n=>{let a=n;if(a.includes('import { ExternalLinkGuard } from "./components/external-link-guard.tsx"')||(a=a.replace(/import { ThemeProvider } from "@\/components\/theme-provider\.tsx"\r?\n/,`import { ThemeProvider } from "@/components/theme-provider.tsx"
|
|
283
283
|
import { ExternalLinkGuard } from "./components/external-link-guard.tsx"
|
|
284
|
-
`)),
|
|
284
|
+
`)),a.includes("<ExternalLinkGuard />"))return a;const o=a.replace(/<ThemeProvider>\r?\n(\s*)<main(?:\s+data-ui-scroll-container)?><App \/><\/main>/,`<ThemeProvider>
|
|
285
285
|
$1<ExternalLinkGuard />
|
|
286
|
-
$1<main data-ui-scroll-container><App /></main>`);if(o===
|
|
287
|
-
`)?
|
|
286
|
+
$1<main data-ui-scroll-container><App /></main>`);if(o===a)throw new u(r,"Could not mount ExternalLinkGuard in the Vite entrypoint.");return o})}function Pt(e){const t=i.join(e,"components/external-link-guard.tsx"),r=i.join(e,"app/layout.tsx");C(t),c(r,n=>{let a=n;if(a.includes('import { ExternalLinkGuard } from "@/components/external-link-guard"')||(a.includes(`import { TooltipProvider } from "@/components/ui/tooltip"
|
|
287
|
+
`)?a=a.replace(`import { TooltipProvider } from "@/components/ui/tooltip"
|
|
288
288
|
`,`import { TooltipProvider } from "@/components/ui/tooltip"
|
|
289
289
|
import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
290
|
-
`):
|
|
290
|
+
`):a=a.replace(`import { ThemeProvider } from "@/components/theme-provider"
|
|
291
291
|
`,`import { ThemeProvider } from "@/components/theme-provider"
|
|
292
292
|
import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
293
|
-
`)),
|
|
293
|
+
`)),a.includes("<ExternalLinkGuard />"))return a;const o=a.replace(/<main(?:\s+data-ui-scroll-container)?>\{children\}<\/main>/,"<main data-ui-scroll-container><ExternalLinkGuard />{children}</main>");if(o===a)throw new u(r,"Could not mount ExternalLinkGuard in the Next.js layout.");return o})}function _t(e){const t=i.join(e,"src/components/external-link-guard.tsx"),r=i.join(e,"src/routes/__root.tsx");C(t),c(r,n=>{let a=n;if(a.includes('import { ExternalLinkGuard } from "../components/external-link-guard"')||(a=a.replace(`import appCss from "../styles.css?url"
|
|
294
294
|
`,`import appCss from "../styles.css?url"
|
|
295
295
|
import { ExternalLinkGuard } from "../components/external-link-guard"
|
|
296
|
-
`)),
|
|
296
|
+
`)),a.includes("<ExternalLinkGuard />"))return a;const o=a.replace(/<main(?:\s+data-ui-scroll-container)?>\{children\}<\/main>/,"<main data-ui-scroll-container><ExternalLinkGuard />{children}</main>");if(o===a)throw new u(r,"Could not mount ExternalLinkGuard in the TanStack Start root route.");return o})}function Ct(e){const t=i.join(e,"app/components/external-link-guard.tsx"),r=i.join(e,"app/root.tsx");C(t),c(r,n=>{let a=n;if(a.includes('import { ExternalLinkGuard } from "./components/external-link-guard"')||(a=a.replace(`import "./app.css"
|
|
297
297
|
`,`import "./app.css"
|
|
298
298
|
import { ExternalLinkGuard } from "./components/external-link-guard"
|
|
299
|
-
`)),
|
|
299
|
+
`)),a.includes("<ExternalLinkGuard />"))return a;const o=a.replace(`<body>
|
|
300
300
|
{children}`,`<body>
|
|
301
301
|
<ExternalLinkGuard />
|
|
302
|
-
{children}`);if(o===
|
|
302
|
+
{children}`);if(o===a)throw new u(r,"Could not mount ExternalLinkGuard in the React Router root layout.");return o})}function Lt(e){const t=i.join(e,"src/components/external-link-guard.tsx"),r=i.join(e,"src/layouts/main.astro");C(t),c(r,n=>{let a=n;if(a.includes('import { ExternalLinkGuard } from "@/components/external-link-guard"')||(a=a.replace(`import "@/styles/global.css"
|
|
303
303
|
`,`import "@/styles/global.css"
|
|
304
304
|
import { ExternalLinkGuard } from "@/components/external-link-guard"
|
|
305
|
-
`)),
|
|
305
|
+
`)),a.includes("<ExternalLinkGuard client:load />"))return a;const o=a.replace(/ <body>\n <main(?:\s+data-ui-scroll-container)?><slot \/><\/main>\n <\/body>/,` <body>
|
|
306
306
|
<main data-ui-scroll-container><ExternalLinkGuard client:load /><slot /></main>
|
|
307
|
-
</body>`);if(o===
|
|
307
|
+
</body>`);if(o===a)throw new u(r,"Could not mount ExternalLinkGuard in the Astro layout.");return o})}async function Tt(e,t){switch(St(e),t.template){case"vite":Dt(e);return;case"next":Pt(e);return;case"start":_t(e);return;case"react-router":Ct(e);return;case"astro":Lt(e);return}}const Z="use tauri::webview::PageLoadEvent;",Gt=` .on_page_load(|webview, payload| {
|
|
308
308
|
if webview.label() == "main" && matches!(payload.event(), PageLoadEvent::Finished) {
|
|
309
309
|
let _ = webview.window().show();
|
|
310
310
|
}
|
|
311
311
|
})
|
|
312
|
-
`;async function
|
|
312
|
+
`;async function Nt(e){v(i.join(e,"src-tauri/tauri.conf.json"),r=>{r.app=r.app||{},r.app.windows=Array.isArray(r.app.windows)?r.app.windows:[{}];const n=r.app.windows[0]||{};return r.app.windows[0]={...n,visible:!1},r});const t=i.join(e,"src-tauri/src/lib.rs");c(t,r=>{let n=r;if(n.includes(Z)||(n.includes(`use tauri::Manager;
|
|
313
313
|
`)?n=n.replace(`use tauri::Manager;
|
|
314
|
-
`,`${
|
|
315
|
-
`):n=`${
|
|
316
|
-
${n}`),n.includes(".on_page_load("))return n;const
|
|
314
|
+
`,`${Z}
|
|
315
|
+
`):n=`${Z}
|
|
316
|
+
${n}`),n.includes(".on_page_load("))return n;const a=n.replace(" .run(tauri::generate_context!())",`${Gt} .run(tauri::generate_context!())`);if(a===n)throw new u(t,"Could not insert the startup flash-prevention page-load hook.");return a})}function At(){const e=i.dirname(K(import.meta.url)),t=[i.resolve(e,"../assets"),i.resolve(e,"../../assets")];return t.find(r=>s.existsSync(r))??t[0]}const It=At();async function Ot(e){ue(i.join(It,"app-icon.png"),i.join(e,"app-icon.png"))}function Rt(e){return`"use client"
|
|
317
317
|
|
|
318
318
|
import { useState } from "react"
|
|
319
319
|
import { Button } from "${e}components/ui/button"
|
|
@@ -356,7 +356,7 @@ export function Greet() {
|
|
|
356
356
|
</div>
|
|
357
357
|
)
|
|
358
358
|
}
|
|
359
|
-
`}function
|
|
359
|
+
`}function zt(e,t){return`import { Greet } from "${t}components/greet"
|
|
360
360
|
|
|
361
361
|
export default function ${e}() {
|
|
362
362
|
return (
|
|
@@ -373,7 +373,7 @@ export default function ${e}() {
|
|
|
373
373
|
</div>
|
|
374
374
|
)
|
|
375
375
|
}
|
|
376
|
-
`}function
|
|
376
|
+
`}function Ft(){return`import { createFileRoute } from "@tanstack/react-router"
|
|
377
377
|
import { Greet } from "@/components/greet"
|
|
378
378
|
|
|
379
379
|
export const Route = createFileRoute("/")({
|
|
@@ -395,7 +395,7 @@ function Home() {
|
|
|
395
395
|
</div>
|
|
396
396
|
)
|
|
397
397
|
}
|
|
398
|
-
`}function
|
|
398
|
+
`}function ke(e,t){switch(e){case"next":return i.join(t,"app/page.tsx");case"vite":return i.join(t,"src/App.tsx");case"start":return i.join(t,"src/routes/index.tsx");case"react-router":return i.join(t,"app/routes/home.tsx");case"astro":return i.join(t,"src/pages/index.astro")}}function Ut(e,t){switch(e){case"next":return i.join(t,"components/greet.tsx");case"vite":case"start":case"astro":return i.join(t,"src/components/greet.tsx");case"react-router":return i.join(t,"app/components/greet.tsx")}}async function Mt(e,t){t.includeStarterUI||await ge(e,"input");const r=t.template==="react-router"?"~/":"@/";if(s.writeFileSync(Ut(t.template,e),Rt(r)),t.includeStarterUI)return;if(t.template==="astro"){s.writeFileSync(ke(t.template,e),`---
|
|
399
399
|
import Layout from "@/layouts/main.astro"
|
|
400
400
|
import { Greet } from "@/components/greet"
|
|
401
401
|
---
|
|
@@ -413,7 +413,7 @@ import { Greet } from "@/components/greet"
|
|
|
413
413
|
</div>
|
|
414
414
|
</div>
|
|
415
415
|
</Layout>
|
|
416
|
-
`);return}const n=t.template==="start"?
|
|
416
|
+
`);return}const n=t.template==="start"?Ft():zt(t.template==="vite"?"App":t.template==="react-router"?"Home":"Page",r);s.writeFileSync(ke(t.template,e),n)}const x="data-ui-scroll-container",L=`
|
|
417
417
|
/* Disable page-level overscroll and rubber-band scrolling so the UI feels more desktop-native. */
|
|
418
418
|
html,
|
|
419
419
|
body {
|
|
@@ -425,20 +425,20 @@ body {
|
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
/* Scope the desktop scroll shell to the generated root container only. */
|
|
428
|
-
[${
|
|
428
|
+
[${x}] {
|
|
429
429
|
height: 100vh;
|
|
430
430
|
height: 100dvh;
|
|
431
431
|
overflow-x: hidden;
|
|
432
432
|
overflow-y: auto;
|
|
433
433
|
overscroll-behavior-y: none;
|
|
434
434
|
}
|
|
435
|
-
`,
|
|
435
|
+
`,Vt=`
|
|
436
436
|
/* Vite mounts into #root, so it needs to inherit the full-height desktop shell. */
|
|
437
437
|
#root {
|
|
438
438
|
height: 100%;
|
|
439
439
|
}
|
|
440
|
-
`;function
|
|
441
|
-
${t}`)}function
|
|
440
|
+
`;function T(e,t){c(e,r=>r.includes("Disable page-level overscroll and rubber-band scrolling")?r:`${r.trimEnd()}
|
|
441
|
+
${t}`)}function G(e,t,r){c(e,n=>{if(n.includes(`<main ${x}>`))return n;const a=n.replace(new RegExp(`<main(?![^>]*${x})([^>]*)>`),`<main ${x}$1>`);if(a!==n)return a;const o=n.replace(t,r);if(o===n)throw new u(e,"Could not insert the scroll container <main> wrapper.");return o})}async function Bt(e,t){switch(t.template){case"next":T(i.join(e,"app/globals.css"),L),G(i.join(e,"app/layout.tsx"),/\{children\}/,`<main ${x}>{children}</main>`);return;case"vite":T(i.join(e,"src/index.css"),`${L}${Vt}`),G(i.join(e,"src/main.tsx"),/<App \/>/,`<main ${x}><App /></main>`);return;case"start":T(i.join(e,"src/styles.css"),L),G(i.join(e,"src/routes/__root.tsx"),/\{children\}/,`<main ${x}>{children}</main>`);return;case"react-router":T(i.join(e,"app/app.css"),L),G(i.join(e,"app/root.tsx"),/return <Outlet \/>/,`return <main ${x}><Outlet /></main>`);return;case"astro":T(i.join(e,"src/styles/global.css"),L),G(i.join(e,"src/layouts/main.astro"),/<slot \/>/,`<main ${x}><slot /></main>`);return;default:throw new u(e,`No scroll container battery implementation exists for template "${t.template}".`)}}const N=`
|
|
442
442
|
@layer base {
|
|
443
443
|
/* Desktop UIs often feel cleaner with accidental text selection disabled by default. */
|
|
444
444
|
body {
|
|
@@ -461,8 +461,8 @@ ${t}`)}function j(e,t,r){c(e,n=>{if(n.includes(`<main ${m}>`))return n;const i=n
|
|
|
461
461
|
@apply select-text;
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
|
-
`;function
|
|
465
|
-
${t}`)}async function
|
|
464
|
+
`;function A(e,t){c(e,r=>r.includes(".ui-selectable")?r:`${r.trimEnd()}
|
|
465
|
+
${t}`)}async function Ht(e,t){switch(t.template){case"next":A(i.join(e,"app/globals.css"),N);return;case"vite":A(i.join(e,"src/index.css"),N);return;case"start":A(i.join(e,"src/styles.css"),N);return;case"react-router":A(i.join(e,"app/app.css"),N);return;case"astro":A(i.join(e,"src/styles/global.css"),N);return;default:throw new u(e,`No selection behavior battery implementation exists for template "${t.template}".`)}}const Wt=`
|
|
466
466
|
[profile.dev]
|
|
467
467
|
incremental = true # Compile your binary in smaller steps.
|
|
468
468
|
|
|
@@ -472,14 +472,19 @@ lto = true # Enables link-time-optimizations.
|
|
|
472
472
|
opt-level = "s" # Prioritizes small binary size. Use \`3\` if you prefer speed.
|
|
473
473
|
panic = "abort" # Higher performance by disabling panic handlers.
|
|
474
474
|
strip = true # Ensures debug symbols are removed.
|
|
475
|
-
`;function
|
|
475
|
+
`;function Kt(e){const t=i.join(e,"src-tauri/Cargo.toml");c(t,r=>r.includes("[profile.release]")?r:`${r.trimEnd()}
|
|
476
476
|
|
|
477
|
-
${
|
|
477
|
+
${Wt}`)}function Yt(e){v(i.join(e,"src-tauri/tauri.conf.json"),t=>(t.build=t.build||{},t.build.removeUnusedCommands=!0,t))}async function Jt(e,t){Kt(e),Yt(e)}function Xt(){const e=i.dirname(K(import.meta.url)),t=[i.resolve(e,"../assets"),i.resolve(e,"../../assets")];return t.find(r=>s.existsSync(r))??t[0]}const qt=Xt();function Zt(e){const t=[];for(const r of e){if(r==="macos-latest"){t.push({name:"macOS Apple Silicon",platform:"macos-latest",args:"--target aarch64-apple-darwin"},{name:"macOS Intel",platform:"macos-latest",args:"--target x86_64-apple-darwin"});continue}if(r==="ubuntu-latest"){t.push({name:"Linux",platform:"ubuntu-22.04",args:""});continue}t.push({name:"Windows",platform:r,args:""})}return t.map(r=>` - name: "${r.name}"
|
|
478
478
|
platform: "${r.platform}"
|
|
479
479
|
args: "${r.args}"`).join(`
|
|
480
|
-
`)}async function
|
|
481
|
-
|
|
482
|
-
|
|
480
|
+
`)}async function $e(e,t){const r=s.readFileSync(i.join(qt,"release.yml.tmpl"),"utf-8").replace("{{PLATFORMS}}",Zt(t.targetOS)),n=i.join(e,".github/workflows");s.mkdirSync(n,{recursive:!0}),s.writeFileSync(i.join(n,"release.yml"),r)}const Q=["vite","next","start","react-router","astro"],S=["windows-latest","macos-latest","ubuntu-latest"];function Qt(e){const t=i.join(e,"package.json");if(!s.existsSync(t))return null;try{return JSON.parse(s.readFileSync(t,"utf-8"))}catch{return null}}function j(e,t){return!!(e.dependencies?.[t]||e.devDependencies?.[t])}function er(e){const t=[{path:"app/layout.tsx",template:"next"},{path:"app/root.tsx",template:"react-router"},{path:"src/routes/__root.tsx",template:"start"},{path:"src/layouts/main.astro",template:"astro"},{path:"src/main.tsx",template:"vite"}];for(const r of t)if(s.existsSync(i.join(e,r.path)))return r.template;return null}function tr(e){return j(e,"next")?"next":j(e,"@tanstack/react-start")||j(e,"@tanstack/start")?"start":j(e,"react-router")||j(e,"@react-router/dev")?"react-router":j(e,"astro")?"astro":j(e,"vite")?"vite":null}function M(e){const t=i.resolve(e);if(!s.existsSync(t))throw new Error(`Directory not found: ${t}`);if(!s.existsSync(i.join(t,"src-tauri")))throw new Error(`No src-tauri/ directory found in ${t}. Run this command inside a Tauri project.`);const r=Qt(t);if(!r)throw new Error(`No readable package.json found in ${t}.`);const n=er(t)??tr(r);if(!n)throw new Error("Could not detect the frontend template (vite, next, start, react-router, astro).");return{template:n,projectDir:t}}function rr(e){switch(e){case"next":return"components";case"react-router":return"app/components";case"vite":case"start":case"astro":return"src/components"}}function je(e,t){const r=rr(t);return[i.join(e,r,"debug-panel.tsx")]}function nr(e,t){const r={vite:{file:"src/main.tsx",importMatch:/import \{ DebugPanel \} from "\.\/components\/debug-panel\.tsx"\r?\n/,jsxMatch:/ *\{import\.meta\.env\.DEV \? <DebugPanel \/> : null\}\r?\n/},next:{file:"app/layout.tsx",importMatch:/import \{ DebugPanel \} from "@\/components\/debug-panel"\r?\n/,jsxMatch:/\{process\.env\.NODE_ENV === "development" \? <DebugPanel \/> : null\}/},start:{file:"src/routes/__root.tsx",importMatch:/import \{ DebugPanel \} from "\.\.\/components\/debug-panel"\r?\n/,jsxMatch:/\{import\.meta\.env\.DEV \? <DebugPanel \/> : null\}/},"react-router":{file:"app/root.tsx",importMatch:/import \{ DebugPanel \} from "\.\/components\/debug-panel"\r?\n/,jsxMatch:/ *\{import\.meta\.env\.DEV \? <DebugPanel \/> : null\}\r?\n/},astro:{file:"src/layouts/main.astro",importMatch:/import \{ DebugPanel \} from "@\/components\/debug-panel"\r?\n/,jsxMatch:/\{import\.meta\.env\.DEV \? <DebugPanel client:load \/> : null\}/}}[t],n=i.join(e,r.file);s.existsSync(n)&&c(n,a=>a.replace(r.importMatch,"").replace(r.jsxMatch,""))}function ar(e){const t=i.join(e,"src-tauri/capabilities/default.json");s.existsSync(t)&&v(t,r=>(Array.isArray(r.permissions)&&(r.permissions=r.permissions.filter(n=>n!=="log:default")),r))}const ee={"debug-panel":{id:"debug-panel",name:"Development debug panel",description:"Dev-only inspector for state, window, host diagnostics, invokes, events, logs.",detectFiles(e,t){return je(e,t).filter(r=>s.existsSync(r))},async apply(e,t){await ye(e,t)},remove(e,t){const r=[],n=je(e,t.template);for(const a of n)s.existsSync(a)&&(s.rmSync(a),r.push(i.relative(e,a)));return nr(e,t.template),ar(e),{removed:r,manual:["src/lib/debug-events.ts and src/lib/tauri.ts are shared with the invoke-example battery \u2014 leave them in place unless you also remove invoke-example","src-tauri/Cargo.toml \u2014 remove `tauri-plugin-log` and `log` dependencies if unused","src-tauri/src/lib.rs \u2014 remove the `tauri_plugin_log` import and `.plugin(...)` registration if unused","package.json \u2014 remove `@tauri-apps/plugin-log` if unused","run `bun install` and `cargo check` in src-tauri/ to confirm"]}}},workflow:{id:"workflow",name:"GitHub release workflow",description:"GitHub Actions workflow that builds and publishes a release per platform.",detectFiles(e){const t=i.join(e,".github/workflows/release.yml");return s.existsSync(t)?[t]:[]},async apply(e,t){await $e(e,t)},remove(e){const t=[],r=i.join(e,".github/workflows/release.yml");return s.existsSync(r)&&(s.rmSync(r),t.push(i.relative(e,r))),{removed:t,manual:[]}}}};function ir(e){const t=ee[e];if(!t)throw new Error(`Unknown battery "${e}". Available: ${Object.keys(ee).join(", ")}`);return t}function Ee(){return Object.values(ee)}function V(e,t,r){const n=e.detectFiles(t,r);return{installed:n.length>0,detectedFiles:n}}const or={"windows-latest":"Windows","macos-latest":"macOS (Apple Silicon + Intel)","ubuntu-latest":"Linux"};function te(e){if(ie(e))throw new Error("Operation cancelled");return e}async function Se(e){const t=["macos-latest","ubuntu-latest","windows-latest"];return e?t:te(await oe({message:"Target platforms for the release workflow",options:S.map(r=>({value:r,label:or[r]??r})),initialValues:t,required:!0}))}function re(e,t,r=[]){const n=i.basename(e);return{projectName:n,packageName:n,template:t,identifier:`com.example.${n}`,preset:"b0",includeSizeOptimization:!1,includeStarterUI:!1,includeInvokeExample:!1,includeWorkflow:!1,targetOS:r,targetDir:e}}async function sr(e,t){const{template:r,projectDir:n}=M(t.targetDir??h.cwd()),a=V(e,n,r);if(a.installed&&!t.force){m.warn(`${e.name} is already installed. Use \`update\` to overwrite, or pass --force.`);for(const p of a.detectedFiles)m.message(` \xB7 ${i.relative(n,p)}`);return}let o=[];e.id==="workflow"&&(o=t.targetOS?.length?t.targetOS:await Se(t.yes));const l=re(n,r,o);m.info(`Installing ${e.name} in ${f.dim(n)}`),await e.apply(n,l),m.success(`${e.name} installed.`)}async function lr(e,t){const{template:r,projectDir:n}=M(t.targetDir??h.cwd());if(!V(e,n,r).installed&&!t.force){m.warn(`${e.name} is not installed yet. Use \`add\` to install, or pass --force to write anyway.`);return}if(!t.yes&&!te(await y({message:`Overwrite ${e.name} files with the latest template?`,initialValue:!0}))){m.info("Update cancelled.");return}let a=[];e.id==="workflow"&&(a=t.targetOS?.length?t.targetOS:await Se(t.yes));const o=re(n,r,a);await e.apply(n,o),m.success(`${e.name} updated.`)}async function cr(e,t){const{template:r,projectDir:n}=M(t.targetDir??h.cwd());if(!V(e,n,r).installed){m.warn(`${e.name} is not installed.`);return}if(!t.yes&&!te(await y({message:`Remove ${e.name} files from this project?`,initialValue:!1}))){m.info("Remove cancelled.");return}const a=re(n,r),o=e.remove(n,a);if(o.removed.length===0)m.warn("No files removed.");else{m.success(`Removed ${o.removed.length} file(s):`);for(const l of o.removed)m.message(` \xB7 ${l}`)}o.manual.length>0&&I(o.manual.map(l=>`- ${l}`).join(`
|
|
481
|
+
`),"Manual cleanup")}function ur(e){const{template:t,projectDir:r}=M(e.targetDir??h.cwd()),n=[];for(const a of Ee()){const o=V(a,r,t),l=o.installed?f.green("\u25CF"):f.dim("\u25CB"),p=o.installed?f.green("installed"):f.dim("not installed");n.push(`${l} ${f.bold(a.id.padEnd(14))} ${p}`),n.push(f.dim(` ${a.description}`))}I(n.join(`
|
|
482
|
+
`),`Batteries (${t})`)}async function pr(e){ae(f.bold(`create-tauri-ui \xB7 ${e.action}`));try{if(e.action==="list"){ur(e),H("Done");return}if(!e.batteryId)throw new Error(`Missing battery name. Available: ${Ee().map(r=>r.id).join(", ")}`);const t=ir(e.batteryId);switch(e.action){case"add":await sr(t,e);break;case"update":await lr(t,e);break;case"remove":await cr(t,e);break}H("Done")}catch(t){const r=t instanceof Error?t.message:String(t);W(r),h.exitCode=1}}const dr=1400,mr=918;async function fr(e,t,r){de(i.join(t,"src-tauri"),i.join(e,"src-tauri")),v(i.join(e,"package.json"),o=>(o.name=r.packageName,o.dependencies=o.dependencies||{},o.dependencies["@tauri-apps/api"]="^2",o.dependencies["@tauri-apps/plugin-opener"]="^2",o.devDependencies=o.devDependencies||{},o.devDependencies["@tauri-apps/cli"]="^2",o.scripts=o.scripts||{},o.scripts.tauri="tauri",o));const n=Ve(r.packageName),a=`${n}_lib`;c(i.join(e,"src-tauri/Cargo.toml"),o=>o.replace(/^name = "tauri-app"$/m,`name = "${n}"`).replace(/^name = "tauri_app_lib"$/m,`name = "${a}"`)),c(i.join(e,"src-tauri/src/main.rs"),o=>o.replace("tauri_app_lib::run()",`${a}::run()`))}async function gr(e,t,r){v(i.join(e,"src-tauri/tauri.conf.json"),n=>{n.productName=t.projectName,n.identifier=t.identifier,n.build={...n.build,...r},n.app=n.app||{},n.app.windows=Array.isArray(n.app.windows)?n.app.windows:[{}];const a=n.app.windows[0]||{};return n.app.windows[0]={...a,title:t.projectName,center:!0,width:dr,height:mr},n})}const De="tauri-ui",Pe="b0",hr={vite:"Vite",next:"Next.js",start:"TanStack Start","react-router":"React Router",astro:"Astro"},wr={"windows-latest":"Windows","macos-latest":"macOS (Apple Silicon, Intel)","ubuntu-22.04":"Linux","ubuntu-latest":"Linux"};function g(e,t="Operation cancelled"){if(ie(e))throw new Error(t);return e}function vr(e){if(Q.includes(e))return e;throw new Error(`Unsupported template "${e}". Expected one of: ${Q.join(", ")}`)}function _e(e){try{return`com.${pe(se.userInfo().username)||"example"}.${e}`}catch{return`com.example.${e}`}}function B(e){return{placeholder:e,defaultValue:e}}function Ce(e){const t=e.trim().replace(/^['"]|['"]$/g,"");return(t.match(/^(?:--preset(?:=|\s+))?(.+)$/)?.[1]??t).trim()}async function xr(e,t=process.cwd()){let r=ze(e.targetDir);if(r||(r=g(await O({message:"Project name",...B(De)}))),r||(r=De),r===".")throw new Error("Scaffolding into the current directory is not supported yet.");const n=i.resolve(t,r),a=i.basename(n),o=Y(a)?a:pe(a);if(s.existsSync(n)&&!Fe(n))if(e.force)s.rmSync(n,{recursive:!0,force:!0});else{if(e.yes)throw new Error(`Target directory "${r}" is not empty. Re-run with --force to overwrite it.`);if(!g(await y({message:`Target directory "${r}" is not empty. Remove it and continue?`,initialValue:!1})))throw new Error("Operation cancelled");s.rmSync(n,{recursive:!0,force:!0})}let l=o;Y(a)||(e.yes?l=o:l=g(await O({message:"Package name",...B(o),validate(w){if(!w||!Y(w))return"Enter a valid package.json name"}})));const p=e.template?vr(e.template):e.yes?"vite":g(await Ie({message:"Frontend template",initialValue:"vite",options:Q.map(w=>({value:w,label:hr[w]}))})),d=e.identifier?e.identifier:e.yes?_e(l):g(await O({message:"App identifier",...B(_e(l))})),b=e.preset?Ce(e.preset):e.yes?Pe:Ce(g(await O({message:"shadcn preset",...B(Pe)}))),E=e.includeStarterUI??(e.yes?!0:g(await y({message:"Include starter UI?",initialValue:!0}))),Ge=e.includeSizeOptimization??(e.yes?!1:g(await y({message:"Optimize app size?",initialValue:!0}))),Ne=e.includeInvokeExample??(e.yes?!0:g(await y({message:"Include Rust invoke example?",initialValue:!0}))),ne=e.includeWorkflow??(e.yes?!0:g(await y({message:"Include GitHub release workflow?",initialValue:!0}))),Ae=ne?e.yes?[...S]:g(await oe({message:"GitHub workflow target operating systems",initialValues:[...S],required:!0,options:S.map(w=>({value:w,label:wr[w]??w}))})):[];return{projectName:a,packageName:l,template:p,identifier:d,preset:b,includeSizeOptimization:Ge,includeStarterUI:E,includeInvokeExample:Ne,includeWorkflow:ne,targetOS:Ae,targetDir:n}}function br(){const e=new URL("../package.json",import.meta.url);return JSON.parse(s.readFileSync(e,"utf-8")).version??"0.0.0"}const yr=new Set(["add","update","remove","list"]);function Le(){console.log(`Usage:
|
|
483
|
+
create-tauri-ui [target-dir] [options] scaffold a new project
|
|
484
|
+
create-tauri-ui <add|update|remove> <battery> manage batteries in an existing project
|
|
485
|
+
create-tauri-ui list list available batteries + install status
|
|
486
|
+
|
|
487
|
+
Scaffold options:
|
|
483
488
|
-t, --template <name> vite | next | start | react-router | astro
|
|
484
489
|
--identifier <value> set the Tauri app identifier
|
|
485
490
|
--preset <value> set the shadcn preset (default: b0)
|
|
@@ -491,8 +496,14 @@ Options:
|
|
|
491
496
|
--no-invoke-example skip the Rust invoke example
|
|
492
497
|
--workflow include the GitHub release workflow
|
|
493
498
|
--no-workflow skip the GitHub release workflow
|
|
494
|
-
|
|
495
|
-
|
|
499
|
+
|
|
500
|
+
Manage options:
|
|
501
|
+
--dir <path> project directory (default: current working dir)
|
|
502
|
+
--target-os <list> comma-separated platforms for workflow (windows-latest,macos-latest,ubuntu-latest)
|
|
503
|
+
-f, --force overwrite an existing target directory / battery
|
|
504
|
+
-y, --yes accept defaults / skip confirmations
|
|
496
505
|
-v, --version display version
|
|
497
|
-
-h, --help display help
|
|
498
|
-
|
|
506
|
+
-h, --help display help
|
|
507
|
+
|
|
508
|
+
Batteries: debug-panel, workflow`)}function kr(e){const[t,...r]=e,n=t,a={action:n},o=[],l=(p,d)=>{const b=r[p+1];if(!b||b.startsWith("-"))throw new Error(`Missing value for ${d}`);return b};for(let p=0;p<r.length;p+=1){const d=r[p];switch(d){case"-h":case"--help":Le(),h.exit(0);break;case"-f":case"--force":a.force=!0;break;case"-y":case"--yes":a.yes=!0;break;case"--dir":a.targetDir=l(p,d),p+=1;break;case"--target-os":{const b=l(p,d).split(",").map(E=>E.trim()).filter(Boolean);for(const E of b)if(!S.includes(E))throw new Error(`Unknown target OS "${E}". Allowed: ${S.join(", ")}`);a.targetOS=b,p+=1;break}default:if(d.startsWith("-"))throw new Error(`Unknown flag: ${d}`);o.push(d)}}if(n!=="list"&&o.length===0)throw new Error(`Missing battery name. Usage: create-tauri-ui ${n} <battery>`);if(o.length>1)throw new Error("Only one battery may be provided per command.");return o[0]&&(a.batteryId=o[0]),a}function $r(e){const t={},r=[],n=(a,o)=>{const l=e[a+1];if(!l||l.startsWith("-"))throw new Error(`Missing value for ${o}`);return l};for(let a=0;a<e.length;a+=1){const o=e[a];switch(o){case"-h":case"--help":t.help=!0;break;case"-v":case"--version":t.version=!0;break;case"-t":case"--template":t.template=n(a,o),a+=1;break;case"--identifier":t.identifier=n(a,o),a+=1;break;case"--preset":t.preset=n(a,o),a+=1;break;case"--size-optimize":t.includeSizeOptimization=!0;break;case"--no-size-optimize":t.includeSizeOptimization=!1;break;case"--starter":t.includeStarterUI=!0;break;case"--no-starter":t.includeStarterUI=!1;break;case"--invoke-example":case"--example":t.includeInvokeExample=!0;break;case"--no-invoke-example":case"--no-example":t.includeInvokeExample=!1;break;case"--workflow":t.includeWorkflow=!0;break;case"--no-workflow":t.includeWorkflow=!1;break;case"-f":case"--force":t.force=!0;break;case"-y":case"--yes":t.yes=!0;break;default:if(o.startsWith("-"))throw new Error(`Unknown flag: ${o}`);r.push(o)}}if(r.length>1)throw new Error("Only one target directory may be provided.");return r[0]&&(t.targetDir=r[0]),t}function jr(e){const t=i.relative(h.cwd(),e)||".";return t.includes(" ")?`"${t}"`:t}function Er(e){s.rmSync(i.join(e,"node_modules"),{recursive:!0,force:!0})}function Sr(e,t){const r=[`cd ${jr(e)}`,"bun install","bun run tauri dev","bunx tauri icon app-icon.png"];I(r.join(`
|
|
509
|
+
`),"Next steps"),t&&I("Configure the GitHub release workflow secrets before publishing builds.","Release workflow")}async function Dr(){try{await F("bun",["--version"])}catch{throw new Error("bun is required. Install it from https://bun.sh.")}}function Te(e){return e instanceof D?{message:e.message,detail:e.stderr.trim()}:e instanceof Error?{message:e.message,detail:""}:{message:"An unknown error occurred.",detail:""}}async function Pr(){const e=h.argv.slice(2),t=e[0];if(t&&yr.has(t)){const l=kr(e);await pr(l);return}const r=$r(e);if(r.help){Le();return}if(r.version){console.log(br());return}ae(f.bold("create-tauri-ui")),await Dr();const n=await xr(r);Me(),J(n.targetDir);const a=Oe();let o;try{a.start("Creating the shadcn frontend scaffold"),await ot(n),a.message("Creating the Tauri native scaffold");const l=await st(n);o=l.tempDir,a.message("Merging the native layer into the frontend project"),await fr(n.targetDir,l.projectDir,n);const p=Qe(n.template);a.message(`Patching the ${n.template} project for Tauri`);try{await p.apply(n.targetDir,n)}catch(d){if(d instanceof u)m.warn(`${d.message} (${d.file})`);else throw d}if(await gr(n.targetDir,n,p.tauriConfig()),n.includeSizeOptimization&&(a.message("Applying the app size optimization battery"),await Jt(n.targetDir,n)),a.message("Applying the startup flash-prevention battery"),await Nt(n.targetDir),a.message("Applying the desktop scroll container battery"),await Bt(n.targetDir,n),a.message("Applying the external link guard battery"),await Tt(n.targetDir,n),n.includeStarterUI){a.message("Installing the starter dashboard");try{await lt(n.targetDir,n)}catch(d){if(d instanceof D)m.warn(d.message),d.stderr.trim()&&m.message(d.stderr.trim());else throw d}}a.message("Applying the development debug panel battery"),await ye(n.targetDir,n),a.message("Applying the desktop selection-behavior battery"),await Ht(n.targetDir,n),n.includeInvokeExample&&(a.message("Adding the Rust invoke example"),await Mt(n.targetDir,n)),n.includeWorkflow&&(a.message("Writing the GitHub release workflow"),await $e(n.targetDir,n)),a.message("Copying the app icon source"),await Ot(n.targetDir),Er(n.targetDir),k(n.targetDir),a.stop("Project ready"),Sr(n.targetDir,n.includeWorkflow),H(`Scaffolded ${f.cyan(n.projectName)} in ${f.dim(n.targetDir)}`)}catch(l){const p=Te(l);a.error("Scaffolding failed"),W(p.message),p.detail&&m.message(p.detail),h.exitCode=1}finally{if(o){k(o);try{$(o)}catch{}}}}Pr().catch(e=>{const t=Te(e);W(t.message),t.detail&&m.message(t.detail),h.exit(1)});
|