create-sygnal-app 1.0.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.
package/index.js ADDED
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as p from '@clack/prompts'
4
+ import { resolve, dirname, join, basename } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync } from 'node:fs'
7
+ import { execSync } from 'node:child_process'
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url))
10
+
11
+ const TEMPLATES = {
12
+ vite: {
13
+ label: 'Vite (SPA)',
14
+ hint: 'Single-page app with Vite + HMR',
15
+ dir: 'template-vite',
16
+ },
17
+ vike: {
18
+ label: 'Vike (SSR)',
19
+ hint: 'File-based routing with SSR, layouts, and data fetching',
20
+ dir: 'template-vike',
21
+ },
22
+ astro: {
23
+ label: 'Astro',
24
+ hint: 'Content-focused site with island hydration',
25
+ dir: 'template-astro',
26
+ },
27
+ }
28
+
29
+ async function main() {
30
+ p.intro('Create Sygnal App')
31
+
32
+ // Project name — use CLI arg if provided
33
+ const argName = process.argv[2]
34
+
35
+ const projectName = argName || await p.text({
36
+ message: 'Project name:',
37
+ placeholder: 'my-sygnal-app',
38
+ defaultValue: 'my-sygnal-app',
39
+ validate(value) {
40
+ if (!value) return 'Project name is required'
41
+ if (/[^\w\-.]/.test(value)) return 'Project name can only contain letters, numbers, hyphens, dots, and underscores'
42
+ },
43
+ })
44
+
45
+ if (p.isCancel(projectName)) {
46
+ p.cancel('Cancelled.')
47
+ process.exit(0)
48
+ }
49
+
50
+ const template = await p.select({
51
+ message: 'Template:',
52
+ options: Object.entries(TEMPLATES).map(([value, { label, hint }]) => ({
53
+ value,
54
+ label,
55
+ hint,
56
+ })),
57
+ })
58
+
59
+ if (p.isCancel(template)) {
60
+ p.cancel('Cancelled.')
61
+ process.exit(0)
62
+ }
63
+
64
+ const installDeps = await p.confirm({
65
+ message: 'Install dependencies?',
66
+ initialValue: true,
67
+ })
68
+
69
+ if (p.isCancel(installDeps)) {
70
+ p.cancel('Cancelled.')
71
+ process.exit(0)
72
+ }
73
+
74
+ const targetDir = resolve(process.cwd(), projectName)
75
+
76
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
77
+ p.cancel(`Directory "${projectName}" already exists and is not empty.`)
78
+ process.exit(1)
79
+ }
80
+
81
+ const s = p.spinner()
82
+
83
+ // Copy template
84
+ s.start('Scaffolding project...')
85
+ const templateDir = join(__dirname, TEMPLATES[template].dir)
86
+ copyDir(templateDir, targetDir)
87
+
88
+ // Update package.json name
89
+ const pkgPath = join(targetDir, 'package.json')
90
+ if (existsSync(pkgPath)) {
91
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
92
+ pkg.name = basename(projectName)
93
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
94
+ }
95
+ s.stop('Project scaffolded.')
96
+
97
+ // Install
98
+ if (installDeps) {
99
+ s.start('Installing dependencies...')
100
+ try {
101
+ execSync('npm install', { cwd: targetDir, stdio: 'ignore' })
102
+ s.stop('Dependencies installed.')
103
+ } catch {
104
+ s.stop('Failed to install dependencies. Run `npm install` manually.')
105
+ }
106
+ }
107
+
108
+ // Done
109
+ const relative = targetDir === process.cwd() ? '.' : projectName
110
+
111
+ p.note([
112
+ `cd ${relative}`,
113
+ 'npm run dev',
114
+ ].join('\n'), 'Next steps')
115
+
116
+ p.outro('Happy building!')
117
+ }
118
+
119
+ function copyDir(src, dest) {
120
+ mkdirSync(dest, { recursive: true })
121
+ for (const entry of readdirSync(src)) {
122
+ const srcPath = join(src, entry)
123
+ const destPath = join(dest, entry)
124
+ if (statSync(srcPath).isDirectory()) {
125
+ copyDir(srcPath, destPath)
126
+ } else {
127
+ writeFileSync(destPath, readFileSync(srcPath))
128
+ }
129
+ }
130
+ }
131
+
132
+ main().catch((err) => {
133
+ console.error(err)
134
+ process.exit(1)
135
+ })
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "create-sygnal-app",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a new Sygnal project",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-sygnal-app": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "template-vite",
12
+ "template-vike",
13
+ "template-astro"
14
+ ],
15
+ "keywords": [
16
+ "sygnal",
17
+ "create",
18
+ "scaffold",
19
+ "template",
20
+ "cli"
21
+ ],
22
+ "author": "Troy Presley <troy.presley@gmail.com>",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/tpresley/sygnal.git",
27
+ "directory": "create-sygnal-app"
28
+ },
29
+ "dependencies": {
30
+ "@clack/prompts": "^0.10.0"
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'astro/config'
2
+ import sygnal from 'sygnal/astro'
3
+
4
+ export default defineConfig({
5
+ integrations: [sygnal()],
6
+ })
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "my-sygnal-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "astro dev",
8
+ "build": "astro build",
9
+ "preview": "astro preview"
10
+ },
11
+ "dependencies": {
12
+ "astro": "^5.0.0",
13
+ "sygnal": "^5.1.0"
14
+ }
15
+ }
@@ -0,0 +1,25 @@
1
+ function Counter({ state }) {
2
+ return (
3
+ <div className="counter-card">
4
+ <p>Count: {state.count}</p>
5
+ <div className="buttons">
6
+ <button className="dec">-</button>
7
+ <button className="inc">+</button>
8
+ </div>
9
+ </div>
10
+ )
11
+ }
12
+
13
+ Counter.initialState = { count: 0 }
14
+
15
+ Counter.intent = ({ DOM }) => ({
16
+ INC: DOM.click('.inc'),
17
+ DEC: DOM.click('.dec'),
18
+ })
19
+
20
+ Counter.model = {
21
+ INC: (state) => ({ ...state, count: state.count + 1 }),
22
+ DEC: (state) => ({ ...state, count: state.count - 1 }),
23
+ }
24
+
25
+ export default Counter
@@ -0,0 +1,31 @@
1
+ ---
2
+ import Counter from '../components/Counter.jsx'
3
+ ---
4
+
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="UTF-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <title>Sygnal + Astro</title>
10
+ <style>
11
+ * { margin: 0; padding: 0; box-sizing: border-box; }
12
+ body {
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
14
+ display: flex;
15
+ justify-content: center;
16
+ align-items: center;
17
+ min-height: 100vh;
18
+ background: #f5f5f5;
19
+ color: #333;
20
+ }
21
+ .page { text-align: center; }
22
+ h1 { font-size: 2.5rem; margin-bottom: 2rem; color: #111; }
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <div class="page">
27
+ <h1>Sygnal + Astro</h1>
28
+ <Counter client:load />
29
+ </div>
30
+ </body>
31
+ </html>
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "my-sygnal-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vike dev",
8
+ "build": "vike build",
9
+ "preview": "vike preview"
10
+ },
11
+ "dependencies": {
12
+ "sygnal": "^5.1.0",
13
+ "vike": "^0.4.250"
14
+ },
15
+ "devDependencies": {
16
+ "vite": "^6.4.1"
17
+ }
18
+ }
@@ -0,0 +1,3 @@
1
+ export default function Head() {
2
+ return <link rel="stylesheet" href="/style.css" />
3
+ }
@@ -0,0 +1,18 @@
1
+ function Layout({ state, innerHTML }) {
2
+ return (
3
+ <div className="layout">
4
+ <nav className="nav">
5
+ <strong>Sygnal + Vike</strong>
6
+ <div className="nav-links">
7
+ <a href="/">Home</a>
8
+ <a href="/about">About</a>
9
+ </div>
10
+ </nav>
11
+ <main className="content" props={{ innerHTML: innerHTML || '' }}></main>
12
+ </div>
13
+ )
14
+ }
15
+
16
+ Layout.initialState = {}
17
+
18
+ export default Layout
@@ -0,0 +1,6 @@
1
+ import vikeSygnal from 'sygnal/config'
2
+
3
+ export default {
4
+ extends: [vikeSygnal],
5
+ title: 'Sygnal + Vike',
6
+ }
@@ -0,0 +1,14 @@
1
+ function Page({ state }) {
2
+ return (
3
+ <div className="page">
4
+ <h1>About</h1>
5
+ <p>{state.description}</p>
6
+ </div>
7
+ )
8
+ }
9
+
10
+ Page.initialState = {
11
+ description: 'Built with Sygnal and Vike.',
12
+ }
13
+
14
+ export default Page
@@ -0,0 +1,3 @@
1
+ export default {
2
+ title: 'About',
3
+ }
@@ -0,0 +1,26 @@
1
+ function Page({ state }) {
2
+ return (
3
+ <div className="page">
4
+ <h1>Welcome</h1>
5
+ <p>Count: {state.count}</p>
6
+ <div className="buttons">
7
+ <button className="dec">-</button>
8
+ <button className="inc">+</button>
9
+ </div>
10
+ </div>
11
+ )
12
+ }
13
+
14
+ Page.initialState = { count: 0 }
15
+
16
+ Page.intent = ({ DOM }) => ({
17
+ INC: DOM.click('.inc'),
18
+ DEC: DOM.click('.dec'),
19
+ })
20
+
21
+ Page.model = {
22
+ INC: (state) => ({ ...state, count: state.count + 1 }),
23
+ DEC: (state) => ({ ...state, count: state.count - 1 }),
24
+ }
25
+
26
+ export default Page
@@ -0,0 +1,3 @@
1
+ export default {
2
+ title: 'Home',
3
+ }
@@ -0,0 +1,71 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
9
+ background: #f5f5f5;
10
+ color: #333;
11
+ }
12
+
13
+ .layout {
14
+ max-width: 800px;
15
+ margin: 0 auto;
16
+ padding: 1rem;
17
+ }
18
+
19
+ .nav {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 2rem;
23
+ padding: 1rem 0;
24
+ border-bottom: 1px solid #ddd;
25
+ margin-bottom: 2rem;
26
+ }
27
+
28
+ .nav-links {
29
+ display: flex;
30
+ gap: 1rem;
31
+ }
32
+
33
+ .nav-links a {
34
+ color: #555;
35
+ text-decoration: none;
36
+ }
37
+
38
+ .nav-links a:hover {
39
+ color: #111;
40
+ }
41
+
42
+ .page {
43
+ text-align: center;
44
+ padding: 2rem 0;
45
+ }
46
+
47
+ h1 {
48
+ font-size: 2rem;
49
+ margin-bottom: 1rem;
50
+ }
51
+
52
+ .buttons {
53
+ display: flex;
54
+ gap: 0.75rem;
55
+ justify-content: center;
56
+ margin-top: 1rem;
57
+ }
58
+
59
+ button {
60
+ font-size: 1.25rem;
61
+ padding: 0.5rem 1.5rem;
62
+ border: 1px solid #ddd;
63
+ border-radius: 8px;
64
+ background: white;
65
+ cursor: pointer;
66
+ transition: background 0.15s;
67
+ }
68
+
69
+ button:hover {
70
+ background: #f0f0f0;
71
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vite'
2
+ import vike from 'vike/plugin'
3
+ import sygnal from 'sygnal/vite'
4
+
5
+ export default defineConfig({
6
+ plugins: [
7
+ sygnal({ disableHmr: true }),
8
+ vike(),
9
+ ],
10
+ })
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Sygnal App</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "my-sygnal-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "sygnal": "^5.1.0"
13
+ },
14
+ "devDependencies": {
15
+ "vite": "^6.4.1"
16
+ }
17
+ }
@@ -0,0 +1,28 @@
1
+ function App({ state }) {
2
+ return (
3
+ <div className="app">
4
+ <h1>Sygnal</h1>
5
+ <div className="counter-card">
6
+ <p>Count: {state.count}</p>
7
+ <div className="buttons">
8
+ <button className="dec">-</button>
9
+ <button className="inc">+</button>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ )
14
+ }
15
+
16
+ App.initialState = { count: 0 }
17
+
18
+ App.intent = ({ DOM }) => ({
19
+ INC: DOM.click('.inc'),
20
+ DEC: DOM.click('.dec'),
21
+ })
22
+
23
+ App.model = {
24
+ INC: (state) => ({ ...state, count: state.count + 1 }),
25
+ DEC: (state) => ({ ...state, count: state.count - 1 }),
26
+ }
27
+
28
+ export default App
@@ -0,0 +1,10 @@
1
+ import { run } from 'sygnal'
2
+ import App from './App.jsx'
3
+ import './style.css'
4
+
5
+ const { hmr, dispose } = run(App)
6
+
7
+ if (import.meta.hot) {
8
+ import.meta.hot.accept('./App.jsx', hmr)
9
+ import.meta.hot.dispose(dispose)
10
+ }
@@ -0,0 +1,57 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ min-height: 100vh;
13
+ background: #f5f5f5;
14
+ color: #333;
15
+ }
16
+
17
+ .app {
18
+ text-align: center;
19
+ }
20
+
21
+ h1 {
22
+ font-size: 2.5rem;
23
+ margin-bottom: 2rem;
24
+ color: #111;
25
+ }
26
+
27
+ .counter-card {
28
+ background: white;
29
+ border-radius: 12px;
30
+ padding: 2rem 3rem;
31
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
32
+ }
33
+
34
+ .counter-card p {
35
+ font-size: 1.5rem;
36
+ margin-bottom: 1rem;
37
+ }
38
+
39
+ .buttons {
40
+ display: flex;
41
+ gap: 0.75rem;
42
+ justify-content: center;
43
+ }
44
+
45
+ button {
46
+ font-size: 1.25rem;
47
+ padding: 0.5rem 1.5rem;
48
+ border: 1px solid #ddd;
49
+ border-radius: 8px;
50
+ background: white;
51
+ cursor: pointer;
52
+ transition: background 0.15s;
53
+ }
54
+
55
+ button:hover {
56
+ background: #f0f0f0;
57
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite'
2
+ import sygnal from 'sygnal/vite'
3
+
4
+ export default defineConfig({
5
+ plugins: [sygnal()],
6
+ })