create-muten 0.0.9 → 0.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.
Files changed (27) hide show
  1. package/README.md +13 -5
  2. package/index.js +54 -8
  3. package/package.json +2 -2
  4. package/template-tauri/muten-logo.png +0 -0
  5. package/template-tauri/src-tauri/Cargo.toml +15 -0
  6. package/template-tauri/src-tauri/build.rs +3 -0
  7. package/template-tauri/src-tauri/capabilities/default.json +7 -0
  8. package/template-tauri/src-tauri/icons/128x128.png +0 -0
  9. package/template-tauri/src-tauri/icons/128x128@2x.png +0 -0
  10. package/template-tauri/src-tauri/icons/32x32.png +0 -0
  11. package/template-tauri/src-tauri/icons/64x64.png +0 -0
  12. package/template-tauri/src-tauri/icons/Square107x107Logo.png +0 -0
  13. package/template-tauri/src-tauri/icons/Square142x142Logo.png +0 -0
  14. package/template-tauri/src-tauri/icons/Square150x150Logo.png +0 -0
  15. package/template-tauri/src-tauri/icons/Square284x284Logo.png +0 -0
  16. package/template-tauri/src-tauri/icons/Square30x30Logo.png +0 -0
  17. package/template-tauri/src-tauri/icons/Square310x310Logo.png +0 -0
  18. package/template-tauri/src-tauri/icons/Square44x44Logo.png +0 -0
  19. package/template-tauri/src-tauri/icons/Square71x71Logo.png +0 -0
  20. package/template-tauri/src-tauri/icons/Square89x89Logo.png +0 -0
  21. package/template-tauri/src-tauri/icons/StoreLogo.png +0 -0
  22. package/template-tauri/src-tauri/icons/icon.icns +0 -0
  23. package/template-tauri/src-tauri/icons/icon.ico +0 -0
  24. package/template-tauri/src-tauri/icons/icon.png +0 -0
  25. package/template-tauri/src-tauri/src/lib.rs +6 -0
  26. package/template-tauri/src-tauri/src/main.rs +6 -0
  27. package/template-tauri/src-tauri/tauri.conf.json +29 -0
package/README.md CHANGED
@@ -61,14 +61,20 @@ In an interactive terminal it prompts for a few things (defaults in parentheses)
61
61
  |---|---|---|
62
62
  | **Project name** | any valid folder name | `muten-app` |
63
63
  | **Template** | `muten` / `muten + React` / `muten + Svelte` | `muten` |
64
- | **Styling** | `css` / `scss` | `css` |
65
- | **Add Tailwind CSS?** | `Y` / `n` (CSS only) | `n` |
66
- | **Add DaisyUI?** | `Y` / `n` (needs Tailwind) | `n` |
64
+ | **Styling** | `CSS` / `SCSS` / `Tailwind CSS` / `DaisyUI` (brings Tailwind) | `CSS` |
65
+ | **Add Vercel deploy config?** | `Y` / `n` | `n` |
66
+ | **Desktop app (Tauri)?** | `Y` / `n` | `n` |
67
67
  | **Package manager** | `npm` / `pnpm` / `yarn` / `bun` | the one that launched it |
68
68
  | **Install deps and start dev now?** | `Y` / `n` | `Y` |
69
69
 
