@sigx/cli 0.2.7 → 0.3.0

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 (37) hide show
  1. package/README.md +8 -59
  2. package/dist/cli.js +4 -3
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/create.d.ts +1 -1
  5. package/dist/commands/create.js +48 -413
  6. package/dist/commands/create.js.map +1 -1
  7. package/dist/commands/scaffold.js +165 -0
  8. package/dist/commands/scaffold.js.map +1 -0
  9. package/dist/index.d.ts +1 -1
  10. package/dist/plugin.d.ts +54 -0
  11. package/dist/plugin.js.map +1 -1
  12. package/dist/shell/index.d.ts +23 -0
  13. package/dist/shell/index.js +503 -0
  14. package/dist/shell/index.js.map +1 -0
  15. package/dist/templates/basic-daisyui/src/App.tsx +15 -4
  16. package/dist/templates/lynx/src/lynx-env.d.ts +1 -0
  17. package/dist/templates/lynx/src/main.tsx +2 -2
  18. package/dist/templates/lynx-daisyui/src/lynx-env.d.ts +1 -0
  19. package/dist/templates/lynx-daisyui/src/main.tsx +2 -2
  20. package/dist/templates/lynx-tailwind/src/lynx-env.d.ts +1 -0
  21. package/dist/templates/lynx-tailwind/src/main.tsx +2 -2
  22. package/dist/templates/ssg/package.json +4 -3
  23. package/dist/templates/ssg-daisyui/package.json +4 -3
  24. package/dist/templates/ssg-tailwind/package.json +4 -3
  25. package/dist/templates/ssr-daisyui/src/pages/Home.tsx +16 -4
  26. package/package.json +7 -3
  27. package/templates/basic-daisyui/src/App.tsx +15 -4
  28. package/templates/lynx/src/lynx-env.d.ts +1 -0
  29. package/templates/lynx/src/main.tsx +2 -2
  30. package/templates/lynx-daisyui/src/lynx-env.d.ts +1 -0
  31. package/templates/lynx-daisyui/src/main.tsx +2 -2
  32. package/templates/lynx-tailwind/src/lynx-env.d.ts +1 -0
  33. package/templates/lynx-tailwind/src/main.tsx +2 -2
  34. package/templates/ssg/package.json +4 -3
  35. package/templates/ssg-daisyui/package.json +4 -3
  36. package/templates/ssg-tailwind/package.json +4 -3
  37. package/templates/ssr-daisyui/src/pages/Home.tsx +16 -4
