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 +135 -0
- package/package.json +32 -0
- package/template-astro/astro.config.mjs +6 -0
- package/template-astro/package.json +15 -0
- package/template-astro/src/components/Counter.jsx +25 -0
- package/template-astro/src/pages/index.astro +31 -0
- package/template-vike/package.json +18 -0
- package/template-vike/pages/+Head.jsx +3 -0
- package/template-vike/pages/+Layout.jsx +18 -0
- package/template-vike/pages/+config.js +6 -0
- package/template-vike/pages/about/+Page.jsx +14 -0
- package/template-vike/pages/about/+config.js +3 -0
- package/template-vike/pages/index/+Page.jsx +26 -0
- package/template-vike/pages/index/+config.js +3 -0
- package/template-vike/public/style.css +71 -0
- package/template-vike/vite.config.js +10 -0
- package/template-vite/index.html +12 -0
- package/template-vite/package.json +17 -0
- package/template-vite/src/App.jsx +28 -0
- package/template-vite/src/main.js +10 -0
- package/template-vite/src/style.css +57 -0
- package/template-vite/vite.config.js +6 -0
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,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,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,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,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,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,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
|
+
}
|