70
- Tailwind is an optional add-on **on top of** CSS (the look layer; you still style via `class("…")`) it
71
- wires `@tailwindcss/vite` + an `@import "tailwindcss"` and notes the setup in the app's `.claude/` guide.
70
+ **Styling is one explicit choice** each is opt-in, nothing is bundled by default: `CSS` (plain) or `SCSS`
71
+ ship no framework; `Tailwind CSS` adds `@tailwindcss/vite` + `@import "tailwindcss"`; `DaisyUI` adds its
72
+ component classes on top (and brings Tailwind). You always style via `class("…")`.
73
+
74
+ **Targets are independent opt-ins** — web, desktop, both, or neither, from the same `.muten` source:
75
+ - **Vercel** writes a `vercel.json` so muten's real-path routes don't 404 on a hard refresh (SPA fallback to `index.html`).
76
+ - **Tauri** adds `src-tauri/` (a native desktop app — ships the OS webview, *not* a browser) + a `tauri` script:
77
+ `npm run tauri dev` / `tauri build`. Needs the [Rust toolchain](https://rustup.rs) installed (not auto-installed).
72
78
 
73
79
  ## Templates (flavors)
74
80
 
@@ -110,6 +116,8 @@ create-muten my-app --css --no-install # just scaffold, decide later
110
116
  | `--css` / `--scss` | pick the stylesheet (default: `css`) |
111
117
  | `--tailwind` | add Tailwind CSS v4 on top of CSS (forces `--css`) |
112
118
  | `--daisyui` | add DaisyUI component classes (implies `--tailwind`) |
119
+ | `--vercel` | add `vercel.json` (SPA fallback so real-path routes work on Vercel) |
120
+ | `--tauri` | add `src-tauri/` — a native desktop app (needs the Rust toolchain) |
113
121
  | `--pm <npm\|pnpm\|yarn\|bun>` | package manager to use (default: detected) |
114
122
  | `--no-install` | scaffold only — don't install or start the dev server |
115
123
  | `--help` | print usage and exit |
package/index.js CHANGED
@@ -15,6 +15,7 @@ import color from 'picocolors';
15
15
 
16
16
  const SELF = dirname(fileURLToPath(import.meta.url));
17
17
  const TEMPLATE = join(SELF, 'template');
18
+ const TAURI_TEMPLATE = join(SELF, 'template-tauri'); // src-tauri/ overlay, copied only when --tauri
18
19
  const TEMPLATES = ['muten', 'react', 'svelte']; // the "template" IS the flavor: pure muten, or muten + a framework for islands
19
20
  const PKG = JSON.parse(readFileSync(join(SELF, 'package.json'), 'utf8'));
20
21
  const PMS = ['npm', 'pnpm', 'yarn', 'bun'];
@@ -113,6 +114,24 @@ DaisyUI adds **component classes** on top of Tailwind — use them in \`class("
113
114
  \`@plugin "daisyui";\` is already in \`src/styles.css\`. Interactive behavior (toggle a modal/dropdown) you build
114
115
  with Muten: \`state\` + \`class(active when isOpen)\` + \`on(click: …)\`.
115
116
  `;
117
+ // Tauri = the SAME web build wrapped in a native OS-webview window (no browser bundled). Desktop target.
118
+ const TAURI_NOTE = (pm) => `
119
+ ## Desktop app (Tauri)
120
+ This app also ships as a native desktop app via Tauri (\`src-tauri/\`). The SAME \`.muten\` frontend runs in
121
+ an OS-webview window — build the UI exactly like the web app (routing works as-is: the webview runs the SPA,
122
+ no server, no URL bar, no fallback needed).
123
+ - \`${pm} run tauri dev\` — run the desktop app (hot-reloads the frontend).
124
+ - \`${pm} run tauri build\` — native installer in \`src-tauri/target/release/bundle/\`.
125
+ - Needs the **Rust toolchain** on the machine (https://rustup.rs) — Tauri compiles a small native shell. Not auto-installed.
126
+ - Custom icon: \`${pm} run tauri icon path/to/logo.png\` regenerates \`src-tauri/icons/\`.
127
+ `;
128
+
129
+ // Deploy on Vercel: muten routes are real paths (History API), so an unmatched path must fall back to
130
+ // index.html (else a hard refresh of /about 404s). Static assets are served first; only routes rewrite.
131
+ const VERCEL_JSON = `{
132
+ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
133
+ }
134
+ `;
116
135
 
117
136
  // Which PM launched us? npm/pnpm/yarn/bun set npm_config_user_agent — the idiomatic default.
118
137
  const detectPM = () => { const ua = process.env.npm_config_user_agent || ''; return PMS.find((p) => ua.startsWith(p + '/')) || 'npm'; };
@@ -132,13 +151,15 @@ async function main() {
132
151
  const has = (f) => argv.includes(f);
133
152
  const val = (f) => { const i = argv.indexOf(f); return i >= 0 ? argv[i + 1] : undefined; };
134
153
  if (has('-v') || has('--version')) { console.log(PKG.version); return; }
135
- if (has('-h') || has('--help')) { console.log('Usage: create-muten [name] [--template muten|react|svelte] [--css|--scss] [--tailwind] [--daisyui] [--pm npm|pnpm|yarn|bun] [--no-install]'); return; }
154
+ if (has('-h') || has('--help')) { console.log('Usage: create-muten [name] [--template muten|react|svelte] [--css|--scss] [--tailwind] [--daisyui] [--vercel] [--tauri] [--pm npm|pnpm|yarn|bun] [--no-install]'); return; }
136
155
 
137
156
  let name = argv.filter((a, i) => !a.startsWith('-') && argv[i - 1] !== '--pm' && argv[i - 1] !== '--template')[0];
138
157
  let template = val('--template') || (has('--react') ? 'react' : has('--svelte') ? 'svelte' : has('--muten') ? 'muten' : undefined); // flavor: muten | react | svelte
139
158
  let style = has('--scss') ? 'scss' : has('--css') ? 'css' : undefined; // the base stylesheet
140
159
  let tailwind = has('--tailwind') ? true : undefined; // optional add-on (CSS only)
141
160
  let daisyui = has('--daisyui') ? true : undefined; // component classes on Tailwind
161
+ let vercel = has('--vercel') ? true : undefined; // a vercel.json with the SPA fallback rewrite
162
+ let tauri = has('--tauri') ? true : undefined; // src-tauri/ → native desktop app
142
163
  let pm = val('--pm');
143
164
  let install = has('--no-install') ? false : undefined;
144
165
  if (name && !validName(name)) { console.error(`Invalid name: "${name}" (letters, digits, . _ -)`); process.exit(1); }
@@ -156,12 +177,19 @@ async function main() {
156
177
  { value: 'react', label: 'muten + React', hint: 'React islands: shadcn, Radix, any React lib' },
157
178
  { value: 'svelte', label: 'muten + Svelte', hint: 'Svelte islands: a lighter runtime' },
158
179
  ] }));