@@ -1 +1 @@
1
- {"version":3,"file":"create.js","names":[],"sources":["../../src/commands/create.tsx"],"sourcesContent":["/** @jsxImportSource @sigx/terminal */\nimport { signal, component, defineApp, Input, Button, ProgressBar, Select, type Define } from '@sigx/terminal';\nimport { existsSync, mkdirSync, readdirSync, statSync, writeFileSync, readFileSync } from 'fs';\nimport { dirname, resolve, join } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\ntype Step = 'name' | 'type' | 'styling' | 'creating' | 'done';\ntype ProjectType = 'basic' | 'ssr' | 'ssg' | 'lynx';\ntype Styling = 'none' | 'tailwind' | 'daisyui';\n\n// Parse CLI args (supports both interactive default and --flag headless mode).\nconst rawArgs = process.argv.slice(2);\nfunction getFlag(name: string): string | undefined {\n const eq = rawArgs.find(a => a.startsWith(`--${name}=`));\n if (eq) return eq.slice(name.length + 3);\n const idx = rawArgs.indexOf(`--${name}`);\n if (idx !== -1 && rawArgs[idx + 1] && !rawArgs[idx + 1].startsWith('-')) return rawArgs[idx + 1];\n return undefined;\n}\nfunction hasFlag(name: string, short?: string): boolean {\n return rawArgs.includes(`--${name}`) || (short ? rawArgs.includes(`-${short}`) : false);\n}\nconst positionalArgs = rawArgs.filter(a => !a.startsWith('-') && a !== 'create');\nconst argProjectName = positionalArgs[0] || '';\nconst argType = getFlag('type') as ProjectType | undefined;\nconst argStyling = getFlag('styling') as Styling | undefined;\nconst flagYes = hasFlag('yes', 'y');\nconst isNonInteractive = !process.stdout.isTTY || !process.stdin.isTTY || flagYes\n || Boolean(argType && argProjectName);\n\nconst projectTypeOptions = [\n { value: 'basic' as ProjectType, label: 'Basic SPA', description: 'Simple single-page application (web)' },\n { value: 'ssr' as ProjectType, label: 'SSR', description: 'Server-side rendering with Express (web)' },\n { value: 'ssg' as ProjectType, label: 'SSG', description: 'Static site with file-based routing & MDX (web)' },\n { value: 'lynx' as ProjectType, label: 'Lynx', description: 'Native mobile app with Lynx runtime' },\n];\n\nconst webStylingOptions = [\n { value: 'none' as Styling, label: 'None', description: 'No CSS framework' },\n { value: 'tailwind' as Styling, label: 'Tailwind CSS', description: 'Utility-first CSS framework' },\n { value: 'daisyui' as Styling, label: 'Tailwind + Daisy UI', description: 'Tailwind with component library' },\n];\n\nconst lynxStylingOptions = [\n { value: 'none' as Styling, label: 'None', description: 'No CSS framework' },\n { value: 'tailwind' as Styling, label: 'Tailwind CSS', description: 'Tailwind with Lynx preset' },\n { value: 'daisyui' as Styling, label: 'Tailwind + Daisy UI', description: 'Lynx + @sigx/lynx-daisyui components' },\n];\n\nconst TEXT_EXTS = new Set([\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'mts', 'cts',\n 'json', 'json5', 'jsonc',\n 'md', 'mdx', 'txt',\n 'html', 'htm', 'css', 'scss', 'sass', 'less',\n 'yml', 'yaml', 'toml', 'xml', 'svg',\n 'gitignore', 'gitattributes', 'editorconfig', 'npmrc', 'nvmrc', 'env',\n]);\n\nfunction isTextExtension(filename: string): boolean {\n // Dotfiles: name after leading dot (.gitignore → \"gitignore\").\n const ext = filename.startsWith('.')\n ? filename.slice(1).toLowerCase()\n : filename.split('.').pop()?.toLowerCase() ?? '';\n return TEXT_EXTS.has(ext);\n}\n\nfunction copyDirectory(src: string, dest: string, projectName: string) {\n if (!existsSync(dest)) {\n mkdirSync(dest, { recursive: true });\n }\n\n const entries = readdirSync(src);\n for (const entry of entries) {\n const srcPath = join(src, entry);\n // Templates ship `gitignore` (no leading dot) because npm strips\n // `.gitignore` from the published tarball. Rename on copy so the\n // generated project has a real `.gitignore`.\n const destName = entry === 'gitignore' ? '.gitignore' : entry;\n const destPath = join(dest, destName);\n const stat = statSync(srcPath);\n\n if (stat.isDirectory()) {\n copyDirectory(srcPath, destPath, projectName);\n } else if (isTextExtension(entry)) {\n let content = readFileSync(srcPath, 'utf-8');\n content = content.replace(/\\{\\{projectName\\}\\}/g, projectName);\n writeFileSync(destPath, content);\n } else {\n // Binary asset — copy bytes verbatim. Reading as UTF-8 would corrupt\n // non-ASCII bytes (e.g. PNG magic 0x89 → U+FFFD).\n writeFileSync(destPath, readFileSync(srcPath));\n }\n }\n}\n\n/**\n * Detect if the target directory is inside a pnpm workspace that includes @sigx packages.\n * If so, rewrite @sigx/* dependency versions to workspace:* in package.json.\n */\nfunction patchWorkspaceDeps(targetDir: string) {\n const pkgPath = join(targetDir, 'package.json');\n if (!existsSync(pkgPath)) return;\n\n // Walk up to find pnpm-workspace.yaml\n let dir = dirname(targetDir);\n let isWorkspace = false;\n for (let i = 0; i < 10; i++) {\n if (existsSync(join(dir, 'pnpm-workspace.yaml'))) {\n isWorkspace = true;\n break;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n if (!isWorkspace) return;\n\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n for (const section of ['dependencies', 'devDependencies'] as const) {\n if (!pkg[section]) continue;\n for (const dep of Object.keys(pkg[section])) {\n if (dep.startsWith('@sigx/')) {\n pkg[section][dep] = 'workspace:*';\n }\n }\n }\n writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + '\\n');\n}\n\nfunction scaffoldProject(opts: {\n projectName: string;\n projectType: ProjectType;\n styling: Styling;\n}): { ok: true } | { ok: false; error: string } {\n const targetDir = resolve(process.cwd(), opts.projectName);\n let templateName: string;\n if (opts.projectType === 'lynx') {\n templateName = opts.styling !== 'none' ? `lynx-${opts.styling}` : 'lynx';\n } else {\n templateName = opts.styling !== 'none' ? `${opts.projectType}-${opts.styling}` : opts.projectType;\n }\n const templateDir = resolve(__dirname, '..', 'templates', templateName);\n if (existsSync(targetDir)) return { ok: false, error: `Directory \"${opts.projectName}\" already exists!` };\n if (!existsSync(templateDir)) return { ok: false, error: `Template \"${templateName}\" not found at ${templateDir}` };\n copyDirectory(templateDir, targetDir, opts.projectName);\n patchWorkspaceDeps(targetDir);\n return { ok: true };\n}\n\ntype CreateState = {\n step: Step;\n projectName: string;\n projectType: ProjectType;\n styling: Styling;\n progress: number;\n error: string;\n};\n\nconst projectTypeLabel = (t: ProjectType): string => {\n switch (t) {\n case 'basic': return 'Basic SPA';\n case 'ssr': return 'SSR';\n case 'ssg': return 'SSG';\n case 'lynx': return 'Lynx (Native)';\n }\n};\n\nconst stylingLabel = (s: Styling): string => {\n switch (s) {\n case 'none': return 'None';\n case 'tailwind': return 'Tailwind CSS';\n case 'daisyui': return 'Tailwind + Daisy UI';\n }\n};\n\n// Each step is its own component so the reconciler sees a different\n// component type at the step slot on every transition. That forces a\n// clean unmount/remount of the focusable inside (Input/Select/Button),\n// which avoids the previous step's key handler lingering and re-firing\n// the submit chain on the next Enter press.\n\nconst StepName = component<\n Define.Prop<\"state\", CreateState, true> &\n Define.Prop<\"onSubmit\", () => void, true>\n>(({ props }) => () => (\n <box>\n <text color=\"gray\"> Step 1 of 3</text>\n <br />\n <br />\n <text color=\"white\"> What is your project called?</text>\n <br />\n <br />\n <Input\n model={() => props.state.projectName}\n label=\" Project Name \"\n placeholder=\"my-sigx-app\"\n autofocus\n onSubmit={props.onSubmit}\n />\n <br />\n <text color=\"gray\"> Press Enter to continue</text>\n </box>\n), { name: 'StepName' });\n\nconst StepType = component<\n Define.Prop<\"state\", CreateState, true> &\n Define.Prop<\"onSubmit\", () => void, true>\n>(({ props }) => () => (\n <box>\n <text color=\"gray\"> Step 2 of 3</text>\n <br />\n <br />\n <text color=\"white\"> What type of project?</text>\n <br />\n <br />\n <Select\n model={() => props.state.projectType}\n label=\" Project Type \"\n options={projectTypeOptions}\n showDescription\n autofocus\n onSubmit={props.onSubmit}\n />\n <br />\n <text color=\"gray\"> ↑/↓ navigate · Enter select</text>\n </box>\n), { name: 'StepType' });\n\nconst StepStyling = component<\n Define.Prop<\"state\", CreateState, true> &\n Define.Prop<\"onSubmit\", () => void, true>\n>(({ props }) => () => (\n <box>\n <text color=\"gray\"> Step 3 of 3</text>\n <br />\n <br />\n <text color=\"white\"> Choose a styling approach:</text>\n <br />\n <br />\n <Select\n model={() => props.state.styling}\n label=\" Styling \"\n options={props.state.projectType === 'lynx' ? lynxStylingOptions : webStylingOptions}\n showDescription\n autofocus\n onSubmit={props.onSubmit}\n />\n <br />\n <text color=\"gray\"> ↑/↓ navigate · Enter select</text>\n </box>\n), { name: 'StepStyling' });\n\nconst StepCreating = component<\n Define.Prop<\"state\", CreateState, true>\n>(({ props }) => () => (\n <box>\n <br />\n <text color=\"yellow\"> Creating project...</text>\n <br />\n <ProgressBar value={props.state.progress} max={100} width={40} color=\"green\" />\n </box>\n), { name: 'StepCreating' });\n\nconst StepDone = component<\n Define.Prop<\"state\", CreateState, true> &\n Define.Prop<\"onExit\", () => void, true>\n>(({ props }) => () => (\n <box>\n <br />\n <text color=\"green\"> ✓ Project \"{props.state.projectName}\" created!</text>\n <br />\n <br />\n <text color=\"gray\"> Type: {projectTypeLabel(props.state.projectType)}</text>\n <br />\n <text color=\"gray\"> Styling: {stylingLabel(props.state.styling)}</text>\n <br />\n <br />\n <text color=\"white\"> Next steps:</text>\n <br />\n <br />\n <text color=\"cyan\"> cd {props.state.projectName}</text>\n <br />\n <text color=\"cyan\"> pnpm install</text>\n <br />\n <text color=\"cyan\"> {props.state.projectType === 'lynx' ? 'sigx dev' : 'pnpm dev'}</text>\n <br />\n <br />\n <Button label=\"Exit\" onClick={props.onExit} />\n </box>\n), { name: 'StepDone' });\n\nconst ErrorScreen = component<\n Define.Prop<\"message\", string, true> &\n Define.Prop<\"onExit\", () => void, true>\n>(({ props }) => () => (\n <box border=\"double\" borderColor=\"red\" label=\" Error \">\n <text color=\"red\"> {props.message}</text>\n <br />\n <br />\n <Button label=\"Exit\" onClick={props.onExit} />\n </box>\n), { name: 'ErrorScreen' });\n\nconst CreateSigx = component(() => {\n const state = signal<CreateState>({\n step: 'name' as Step,\n projectName: argProjectName || 'my-sigx-app',\n projectType: 'basic' as ProjectType,\n styling: 'none' as Styling,\n progress: 0,\n error: '',\n });\n\n // Defense in depth: gate each handler on the current step. If a\n // previous step's focusable lingers across a transition (reconciler\n // edge case observed in the TUI runtime) and its onSubmit fires on a\n // later Enter press, the guard makes it a no-op instead of re-running\n // the scaffolder or rewinding the wizard.\n const createProject = () => {\n if (state.step !== 'styling') return;\n state.step = 'creating';\n state.progress = 30;\n const result = scaffoldProject({\n projectName: state.projectName,\n projectType: state.projectType,\n styling: state.styling,\n });\n if (!result.ok) {\n state.error = result.error;\n return;\n }\n state.progress = 100;\n state.step = 'done';\n };\n\n const handleNameSubmit = () => {\n if (state.step !== 'name') return;\n if (state.projectName.trim()) {\n state.step = 'type';\n }\n };\n\n const handleTypeSubmit = () => {\n if (state.step !== 'type') return;\n state.step = 'styling';\n };\n\n const handleStylingSubmit = () => {\n if (state.step !== 'styling') return;\n createProject();\n };\n\n const handleExit = () => {\n process.exit(0);\n };\n\n return () => {\n if (state.error) {\n return <ErrorScreen message={state.error} onExit={handleExit} />;\n }\n\n return (\n <box>\n <text color=\"cyan\"> ⚡ Create SignalX App</text>\n <br />\n {(() => {\n switch (state.step) {\n case 'name': return <StepName state={state} onSubmit={handleNameSubmit} />;\n case 'type': return <StepType state={state} onSubmit={handleTypeSubmit} />;\n case 'styling': return <StepStyling state={state} onSubmit={handleStylingSubmit} />;\n case 'creating': return <StepCreating state={state} />;\n case 'done': return <StepDone state={state} onExit={handleExit} />;\n }\n })()}\n </box>\n );\n };\n});\n\nfunction runHeadless(): number {\n const validTypes: ProjectType[] = ['basic', 'ssr', 'ssg', 'lynx'];\n const validStyling: Styling[] = ['none', 'tailwind', 'daisyui'];\n\n const projectName = argProjectName || 'my-sigx-app';\n const projectType: ProjectType = argType ?? 'basic';\n const styling: Styling = argStyling ?? 'none';\n\n if (!validTypes.includes(projectType)) {\n console.error(`Error: --type must be one of ${validTypes.join(', ')}`);\n return 2;\n }\n if (!validStyling.includes(styling)) {\n console.error(`Error: --styling must be one of ${validStyling.join(', ')}`);\n return 2;\n }\n\n console.log(`\\n ⚡ Creating SignalX app \"${projectName}\"`);\n console.log(` type: ${projectType}`);\n console.log(` styling: ${styling}\\n`);\n\n const result = scaffoldProject({ projectName, projectType, styling });\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n return 1;\n }\n\n console.log(` ✓ Project created\\n`);\n console.log(` Next steps:`);\n console.log(` cd ${projectName}`);\n console.log(` pnpm install`);\n console.log(` ${projectType === 'lynx' ? 'sigx dev' : 'pnpm dev'}\\n`);\n return 0;\n}\n\nexport function runCreate() {\n if (isNonInteractive) {\n process.exit(runHeadless());\n }\n defineApp(<CreateSigx />).mount({ clearConsole: true });\n // Keep process alive for TUI\n setInterval(() => { }, 10000);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create.js","names":[],"sources":["../../src/commands/create.ts"],"sourcesContent":["/**\n * `sigx create` — interactive scaffolder on the @sigx/terminal prompt kit,\n * with a flag-driven headless mode for CI (`--type`, `--styling`, `--yes`,\n * or any non-TTY stdio).\n */\nimport { intro, outro, note, cancel, isCancel, text, select, spinner } from '@sigx/terminal';\nimport {\n scaffoldProject,\n projectTypeOptions,\n webStylingOptions,\n lynxStylingOptions,\n type ProjectType,\n type Styling,\n} from './scaffold.js';\n\n// Parse CLI args (supports both interactive default and --flag headless mode).\nconst rawArgs = process.argv.slice(2);\nfunction getFlag(name: string): string | undefined {\n const eq = rawArgs.find(a => a.startsWith(`--${name}=`));\n if (eq) return eq.slice(name.length + 3);\n const idx = rawArgs.indexOf(`--${name}`);\n if (idx !== -1 && rawArgs[idx + 1] && !rawArgs[idx + 1].startsWith('-')) return rawArgs[idx + 1];\n return undefined;\n}\nfunction hasFlag(name: string, short?: string): boolean {\n return rawArgs.includes(`--${name}`) || (short ? rawArgs.includes(`-${short}`) : false);\n}\nconst positionalArgs = rawArgs.filter(a => !a.startsWith('-') && a !== 'create');\nconst argProjectName = positionalArgs[0] || '';\nconst argType = getFlag('type') as ProjectType | undefined;\nconst argStyling = getFlag('styling') as Styling | undefined;\nconst flagYes = hasFlag('yes', 'y');\nconst isNonInteractive = !process.stdout.isTTY || !process.stdin.isTTY || flagYes\n || Boolean(argType && argProjectName);\n\nfunction runHeadless(): number {\n const validTypes: ProjectType[] = ['basic', 'ssr', 'ssg', 'lynx'];\n const validStyling: Styling[] = ['none', 'tailwind', 'daisyui'];\n\n const projectName = argProjectName || 'my-sigx-app';\n const projectType: ProjectType = argType ?? 'basic';\n const styling: Styling = argStyling ?? 'none';\n\n if (!validTypes.includes(projectType)) {\n console.error(`Error: --type must be one of ${validTypes.join(', ')}`);\n return 2;\n }\n if (!validStyling.includes(styling)) {\n console.error(`Error: --styling must be one of ${validStyling.join(', ')}`);\n return 2;\n }\n\n console.log(`\\n ⚡ Creating SignalX app \"${projectName}\"`);\n console.log(` type: ${projectType}`);\n console.log(` styling: ${styling}\\n`);\n\n const result = scaffoldProject({ projectName, projectType, styling });\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n return 1;\n }\n\n console.log(` ✓ Project created\\n`);\n console.log(` Next steps:`);\n console.log(` cd ${projectName}`);\n console.log(` pnpm install`);\n console.log(` ${projectType === 'lynx' ? 'sigx dev' : 'pnpm dev'}\\n`);\n return 0;\n}\n\nfunction bail(): never {\n cancel('Cancelled — nothing was created.');\n process.exit(130);\n}\n\nexport async function runCreate(): Promise<void> {\n if (isNonInteractive) {\n process.exit(runHeadless());\n }\n\n intro('⚡ Create SignalX App');\n\n const projectName = await text({\n message: 'Project name',\n placeholder: 'my-sigx-app',\n initialValue: argProjectName || 'my-sigx-app',\n validate: (v: string) => (v.trim() ? undefined : 'Project name is required'),\n });\n if (isCancel(projectName)) bail();\n\n const projectType = await select<ProjectType>({\n message: 'Project type',\n initialValue: argType ?? 'basic',\n options: projectTypeOptions,\n });\n if (isCancel(projectType)) bail();\n\n const styling = await select<Styling>({\n message: 'Styling',\n initialValue: argStyling ?? 'none',\n options: projectType === 'lynx' ? lynxStylingOptions : webStylingOptions,\n });\n if (isCancel(styling)) bail();\n\n const s = spinner();\n s.start(`Scaffolding ${projectName}`);\n const result = scaffoldProject({ projectName, projectType, styling });\n if (!result.ok) {\n s.stop(result.error, 'error');\n process.exit(1);\n }\n s.stop(`Created ${projectName} (${projectType}${styling !== 'none' ? ` + ${styling}` : ''})`);\n\n note(\n `cd ${projectName}\\npnpm install\\n${projectType === 'lynx' ? 'sigx dev' : 'pnpm dev'}`,\n 'Next steps',\n );\n outro('Happy hacking!');\n process.exit(0);\n}\n"],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
2
+ import { dirname, join, resolve } from "path";
3
+ import { fileURLToPath } from "url";
4
+ //#region src/commands/scaffold.ts
5
+ /**
6
+ * Pure scaffolding helpers shared by the interactive and headless `create`
7
+ * paths — template copy with `{{projectName}}` substitution, gitignore
8
+ * rename, binary-safe asset copy, and workspace dependency patching.
9
+ */
10
+ var __dirname = dirname(fileURLToPath(import.meta.url));
11
+ var projectTypeOptions = [
12
+ {
13
+ value: "basic",
14
+ label: "Basic SPA",
15
+ description: "Simple single-page application (web)"
16
+ },
17
+ {
18
+ value: "ssr",
19
+ label: "SSR",
20
+ description: "Server-side rendering with Express (web)"
21
+ },
22
+ {
23
+ value: "ssg",
24
+ label: "SSG",
25
+ description: "Static site with file-based routing & MDX (web)"
26
+ },
27
+ {
28
+ value: "lynx",
29
+ label: "Lynx",
30
+ description: "Native mobile app with Lynx runtime"
31
+ }
32
+ ];
33
+ var webStylingOptions = [
34
+ {
35
+ value: "none",
36
+ label: "None",
37
+ description: "No CSS framework"
38
+ },
39
+ {
40
+ value: "tailwind",
41
+ label: "Tailwind CSS",
42
+ description: "Utility-first CSS framework"
43
+ },
44
+ {
45
+ value: "daisyui",
46
+ label: "Tailwind + Daisy UI",
47
+ description: "Tailwind with component library"
48
+ }
49
+ ];
50
+ var lynxStylingOptions = [
51
+ {
52
+ value: "none",
53
+ label: "None",
54
+ description: "No CSS framework"
55
+ },
56
+ {
57
+ value: "tailwind",
58
+ label: "Tailwind CSS",
59
+ description: "Tailwind with Lynx preset"
60
+ },
61
+ {
62
+ value: "daisyui",
63
+ label: "Tailwind + Daisy UI",
64
+ description: "Lynx + @sigx/lynx-daisyui components"
65
+ }
66
+ ];
67
+ var TEXT_EXTS = new Set([
68
+ "ts",
69
+ "tsx",
70
+ "js",
71
+ "jsx",
72
+ "mjs",
73
+ "cjs",
74
+ "mts",
75
+ "cts",
76
+ "json",
77
+ "json5",
78
+ "jsonc",
79
+ "md",
80
+ "mdx",
81
+ "txt",
82
+ "html",
83
+ "htm",
84
+ "css",
85
+ "scss",
86
+ "sass",
87
+ "less",
88
+ "yml",
89
+ "yaml",
90
+ "toml",
91
+ "xml",
92
+ "svg",
93
+ "gitignore",
94
+ "gitattributes",
95
+ "editorconfig",
96
+ "npmrc",
97
+ "nvmrc",
98
+ "env"
99
+ ]);
100
+ function isTextExtension(filename) {
101
+ const ext = filename.startsWith(".") ? filename.slice(1).toLowerCase() : filename.split(".").pop()?.toLowerCase() ?? "";
102
+ return TEXT_EXTS.has(ext);
103
+ }
104
+ function copyDirectory(src, dest, projectName) {
105
+ if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
106
+ const entries = readdirSync(src);
107
+ for (const entry of entries) {
108
+ const srcPath = join(src, entry);
109
+ const destPath = join(dest, entry === "gitignore" ? ".gitignore" : entry);
110
+ if (statSync(srcPath).isDirectory()) copyDirectory(srcPath, destPath, projectName);
111
+ else if (isTextExtension(entry)) {
112
+ let content = readFileSync(srcPath, "utf-8");
113
+ content = content.replace(/\{\{projectName\}\}/g, projectName);
114
+ writeFileSync(destPath, content);
115
+ } else writeFileSync(destPath, readFileSync(srcPath));
116
+ }
117
+ }
118
+ /**
119
+ * Detect if the target directory is inside a pnpm workspace that includes @sigx packages.
120
+ * If so, rewrite @sigx/* dependency versions to workspace:* in package.json.
121
+ */
122
+ function patchWorkspaceDeps(targetDir) {
123
+ const pkgPath = join(targetDir, "package.json");
124
+ if (!existsSync(pkgPath)) return;
125
+ let dir = dirname(targetDir);
126
+ let isWorkspace = false;
127
+ for (let i = 0; i < 10; i++) {
128
+ if (existsSync(join(dir, "pnpm-workspace.yaml"))) {
129
+ isWorkspace = true;
130
+ break;
131
+ }
132
+ const parent = dirname(dir);
133
+ if (parent === dir) break;
134
+ dir = parent;
135
+ }
136
+ if (!isWorkspace) return;
137
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
138
+ for (const section of ["dependencies", "devDependencies"]) {
139
+ if (!pkg[section]) continue;
140
+ for (const dep of Object.keys(pkg[section])) if (dep.startsWith("@sigx/")) pkg[section][dep] = "workspace:*";
141
+ }
142
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + "\n");
143
+ }
144
+ function scaffoldProject(opts) {
145
+ const targetDir = resolve(process.cwd(), opts.projectName);
146
+ let templateName;
147
+ if (opts.projectType === "lynx") templateName = opts.styling !== "none" ? `lynx-${opts.styling}` : "lynx";
148
+ else templateName = opts.styling !== "none" ? `${opts.projectType}-${opts.styling}` : opts.projectType;
149
+ const templateDir = resolve(__dirname, "..", "templates", templateName);
150
+ if (existsSync(targetDir)) return {
151
+ ok: false,
152
+ error: `Directory "${opts.projectName}" already exists!`
153
+ };
154
+ if (!existsSync(templateDir)) return {
155
+ ok: false,
156
+ error: `Template "${templateName}" not found at ${templateDir}`
157
+ };
158
+ copyDirectory(templateDir, targetDir, opts.projectName);
159
+ patchWorkspaceDeps(targetDir);
160
+ return { ok: true };
161
+ }
162
+ //#endregion
163
+ export { copyDirectory, isTextExtension, lynxStylingOptions, patchWorkspaceDeps, projectTypeOptions, scaffoldProject, webStylingOptions };
164
+
165
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","names":[],"sources":["../../src/commands/scaffold.ts"],"sourcesContent":["/**\n * Pure scaffolding helpers shared by the interactive and headless `create`\n * paths — template copy with `{{projectName}}` substitution, gitignore\n * rename, binary-safe asset copy, and workspace dependency patching.\n */\nimport { existsSync, mkdirSync, readdirSync, statSync, writeFileSync, readFileSync } from 'fs';\nimport { dirname, resolve, join } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport type ProjectType = 'basic' | 'ssr' | 'ssg' | 'lynx';\nexport type Styling = 'none' | 'tailwind' | 'daisyui';\n\nexport const projectTypeOptions = [\n { value: 'basic' as ProjectType, label: 'Basic SPA', description: 'Simple single-page application (web)' },\n { value: 'ssr' as ProjectType, label: 'SSR', description: 'Server-side rendering with Express (web)' },\n { value: 'ssg' as ProjectType, label: 'SSG', description: 'Static site with file-based routing & MDX (web)' },\n { value: 'lynx' as ProjectType, label: 'Lynx', description: 'Native mobile app with Lynx runtime' },\n];\n\nexport const webStylingOptions = [\n { value: 'none' as Styling, label: 'None', description: 'No CSS framework' },\n { value: 'tailwind' as Styling, label: 'Tailwind CSS', description: 'Utility-first CSS framework' },\n { value: 'daisyui' as Styling, label: 'Tailwind + Daisy UI', description: 'Tailwind with component library' },\n];\n\nexport const lynxStylingOptions = [\n { value: 'none' as Styling, label: 'None', description: 'No CSS framework' },\n { value: 'tailwind' as Styling, label: 'Tailwind CSS', description: 'Tailwind with Lynx preset' },\n { value: 'daisyui' as Styling, label: 'Tailwind + Daisy UI', description: 'Lynx + @sigx/lynx-daisyui components' },\n];\n\nconst TEXT_EXTS = new Set([\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'mts', 'cts',\n 'json', 'json5', 'jsonc',\n 'md', 'mdx', 'txt',\n 'html', 'htm', 'css', 'scss', 'sass', 'less',\n 'yml', 'yaml', 'toml', 'xml', 'svg',\n 'gitignore', 'gitattributes', 'editorconfig', 'npmrc', 'nvmrc', 'env',\n]);\n\nexport function isTextExtension(filename: string): boolean {\n // Dotfiles: name after leading dot (.gitignore → \"gitignore\").\n const ext = filename.startsWith('.')\n ? filename.slice(1).toLowerCase()\n : filename.split('.').pop()?.toLowerCase() ?? '';\n return TEXT_EXTS.has(ext);\n}\n\nexport function copyDirectory(src: string, dest: string, projectName: string) {\n if (!existsSync(dest)) {\n mkdirSync(dest, { recursive: true });\n }\n\n const entries = readdirSync(src);\n for (const entry of entries) {\n const srcPath = join(src, entry);\n // Templates ship `gitignore` (no leading dot) because npm strips\n // `.gitignore` from the published tarball. Rename on copy so the\n // generated project has a real `.gitignore`.\n const destName = entry === 'gitignore' ? '.gitignore' : entry;\n const destPath = join(dest, destName);\n const stat = statSync(srcPath);\n\n if (stat.isDirectory()) {\n copyDirectory(srcPath, destPath, projectName);\n } else if (isTextExtension(entry)) {\n let content = readFileSync(srcPath, 'utf-8');\n content = content.replace(/\\{\\{projectName\\}\\}/g, projectName);\n writeFileSync(destPath, content);\n } else {\n // Binary asset — copy bytes verbatim. Reading as UTF-8 would corrupt\n // non-ASCII bytes (e.g. PNG magic 0x89 → U+FFFD).\n writeFileSync(destPath, readFileSync(srcPath));\n }\n }\n}\n\n/**\n * Detect if the target directory is inside a pnpm workspace that includes @sigx packages.\n * If so, rewrite @sigx/* dependency versions to workspace:* in package.json.\n */\nexport function patchWorkspaceDeps(targetDir: string) {\n const pkgPath = join(targetDir, 'package.json');\n if (!existsSync(pkgPath)) return;\n\n // Walk up to find pnpm-workspace.yaml\n let dir = dirname(targetDir);\n let isWorkspace = false;\n for (let i = 0; i < 10; i++) {\n if (existsSync(join(dir, 'pnpm-workspace.yaml'))) {\n isWorkspace = true;\n break;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n if (!isWorkspace) return;\n\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n for (const section of ['dependencies', 'devDependencies'] as const) {\n if (!pkg[section]) continue;\n for (const dep of Object.keys(pkg[section])) {\n if (dep.startsWith('@sigx/')) {\n pkg[section][dep] = 'workspace:*';\n }\n }\n }\n writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + '\\n');\n}\n\nexport function scaffoldProject(opts: {\n projectName: string;\n projectType: ProjectType;\n styling: Styling;\n}): { ok: true } | { ok: false; error: string } {\n const targetDir = resolve(process.cwd(), opts.projectName);\n let templateName: string;\n if (opts.projectType === 'lynx') {\n templateName = opts.styling !== 'none' ? `lynx-${opts.styling}` : 'lynx';\n } else {\n templateName = opts.styling !== 'none' ? `${opts.projectType}-${opts.styling}` : opts.projectType;\n }\n const templateDir = resolve(__dirname, '..', 'templates', templateName);\n if (existsSync(targetDir)) return { ok: false, error: `Directory \"${opts.projectName}\" already exists!` };\n if (!existsSync(templateDir)) return { ok: false, error: `Template \"${templateName}\" not found at ${templateDir}` };\n copyDirectory(templateDir, targetDir, opts.projectName);\n patchWorkspaceDeps(targetDir);\n return { ok: true };\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { definePlugin } from './plugin.js';
2
- export type { SigxPlugin, PluginCommand, CommandContext, ArgDef, Logger } from './plugin.js';
2
+ export type { SigxPlugin, PluginCommand, CommandContext, ArgDef, Logger, ShellNode, StatusItem, ShellTab, SlashCommand, Shortcut, ShellLogStore, ShellHandle, TuiContribution } from './plugin.js';
package/dist/plugin.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface CommandContext {
7
7
  cwd: string;
8
8
  args: Record<string, unknown>;
9
9
  logger: Logger;
10
+ /** All plugins discovered for this project (for runShell({ plugins })). */
11
+ plugins?: SigxPlugin[];
10
12
  }
11
13
  export interface Logger {
12
14
  log: (msg: string) => void;
@@ -18,9 +20,61 @@ export interface PluginCommand {
18
20
  args?: Record<string, ArgDef>;
19
21
  run: (ctx: CommandContext) => Promise<void>;
20
22
  }
23
+ /** Opaque renderable returned by a tab's render() — author with JSX. */
24
+ export type ShellNode = unknown;
25
+ export interface StatusItem {
26
+ label: string;
27
+ value: string;
28
+ /** Theme token, e.g. 'success' | 'warn' | 'danger' | 'dim' | 'accent' */
29
+ tone?: string;
30
+ }
31
+ export interface ShellTab {
32
+ id: string;
33
+ label: string;
34
+ render: () => ShellNode;
35
+ }
36
+ export interface SlashCommand {
37
+ /** Includes the leading slash, e.g. '/reload'. */
38
+ name: string;
39
+ description: string;
40
+ run: (shell: ShellHandle) => void | Promise<void>;
41
+ }
42
+ export interface Shortcut {
43
+ /** Single key, active only while the command input is empty. */
44
+ key: string;
45
+ label: string;
46
+ run: (shell: ShellHandle) => void | Promise<void>;
47
+ }
48
+ export interface ShellLogStore {
49
+ push: (chunk: string) => void;
50
+ }
51
+ export interface ShellHandle {
52
+ isInteractive: boolean;
53
+ say: (text?: string) => void;
54
+ store: ShellLogStore;
55
+ setStatus: (items: StatusItem[]) => void;
56
+ switchTab: (id: string) => void;
57
+ pushView: (id: string) => void;
58
+ popView: () => void;
59
+ /** Register teardown (runs on exit()/Ctrl+C/SIGTERM/SIGHUP/SIGINT,
60
+ * after the host's onExit, most-recent-first). Returns unsubscribe. */
61
+ onExit: (cb: () => void | Promise<void>) => () => void;
62
+ exit: (code?: number) => void;
63
+ }
64
+ export interface TuiContribution {
65
+ tabs?: ShellTab[];
66
+ commands?: SlashCommand[];
67
+ shortcuts?: Shortcut[];
68
+ status?: () => StatusItem[];
69
+ /** Called once when the hosting shell comes up; a returned function is
70
+ * registered as teardown. Start servers/watchers here. */
71
+ setup?: (shell: ShellHandle) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>;
72
+ }
21
73
  export interface SigxPlugin {
22
74
  name: string;
23
75
  detect: (cwd: string) => boolean;
24
76
  commands: Record<string, PluginCommand>;
77
+ /** Optional TUI contributions merged into any plugin-hosted shell. */
78
+ tui?: TuiContribution;
25
79
  }
26
80
  export declare function definePlugin(plugin: SigxPlugin): SigxPlugin;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * Plugin interface for the sigx CLI.\n *\n * Packages that want to extend the CLI declare a `\"sigx-cli\"` field\n * in their package.json pointing to a module that default-exports\n * a SigxPlugin created with `definePlugin()`.\n */\n\nexport interface ArgDef {\n type: 'string' | 'boolean';\n description?: string;\n default?: string | boolean;\n}\n\nexport interface CommandContext {\n cwd: string;\n args: Record<string, unknown>;\n logger: Logger;\n}\n\nexport interface Logger {\n log: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n}\n\nexport interface PluginCommand {\n description: string;\n args?: Record<string, ArgDef>;\n run: (ctx: CommandContext) => Promise<void>;\n}\n\nexport interface SigxPlugin {\n /** Unique plugin name (e.g. 'ssg', 'lynx') */\n name: string;\n /** Return true if this plugin handles the current project */\n detect: (cwd: string) => boolean;\n /** Commands this plugin provides */\n commands: Record<string, PluginCommand>;\n}\n\n/**\n * Define a sigx CLI plugin. Identity function for type safety.\n */\nexport function definePlugin(plugin: SigxPlugin): SigxPlugin {\n return plugin;\n}\n"],"mappings":";;;;AA4CA,SAAgB,aAAa,QAAgC;CACzD,OAAO"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * Plugin interface for the sigx CLI.\n *\n * Packages that want to extend the CLI declare a `\"sigx-cli\"` field\n * in their package.json pointing to a module that default-exports\n * a SigxPlugin created with `definePlugin()`.\n *\n * This module is deliberately dependency-free: plugins import it via\n * `@sigx/cli/plugin` without pulling the TUI stack. The TUI types below\n * (`ShellTab`, `ShellHandle`, …) are structural contracts consumed by\n * `runShell` from `@sigx/cli/shell`.\n */\n\nexport interface ArgDef {\n type: 'string' | 'boolean';\n description?: string;\n default?: string | boolean;\n}\n\nexport interface CommandContext {\n cwd: string;\n args: Record<string, unknown>;\n logger: Logger;\n /**\n * All plugins discovered for this project — lets a shell-hosting command\n * (e.g. lynx `dev`) merge peer plugins' TUI contributions via\n * `runShell({ plugins })` from `@sigx/cli/shell`.\n */\n plugins?: SigxPlugin[];\n}\n\nexport interface Logger {\n log: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n}\n\nexport interface PluginCommand {\n description: string;\n args?: Record<string, ArgDef>;\n run: (ctx: CommandContext) => Promise<void>;\n}\n\n/**\n * Opaque renderable returned by a tab's `render()`. Author it with JSX\n * (`@jsxImportSource @sigx/terminal`); typed as `unknown` so this module\n * stays dependency-free — the shell passes it straight to the renderer.\n */\nexport type ShellNode = unknown;\n\n/** One entry in the shell's status line. `tone` is a theme token. */\nexport interface StatusItem {\n label: string;\n value: string;\n /** e.g. 'success' | 'warn' | 'danger' | 'dim' | 'accent' */\n tone?: string;\n}\n\n/** A tab in the shell's tab strip. */\nexport interface ShellTab {\n id: string;\n label: string;\n render: () => ShellNode;\n}\n\n/** A `/command` offered in the shell input's intellisense. */\nexport interface SlashCommand {\n /** Includes the leading slash, e.g. '/reload'. */\n name: string;\n description: string;\n run: (shell: ShellHandle) => void | Promise<void>;\n}\n\n/** A single-key shortcut, active only while the command input is empty. */\nexport interface Shortcut {\n key: string;\n label: string;\n run: (shell: ShellHandle) => void | Promise<void>;\n}\n\n/** Structural subset of @sigx/terminal's LogStore — keeps this module dep-free. */\nexport interface ShellLogStore {\n push: (chunk: string) => void;\n}\n\n/**\n * Handle to a running shell, passed to slash commands, shortcuts, and the\n * host's onReady. In non-TTY environments (`isInteractive: false`) the shell\n * never mounts: `say` writes plain lines, the store streams through, and the\n * navigation methods are no-ops — callers keep a single code path.\n */\nexport interface ShellHandle {\n isInteractive: boolean;\n /** Print a permanent transcript line above the live region. */\n say: (text?: string) => void;\n /** The main streaming log store (feeds the host's Logs tab). */\n store: ShellLogStore;\n setStatus: (items: StatusItem[]) => void;\n switchTab: (id: string) => void;\n pushView: (id: string) => void;\n popView: () => void;\n /**\n * Register teardown to run BEFORE the process exits — on `exit()`,\n * Ctrl+C/q, and external SIGTERM/SIGHUP/SIGINT. Subscribers run after\n * the host's onExit, most-recently-registered first. Returns an\n * unsubscribe. Use this for servers, watchers, locks.\n */\n onExit: (cb: () => void | Promise<void>) => () => void;\n exit: (code?: number) => void;\n}\n\n/**\n * TUI contributions a plugin offers to whichever plugin hosts the shell:\n * tabs, slash commands, shortcuts, and status-line items.\n */\nexport interface TuiContribution {\n tabs?: ShellTab[];\n commands?: SlashCommand[];\n shortcuts?: Shortcut[];\n status?: () => StatusItem[];\n /**\n * Lifecycle: called once when the hosting shell comes up (interactive or\n * plain). Start servers/watchers here; an optionally returned function is\n * registered as teardown (equivalent to `shell.onExit(fn)`).\n */\n setup?: (shell: ShellHandle) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>;\n}\n\nexport interface SigxPlugin {\n /** Unique plugin name (e.g. 'ssg', 'lynx') */\n name: string;\n /** Return true if this plugin handles the current project */\n detect: (cwd: string) => boolean;\n /** Commands this plugin provides */\n commands: Record<string, PluginCommand>;\n /** Optional TUI contributions merged into any plugin-hosted shell. */\n tui?: TuiContribution;\n}\n\n/**\n * Define a sigx CLI plugin. Identity function for type safety.\n */\nexport function definePlugin(plugin: SigxPlugin): SigxPlugin {\n return plugin;\n}\n"],"mappings":";;;;AA8IA,SAAgB,aAAa,QAAgC;CACzD,OAAO"}
@@ -0,0 +1,23 @@
1
+ import type { SigxPlugin, ShellTab, SlashCommand, Shortcut, StatusItem, ShellHandle, TuiContribution, Logger } from '../plugin.js';
2
+ export type { ShellTab, SlashCommand, Shortcut, StatusItem, ShellHandle, TuiContribution } from '../plugin.js';
3
+ export interface ShellConfig {
4
+ /** 'fullscreen': alt-screen dashboard (title bar, tabs, palette);
5
+ * 'inline' (default): transcript shape with bottom-anchored input. */
6
+ mode?: 'inline' | 'fullscreen';
7
+ /** Design theme (themed canvas in fullscreen). Default 'obsidian'. */
8
+ theme?: string;
9
+ title: string;
10
+ version?: string;
11
+ logo?: { rows: string[]; palette: Record<string, string> };
12
+ tabs: ShellTab[];
13
+ commands?: SlashCommand[];
14
+ shortcuts?: Shortcut[];
15
+ status?: () => StatusItem[];
16
+ plugins?: SigxPlugin[];
17
+ onReady?: (shell: ShellHandle) => void | Promise<void>;
18
+ onExit?: () => void | Promise<void>;
19
+ }
20
+ export declare function runShell(config: ShellConfig, opts?: { interactive?: boolean }): Promise<ShellHandle>;
21
+ export declare function createShellLogger(shell: ShellHandle): Logger;
22
+ export declare function collectTuiContributions(plugins: SigxPlugin[]): TuiContribution[];
23
+ export declare function mergeShellConfig(config: ShellConfig, contributions: TuiContribution[], logger?: Logger): ShellConfig;