create-murasaki 0.0.5 → 0.0.7

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/index.mjs CHANGED
@@ -139,8 +139,111 @@ async function promptForName() {
139
139
  return answer.trim() || 'my-app'
140
140
  }
141
141
 
142
+ async function promptForLinter() {
143
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
144
+ const answer = await rl.question(` ${c(DEEP)}?${c(RESET)} ${c(BOLD)}Linter${c(RESET)} ${c(DIM)}(biome / eslint / none) [biome]:${c(RESET)} `)
145
+ rl.close()
146
+ const v = answer.trim().toLowerCase()
147
+ if (v === 'eslint' || v === 'e') return 'eslint'
148
+ if (v === 'none' || v === 'n') return 'none'
149
+ return 'biome' // default
150
+ }
151
+
152
+ // ── Linter installers ─────────────────────────────────────────────────
153
+ async function applyBiome(targetDir) {
154
+ const biomeJson = `{
155
+ "$schema": "https://biomejs.dev/schemas/2.5.1/schema.json",
156
+ "vcs": {
157
+ "enabled": true,
158
+ "clientKind": "git",
159
+ "useIgnoreFile": true
160
+ },
161
+ "files": {
162
+ "includes": ["src/**/*.{ts,tsx,js,jsx}", "!**/node_modules", "!**/dist"]
163
+ },
164
+ "formatter": {
165
+ "enabled": true,
166
+ "indentStyle": "space",
167
+ "indentWidth": 2,
168
+ "lineWidth": 100
169
+ },
170
+ "linter": {
171
+ "enabled": true,
172
+ "rules": { "recommended": true }
173
+ },
174
+ "javascript": {
175
+ "formatter": {
176
+ "quoteStyle": "single",
177
+ "semicolons": "asNeeded",
178
+ "trailingCommas": "all"
179
+ }
180
+ }
181
+ }
182
+ `
183
+ await writeFile(join(targetDir, 'biome.json'), biomeJson)
184
+ await patchPackageJson(targetDir, (pkg) => {
185
+ pkg.devDependencies = { ...(pkg.devDependencies || {}), '@biomejs/biome': '^2.5.1' }
186
+ pkg.scripts = {
187
+ ...(pkg.scripts || {}),
188
+ check: 'biome check',
189
+ format: 'biome format --write',
190
+ lint: 'biome lint',
191
+ fix: 'biome check --write',
192
+ }
193
+ })
194
+ }
195
+
196
+ async function applyEslint(targetDir) {
197
+ const eslintConfig = `// eslint.config.js — flat config (ESLint v9+)
198
+
199
+ import js from '@eslint/js'
200
+ import tseslint from 'typescript-eslint'
201
+ import reactPlugin from 'eslint-plugin-react'
202
+ import reactHooks from 'eslint-plugin-react-hooks'
203
+
204
+ export default tseslint.config(
205
+ js.configs.recommended,
206
+ ...tseslint.configs.recommended,
207
+ {
208
+ files: ['src/**/*.{ts,tsx,js,jsx}'],
209
+ plugins: { react: reactPlugin, 'react-hooks': reactHooks },
210
+ rules: {
211
+ ...reactPlugin.configs.recommended.rules,
212
+ ...reactHooks.configs.recommended.rules,
213
+ 'react/react-in-jsx-scope': 'off',
214
+ },
215
+ settings: { react: { version: 'detect' } },
216
+ },
217
+ { ignores: ['node_modules/**', 'dist/**'] },
218
+ )
219
+ `
220
+ await writeFile(join(targetDir, 'eslint.config.js'), eslintConfig)
221
+ await patchPackageJson(targetDir, (pkg) => {
222
+ pkg.devDependencies = {
223
+ ...(pkg.devDependencies || {}),
224
+ 'eslint': '^9.20.0',
225
+ '@eslint/js': '^9.20.0',
226
+ 'typescript-eslint': '^8.20.0',
227
+ 'eslint-plugin-react': '^7.37.0',
228
+ 'eslint-plugin-react-hooks': '^5.1.0',
229
+ }
230
+ pkg.scripts = {
231
+ ...(pkg.scripts || {}),
232
+ lint: 'eslint .',
233
+ 'lint:fix': 'eslint . --fix',
234
+ }
235
+ })
236
+ }
237
+
238
+ async function patchPackageJson(targetDir, mutator) {
239
+ const pkgPath = join(targetDir, 'package.json')
240
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
241
+ mutator(pkg)
242
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
243
+ }
244
+
142
245
  // ── Scaffold ───────────────────────────────────────────────────────────
