@semos-labs/create-glyph 0.1.74

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 (2) hide show
  1. package/dist/index.js +325 -0
  2. package/package.json +36 -0
package/dist/index.js ADDED
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import fs from "fs";
5
+ import path from "path";
6
+ var bold = (s) => `\x1B[1m${s}\x1B[22m`;
7
+ var dim = (s) => `\x1B[2m${s}\x1B[22m`;
8
+ var cyan = (s) => `\x1B[36m${s}\x1B[39m`;
9
+ var green = (s) => `\x1B[32m${s}\x1B[39m`;
10
+ var red = (s) => `\x1B[31m${s}\x1B[39m`;
11
+ function detectPackageManager() {
12
+ const ua = process.env.npm_config_user_agent ?? "";
13
+ if (ua.startsWith("bun")) return "bun";
14
+ if (ua.startsWith("pnpm")) return "pnpm";
15
+ if (ua.startsWith("yarn")) return "yarn";
16
+ const execPath = process.argv0 || process.execPath || "";
17
+ if (execPath.includes("bun")) return "bun";
18
+ if (execPath.includes("pnpm")) return "pnpm";
19
+ if (execPath.includes("yarn")) return "yarn";
20
+ return "npm";
21
+ }
22
+ function getInstallCommand(pm) {
23
+ switch (pm) {
24
+ case "bun":
25
+ return "bun install";
26
+ case "pnpm":
27
+ return "pnpm install";
28
+ case "yarn":
29
+ return "yarn";
30
+ case "npm":
31
+ return "npm install";
32
+ }
33
+ }
34
+ function getRunCommand(pm) {
35
+ switch (pm) {
36
+ case "bun":
37
+ return "bun dev";
38
+ case "pnpm":
39
+ return "pnpm dev";
40
+ case "yarn":
41
+ return "yarn dev";
42
+ case "npm":
43
+ return "npm run dev";
44
+ }
45
+ }
46
+ function templatePackageJson(name) {
47
+ return JSON.stringify(
48
+ {
49
+ name,
50
+ version: "0.0.1",
51
+ private: true,
52
+ type: "module",
53
+ scripts: {
54
+ dev: "tsx src/main.tsx",
55
+ build: "tsup src/main.tsx --format esm --target node18"
56
+ },
57
+ dependencies: {
58
+ "@semos-labs/glyph": "latest",
59
+ react: "^19.0.0"
60
+ },
61
+ devDependencies: {
62
+ "@types/node": "^20",
63
+ "@types/react": "^19",
64
+ tsx: "^4",
65
+ tsup: "^8",
66
+ typescript: "^5.5"
67
+ }
68
+ },
69
+ null,
70
+ 2
71
+ );
72
+ }
73
+ function templateTsconfig() {
74
+ return JSON.stringify(
75
+ {
76
+ compilerOptions: {
77
+ lib: ["ESNext"],
78
+ target: "ES2022",
79
+ module: "ESNext",
80
+ moduleResolution: "bundler",
81
+ jsx: "react-jsx",
82
+ strict: true,
83
+ skipLibCheck: true,
84
+ esModuleInterop: true,
85
+ forceConsistentCasingInFileNames: true,
86
+ declaration: true,
87
+ sourceMap: true,
88
+ noEmit: true
89
+ },
90
+ include: ["src"]
91
+ },
92
+ null,
93
+ 2
94
+ );
95
+ }
96
+ function templateMainTsx(name) {
97
+ const displayName = name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
98
+ return `import React, { useState, useCallback } from "react";
99
+ import {
100
+ render,
101
+ Box,
102
+ Text,
103
+ Input,
104
+ Button,
105
+ Checkbox,
106
+ Keybind,
107
+ Progress,
108
+ Spacer,
109
+ useApp,
110
+ } from "@semos-labs/glyph";
111
+
112
+ // \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
113
+
114
+ interface Todo {
115
+ id: number;
116
+ text: string;
117
+ done: boolean;
118
+ }
119
+
120
+ let nextId = 1;
121
+
122
+ // \u2500\u2500 App \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
123
+
124
+ function App() {
125
+ const { exit } = useApp();
126
+
127
+ const [todos, setTodos] = useState<Todo[]>([
128
+ { id: nextId++, text: "Learn Glyph basics", done: true },
129
+ { id: nextId++, text: "Build something cool", done: false },
130
+ { id: nextId++, text: "Ship it", done: false },
131
+ ]);
132
+ const [newTodo, setNewTodo] = useState("");
133
+
134
+ const doneCount = todos.filter((t) => t.done).length;
135
+ const progress = todos.length > 0 ? doneCount / todos.length : 0;
136
+
137
+ const handleAdd = useCallback(() => {
138
+ const text = newTodo.trim();
139
+ if (!text) return;
140
+ setTodos((prev) => [...prev, { id: nextId++, text, done: false }]);
141
+ setNewTodo("");
142
+ }, [newTodo]);
143
+
144
+ const handleToggle = useCallback((id: number) => {
145
+ setTodos((prev) =>
146
+ prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
147
+ );
148
+ }, []);
149
+
150
+ const handleDelete = useCallback((id: number) => {
151
+ setTodos((prev) => prev.filter((t) => t.id !== id));
152
+ }, []);
153
+
154
+ return (
155
+ <Box
156
+ style={{
157
+ flexDirection: "column",
158
+ width: "100%",
159
+ height: "100%",
160
+ padding: 1,
161
+ gap: 1,
162
+ }}
163
+ >
164
+ {/* Quit shortcut */}
165
+ <Keybind keypress="q" onPress={() => exit()} />
166
+
167
+ {/* Header */}
168
+ <Box style={{ flexDirection: "row", alignItems: "center" }}>
169
+ <Text style={{ bold: true, color: "cyanBright" }}>\u2726 ${displayName}</Text>
170
+ <Spacer />
171
+ <Text style={{ dim: true }}>
172
+ {doneCount}/{todos.length} done
173
+ </Text>
174
+ </Box>
175
+
176
+ {/* Progress bar */}
177
+ <Progress value={progress} />
178
+
179
+ {/* Add todo */}
180
+ <Box style={{ flexDirection: "row", gap: 1 }}>
181
+ <Box style={{ flexGrow: 1 }}>
182
+ <Input
183
+ value={newTodo}
184
+ onChange={setNewTodo}
185
+ placeholder="What needs to be done?"
186
+ onSubmit={handleAdd}
187
+ style={{ bg: "blackBright", paddingX: 1 }}
188
+ focusedStyle={{ bg: "white", color: "black", paddingX: 1 }}
189
+ />
190
+ </Box>
191
+ <Button
192
+ label=" add "
193
+ onPress={handleAdd}
194
+ style={{ bg: "blackBright", paddingX: 1 }}
195
+ focusedStyle={{ bg: "cyan", color: "black", paddingX: 1 }}
196
+ />
197
+ </Box>
198
+
199
+ {/* Todo list */}
200
+ <Box style={{ flexDirection: "column" }}>
201
+ {todos.length === 0 && (
202
+ <Text style={{ dim: true, italic: true }}>No todos yet. Add one above!</Text>
203
+ )}
204
+ {todos.map((todo) => (
205
+ <Box key={todo.id} style={{ flexDirection: "row", gap: 1, alignItems: "center" }}>
206
+ <Checkbox
207
+ checked={todo.done}
208
+ onChange={() => handleToggle(todo.id)}
209
+ label={todo.text}
210
+ style={todo.done ? { dim: true } : {}}
211
+ focusedStyle={{ color: "cyanBright" }}
212
+ />
213
+ <Spacer />
214
+ <Button
215
+ label=" \xD7 "
216
+ onPress={() => handleDelete(todo.id)}
217
+ style={{ dim: true }}
218
+ focusedStyle={{ color: "red" }}
219
+ />
220
+ </Box>
221
+ ))}
222
+ </Box>
223
+
224
+ {/* Footer */}
225
+ <Spacer />
226
+ <Text style={{ dim: true }}>
227
+ tab navigate \xB7 space toggle \xB7 enter submit \xB7 q quit
228
+ </Text>
229
+ </Box>
230
+ );
231
+ }
232
+
233
+ render(<App />);
234
+ `;
235
+ }
236
+ function templateGitignore() {
237
+ return `node_modules/
238
+ dist/
239
+ *.tsbuildinfo
240
+ .DS_Store
241
+ `;
242
+ }
243
+ function printUsage() {
244
+ console.log();
245
+ console.log(` ${bold("create-glyph")} ${dim("\u2014 scaffold a new Glyph terminal UI app")}`);
246
+ console.log();
247
+ console.log(` ${bold("Usage:")}`);
248
+ console.log(` ${cyan("bun create @semos-labs/glyph")} ${dim("<project-name>")}`);
249
+ console.log(` ${cyan("npm create @semos-labs/glyph")} ${dim("<project-name>")}`);
250
+ console.log(` ${cyan("pnpm create @semos-labs/glyph")} ${dim("<project-name>")}`);
251
+ console.log(` ${cyan("yarn create @semos-labs/glyph")} ${dim("<project-name>")}`);
252
+ console.log();
253
+ console.log(` ${bold("Options:")}`);
254
+ console.log(` ${cyan("-h, --help")} Show this help message`);
255
+ console.log();
256
+ }
257
+ function main() {
258
+ const args = process.argv.slice(2);
259
+ if (args.includes("-h") || args.includes("--help") || args.length === 0) {
260
+ printUsage();
261
+ if (args.length === 0) {
262
+ console.log(
263
+ ` ${red("\u2717")} Missing project name. Pass it as an argument.
264
+ `
265
+ );
266
+ }
267
+ process.exit(args.includes("-h") || args.includes("--help") ? 0 : 1);
268
+ }
269
+ const projectName = args.find((a) => !a.startsWith("-"));
270
+ if (!projectName) {
271
+ console.log(`
272
+ ${red("\u2717")} Missing project name.
273
+ `);
274
+ printUsage();
275
+ process.exit(1);
276
+ }
277
+ if (!/^[a-zA-Z0-9_@][a-zA-Z0-9._\-/]*$/.test(projectName)) {
278
+ console.log(
279
+ `
280
+ ${red("\u2717")} Invalid project name: ${bold(projectName)}
281
+ `
282
+ );
283
+ process.exit(1);
284
+ }
285
+ const targetDir = path.resolve(process.cwd(), projectName);
286
+ const dirName = path.basename(targetDir);
287
+ const pm = detectPackageManager();
288
+ console.log();
289
+ console.log(` ${cyan("\u25C6")} ${bold("Creating")} ${green(dirName)}${dim("...")}`);
290
+ if (fs.existsSync(targetDir)) {
291
+ const entries = fs.readdirSync(targetDir);
292
+ if (entries.length > 0) {
293
+ console.log(
294
+ `
295
+ ${red("\u2717")} Directory ${bold(dirName)} already exists and is not empty.
296
+ `
297
+ );
298
+ process.exit(1);
299
+ }
300
+ }
301
+ fs.mkdirSync(path.join(targetDir, "src"), { recursive: true });
302
+ const files = [
303
+ ["package.json", templatePackageJson(dirName)],
304
+ ["tsconfig.json", templateTsconfig()],
305
+ ["src/main.tsx", templateMainTsx(dirName)],
306
+ [".gitignore", templateGitignore()]
307
+ ];
308
+ for (const [filePath, content] of files) {
309
+ const fullPath = path.join(targetDir, filePath);
310
+ fs.writeFileSync(fullPath, content, "utf-8");
311
+ console.log(` ${dim("\u251C")} ${green("+")} ${filePath}`);
312
+ }
313
+ console.log();
314
+ console.log(` ${green("\u2713")} Project created!`);
315
+ console.log();
316
+ console.log(` ${bold("Next steps:")}`);
317
+ console.log();
318
+ console.log(` ${cyan("cd")} ${dirName}`);
319
+ console.log(` ${cyan(getInstallCommand(pm))}`);
320
+ console.log(` ${cyan(getRunCommand(pm))}`);
321
+ console.log();
322
+ console.log(` ${dim("Happy hacking! \u2726")}`);
323
+ console.log();
324
+ }
325
+ main();
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@semos-labs/create-glyph",
3
+ "version": "0.1.74",
4
+ "description": "Scaffold a new Glyph terminal UI app",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-glyph": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsup --watch"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^20",
19
+ "tsup": "^8",
20
+ "typescript": "^5.5"
21
+ },
22
+ "keywords": [
23
+ "glyph",
24
+ "create",
25
+ "scaffold",
26
+ "terminal",
27
+ "tui",
28
+ "cli",
29
+ "react"
30
+ ],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/nick-skriabin/glyph.git"
35
+ }
36
+ }