create-arsh-tauri 0.0.0-stage → 0.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -2
  3. package/index.js +239 -0
  4. package/package.json +43 -4
  5. package/template/.eslintrc.cjs +18 -0
  6. package/template/.github/workflows/ci.yml +50 -0
  7. package/template/AGENTS.md +50 -0
  8. package/template/README.md +51 -0
  9. package/template/gitignore +37 -0
  10. package/template/index.html +12 -0
  11. package/template/package-lock.json +4454 -0
  12. package/template/package.json +42 -0
  13. package/template/src/App.tsx +35 -0
  14. package/template/src/index.css +8 -0
  15. package/template/src/lib/tauri.ts +21 -0
  16. package/template/src/lib/utils.test.ts +17 -0
  17. package/template/src/lib/utils.ts +6 -0
  18. package/template/src/main.tsx +10 -0
  19. package/template/src/ui/appLayout.tsx +35 -0
  20. package/template/src/ui/button.tsx +63 -0
  21. package/template/src/ui/dialog.tsx +71 -0
  22. package/template/src/ui/index.ts +8 -0
  23. package/template/src/ui/navItem.tsx +37 -0
  24. package/template/src/ui/spinner.tsx +33 -0
  25. package/template/src/ui/styles.css +96 -0
  26. package/template/src/ui/switch.tsx +77 -0
  27. package/template/src/ui/titleBar.tsx +157 -0
  28. package/template/src/ui/tooltip.tsx +141 -0
  29. package/template/src/vite-env.d.ts +1 -0
  30. package/template/src-tauri/Cargo.lock +4316 -0
  31. package/template/src-tauri/Cargo.toml +26 -0
  32. package/template/src-tauri/build.rs +3 -0
  33. package/template/src-tauri/capabilities/default.json +16 -0
  34. package/template/src-tauri/icons/128x128.png +0 -0
  35. package/template/src-tauri/icons/128x128@2x.png +0 -0
  36. package/template/src-tauri/icons/32x32.png +0 -0
  37. package/template/src-tauri/icons/icon.icns +0 -0
  38. package/template/src-tauri/icons/icon.ico +0 -0
  39. package/template/src-tauri/src/lib.rs +14 -0
  40. package/template/src-tauri/src/main.rs +6 -0
  41. package/template/src-tauri/tauri.conf.json +40 -0
  42. package/template/temp/.gitkeep +0 -0
  43. package/template/tsconfig.json +25 -0
  44. package/template/tsconfig.node.json +12 -0
  45. package/template/vite.config.ts +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Liz@Arshdelight
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ # create-arsh-tauri
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/create-arsh-tauri)](https://www.npmjs.com/package/create-arsh-tauri)
4
+
5
+ The lightweight desktop stack we use for client work, one command away: **Tauri 2 + Vite + React 19 + TypeScript + Tailwind CSS 4**, a frameless window with a hand-rolled title bar, the same UI kit as `create-arsh-electron` — and Rust-side release polish (LTO, strip) done at the factory level.
6
+
7
+ ```bash
8
+ npm create arsh-tauri@latest my-app
9
+ ```
10
+
11
+ ## What you get
12
+
13
+ ```
14
+ ├── src-tauri/ # Rust backend: commands, window config, bundle settings
15
+ ├── src/ # frontend (React + Tailwind 4)
16
+ │ └── ui/ # AppLayout, TitleBar, NavItem, Button, Dialog,
17
+ │ │ # Tooltip, Spinner, Switch — theme tokens in
18
+ │ │ # plain CSS variables, same kit as arsh-electron
19
+ ├── AGENTS.md # bootstrap: the first AI session interviews you
20
+ │ # and writes this project's conventions
21
+ ├── src-tauri/tauri.conf.json
22
+ └── vite.config.ts
23
+ ```
24
+
25
+ - **Frameless window, custom title bar** — minimize / maximize / close via Tauri's window API, double-click to toggle maximize, an optional always-on-top pin, drag regions already wired up
26
+ - **Minimal single-page starter** — a counter button plus a `greet` round-trip through Rust, so the bridge is proven before you write real code
27
+ - **Test & CI baseline** — vitest wired up (`npm test`), a GitHub Actions workflow running lint + typecheck + tests + `cargo check` on push/PR
28
+ - **Packaging polish built in** — release profile with LTO + strip for small binaries, fixed Vite dev port (1420) that the Tauri CLI expects, a complete `.gitignore`, and git initialized with a first commit so AI edits are diffable from day one
29
+ - **Tiny output** — a scaffolded app builds to a few MB, no bundled runtime browser
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ npm create arsh-tauri@latest [project-name]
35
+ npm create arsh-tauri@latest [project-name] -- --scope <name>
36
+
37
+ # non-interactive
38
+ npm create arsh-tauri@latest my-app -- --yes
39
+ ```
40
+
41
+ | Flag | Description |
42
+ |---|---|
43
+ | `--scope <name>` | bundle identifier namespace, `com.<scope>.<app>` (default: `arshdelight`) |
44
+ | `--yes` | Skip prompts, use defaults (git init included) |
45
+
46
+ The CLI asks two questions — project name, git init or not — and derives everything else: `package.json` name (and `author`, from your git config), Rust crate + lib names, bundle `identifier`, `productName`, window title and title-bar brand. Answer `arsh-demo` and you get crate `arsh-demo`, lib `arsh_demo_lib`, product `ArshDemo` / `com.arshdelight.arshdemo`; add `--scope mycompany` and it becomes `com.mycompany.arshdemo`.
47
+
48
+ ## Prerequisites
49
+
50
+ - Node.js ≥ 20.11
51
+ - Rust ≥ 1.77.2 (`rustup` — https://rustup.rs)
52
+ - WebView2 (preinstalled on Windows 10/11) — see https://tauri.app/start/prerequisites/
53
+
54
+ ## License
55
+
56
+ MIT
package/index.js ADDED
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { execFileSync } from 'node:child_process'
5
+ import process from 'node:process'
6
+ import * as p from '@clack/prompts'
7
+
8
+ const templateDir = path.join(import.meta.dirname, 'template')
9
+
10
+ const usage = [
11
+ 'Usage: npm create arsh-tauri@latest [project-name] [--scope <name>] [--yes]',
12
+ '',
13
+ ' --scope <name> appId namespace (default: arshdelight)',
14
+ ' --yes Skip prompts and use defaults (git init included)',
15
+ ].join('\n')
16
+
17
+ function slugify(name) {
18
+ return name.trim().toLowerCase().replace(/[^a-z\d-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')
19
+ }
20
+
21
+ function toProductName(name) {
22
+ return name
23
+ .split(/[^a-zA-Z\d]+/)
24
+ .filter(Boolean)
25
+ .map((w) => w[0].toUpperCase() + w.slice(1))
26
+ .join('') || 'ArshApp'
27
+ }
28
+
29
+ function isEmptyDir(dir) {
30
+ return !fs.existsSync(dir) || fs.readdirSync(dir).length === 0
31
+ }
32
+
33
+ function emptyDir(dir) {
34
+ for (const entry of fs.readdirSync(dir)) {
35
+ fs.rmSync(path.join(dir, entry), { recursive: true, force: true })
36
+ }
37
+ }
38
+
39
+ function gitAvailable() {
40
+ try {
41
+ execFileSync('git', ['--version'], { stdio: 'ignore' })
42
+ return true
43
+ } catch {
44
+ return false
45
+ }
46
+ }
47
+
48
+ function gitAuthor() {
49
+ try {
50
+ const name = execFileSync('git', ['config', 'user.name'], { stdio: 'pipe' }).toString().trim()
51
+ if (!name) return null
52
+ const email = execFileSync('git', ['config', 'user.email'], { stdio: 'pipe' }).toString().trim()
53
+ return email ? { name, email } : { name }
54
+ } catch {
55
+ return null
56
+ }
57
+ }
58
+
59
+ function initGit(projectDir) {
60
+ const run = (args) => execFileSync('git', args, { cwd: projectDir, stdio: 'ignore' })
61
+ run(['init'])
62
+ run(['add', '-A'])
63
+ try {
64
+ run(['commit', '-m', 'chore: initial commit'])
65
+ } catch {
66
+ p.log.warn('git commit skipped — set user.name / user.email, then commit manually')
67
+ }
68
+ }
69
+
70
+ function applyRenames(projectDir, { packageName, productName, appId, libName, author }) {
71
+ const replace = (file, pairs) => {
72
+ const file_ = path.join(projectDir, file)
73
+ let content = fs.readFileSync(file_, 'utf8')
74
+ for (const [from, to] of pairs) content = content.split(from).join(to)
75
+ fs.writeFileSync(file_, content)
76
+ }
77
+ replace('package.json', [['"name": "arsh-tauri-app"', `"name": "${packageName}"`]])
78
+ if (author) {
79
+ replace('package.json', [['"author": "Your Name"', `"author": ${JSON.stringify(author)}`]])
80
+ }
81
+ replace('src-tauri/tauri.conf.json', [
82
+ ['com.arshdelight.arshapp', appId],
83
+ ['"productName": "ArshApp"', `"productName": "${productName}"`],
84
+ ['"title": "ArshApp"', `"title": "${productName}"`],
85
+ ])
86
+ replace('src-tauri/Cargo.toml', [
87
+ ['name = "arsh-tauri-app"', `name = "${packageName}"`],
88
+ ['arsh_tauri_app_lib', libName],
89
+ ])
90
+ replace('src-tauri/src/main.rs', [['arsh_tauri_app_lib', libName]])
91
+ replace('index.html', [['Arsh Tauri', productName]])
92
+ replace('src/App.tsx', [['brand="Arsh Tauri"', `brand="${productName}"`]])
93
+ replace('README.md', [['# Arsh Tauri', `# ${productName}`]])
94
+ }
95
+
96
+ async function main() {
97
+ const args = process.argv.slice(2)
98
+ if (args.includes('-h') || args.includes('--help')) {
99
+ console.log(usage)
100
+ return
101
+ }
102
+
103
+ const yes = args.includes('--yes')
104
+ const scopeIdx = args.indexOf('--scope')
105
+ const scopeArg = scopeIdx !== -1 ? args[scopeIdx + 1] : args.find((a) => a.startsWith('--scope='))
106
+ const positional = args.find((a) => !a.startsWith('-') && a !== scopeArg)
107
+ const nonInteractive = !process.stdout.isTTY || yes
108
+
109
+ p.intro('create-arsh-tauri')
110
+
111
+ let scopeSlug = 'arshdelight'
112
+ if (scopeArg !== undefined) {
113
+ const raw = scopeArg.startsWith('--scope=') ? scopeArg.slice('--scope='.length) : scopeArg
114
+ scopeSlug = slugify(raw)
115
+ if (!scopeSlug) {
116
+ p.log.error('Scope must contain letters or digits')
117
+ process.exitCode = 1
118
+ return
119
+ }
120
+ }
121
+
122
+ let projectName = positional
123
+ if (!projectName) {
124
+ if (nonInteractive) {
125
+ p.log.error(`Project name is required in non-interactive mode.\n${usage}`)
126
+ process.exitCode = 1
127
+ return
128
+ }
129
+ const res = await p.text({
130
+ message: 'Project name',
131
+ placeholder: 'my-app',
132
+ validate: (v) => {
133
+ if (!v || !v.trim()) return 'Please enter a project name'
134
+ if (!slugify(v)) return 'Must contain letters or digits'
135
+ },
136
+ })
137
+ if (p.isCancel(res)) return
138
+ projectName = res
139
+ }
140
+
141
+ const slug = slugify(projectName)
142
+ if (!slug) {
143
+ p.log.error('Project name must contain letters or digits')
144
+ process.exitCode = 1
145
+ return
146
+ }
147
+ const projectDir = path.resolve(process.cwd(), slug)
148
+ const packageName = slug
149
+ const productName = toProductName(slug)
150
+ const appId = `com.${scopeSlug}.${slug.replace(/-/g, '')}`
151
+ const libName = `${slug.replace(/-/g, '_')}_lib`
152
+ const author = gitAuthor()
153
+
154
+ if (!isEmptyDir(projectDir)) {
155
+ let overwrite = yes
156
+ if (process.stdout.isTTY && !yes) {
157
+ const res = await p.confirm({
158
+ message: `Directory "${slug}" is not empty. Remove existing files and continue?`,
159
+ initialValue: false,
160
+ })
161
+ if (p.isCancel(res)) return
162
+ overwrite = res
163
+ }
164
+ if (!overwrite) {
165
+ p.log.warn('Cancelled — directory left untouched')
166
+ return
167
+ }
168
+ emptyDir(projectDir)
169
+ }
170
+
171
+ const spinner = p.spinner()
172
+ spinner.start('Scaffolding project…')
173
+ // the published tarball never contains these dirs; skipping them here keeps
174
+ // local runs (template with node_modules / cargo target) identical to prod
175
+ fs.cpSync(templateDir, projectDir, {
176
+ recursive: true,
177
+ filter: (src) => {
178
+ const rel = path.relative(templateDir, src)
179
+ if (!rel) return true
180
+ const segs = rel.split(path.sep)
181
+ const [top] = segs
182
+ if (segs.includes('node_modules') || segs.includes('target')) return false
183
+ return !(top.startsWith('.temp') || top === 'dist' || (top === 'src-tauri' && segs[1] === 'gen'))
184
+ },
185
+ })
186
+ // npm publish strips .gitignore from the tarball, so the template ships it
187
+ // dotless and it gets its dot back here
188
+ for (const f of ['gitignore', 'temp']) {
189
+ fs.renameSync(path.join(projectDir, f), path.join(projectDir, `.${f}`))
190
+ }
191
+ applyRenames(projectDir, { packageName, productName, appId, libName, author })
192
+
193
+ let gitNote = ''
194
+ if (gitAvailable()) {
195
+ let doGit = true
196
+ if (process.stdout.isTTY && !yes) {
197
+ const res = await p.confirm({ message: 'Initialize a git repository and make the first commit?', initialValue: true })
198
+ if (p.isCancel(res)) return
199
+ doGit = res
200
+ }
201
+ if (doGit) {
202
+ try {
203
+ initGit(projectDir)
204
+ gitNote = 'git repository initialized with a first commit'
205
+ } catch {
206
+ gitNote = 'git initialization failed — you can run it manually'
207
+ }
208
+ }
209
+ } else {
210
+ gitNote = 'git not found — skipped version control setup'
211
+ }
212
+ spinner.stop('Project scaffolded')
213
+
214
+ const nextSteps = [
215
+ `cd ${slug}`,
216
+ 'npm install',
217
+ 'npm run dev',
218
+ ]
219
+ p.note(
220
+ [
221
+ ...nextSteps,
222
+ '',
223
+ 'npm run dev compiles the Rust backend on first run —',
224
+ 'grab a coffee, later runs are instant.',
225
+ '',
226
+ 'Then open the project with any AI coding tool —',
227
+ 'AGENTS.md is a bootstrap: the first AI session will interview you',
228
+ 'and write the project conventions.',
229
+ gitNote,
230
+ ].join('\n'),
231
+ 'Next steps',
232
+ )
233
+ p.outro(`Done. Build something marvelous with ${productName}.`)
234
+ }
235
+
236
+ main().catch((err) => {
237
+ p.log.error(String(err?.message ?? err))
238
+ process.exitCode = 1
239
+ })
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "create-arsh-tauri",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Scaffold an Arshdelight-style Tauri 2 + React + TypeScript desktop app — frameless window, UI primitives, and Rust-side build polish built in.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-arsh-tauri": "index.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Arshdelight/create-arsh-tauri.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Arshdelight/create-arsh-tauri/issues"
15
+ },
16
+ "homepage": "https://github.com/Arshdelight/create-arsh-tauri#readme",
17
+ "files": [
18
+ "index.js",
19
+ "template",
20
+ "!template/node_modules",
21
+ "!template/src-tauri/target",
22
+ "!template/dist",
23
+ "!template/src-tauri/gen/schemas"
24
+ ],
25
+ "engines": {
26
+ "node": ">=20.11"
27
+ },
28
+ "keywords": [
29
+ "tauri",
30
+ "react",
31
+ "typescript",
32
+ "scaffold",
33
+ "create",
34
+ "starter",
35
+ "template"
36
+ ],
37
+ "license": "MIT",
38
+ "author": {
39
+ "name": "Liz@Arshdelight",
40
+ "email": "cjhzisme@163.com"
41
+ },
42
+ "dependencies": {
43
+ "@clack/prompts": "^1.8.1"
44
+ }
45
+ }
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: { browser: true, es2020: true },
4
+ extends: [
5
+ 'eslint:recommended',
6
+ 'plugin:@typescript-eslint/recommended',
7
+ 'plugin:react-hooks/recommended',
8
+ ],
9
+ ignorePatterns: ['dist', 'dist-electron', 'release', '.temp', '*.local'],
10
+ parser: '@typescript-eslint/parser',
11
+ plugins: ['react-refresh'],
12
+ rules: {
13
+ 'react-refresh/only-export-components': [
14
+ 'warn',
15
+ { allowConstantExport: true },
16
+ ],
17
+ },
18
+ }
@@ -0,0 +1,50 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ web:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 22
18
+ cache: 'npm'
19
+
20
+ - run: npm ci
21
+
22
+ - run: npm run lint
23
+
24
+ - run: npx tsc --noEmit
25
+
26
+ - run: npm test
27
+
28
+ rust:
29
+ runs-on: windows-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+
33
+ - uses: actions/setup-node@v4
34
+ with:
35
+ node-version: 22
36
+ cache: 'npm'
37
+
38
+ - uses: dtolnay/rust-toolchain@stable
39
+
40
+ - uses: Swatinem/rust-cache@v2
41
+ with:
42
+ workspaces: src-tauri
43
+
44
+ # generate_context! embeds the frontend assets, so dist/ must exist
45
+ - run: npm ci
46
+
47
+ - run: npm run build:web
48
+
49
+ - run: cargo check
50
+ working-directory: src-tauri
@@ -0,0 +1,50 @@
1
+ # AGENTS.md — bootstrap
2
+
3
+ This file bootstraps the real AGENTS.md. Instead of shipping opinions the user
4
+ never asked for, your **first session** in this project runs a short interview
5
+ and rewrites this file into the project's actual conventions.
6
+
7
+ ## First session: run the interview
8
+
9
+ If this file still contains the "Engineering rules" section kept verbatim, the
10
+ interview has not happened yet. Ask everything in **one round** (not one message
11
+ per question), accept short answers, and skip any question the user answers
12
+ with "default".
13
+
14
+ 1. **Commit style** — prefix convention (`feat:` / `fix:` / `chore:` / none)?
15
+ Message language? Subject-line rules (length, imperative, capitalization)?
16
+ 2. **Collaboration style** — plan first and wait for approval, or act directly?
17
+ Where is the line between "just do it" and "ask first"? How much refactoring
18
+ is welcome while touching nearby code?
19
+ 3. **Language** — for conversation, code comments, docs, and UI strings?
20
+ 4. **Delivery bar** — besides `npm run lint` and `npx tsc --noEmit`, what must
21
+ pass before work counts as done (tests, `cargo check`, packaging smoke)?
22
+ 5. **Anything else** — pet peeves, forbidden actions, naming or structure
23
+ preferences worth writing down?
24
+
25
+ Then rewrite this file:
26
+
27
+ - Keep the file name `AGENTS.md` and keep the `## Engineering rules` section
28
+ intact — translate it only if the user asked for another language.
29
+ - Structure the rest around the interview answers.
30
+ - Zero filler. No platitudes ("write clean code", "be concise"), nothing the
31
+ template already enforces by construction. Every line must change how you act.
32
+ - The interview is one-time. Once this bootstrap section is gone, never ask again.
33
+
34
+ ## Engineering rules
35
+
36
+ Three rules that normal, well-scoped tasks would otherwise silently violate.
37
+ Keep them intact.
38
+
39
+ - **Dependency partition**: `dependencies` in package.json stays empty — the
40
+ Vite frontend bundles everything, so all JS libraries go to
41
+ `devDependencies`. Rust dependencies live in `src-tauri/Cargo.toml` and are
42
+ added only when actually used.
43
+ - **Frontend never touches system capabilities directly**: all native calls go
44
+ through `@tauri-apps/api` (`invoke` / `listen`, see `src/lib/tauri.ts`).
45
+ New commands: `#[tauri::command]` in `src-tauri/src/lib.rs`, registered in
46
+ `generate_handler!`, permission entries in `src-tauri/capabilities/` when
47
+ needed.
48
+ - **Definition of done**: `npm run lint` (zero warnings), `npx tsc --noEmit`,
49
+ and `npm test` all pass before work counts as complete; run `cargo check`
50
+ in `src-tauri/` whenever Rust code or capabilities change.
@@ -0,0 +1,51 @@
1
+ # Arsh Tauri
2
+
3
+ Tauri 2 + React + TypeScript desktop app.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js ≥ 20.11
8
+ - Rust ≥ 1.77.2(`rustup` — https://rustup.rs)
9
+ - WebView2(Windows 10/11 自带)
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ npm install
15
+ npm run dev # 启动桌面应用(首次会编译 Rust,之后秒开)
16
+ npm run dev:web # 只跑前端(UI 开发,无 Rust 桥接,自动降级)
17
+ npm run build # 类型检查 + 前端构建 + 打包安装器到 src-tauri/target/release/bundle/
18
+ npm run lint # ESLint,零警告
19
+ npm test # vitest
20
+ ```
21
+
22
+ ## Structure
23
+
24
+ ```
25
+ ├── src-tauri/ # Rust 后端
26
+ │ ├── src/lib.rs # run() + 所有 #[tauri::command]
27
+ │ ├── src/main.rs # 入口
28
+ │ ├── capabilities/ # 权限配置(默认含无边框窗口控制)
29
+ │ ├── tauri.conf.json # 窗口 / 打包 / dev server 配置
30
+ │ └── icons/ # 应用图标(npx tauri icon <png> 重新生成)
31
+ ├── src/
32
+ │ ├── ui/ # AppLayout, TitleBar, NavItem, Button, Dialog,
33
+ │ │ # Tooltip, Spinner, Switch + theme tokens (styles.css)
34
+ │ ├── lib/ # cn() 和 Tauri 桥接检测
35
+ │ ├── App.tsx # 入口页(含 invoke 示例)
36
+ │ └── index.css
37
+ ├── AGENTS.md # project conventions for AI coding tools
38
+ └── vite.config.ts
39
+ ```
40
+
41
+ ## Packaging
42
+
43
+ - 自定义图标:放一张 ≥ 1024×1024 的方形 PNG,跑 `npx tauri icon <png>` 重新生成 `src-tauri/icons/`。
44
+ - 安装器元数据在 `src-tauri/tauri.conf.json`(`identifier`、`productName`)。
45
+ - 发布体积优化(LTO/strip)在 `src-tauri/Cargo.toml` 的 `[profile.release]`。
46
+
47
+ ## AI coding tools
48
+
49
+ `AGENTS.md` holds the project conventions. On the first session it walks the
50
+ tool through a short interview and writes the final conventions for this
51
+ project.
@@ -0,0 +1,37 @@
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
+ release
13
+ src-tauri/target
14
+ src-tauri/gen/schemas
15
+ *.local
16
+
17
+ # 本地草稿 / AI 协作产物(约定:临时文件一律进 .temp/)
18
+ .temp*
19
+ .zcode/
20
+ .zcodeignore
21
+
22
+ # Editor directories and files
23
+ .vscode/*
24
+ !.vscode/extensions.json
25
+ .idea
26
+ .DS_Store
27
+ *.suo
28
+ *.ntvs*
29
+ *.njsproj
30
+ *.sln
31
+ *.sw?
32
+
33
+ # AGENTS.md 默认随模板入库;若想保持本地不提交,取消下一行注释
34
+ # AGENTS.md
35
+
36
+ # test coverage
37
+ coverage/
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Arsh Tauri</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>