create-murasaki 0.0.6 → 0.0.8
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 +153 -10
- package/package.json +5 -2
package/index.mjs
CHANGED
|
@@ -6,8 +6,8 @@ import { cp, mkdir, readFile, writeFile, stat } from 'node:fs/promises'
|
|
|
6
6
|
import { existsSync } from 'node:fs'
|
|
7
7
|
import { dirname, join, resolve } from 'node:path'
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
|
-
import { createInterface } from 'node:readline/promises'
|
|
10
9
|
import { spawnSync } from 'node:child_process'
|
|
10
|
+
import { text, select, isCancel, cancel, intro, outro } from '@clack/prompts'
|
|
11
11
|
|
|
12
12
|
// ── ANSI truecolor (Oomurasaki palette) ────────────────────────────────
|
|
13
13
|
const BRIGHT = '\x1b[38;2;168;85;247m'
|
|
@@ -132,15 +132,136 @@ function runInstall(targetDir, pm) {
|
|
|
132
132
|
return result.status === 0
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
function exitIfCancel(value) {
|
|
136
|
+
if (isCancel(value)) {
|
|
137
|
+
cancel('Cancelled.')
|
|
138
|
+
process.exit(0)
|
|
139
|
+
}
|
|
140
|
+
return value
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
async function promptForName() {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
const value = await text({
|
|
145
|
+
message: 'Project name',
|
|
146
|
+
placeholder: 'my-app',
|
|
147
|
+
defaultValue: 'my-app',
|
|
148
|
+
validate(v) {
|
|
149
|
+
const t = (v || '').trim()
|
|
150
|
+
if (!t) return // empty → use defaultValue
|
|
151
|
+
if (!isValidPackageName(t)) return 'Use lowercase letters, digits, dot, hyphen, underscore. Start with a letter or digit.'
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
return exitIfCancel(value)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function promptForLinter() {
|
|
158
|
+
const value = await select({
|
|
159
|
+
message: 'Which linter would you like to use?',
|
|
160
|
+
options: [
|
|
161
|
+
{ value: 'biome', label: 'Biome', hint: 'fast, single tool, recommended' },
|
|
162
|
+
{ value: 'eslint', label: 'ESLint', hint: 'classic, huge ecosystem' },
|
|
163
|
+
{ value: 'none', label: 'None', hint: 'add your own later' },
|
|
164
|
+
],
|
|
165
|
+
initialValue: 'biome',
|
|
166
|
+
})
|
|
167
|
+
return exitIfCancel(value)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Linter installers ─────────────────────────────────────────────────
|
|
171
|
+
async function applyBiome(targetDir) {
|
|
172
|
+
const biomeJson = `{
|
|
173
|
+
"$schema": "https://biomejs.dev/schemas/2.5.1/schema.json",
|
|
174
|
+
"vcs": {
|
|
175
|
+
"enabled": true,
|
|
176
|
+
"clientKind": "git",
|
|
177
|
+
"useIgnoreFile": true
|
|
178
|
+
},
|
|
179
|
+
"files": {
|
|
180
|
+
"includes": ["src/**/*.{ts,tsx,js,jsx}", "!**/node_modules", "!**/dist"]
|
|
181
|
+
},
|
|
182
|
+
"formatter": {
|
|
183
|
+
"enabled": true,
|
|
184
|
+
"indentStyle": "space",
|
|
185
|
+
"indentWidth": 2,
|
|
186
|
+
"lineWidth": 100
|
|
187
|
+
},
|
|
188
|
+
"linter": {
|
|
189
|
+
"enabled": true,
|
|
190
|
+
"rules": { "recommended": true }
|
|
191
|
+
},
|
|
192
|
+
"javascript": {
|
|
193
|
+
"formatter": {
|
|
194
|
+
"quoteStyle": "single",
|
|
195
|
+
"semicolons": "asNeeded",
|
|
196
|
+
"trailingCommas": "all"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
`
|
|
201
|
+
await writeFile(join(targetDir, 'biome.json'), biomeJson)
|
|
202
|
+
await patchPackageJson(targetDir, (pkg) => {
|
|
203
|
+
pkg.devDependencies = { ...(pkg.devDependencies || {}), '@biomejs/biome': '^2.5.1' }
|
|
204
|
+
pkg.scripts = {
|
|
205
|
+
...(pkg.scripts || {}),
|
|
206
|
+
check: 'biome check',
|
|
207
|
+
format: 'biome format --write',
|
|
208
|
+
lint: 'biome lint',
|
|
209
|
+
fix: 'biome check --write',
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function applyEslint(targetDir) {
|
|
215
|
+
const eslintConfig = `// eslint.config.js — flat config (ESLint v9+)
|
|
216
|
+
|
|
217
|
+
import js from '@eslint/js'
|
|
218
|
+
import tseslint from 'typescript-eslint'
|
|
219
|
+
import reactPlugin from 'eslint-plugin-react'
|
|
220
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
221
|
+
|
|
222
|
+
export default tseslint.config(
|
|
223
|
+
js.configs.recommended,
|
|
224
|
+
...tseslint.configs.recommended,
|
|
225
|
+
{
|
|
226
|
+
files: ['src/**/*.{ts,tsx,js,jsx}'],
|
|
227
|
+
plugins: { react: reactPlugin, 'react-hooks': reactHooks },
|
|
228
|
+
rules: {
|
|
229
|
+
...reactPlugin.configs.recommended.rules,
|
|
230
|
+
...reactHooks.configs.recommended.rules,
|
|
231
|
+
'react/react-in-jsx-scope': 'off',
|
|
232
|
+
},
|
|
233
|
+
settings: { react: { version: 'detect' } },
|
|
234
|
+
},
|
|
235
|
+
{ ignores: ['node_modules/**', 'dist/**'] },
|
|
236
|
+
)
|
|
237
|
+
`
|
|
238
|
+
await writeFile(join(targetDir, 'eslint.config.js'), eslintConfig)
|
|
239
|
+
await patchPackageJson(targetDir, (pkg) => {
|
|
240
|
+
pkg.devDependencies = {
|
|
241
|
+
...(pkg.devDependencies || {}),
|
|
242
|
+
'eslint': '^9.20.0',
|
|
243
|
+
'@eslint/js': '^9.20.0',
|
|
244
|
+
'typescript-eslint': '^8.20.0',
|
|
245
|
+
'eslint-plugin-react': '^7.37.0',
|
|
246
|
+
'eslint-plugin-react-hooks': '^5.1.0',
|
|
247
|
+
}
|
|
248
|
+
pkg.scripts = {
|
|
249
|
+
...(pkg.scripts || {}),
|
|
250
|
+
lint: 'eslint .',
|
|
251
|
+
'lint:fix': 'eslint . --fix',
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function patchPackageJson(targetDir, mutator) {
|
|
257
|
+
const pkgPath = join(targetDir, 'package.json')
|
|
258
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
259
|
+
mutator(pkg)
|
|
260
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
140
261
|
}
|
|
141
262
|
|
|
142
263
|
// ── Scaffold ───────────────────────────────────────────────────────────
|
|
143
|
-
async function scaffold(projectName) {
|
|
264
|
+
async function scaffold(projectName, linter) {
|
|
144
265
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
145
266
|
const templateDir = join(__dirname, 'templates', 'default')
|
|
146
267
|
const targetDir = resolve(process.cwd(), projectName)
|
|
@@ -167,6 +288,15 @@ async function scaffold(projectName) {
|
|
|
167
288
|
const pkgPatched = pkgRaw.replace(/"__PROJECT_NAME__"/, JSON.stringify(projectName))
|
|
168
289
|
await writeFile(pkgPath, pkgPatched)
|
|
169
290
|
|
|
291
|
+
// Apply linter overlay
|
|
292
|
+
if (linter === 'biome') {
|
|
293
|
+
log(` ${c(DIM)}○${c(RESET)} Adding ${c(BOLD)}Biome${c(RESET)}...`)
|
|
294
|
+
await applyBiome(targetDir)
|
|
295
|
+
} else if (linter === 'eslint') {
|
|
296
|
+
log(` ${c(DIM)}○${c(RESET)} Adding ${c(BOLD)}ESLint${c(RESET)}...`)
|
|
297
|
+
await applyEslint(targetDir)
|
|
298
|
+
}
|
|
299
|
+
|
|
170
300
|
log(` ${c(GREEN)}${c(BOLD)}✓${c(RESET)} Created ${c(BOLD)}${projectName}/${c(RESET)}`)
|
|
171
301
|
|
|
172
302
|
// Install dependencies (Next.js-like behavior)
|
|
@@ -199,13 +329,26 @@ async function scaffold(projectName) {
|
|
|
199
329
|
// ── Main ──────────────────────────────────────────────────────────────
|
|
200
330
|
const banner = renderBanner()
|
|
201
331
|
process.stdout.write('\n' + banner + '\n\n')
|
|
202
|
-
process.stdout.write(` ${c(DIM)}desktop apps for Next.js developers${c(RESET)}\n`)
|
|
332
|
+
process.stdout.write(` ${c(DIM)}desktop apps for Next.js developers${c(RESET)}\n\n`)
|
|
333
|
+
|
|
334
|
+
// ── Parse args ────────────────────────────────────────────────────────
|
|
335
|
+
const argName = process.argv[2] && !process.argv[2].startsWith('--') ? process.argv[2] : null
|
|
336
|
+
const argLinter = (() => {
|
|
337
|
+
const i = process.argv.indexOf('--linter')
|
|
338
|
+
if (i >= 0 && process.argv[i + 1]) {
|
|
339
|
+
const v = process.argv[i + 1].toLowerCase()
|
|
340
|
+
if (v === 'biome' || v === 'eslint' || v === 'none') return v
|
|
341
|
+
}
|
|
342
|
+
return null
|
|
343
|
+
})()
|
|
203
344
|
|
|
204
|
-
|
|
205
|
-
const projectName = argName
|
|
345
|
+
intro('🦋 create-murasaki')
|
|
346
|
+
const projectName = argName || (await promptForName())
|
|
347
|
+
const linter = argLinter || (await promptForLinter())
|
|
348
|
+
outro('Setting things up...')
|
|
206
349
|
|
|
207
350
|
try {
|
|
208
|
-
await scaffold(projectName)
|
|
351
|
+
await scaffold(projectName, linter)
|
|
209
352
|
} catch (err) {
|
|
210
353
|
log(`\n ${c(RED)}✗${c(RESET)} Scaffold failed: ${err.message}\n`)
|
|
211
354
|
process.exit(1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-murasaki",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Scaffolder for Murasaki apps. Run with `npm create murasaki@latest`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"murasaki",
|
|
@@ -29,5 +29,8 @@
|
|
|
29
29
|
"templates",
|
|
30
30
|
"README.md",
|
|
31
31
|
"LICENSE"
|
|
32
|
-
]
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^1.6.0"
|
|
35
|
+
}
|
|
33
36
|
}
|