create-murasaki 0.0.0 → 0.0.2

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
@@ -1,11 +1,172 @@
1
1
  #!/usr/bin/env node
2
2
  // create-murasaki — Scaffolder for Murasaki apps.
3
- // 🌱 Pre-alpha: not implemented yet.
3
+ // Usage: npm create murasaki@latest my-app
4
4
 
5
- console.log(`
6
- 🟣 Murasaki desktop framework for Next.js developers
5
+ import { cp, mkdir, readFile, writeFile, stat } from 'node:fs/promises'
6
+ import { existsSync } from 'node:fs'
7
+ import { dirname, join, resolve } from 'node:path'
8
+ import { fileURLToPath } from 'node:url'
9
+ import { createInterface } from 'node:readline/promises'
7
10
 
8
- create-murasaki is not implemented yet.
9
- Follow progress at https://github.com/murasakijs/murasaki
11
+ // ── ANSI truecolor (Oomurasaki palette) ────────────────────────────────
12
+ const BRIGHT = '\x1b[38;2;168;85;247m'
13
+ const DEEP = '\x1b[38;2;91;33;182m'
14
+ const CREAM = '\x1b[38;2;250;245;232m'
15
+ const DARK = '\x1b[38;2;59;7;100m'
16
+ const DIM = '\x1b[38;2;136;136;153m'
17
+ const GREEN = '\x1b[38;2;76;175;80m'
18
+ const RED = '\x1b[38;2;239;68;68m'
19
+ const BOLD = '\x1b[1m'
20
+ const RESET = '\x1b[0m'
21
+
22
+ const BG_BRIGHT = '\x1b[48;2;168;85;247m'
23
+ const BG_DEEP = '\x1b[48;2;91;33;182m'
24
+ const BG_CREAM = '\x1b[48;2;250;245;232m'
25
+ const BG_DARK = '\x1b[48;2;59;7;100m'
26
+
27
+ const noColor = process.env.NO_COLOR || !process.stdout.isTTY
28
+ const c = (code) => (noColor ? '' : code)
29
+
30
+ // ── H4 butterfly grid (19 col × 12 row) ────────────────────────────────
31
+ const GRID = [
32
+ '.....b.......b.....',
33
+ '......b.....b......',
34
+ '...bbbb.....bbbb...',
35
+ '..bbbbbb...bbbbbb..',
36
+ '.bbbbcbbb.bbbcbbbb.',
37
+ '.bbbbbbbb.bbbbbbbb.',
38
+ '..bbbbbbb.bbbbbbb..',
39
+ '...bbbbb...bbbbb...',
40
+ '...................',
41
+ '.....ddd...ddd.....',
42
+ '....ddddd.ddddd....',
43
+ '.....dddd.dddd.....',
44
+ ]
45
+ const FG_OF = { b: BRIGHT, d: DEEP, c: CREAM, k: DARK }
46
+ const BG_OF = { b: BG_BRIGHT, d: BG_DEEP, c: BG_CREAM, k: BG_DARK }
47
+ const GRID_WIDTH = GRID[0].length
48
+
49
+ function renderButterflyLines() {
50
+ const out = []
51
+ for (let r = 0; r < GRID.length; r += 2) {
52
+ const top = GRID[r] || '.'.repeat(GRID_WIDTH)
53
+ const bot = GRID[r + 1] || '.'.repeat(GRID_WIDTH)
54
+ let line = ''
55
+ for (let col = 0; col < GRID_WIDTH; col++) {
56
+ const tCh = top[col]
57
+ const bCh = bot[col]
58
+ const tFg = FG_OF[tCh]
59
+ const bFg = FG_OF[bCh]
60
+ if (!tFg && !bFg) line += ' '
61
+ else if (tFg && !bFg) line += c(tFg) + '▀' + c(RESET)
62
+ else if (!tFg && bFg) line += c(bFg) + '▄' + c(RESET)
63
+ else if (tCh === bCh) line += c(tFg) + '█' + c(RESET)
64
+ else line += c(tFg) + c(BG_OF[bCh]) + '▀' + c(RESET)
65
+ }
66
+ out.push(line)
67
+ }
68
+ return out
69
+ }
70
+
71
+ const WORDMARK_LINES = [
72
+ ' _ _ ',
73
+ ' _ __ ___ _ _ _ __ __ _ ___ __ _ | | _(_)',
74
+ "| '_ ` _ \\| | | | '__/ _` / __|/ _` || |/ /| |",
75
+ '| | | | | | |_| | | | (_| \\__ \\ (_| || < | |',
76
+ '|_| |_| |_|\\__,_|_| \\__,_|___/\\__,_||_|\\_\\|_|',
77
+ ]
78
+
79
+ function colorize(line, color, opts = {}) {
80
+ return (opts.bold ? c(BOLD) : '') + c(color) + line + c(RESET)
81
+ }
82
+
83
+ function renderBanner() {
84
+ const bf = renderButterflyLines()
85
+ const wm = WORDMARK_LINES.map((l) => colorize(l, BRIGHT, { bold: true }))
86
+ const gap = ' '
87
+ const total = Math.max(bf.length, wm.length)
88
+ const wmOffset = Math.max(0, Math.floor((bf.length - wm.length) / 2))
89
+ const blankBf = ' '.repeat(GRID_WIDTH)
90
+ const lines = []
91
+ for (let i = 0; i < total; i++) {
92
+ const bfLine = bf[i] !== undefined ? bf[i] : blankBf
93
+ const wmIdx = i - wmOffset
94
+ const wmLine = (wmIdx >= 0 && wmIdx < wm.length) ? wm[wmIdx] : ''
95
+ lines.push(' ' + bfLine + gap + wmLine)
96
+ }
97
+ return lines.join('\n')
98
+ }
99
+
100
+ // ── Output helpers ─────────────────────────────────────────────────────
101
+ const log = (s) => process.stdout.write(s + '\n')
102
+
103
+ // ── Validation ─────────────────────────────────────────────────────────
104
+ function isValidPackageName(name) {
105
+ // Basic npm package name rules
106
+ return /^[a-z0-9][a-z0-9._-]*$/.test(name)
107
+ }
108
+
109
+ async function promptForName() {
110
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
111
+ const answer = await rl.question(` ${c(DEEP)}?${c(RESET)} ${c(BOLD)}Project name${c(RESET)} ${c(DIM)}(my-app):${c(RESET)} `)
112
+ rl.close()
113
+ return answer.trim() || 'my-app'
114
+ }
115
+
116
+ // ── Scaffold ───────────────────────────────────────────────────────────
117
+ async function scaffold(projectName) {
118
+ const __dirname = dirname(fileURLToPath(import.meta.url))
119
+ const templateDir = join(__dirname, 'templates', 'default')
120
+ const targetDir = resolve(process.cwd(), projectName)
121
+
122
+ // Validate
123
+ if (!isValidPackageName(projectName)) {
124
+ log(`\n ${c(RED)}✗${c(RESET)} Invalid project name: ${c(BOLD)}${projectName}${c(RESET)}`)
125
+ log(` ${c(DIM)}must match: ${c(RESET)}${c(DIM)}/^[a-z0-9][a-z0-9._-]*$/${c(RESET)}\n`)
126
+ process.exit(1)
127
+ }
128
+ if (existsSync(targetDir)) {
129
+ log(`\n ${c(RED)}✗${c(RESET)} Target directory already exists: ${c(BOLD)}${targetDir}${c(RESET)}\n`)
130
+ process.exit(1)
131
+ }
132
+
133
+ // Copy template
134
+ log(`\n ${c(DIM)}○${c(RESET)} Creating ${c(BOLD)}${projectName}/${c(RESET)} from template...`)
135
+ await mkdir(targetDir, { recursive: true })
136
+ await cp(templateDir, targetDir, { recursive: true })
137
+
138
+ // Patch package.json name
139
+ const pkgPath = join(targetDir, 'package.json')
140
+ const pkgRaw = await readFile(pkgPath, 'utf8')
141
+ const pkgPatched = pkgRaw.replace(/"__PROJECT_NAME__"/, JSON.stringify(projectName))
142
+ await writeFile(pkgPath, pkgPatched)
143
+
144
+ log(` ${c(GREEN)}${c(BOLD)}✓${c(RESET)} Created ${c(BOLD)}${projectName}/${c(RESET)}`)
145
+
146
+ // Next steps
147
+ log(`
148
+ ${c(DIM)}Next:${c(RESET)}
149
+ ${c(BRIGHT)}cd${c(RESET)} ${projectName}
150
+ ${c(BRIGHT)}npm install${c(RESET)} ${c(DIM)}# or pnpm install${c(RESET)}
151
+ ${c(BRIGHT)}npm run dev${c(RESET)} ${c(DIM)}# starts the desktop window with HMR${c(RESET)}
152
+
153
+ ${c(DIM)}docs${c(RESET)} ${c(DIM)}https://github.com/murasakijs/murasaki${c(RESET)}
10
154
  `)
155
+ }
156
+
157
+ // ── Main ──────────────────────────────────────────────────────────────
158
+ const banner = renderBanner()
159
+ process.stdout.write('\n' + banner + '\n\n')
160
+ process.stdout.write(` ${c(DIM)}desktop apps for Next.js developers${c(RESET)}\n`)
161
+
162
+ const argName = process.argv[2]
163
+ const projectName = argName || (await promptForName())
164
+
165
+ try {
166
+ await scaffold(projectName)
167
+ } catch (err) {
168
+ log(`\n ${c(RED)}✗${c(RESET)} Scaffold failed: ${err.message}\n`)
169
+ process.exit(1)
170
+ }
171
+
11
172
  process.exit(0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-murasaki",
3
- "version": "0.0.0",
3
+ "version": "0.0.2",
4
4
  "description": "Scaffolder for Murasaki apps. Run with `npm create murasaki@latest`.",
5
5
  "keywords": [
6
6
  "murasaki",
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "murasaki dev"
8
+ },
9
+ "dependencies": {
10
+ "murasaki": "^0.0.1",
11
+ "react": "^19.2.7",
12
+ "react-dom": "^19.2.7"
13
+ },
14
+ "devDependencies": {
15
+ "@types/react": "^19.2.17",
16
+ "@types/react-dom": "^19.2.3"
17
+ }
18
+ }
@@ -0,0 +1,11 @@
1
+ // src/app.tsx — your app. Edit me and the window reloads in place.
2
+
3
+ export default function App() {
4
+ return (
5
+ <main>
6
+ <h1>Hello, Murasaki 🦋</h1>
7
+ <p>This view lives in <code>src/app.tsx</code>.</p>
8
+ <p className="hint">Edit the file and the window reloads instantly.</p>
9
+ </main>
10
+ )
11
+ }
@@ -0,0 +1,54 @@
1
+ // src/layout.tsx — wraps your app. Edit me to change the global shell.
2
+
3
+ import type { ReactNode } from 'react'
4
+
5
+ export default function Layout({ children }: { children: ReactNode }) {
6
+ return (
7
+ <html lang="en">
8
+ <head>
9
+ <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
+ </head>
51
+ <body>{children}</body>
52
+ </html>
53
+ )
54
+ }