143
- async function scaffold(projectName) {
246
+ async function scaffold(projectName, linter) {
144
247
  const __dirname = dirname(fileURLToPath(import.meta.url))
145
248
  const templateDir = join(__dirname, 'templates', 'default')
146
249
  const targetDir = resolve(process.cwd(), projectName)
@@ -167,6 +270,15 @@ async function scaffold(projectName) {
167
270
  const pkgPatched = pkgRaw.replace(/"__PROJECT_NAME__"/, JSON.stringify(projectName))
168
271
  await writeFile(pkgPath, pkgPatched)
169
272
 
273
+ // Apply linter overlay
274
+ if (linter === 'biome') {
275
+ log(` ${c(DIM)}○${c(RESET)} Adding ${c(BOLD)}Biome${c(RESET)}...`)
276
+ await applyBiome(targetDir)
277
+ } else if (linter === 'eslint') {
278
+ log(` ${c(DIM)}○${c(RESET)} Adding ${c(BOLD)}ESLint${c(RESET)}...`)
279
+ await applyEslint(targetDir)
280
+ }
281
+
170
282
  log(` ${c(GREEN)}${c(BOLD)}✓${c(RESET)} Created ${c(BOLD)}${projectName}/${c(RESET)}`)
171
283
 
172
284
  // Install dependencies (Next.js-like behavior)
@@ -201,11 +313,22 @@ const banner = renderBanner()
201
313
  process.stdout.write('\n' + banner + '\n\n')
202
314
  process.stdout.write(` ${c(DIM)}desktop apps for Next.js developers${c(RESET)}\n`)
203
315
 
204
- const argName = process.argv[2]
316
+ // ── Parse args ────────────────────────────────────────────────────────
317
+ const argName = process.argv[2] && !process.argv[2].startsWith('--') ? process.argv[2] : null
318
+ const argLinter = (() => {
319
+ const i = process.argv.indexOf('--linter')
320
+ if (i >= 0 && process.argv[i + 1]) {
321
+ const v = process.argv[i + 1].toLowerCase()
322
+ if (v === 'biome' || v === 'eslint' || v === 'none') return v
323
+ }
324
+ return null
325
+ })()
326
+
205
327
  const projectName = argName || (await promptForName())
328
+ const linter = argLinter || (await promptForLinter())
206
329
 
207
330
  try {
208
- await scaffold(projectName)
331
+ await scaffold(projectName, linter)
209
332
  } catch (err) {
210
333
  log(`\n ${c(RED)}✗${c(RESET)} Scaffold failed: ${err.message}\n`)
211
334
  process.exit(1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-murasaki",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Scaffolder for Murasaki apps. Run with `npm create murasaki@latest`.",
5
5
  "keywords": [
6
6
  "murasaki",
@@ -7,7 +7,7 @@
7
7
  "dev": "murasaki dev"
8
8
  },
9
9
  "dependencies": {
10
- "murasaki": "^0.0.2",
10
+ "murasaki": "^0.0.3",
11
11
  "react": "^19.2.7",
12
12
  "react-dom": "^19.2.7"
13
13
  },
@@ -0,0 +1,45 @@
1
+ /* src/globals.css — global styles. Edit me. */
2
+
3
+ :root { color-scheme: light dark; }
4
+ * { box-sizing: border-box; }
5
+
6
+ body {
7
+ margin: 0;
8
+ min-height: 100vh;
9
+ display: grid;
10
+ place-items: center;
11
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
12
+ background: linear-gradient(135deg, #faf8ff 0%, #f3eafe 100%);
13
+ color: #1a0a33;
14
+ }
15
+
16
+ @media (prefers-color-scheme: dark) {
17
+ body {
18
+ background: linear-gradient(135deg, #0a0612 0%, #1a0a33 100%);
19
+ color: #faf8ff;
20
+ }
21
+ }
22
+
23
+ main { text-align: center; padding: 40px; }
24
+
25
+ h1 {
26
+ font-size: 96px;
27
+ margin: 0;
28
+ background: linear-gradient(135deg, #5B21B6 0%, #A855F7 100%);
29
+ -webkit-background-clip: text;
30
+ background-clip: text;
31
+ color: transparent;
32
+ font-weight: 800;
33
+ letter-spacing: -0.04em;
34
+ }
35
+
36
+ p { margin-top: 16px; font-size: 18px; opacity: 0.7; }
37
+ .hint { opacity: 0.45; font-size: 14px; }
38
+
39
+ code {
40
+ font-family: 'SF Mono', Menlo, monospace;
41
+ background: rgba(168, 85, 247, 0.12);
42
+ padding: 2px 8px;
43
+ border-radius: 4px;
44
+ font-size: 0.9em;
45
+ }
@@ -1,52 +1,23 @@
1
1
  // src/layout.tsx — wraps your app. Edit me to change the global shell.
2
+ // Global styles live in src/globals.css (auto-injected by murasaki).
2
3
 
3
4
  import type { ReactNode } from 'react'
5
+ import type { Metadata } from 'murasaki'
6
+
7
+ export const metadata: Metadata = {
8
+ title: 'My Murasaki App',
9
+ description: 'A desktop app built with Murasaki.',
10
+ window: {
11
+ width: 1280,
12
+ height: 800,
13
+ },
14
+ }
4
15
 
5
16
  export default function Layout({ children }: { children: ReactNode }) {
6
17
  return (
7
18
  <html lang="en">
8
19
  <head>
9
20
  <meta charSet="utf-8" />
10
- <title>Murasaki App</title>
11
- <style>{`
12
- :root { color-scheme: light dark; }
13
- * { box-sizing: border-box; }
14
- body {
15
- margin: 0;
16
- min-height: 100vh;
17
- display: grid;
18
- place-items: center;
19
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
20
- background: linear-gradient(135deg, #faf8ff 0%, #f3eafe 100%);
21
- color: #1a0a33;
22
- }
23
- @media (prefers-color-scheme: dark) {
24
- body {
25
- background: linear-gradient(135deg, #0a0612 0%, #1a0a33 100%);
26
- color: #faf8ff;
27
- }
28
- }
29
- main { text-align: center; padding: 40px; }
30
- h1 {
31
- font-size: 96px;
32
- margin: 0;
33
- background: linear-gradient(135deg, #5B21B6 0%, #A855F7 100%);
34
- -webkit-background-clip: text;
35
- background-clip: text;
36
- color: transparent;
37
- font-weight: 800;
38
- letter-spacing: -0.04em;
39
- }
40
- p { margin-top: 16px; font-size: 18px; opacity: 0.7; }
41
- .hint { opacity: 0.45; font-size: 14px; }
42
- code {
43
- font-family: 'SF Mono', Menlo, monospace;
44
- background: rgba(168, 85, 247, 0.12);
45
- padding: 2px 8px;
46
- border-radius: 4px;
47
- font-size: 0.9em;
48
- }
49
- `}</style>
50
21
  </head>
51
22
  <body>{children}</body>
52
23
  </html>
@@ -10,8 +10,7 @@
10
10
  "skipLibCheck": true,
11
11
  "resolveJsonModule": true,
12
12
  "isolatedModules": true,
13
- "noEmit": true,
14
- "types": ["node"]
13
+ "noEmit": true
15
14
  },
16
15
  "include": ["src/**/*"]
17
16
  }