159
- if (!style) style = keep(await select({ message: 'Styling', options: [
160
- { value: 'css', label: 'CSS', hint: 'plain, zero deps' },
161
- { value: 'scss', label: 'SCSS', hint: 'adds sass' },
162
- ] }));
163
- if (tailwind === undefined) tailwind = style === 'css' ? keep(await confirm({ message: 'Add Tailwind CSS?', initialValue: false })) : false;
164
- if (tailwind && daisyui === undefined) daisyui = keep(await confirm({ message: 'Add DaisyUI? (component classes: btn, card, modal)', initialValue: false }));
180
+ if (!style && tailwind === undefined) { // ONE explicit styling choice — each is opt-in, "CSS" = nothing extra
181
+ const styling = keep(await select({ message: 'Styling', options: [
182
+ { value: 'css', label: 'CSS', hint: 'plain — no framework, zero deps' },
183
+ { value: 'scss', label: 'SCSS', hint: 'adds sass' },
184
+ { value: 'tailwind', label: 'Tailwind CSS', hint: 'utility classes on top of CSS' },
185
+ { value: 'daisyui', label: 'DaisyUI', hint: 'component classes (btn, card, modal) brings Tailwind with it' },
186
+ ] }));
187
+ style = styling === 'scss' ? 'scss' : 'css';
188
+ tailwind = styling === 'tailwind' || styling === 'daisyui';
189
+ daisyui = styling === 'daisyui';
190
+ }
191
+ if (vercel === undefined) vercel = keep(await confirm({ message: 'Add Vercel deploy config? (vercel.json — fixes real-path routing on Vercel)', initialValue: false }));
192
+ if (tauri === undefined) tauri = keep(await confirm({ message: 'Desktop app? (Tauri — native window, ships the OS webview, needs Rust)', initialValue: false }));
165
193
  }
166
194
  name = name || 'muten-app';
167
195
  template = template || 'muten';
