create-murasaki 0.0.1 → 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,5 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // create-murasaki — Scaffolder for Murasaki apps.
3
+ // Usage: npm create murasaki@latest my-app
4
+
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'
3
10
 
4
11
  // ── ANSI truecolor (Oomurasaki palette) ────────────────────────────────
5
12
  const BRIGHT = '\x1b[38;2;168;85;247m'
@@ -7,10 +14,11 @@ const DEEP = '\x1b[38;2;91;33;182m'
7
14
  const CREAM = '\x1b[38;2;250;245;232m'
8
15
  const DARK = '\x1b[38;2;59;7;100m'
9
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'
10
19
  const BOLD = '\x1b[1m'
11
20
  const RESET = '\x1b[0m'
12
21
 
13
- // Background versions for half-block compositing
14
22
  const BG_BRIGHT = '\x1b[48;2;168;85;247m'
15
23
  const BG_DEEP = '\x1b[48;2;91;33;182m'
16
24
  const BG_CREAM = '\x1b[48;2;250;245;232m'
@@ -19,7 +27,7 @@ const BG_DARK = '\x1b[48;2;59;7;100m'
19
27
  const noColor = process.env.NO_COLOR || !process.stdout.isTTY
20
28
  const c = (code) => (noColor ? '' : code)
21
29
 
22
- // ── H4 butterfly grid (19 col × 12 row) — ichi's revised Figma version ─
30
+ // ── H4 butterfly grid (19 col × 12 row) ────────────────────────────────
23
31
  const GRID = [
24
32
  '.....b.......b.....',
25
33
  '......b.....b......',
@@ -34,13 +42,10 @@ const GRID = [
34
42
  '....ddddd.ddddd....',
35
43
  '.....dddd.dddd.....',
36
44
  ]
37
-
38
45
  const FG_OF = { b: BRIGHT, d: DEEP, c: CREAM, k: DARK }
39
46
  const BG_OF = { b: BG_BRIGHT, d: BG_DEEP, c: BG_CREAM, k: BG_DARK }
40
47
  const GRID_WIDTH = GRID[0].length
41
48
 
42
- // Compress 2 grid rows into 1 terminal row using half-block ▀
43
- // (top half = fg color, bottom half = bg color)
44
49
  function renderButterflyLines() {
45
50
  const out = []
46
51
  for (let r = 0; r < GRID.length; r += 2) {
@@ -52,28 +57,17 @@ function renderButterflyLines() {
52
57
  const bCh = bot[col]
53
58
  const tFg = FG_OF[tCh]
54
59
  const bFg = FG_OF[bCh]
55
-
56
- if (!tFg && !bFg) {
57
- line += ' '
58
- } else if (tFg && !bFg) {
59
- line += c(tFg) + '▀' + c(RESET)
60
- } else if (!tFg && bFg) {
61
- line += c(bFg) + '▄' + c(RESET)
62
- } else {
63
- // Both halves filled
64
- if (tCh === bCh) {
65
- line += c(tFg) + '█' + c(RESET)
66
- } else {
67
- line += c(tFg) + c(BG_OF[bCh]) + '▀' + c(RESET)
68
- }
69
- }
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)
70
65
  }
71
66
  out.push(line)
72
67
  }
73
68
  return out
74
69
  }
75
70
 
76
- // ── figlet "Standard" wordmark ─────────────────────────────────────────
77
71
  const WORDMARK_LINES = [
78
72
  ' _ _ ',
79
73
  ' _ __ ___ _ _ _ __ __ _ ___ __ _ | | _(_)',
@@ -83,20 +77,16 @@ const WORDMARK_LINES = [
83
77
  ]
84
78
 
85
79
  function colorize(line, color, opts = {}) {
86
- const prefix = (opts.bold ? c(BOLD) : '') + c(color)
87
- return prefix + line + c(RESET)
80
+ return (opts.bold ? c(BOLD) : '') + c(color) + line + c(RESET)
88
81
  }
89
82
 
90
- // ── Render banner: butterfly LEFT, wordmark RIGHT, vertically centered ─
91
83
  function renderBanner() {
92
- const bf = renderButterflyLines() // now 6 lines
84
+ const bf = renderButterflyLines()
93
85
  const wm = WORDMARK_LINES.map((l) => colorize(l, BRIGHT, { bold: true }))
94
86
  const gap = ' '
95
-
96
87
  const total = Math.max(bf.length, wm.length)
97
88
  const wmOffset = Math.max(0, Math.floor((bf.length - wm.length) / 2))
98
89
  const blankBf = ' '.repeat(GRID_WIDTH)
99
-
100
90
  const lines = []
101
91
  for (let i = 0; i < total; i++) {
102
92
  const bfLine = bf[i] !== undefined ? bf[i] : blankBf
@@ -107,16 +97,76 @@ function renderBanner() {
107
97
  return lines.join('\n')
108
98
  }
109
99
 
110
- // ── Output ─────────────────────────────────────────────────────────────
111
- process.stdout.write('\n' + renderBanner() + '\n')
112
- process.stdout.write(`
113
- ${c(DIM)}desktop apps for Next.js developers${c(RESET)}
100
+ // ── Output helpers ─────────────────────────────────────────────────────
101
+ const log = (s) => process.stdout.write(s + '\n')
114
102
 
115
- ${c(DEEP)}docs${c(RESET)} ${c(DIM)}https://github.com/murasakijs/murasaki${c(RESET)}
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
+ }
116
115
 
117
- ${c(DIM)}🌱 Pre-alpha scaffolder not implemented yet.${c(RESET)}
118
- ${c(DIM)}Follow progress on GitHub or watch this space.${c(RESET)}
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
+ }
119
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)}
120
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
+ }
121
171
 
122
172
  process.exit(0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-murasaki",
3
- "version": "0.0.1",
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
+ }