create-gitton-plugin 0.0.4

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.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,839 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import * as p2 from "@clack/prompts";
6
+ import chalk from "chalk";
7
+ import path2 from "path";
8
+
9
+ // src/prompts.ts
10
+ import * as p from "@clack/prompts";
11
+ var EXTENSION_POINTS = [
12
+ { value: "sidebar", label: "Sidebar Panel", hint: "Add a panel to the sidebar" },
13
+ { value: "settingsTab", label: "Settings Tab", hint: "Add a tab in the settings page" },
14
+ { value: "repositorySettings", label: "Repository Settings", hint: "Add settings specific to repository" },
15
+ { value: "markdown", label: "Markdown Renderer", hint: "Custom markdown rendering" },
16
+ { value: "editor", label: "Editor", hint: "Custom file editor" }
17
+ ];
18
+ var PERMISSIONS = [
19
+ { value: "ui:sidebar", label: "UI: Sidebar", hint: "Display UI in sidebar" },
20
+ { value: "ui:settings", label: "UI: Settings", hint: "Display UI in settings" },
21
+ { value: "ui:repositorySettings", label: "UI: Repository Settings", hint: "Display UI in repository settings" },
22
+ { value: "ui:markdown", label: "UI: Markdown", hint: "Render markdown content" },
23
+ { value: "ui:editor", label: "UI: Editor", hint: "Provide custom editor" },
24
+ { value: "settings:read", label: "Settings: Read", hint: "Read plugin settings" },
25
+ { value: "settings:write", label: "Settings: Write", hint: "Write plugin settings" },
26
+ { value: "network:fetch", label: "Network: Fetch", hint: "Make network requests" },
27
+ { value: "git:read", label: "Git: Read", hint: "Read git repository" },
28
+ { value: "git:write", label: "Git: Write", hint: "Write to git repository" },
29
+ { value: "git:hooks", label: "Git: Hooks", hint: "Manage git hooks" }
30
+ ];
31
+ function getDefaultPermissions(extensionPoints) {
32
+ const permissions = [];
33
+ for (const ep of extensionPoints) {
34
+ switch (ep) {
35
+ case "sidebar":
36
+ permissions.push("ui:sidebar");
37
+ break;
38
+ case "settingsTab":
39
+ permissions.push("ui:settings");
40
+ break;
41
+ case "repositorySettings":
42
+ permissions.push("ui:repositorySettings");
43
+ break;
44
+ case "markdown":
45
+ permissions.push("ui:markdown");
46
+ break;
47
+ case "editor":
48
+ permissions.push("ui:editor");
49
+ break;
50
+ }
51
+ }
52
+ return [...new Set(permissions)];
53
+ }
54
+ async function runPrompts(options) {
55
+ p.intro("Create Gitton Plugin");
56
+ if (options.yes) {
57
+ const name2 = options.name || "my-plugin";
58
+ const extensionPoints2 = options.extensionPoints ? options.extensionPoints.split(",") : ["sidebar"];
59
+ const permissions2 = options.permissions ? options.permissions.split(",") : getDefaultPermissions(extensionPoints2);
60
+ return {
61
+ name: name2,
62
+ displayName: options.displayName || name2.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
63
+ description: options.description || `A Gitton plugin`,
64
+ author: options.author || "Your Name",
65
+ extensionPoints: extensionPoints2,
66
+ permissions: permissions2,
67
+ useTailwind: options.tailwind ?? true,
68
+ useReact: options.react ?? true
69
+ };
70
+ }
71
+ const name = options.name ?? await p.text({
72
+ message: "Plugin name (kebab-case):",
73
+ placeholder: "my-awesome-plugin",
74
+ validate: (value) => {
75
+ if (!value) return "Name is required";
76
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
77
+ return "Name must be kebab-case (lowercase letters, numbers, hyphens)";
78
+ }
79
+ return void 0;
80
+ }
81
+ });
82
+ if (p.isCancel(name)) {
83
+ p.cancel("Operation cancelled");
84
+ return null;
85
+ }
86
+ const displayName = options.displayName ?? await p.text({
87
+ message: "Display name:",
88
+ placeholder: "My Awesome Plugin",
89
+ initialValue: String(name).split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")
90
+ });
91
+ if (p.isCancel(displayName)) {
92
+ p.cancel("Operation cancelled");
93
+ return null;
94
+ }
95
+ const description = options.description ?? await p.text({
96
+ message: "Description:",
97
+ placeholder: "A brief description of your plugin"
98
+ });
99
+ if (p.isCancel(description)) {
100
+ p.cancel("Operation cancelled");
101
+ return null;
102
+ }
103
+ const author = options.author ?? await p.text({
104
+ message: "Author:",
105
+ placeholder: "Your Name"
106
+ });
107
+ if (p.isCancel(author)) {
108
+ p.cancel("Operation cancelled");
109
+ return null;
110
+ }
111
+ const extensionPoints = options.extensionPoints ? options.extensionPoints.split(",") : await p.multiselect({
112
+ message: "Extension points (select with space):",
113
+ options: EXTENSION_POINTS,
114
+ required: true
115
+ });
116
+ if (p.isCancel(extensionPoints)) {
117
+ p.cancel("Operation cancelled");
118
+ return null;
119
+ }
120
+ const defaultPermissions = getDefaultPermissions(extensionPoints);
121
+ const permissions = options.permissions ? options.permissions.split(",") : await p.multiselect({
122
+ message: "Permissions (select with space):",
123
+ options: PERMISSIONS,
124
+ initialValues: defaultPermissions,
125
+ required: true
126
+ });
127
+ if (p.isCancel(permissions)) {
128
+ p.cancel("Operation cancelled");
129
+ return null;
130
+ }
131
+ const useReact = options.react ?? await p.confirm({
132
+ message: "Use React?",
133
+ initialValue: true
134
+ });
135
+ if (p.isCancel(useReact)) {
136
+ p.cancel("Operation cancelled");
137
+ return null;
138
+ }
139
+ const useTailwind = options.tailwind ?? await p.confirm({
140
+ message: "Use Tailwind CSS?",
141
+ initialValue: true
142
+ });
143
+ if (p.isCancel(useTailwind)) {
144
+ p.cancel("Operation cancelled");
145
+ return null;
146
+ }
147
+ return {
148
+ name: String(name),
149
+ displayName: String(displayName),
150
+ description: String(description),
151
+ author: String(author),
152
+ extensionPoints,
153
+ permissions,
154
+ useTailwind: Boolean(useTailwind),
155
+ useReact: Boolean(useReact)
156
+ };
157
+ }
158
+
159
+ // src/generator.ts
160
+ import fs from "fs/promises";
161
+ import path from "path";
162
+ async function generatePlugin(config, outputDir) {
163
+ const pluginDir = path.join(outputDir, `plugin-${config.name}`);
164
+ await fs.mkdir(pluginDir, { recursive: true });
165
+ await fs.mkdir(path.join(pluginDir, "src"), { recursive: true });
166
+ await fs.writeFile(
167
+ path.join(pluginDir, "package.json"),
168
+ generatePackageJson(config)
169
+ );
170
+ await fs.writeFile(
171
+ path.join(pluginDir, "tsconfig.json"),
172
+ generateTsConfig()
173
+ );
174
+ await fs.writeFile(
175
+ path.join(pluginDir, "vite.config.ts"),
176
+ generateViteConfig(config)
177
+ );
178
+ if (config.useTailwind) {
179
+ await fs.writeFile(
180
+ path.join(pluginDir, "tailwind.config.js"),
181
+ generateTailwindConfig()
182
+ );
183
+ await fs.writeFile(
184
+ path.join(pluginDir, "postcss.config.js"),
185
+ generatePostcssConfig()
186
+ );
187
+ await fs.writeFile(
188
+ path.join(pluginDir, "src", "globals.css"),
189
+ generateGlobalsCss()
190
+ );
191
+ }
192
+ await fs.mkdir(path.join(pluginDir, "src", "types"), { recursive: true });
193
+ await fs.writeFile(
194
+ path.join(pluginDir, "src", "types", "gitton.ts"),
195
+ generateGittonTypes()
196
+ );
197
+ if (config.useTailwind) {
198
+ await fs.mkdir(path.join(pluginDir, "src", "lib"), { recursive: true });
199
+ await fs.writeFile(
200
+ path.join(pluginDir, "src", "lib", "utils.ts"),
201
+ generateUtils()
202
+ );
203
+ }
204
+ await fs.writeFile(
205
+ path.join(pluginDir, ".gitignore"),
206
+ generateGitignore()
207
+ );
208
+ await fs.writeFile(
209
+ path.join(pluginDir, ".npmignore"),
210
+ generateNpmignore()
211
+ );
212
+ for (const ep of config.extensionPoints) {
213
+ await generateExtensionPoint(pluginDir, ep, config);
214
+ }
215
+ }
216
+ function generatePackageJson(config) {
217
+ const gittonConfig = {
218
+ displayName: config.displayName,
219
+ version: "0.0.0",
220
+ description: config.description,
221
+ author: config.author,
222
+ permissions: config.permissions,
223
+ extensionPoints: {}
224
+ };
225
+ const extensionPoints = {};
226
+ for (const ep of config.extensionPoints) {
227
+ switch (ep) {
228
+ case "sidebar":
229
+ extensionPoints.sidebar = {
230
+ entry: "ui/src/sidebar/index.html",
231
+ icon: "puzzle",
232
+ position: "bottom"
233
+ };
234
+ break;
235
+ case "settingsTab":
236
+ extensionPoints.settingsTab = {
237
+ entry: "ui/src/settings/index.html",
238
+ label: config.displayName
239
+ };
240
+ break;
241
+ case "repositorySettings":
242
+ extensionPoints.repositorySettings = {
243
+ entry: "ui/src/repository-settings/index.html",
244
+ label: config.displayName
245
+ };
246
+ break;
247
+ case "markdown":
248
+ extensionPoints.markdown = {
249
+ name: config.displayName,
250
+ filePatterns: ["*.md"],
251
+ priority: 10,
252
+ entry: "ui/src/markdown/index.html"
253
+ };
254
+ break;
255
+ case "editor":
256
+ extensionPoints.editor = {
257
+ name: config.displayName,
258
+ filePatterns: ["*"],
259
+ priority: 1,
260
+ entry: "ui/src/editor/index.html"
261
+ };
262
+ break;
263
+ }
264
+ }
265
+ gittonConfig.extensionPoints = extensionPoints;
266
+ const devDependencies = {
267
+ typescript: "^5.7.3",
268
+ vite: "^6.0.11"
269
+ };
270
+ const dependencies = {};
271
+ if (config.useReact) {
272
+ devDependencies["@types/react"] = "^18.3.18";
273
+ devDependencies["@types/react-dom"] = "^18.3.5";
274
+ devDependencies["@vitejs/plugin-react"] = "^4.3.4";
275
+ dependencies.react = "^18.3.1";
276
+ dependencies["react-dom"] = "^18.3.1";
277
+ }
278
+ if (config.useTailwind) {
279
+ devDependencies.autoprefixer = "^10.4.20";
280
+ devDependencies.postcss = "^8.5.1";
281
+ devDependencies.tailwindcss = "^3.4.17";
282
+ dependencies.clsx = "^2.1.1";
283
+ dependencies["tailwind-merge"] = "^2.6.0";
284
+ }
285
+ const pkg = {
286
+ name: `@gitton-dev/plugin-${config.name}`,
287
+ version: "0.0.1",
288
+ description: config.description,
289
+ type: "module",
290
+ scripts: {
291
+ dev: "vite",
292
+ build: "vite build",
293
+ preview: "vite preview",
294
+ clean: "rm -rf dist ui node_modules/.vite"
295
+ },
296
+ gitton: gittonConfig,
297
+ files: ["ui", "package.json"],
298
+ keywords: ["gitton", "gitton-plugin", config.name],
299
+ author: config.author,
300
+ license: "MIT",
301
+ repository: {
302
+ type: "git",
303
+ url: "git@github.com:gitton-dev/gitton-plugins.git",
304
+ directory: `packages/plugin-${config.name}`
305
+ },
306
+ bugs: {
307
+ url: "https://github.com/gitton-dev/gitton-plugins/issues"
308
+ },
309
+ homepage: "https://jsers.dev/service/gitton",
310
+ devDependencies,
311
+ dependencies
312
+ };
313
+ return JSON.stringify(pkg, null, 2) + "\n";
314
+ }
315
+ function generateTsConfig() {
316
+ const tsconfig = {
317
+ compilerOptions: {
318
+ target: "ES2020",
319
+ useDefineForClassFields: true,
320
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
321
+ module: "ESNext",
322
+ skipLibCheck: true,
323
+ moduleResolution: "bundler",
324
+ allowImportingTsExtensions: true,
325
+ resolveJsonModule: true,
326
+ isolatedModules: true,
327
+ noEmit: true,
328
+ jsx: "react-jsx",
329
+ strict: true,
330
+ noUnusedLocals: true,
331
+ noUnusedParameters: true,
332
+ noFallthroughCasesInSwitch: true,
333
+ baseUrl: ".",
334
+ paths: {
335
+ "@/*": ["src/*"]
336
+ }
337
+ },
338
+ include: ["src"]
339
+ };
340
+ return JSON.stringify(tsconfig, null, 2) + "\n";
341
+ }
342
+ function generateViteConfig(config) {
343
+ const inputs = [];
344
+ for (const ep of config.extensionPoints) {
345
+ switch (ep) {
346
+ case "sidebar":
347
+ inputs.push(`sidebar: resolve(__dirname, 'src/sidebar/index.html')`);
348
+ break;
349
+ case "settingsTab":
350
+ inputs.push(`settings: resolve(__dirname, 'src/settings/index.html')`);
351
+ break;
352
+ case "repositorySettings":
353
+ inputs.push(`repositorySettings: resolve(__dirname, 'src/repository-settings/index.html')`);
354
+ break;
355
+ case "markdown":
356
+ inputs.push(`markdown: resolve(__dirname, 'src/markdown/index.html')`);
357
+ break;
358
+ case "editor":
359
+ inputs.push(`editor: resolve(__dirname, 'src/editor/index.html')`);
360
+ break;
361
+ }
362
+ }
363
+ const plugins = config.useReact ? `[react()]` : `[]`;
364
+ const imports = config.useReact ? `import { defineConfig } from 'vite'
365
+ import react from '@vitejs/plugin-react'
366
+ import { resolve } from 'path'` : `import { defineConfig } from 'vite'
367
+ import { resolve } from 'path'`;
368
+ return `${imports}
369
+
370
+ export default defineConfig({
371
+ plugins: ${plugins},
372
+ base: './',
373
+ server: {
374
+ port: 5273
375
+ },
376
+ build: {
377
+ outDir: 'ui',
378
+ emptyOutDir: true,
379
+ rollupOptions: {
380
+ input: {
381
+ ${inputs.join(",\n ")}
382
+ },
383
+ output: {
384
+ entryFileNames: 'assets/[name]-[hash].js',
385
+ chunkFileNames: 'assets/[name]-[hash].js',
386
+ assetFileNames: 'assets/[name]-[hash].[ext]'
387
+ }
388
+ }
389
+ },
390
+ resolve: {
391
+ alias: {
392
+ '@': resolve(__dirname, 'src')
393
+ }
394
+ }
395
+ })
396
+ `;
397
+ }
398
+ function generateTailwindConfig() {
399
+ return `/** @type {import('tailwindcss').Config} */
400
+ export default {
401
+ darkMode: 'media',
402
+ content: [
403
+ './src/**/*.{js,ts,jsx,tsx,html}'
404
+ ],
405
+ theme: {
406
+ extend: {
407
+ colors: {
408
+ border: 'hsl(var(--border))',
409
+ input: 'hsl(var(--input))',
410
+ ring: 'hsl(var(--ring))',
411
+ background: 'hsl(var(--background))',
412
+ foreground: 'hsl(var(--foreground))',
413
+ primary: {
414
+ DEFAULT: 'hsl(var(--primary))',
415
+ foreground: 'hsl(var(--primary-foreground))'
416
+ },
417
+ secondary: {
418
+ DEFAULT: 'hsl(var(--secondary))',
419
+ foreground: 'hsl(var(--secondary-foreground))'
420
+ },
421
+ destructive: {
422
+ DEFAULT: 'hsl(var(--destructive))',
423
+ foreground: 'hsl(var(--destructive-foreground))'
424
+ },
425
+ muted: {
426
+ DEFAULT: 'hsl(var(--muted))',
427
+ foreground: 'hsl(var(--muted-foreground))'
428
+ },
429
+ accent: {
430
+ DEFAULT: 'hsl(var(--accent))',
431
+ foreground: 'hsl(var(--accent-foreground))'
432
+ }
433
+ },
434
+ borderRadius: {
435
+ lg: 'var(--radius)',
436
+ md: 'calc(var(--radius) - 2px)',
437
+ sm: 'calc(var(--radius) - 4px)'
438
+ }
439
+ }
440
+ },
441
+ plugins: []
442
+ }
443
+ `;
444
+ }
445
+ function generatePostcssConfig() {
446
+ return `export default {
447
+ plugins: {
448
+ tailwindcss: {},
449
+ autoprefixer: {}
450
+ }
451
+ }
452
+ `;
453
+ }
454
+ function generateGitignore() {
455
+ return `# Dependencies
456
+ node_modules/
457
+
458
+ # Build output
459
+ dist/
460
+ ui/
461
+
462
+ # Editor
463
+ .vscode/
464
+ .idea/
465
+ *.swp
466
+ *.swo
467
+
468
+ # OS
469
+ .DS_Store
470
+ Thumbs.db
471
+
472
+ # Logs
473
+ *.log
474
+ npm-debug.log*
475
+
476
+ # Cache
477
+ .vite/
478
+ *.tsbuildinfo
479
+ `;
480
+ }
481
+ function generateNpmignore() {
482
+ return `# Source files (ui/ contains built assets, so don't ignore it)
483
+ src/
484
+
485
+ # Config files
486
+ tsconfig.json
487
+ vite.config.ts
488
+ tailwind.config.js
489
+ postcss.config.js
490
+ .gitignore
491
+
492
+ # Development
493
+ node_modules/
494
+ .vite/
495
+ *.tsbuildinfo
496
+
497
+ # Editor
498
+ .vscode/
499
+ .idea/
500
+ *.swp
501
+ *.swo
502
+
503
+ # OS
504
+ .DS_Store
505
+ Thumbs.db
506
+
507
+ # Logs
508
+ *.log
509
+ npm-debug.log*
510
+ `;
511
+ }
512
+ function generateGlobalsCss() {
513
+ return `@tailwind base;
514
+ @tailwind components;
515
+ @tailwind utilities;
516
+
517
+ @layer base {
518
+ :root {
519
+ --background: 0 0% 100%;
520
+ --foreground: 0 0% 3.9%;
521
+ --muted: 0 0% 96.1%;
522
+ --muted-foreground: 0 0% 45.1%;
523
+ --popover: 0 0% 100%;
524
+ --popover-foreground: 0 0% 3.9%;
525
+ --card: 0 0% 100%;
526
+ --card-foreground: 0 0% 3.9%;
527
+ --border: 0 0% 89.8%;
528
+ --input: 0 0% 89.8%;
529
+ --primary: 0 0% 9%;
530
+ --primary-foreground: 0 0% 98%;
531
+ --secondary: 0 0% 96.1%;
532
+ --secondary-foreground: 0 0% 9%;
533
+ --accent: 0 0% 96.1%;
534
+ --accent-foreground: 0 0% 9%;
535
+ --destructive: 0 84.2% 60.2%;
536
+ --destructive-foreground: 0 0% 98%;
537
+ --ring: 0 0% 3.9%;
538
+ --radius: 0.5rem;
539
+ }
540
+
541
+ :root.dark,
542
+ .dark {
543
+ --background: 0 0% 3.9%;
544
+ --foreground: 0 0% 98%;
545
+ --muted: 0 0% 14.9%;
546
+ --muted-foreground: 0 0% 63.9%;
547
+ --popover: 0 0% 3.9%;
548
+ --popover-foreground: 0 0% 98%;
549
+ --card: 0 0% 3.9%;
550
+ --card-foreground: 0 0% 98%;
551
+ --border: 0 0% 14.9%;
552
+ --input: 0 0% 14.9%;
553
+ --primary: 0 0% 98%;
554
+ --primary-foreground: 0 0% 9%;
555
+ --secondary: 0 0% 14.9%;
556
+ --secondary-foreground: 0 0% 98%;
557
+ --accent: 0 0% 14.9%;
558
+ --accent-foreground: 0 0% 98%;
559
+ --destructive: 0 62.8% 30.6%;
560
+ --destructive-foreground: 0 0% 98%;
561
+ --ring: 0 0% 83.1%;
562
+ }
563
+ }
564
+
565
+ @media (prefers-color-scheme: dark) {
566
+ :root:not(.light) {
567
+ --background: 0 0% 3.9%;
568
+ --foreground: 0 0% 98%;
569
+ --muted: 0 0% 14.9%;
570
+ --muted-foreground: 0 0% 63.9%;
571
+ --popover: 0 0% 3.9%;
572
+ --popover-foreground: 0 0% 98%;
573
+ --card: 0 0% 3.9%;
574
+ --card-foreground: 0 0% 98%;
575
+ --border: 0 0% 14.9%;
576
+ --input: 0 0% 14.9%;
577
+ --primary: 0 0% 98%;
578
+ --primary-foreground: 0 0% 9%;
579
+ --secondary: 0 0% 14.9%;
580
+ --secondary-foreground: 0 0% 98%;
581
+ --accent: 0 0% 14.9%;
582
+ --accent-foreground: 0 0% 98%;
583
+ --destructive: 0 62.8% 30.6%;
584
+ --destructive-foreground: 0 0% 98%;
585
+ --ring: 0 0% 83.1%;
586
+ }
587
+ }
588
+
589
+ @layer base {
590
+ * {
591
+ @apply border-border;
592
+ }
593
+ body {
594
+ @apply bg-background text-foreground;
595
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
596
+ }
597
+ }
598
+ `;
599
+ }
600
+ function generateGittonTypes() {
601
+ return `export interface GhRunResult {
602
+ success: boolean
603
+ stdout: string
604
+ stderr: string
605
+ error?: string
606
+ }
607
+
608
+ export interface FsResult {
609
+ success: boolean
610
+ error?: string
611
+ }
612
+
613
+ export interface FsReadResult extends FsResult {
614
+ content?: string
615
+ }
616
+
617
+ export interface FsReaddirEntry {
618
+ name: string
619
+ isDirectory: boolean
620
+ isFile: boolean
621
+ }
622
+
623
+ export interface FsReaddirResult extends FsResult {
624
+ entries: FsReaddirEntry[]
625
+ }
626
+
627
+ export interface FsExistsResult {
628
+ exists: boolean
629
+ }
630
+
631
+ export interface FsStatResult extends FsResult {
632
+ stat?: {
633
+ size: number
634
+ mode: number
635
+ isFile: boolean
636
+ isDirectory: boolean
637
+ mtime: string
638
+ }
639
+ }
640
+
641
+ export interface GittonSettings {
642
+ get(key: string): Promise<unknown>
643
+ set(key: string, value: unknown): Promise<void>
644
+ getAll(): Promise<Record<string, unknown>>
645
+ }
646
+
647
+ export interface GittonUI {
648
+ showNotification(message: string, type?: 'info' | 'error' | 'warning'): void
649
+ openExternal(url: string): Promise<{ success: boolean }>
650
+ getTheme(): Promise<{ theme: 'light' | 'dark' }>
651
+ }
652
+
653
+ export interface GittonGh {
654
+ run(args: string[]): Promise<GhRunResult>
655
+ }
656
+
657
+ export interface GittonFs {
658
+ readFile(path: string): Promise<FsReadResult>
659
+ writeFile(path: string, content: string): Promise<FsResult>
660
+ readdir(path: string): Promise<FsReaddirResult>
661
+ exists(path: string): Promise<FsExistsResult>
662
+ unlink(path: string): Promise<FsResult>
663
+ chmod(path: string, mode: number): Promise<FsResult>
664
+ stat(path: string): Promise<FsStatResult>
665
+ }
666
+
667
+ export interface GittonContext {
668
+ repoPath: string | null
669
+ theme: 'light' | 'dark'
670
+ }
671
+
672
+ export interface GittonAPI {
673
+ pluginId: string
674
+ settings: GittonSettings
675
+ ui: GittonUI
676
+ gh: GittonGh
677
+ fs: GittonFs
678
+ context: GittonContext
679
+ }
680
+
681
+ declare global {
682
+ interface Window {
683
+ gitton: GittonAPI
684
+ }
685
+ }
686
+ `;
687
+ }
688
+ function generateUtils() {
689
+ return `import { type ClassValue, clsx } from 'clsx'
690
+ import { twMerge } from 'tailwind-merge'
691
+
692
+ export function cn(...inputs: ClassValue[]) {
693
+ return twMerge(clsx(inputs))
694
+ }
695
+ `;
696
+ }
697
+ async function generateExtensionPoint(pluginDir, extensionPoint, config) {
698
+ const dirName = getExtensionPointDir(extensionPoint);
699
+ const dir = path.join(pluginDir, "src", dirName);
700
+ await fs.mkdir(dir, { recursive: true });
701
+ await fs.writeFile(
702
+ path.join(dir, "index.html"),
703
+ generateIndexHtml(config.displayName, extensionPoint)
704
+ );
705
+ if (config.useReact) {
706
+ await fs.writeFile(
707
+ path.join(dir, "main.tsx"),
708
+ generateReactMain(config, extensionPoint)
709
+ );
710
+ await fs.mkdir(path.join(dir, "components"), { recursive: true });
711
+ await fs.writeFile(
712
+ path.join(dir, "components", `${getComponentName(extensionPoint)}.tsx`),
713
+ generateReactComponent(config, extensionPoint)
714
+ );
715
+ } else {
716
+ await fs.writeFile(
717
+ path.join(dir, "main.ts"),
718
+ generateVanillaMain(config, extensionPoint)
719
+ );
720
+ }
721
+ }
722
+ function getExtensionPointDir(ep) {
723
+ switch (ep) {
724
+ case "sidebar":
725
+ return "sidebar";
726
+ case "settingsTab":
727
+ return "settings";
728
+ case "repositorySettings":
729
+ return "repository-settings";
730
+ case "markdown":
731
+ return "markdown";
732
+ case "editor":
733
+ return "editor";
734
+ }
735
+ }
736
+ function getComponentName(ep) {
737
+ switch (ep) {
738
+ case "sidebar":
739
+ return "Sidebar";
740
+ case "settingsTab":
741
+ return "Settings";
742
+ case "repositorySettings":
743
+ return "RepositorySettings";
744
+ case "markdown":
745
+ return "MarkdownRenderer";
746
+ case "editor":
747
+ return "Editor";
748
+ }
749
+ }
750
+ function generateIndexHtml(displayName, ep) {
751
+ const title = `${displayName} - ${getComponentName(ep)}`;
752
+ return `<!DOCTYPE html>
753
+ <html lang="en">
754
+ <head>
755
+ <meta charset="UTF-8" />
756
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
757
+ <title>${title}</title>
758
+ </head>
759
+ <body>
760
+ <div id="root"></div>
761
+ <script type="module" src="./main.tsx"></script>
762
+ </body>
763
+ </html>
764
+ `;
765
+ }
766
+ function generateReactMain(config, ep) {
767
+ const componentName = getComponentName(ep);
768
+ const cssImport = config.useTailwind ? `import '../globals.css'
769
+ ` : "";
770
+ return `import React from 'react'
771
+ import ReactDOM from 'react-dom/client'
772
+ import { ${componentName} } from './components/${componentName}'
773
+ ${cssImport}
774
+ ReactDOM.createRoot(document.getElementById('root')!).render(
775
+ <React.StrictMode>
776
+ <${componentName} />
777
+ </React.StrictMode>
778
+ )
779
+ `;
780
+ }
781
+ function generateReactComponent(config, ep) {
782
+ const componentName = getComponentName(ep);
783
+ const baseClasses = config.useTailwind ? ' className="p-4"' : "";
784
+ return `export function ${componentName}() {
785
+ return (
786
+ <div${baseClasses}>
787
+ <h1>${config.displayName}</h1>
788
+ <p>${config.description}</p>
789
+ </div>
790
+ )
791
+ }
792
+ `;
793
+ }
794
+ function generateVanillaMain(config, _ep) {
795
+ return `const root = document.getElementById('root')
796
+
797
+ if (root) {
798
+ root.innerHTML = \`
799
+ <div>
800
+ <h1>${config.displayName}</h1>
801
+ <p>${config.description}</p>
802
+ </div>
803
+ \`
804
+ }
805
+ `;
806
+ }
807
+
808
+ // src/index.ts
809
+ var program = new Command();
810
+ program.name("create-gitton-plugin").description("Create a new Gitton plugin").version("0.0.1").argument("[directory]", "Output directory", ".").option("-n, --name <name>", "Plugin name (kebab-case)").option("-d, --display-name <displayName>", "Display name").option("--description <description>", "Plugin description").option("-a, --author <author>", "Author name").option("-e, --extension-points <points>", "Extension points (comma-separated: sidebar,settingsTab,repositorySettings,markdown,editor)").option("-p, --permissions <permissions>", "Permissions (comma-separated)").option("--tailwind", "Use Tailwind CSS").option("--no-tailwind", "Do not use Tailwind CSS").option("--react", "Use React").option("--no-react", "Do not use React").option("-y, --yes", "Skip prompts and use defaults").action(async (directory, options) => {
811
+ try {
812
+ const config = await runPrompts(options);
813
+ if (!config) {
814
+ process.exit(1);
815
+ }
816
+ const outputDir = path2.resolve(directory);
817
+ const spinner2 = p2.spinner();
818
+ spinner2.start("Generating plugin...");
819
+ await generatePlugin(config, outputDir);
820
+ spinner2.stop("Plugin generated!");
821
+ const pluginDir = path2.join(outputDir, `plugin-${config.name}`);
822
+ p2.note(
823
+ [
824
+ `${chalk.green("Plugin created at:")} ${pluginDir}`,
825
+ "",
826
+ `${chalk.cyan("Next steps:")}`,
827
+ ` cd plugin-${config.name}`,
828
+ " pnpm install",
829
+ " pnpm dev"
830
+ ].join("\n"),
831
+ "Done!"
832
+ );
833
+ p2.outro("Happy coding!");
834
+ } catch (error) {
835
+ console.error(chalk.red(`Error: ${error.message}`));
836
+ process.exit(1);
837
+ }
838
+ });
839
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "create-gitton-plugin",
3
+ "version": "0.0.4",
4
+ "description": "Create a new Gitton plugin with interactive prompts",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-gitton-plugin": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "keywords": [
13
+ "gitton",
14
+ "gitton-plugin",
15
+ "create",
16
+ "scaffold",
17
+ "cli"
18
+ ],
19
+ "author": "godai",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git@github.com:gitton-dev/gitton-plugins.git",
24
+ "directory": "packages/create-gitton-plugin"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/gitton-dev/gitton-plugins/issues"
28
+ },
29
+ "homepage": "https://jsers.dev/service/gitton",
30
+ "dependencies": {
31
+ "@clack/prompts": "^0.8.0",
32
+ "chalk": "^5.4.1",
33
+ "commander": "^13.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.13.4",
37
+ "tsup": "^8.4.0",
38
+ "typescript": "^5.7.3"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup src/index.ts --format esm --dts --clean",
42
+ "dev": "tsup src/index.ts --format esm --watch",
43
+ "clean": "rm -rf dist"
44
+ }
45
+ }