@@ -169,6 +197,8 @@ async function main() {
169
197
  if (daisyui) tailwind = true; // DaisyUI is a Tailwind plugin
170
198
  if (tailwind === undefined) tailwind = false;
171
199
  if (daisyui === undefined) daisyui = false;
200
+ if (vercel === undefined) vercel = false;
201
+ if (tauri === undefined) tauri = false;
172
202
  if (tailwind) style = 'css'; // Tailwind v4 is CSS-native (not SCSS)
173
203
  const svelte = template === 'svelte'; // the flavor IS the islands choice
174
204
  const react = template === 'react';
@@ -183,6 +213,7 @@ async function main() {
183
213
  cpSync(TEMPLATE, target, { recursive: true });
184
214
  const ignore = join(target, '_gitignore');
185
215
  if (existsSync(ignore)) renameSync(ignore, join(target, '.gitignore'));
216
+ if (vercel) writeFileSync(join(target, 'vercel.json'), VERCEL_JSON); // SPA fallback so real-path routes work on Vercel
186
217
 
187
218
  const pkgPath = join(target, 'package.json');
188
219
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
@@ -204,10 +235,25 @@ async function main() {
204
235
  if (svelte) { addDep({ svelte: '^5.0.0' }); addDev({ '@sveltejs/vite-plugin-svelte': '^7.0.0' }); }
205
236
  if (react) { addDep({ react: '^19.0.0', 'react-dom': '^19.0.0' }); addDev({ '@vitejs/plugin-react': '^6.0.0' }); }
206
237
  if (svelte || react) appendAgents(ISLANDS_NOTE({ svelte, react }));
238
+ if (tauri) { // native desktop wrapper around the same web build (dist)
239
+ cpSync(join(TAURI_TEMPLATE, 'src-tauri'), join(target, 'src-tauri'), { recursive: true });
240
+ writeFileSync(join(target, 'src-tauri', '.gitignore'), '/target\n/gen/schemas\n'); // npm strips real .gitignore from packages
241
+ const confPath = join(target, 'src-tauri', 'tauri.conf.json');
242
+ const conf = JSON.parse(readFileSync(confPath, 'utf8'));
243
+ conf.productName = name;
244
+ conf.identifier = `com.muten.${name.replace(/[^a-z0-9]/gi, '').toLowerCase() || 'app'}`; // reverse-DNS, no dashes/underscores
245
+ conf.app.windows[0].title = name;
246
+ conf.build.beforeDevCommand = `${pm} run dev`;
247
+ conf.build.beforeBuildCommand = `${pm} run build`;
248
+ writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
249
+ addDev({ '@tauri-apps/cli': '^2.0.0' });
250
+ pkg.scripts = { ...pkg.scripts, tauri: 'tauri' };
251
+ appendAgents(TAURI_NOTE(pm));
252
+ }
207
253
  writeFileSync(join(target, 'vite.config.mjs'), viteConfig({ tailwind, svelte, react })); // composed: muten + chosen plugins
208
254
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
209
255
 
210
- const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}`;
256
+ const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}${vercel ? ' + Vercel' : ''}${tauri ? ' + Tauri' : ''}`;
211
257
  if (!install) {
212
258
  if (process.stdin.isTTY) { note(`cd ${name}\n${pm} install\n${pm} run dev`, 'Next steps'); outro(color.green(`Created ${name} (${desc})`)); }
213
259
  else console.log(`\n Created ${name} (${desc}, ${pm})\n cd ${name} && ${pm} install && ${pm} run dev\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-muten",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Scaffold a new Muten app.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,5 +19,5 @@
19
19
  "picocolors": "^1.0.1"
20
20
  },
21
21
  "keywords": ["muten", "create-muten", "scaffold", "starter", "cli", "frontend"],
22
- "files": ["index.js", "template", "overlays"]
22
+ "files": ["index.js", "template", "template-tauri", "overlays"]
23
23
  }
Binary file
@@ -0,0 +1,15 @@
1
+ [package]
2
+ name = "muten-app"
3
+ version = "0.1.0"
4
+ description = "A muten desktop app"
5
+ edition = "2021"
6
+
7
+ [lib]
8
+ name = "app_lib"
9
+ crate-type = ["staticlib", "cdylib", "rlib"]
10
+
11
+ [build-dependencies]
12
+ tauri-build = { version = "2", features = [] }
13
+
14
+ [dependencies]
15
+ tauri = { version = "2", features = [] }
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../gen/schemas/desktop-schema.json",
3
+ "identifier": "default",
4
+ "description": "enables the default permissions",
5
+ "windows": ["main"],
6
+ "permissions": ["core:default"]
7
+ }
@@ -0,0 +1,6 @@
1
+ #[cfg_attr(mobile, tauri::mobile_entry_point)]
2
+ pub fn run() {
3
+ tauri::Builder::default()
4
+ .run(tauri::generate_context!())
5
+ .expect("error while running tauri application");
6
+ }
@@ -0,0 +1,6 @@
1
+ // Prevents an extra console window on Windows in release, do NOT remove!!
2
+ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
+
4
+ fn main() {
5
+ app_lib::run()
6
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://schema.tauri.app/config/2",
3
+ "productName": "muten-app",
4
+ "version": "0.1.0",
5
+ "identifier": "com.muten.app",
6
+ "build": {
7
+ "frontendDist": "../dist",
8
+ "devUrl": "http://localhost:5173",
9
+ "beforeDevCommand": "npm run dev",
10
+ "beforeBuildCommand": "npm run build"
11
+ },
12
+ "app": {
13
+ "windows": [
14
+ { "title": "muten-app", "width": 1000, "height": 700 }
15
+ ],
16
+ "security": { "csp": null }
17
+ },
18
+ "bundle": {
19
+ "active": true,
20
+ "targets": "all",
21
+ "icon": [
22
+ "icons/32x32.png",
23
+ "icons/128x128.png",
24
+ "icons/128x128@2x.png",
25
+ "icons/icon.icns",
26
+ "icons/icon.ico"
27
+ ]
28
+ }
29
+ }