aiplang 2.1.0 → 2.1.2
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/README.md +86 -151
- package/aiplang-knowledge.md +172 -129
- package/bin/aiplang.js +8 -8
- package/package.json +1 -1
- package/runtime/aiplang-hydrate.js +3 -3
- package/runtime/aiplang-runtime.js +5 -5
- package/server/server.js +336 -5
- package/bin/flux.js +0 -572
- package/runtime/flux-hydrate.js +0 -473
- package/runtime/flux-runtime.js +0 -1100
package/aiplang-knowledge.md
CHANGED
|
@@ -1,208 +1,243 @@
|
|
|
1
|
-
#
|
|
1
|
+
# aiplang — Claude Project Knowledge
|
|
2
2
|
|
|
3
|
-
You are
|
|
3
|
+
You are an aiplang code generator. When asked to build any web app, page, or component, respond ONLY with valid aiplang syntax. No explanation unless asked. No React, no HTML, no other frameworks.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## aiplang syntax reference
|
|
8
8
|
|
|
9
9
|
### File structure
|
|
10
10
|
```
|
|
11
11
|
~theme ... (optional global theme vars)
|
|
12
|
-
%id theme /route (page declaration)
|
|
12
|
+
%id theme /route (page declaration — required)
|
|
13
13
|
@var = default (reactive state)
|
|
14
14
|
~mount GET /api => @var (fetch on load)
|
|
15
|
-
~interval 10000 GET /api => @var (
|
|
15
|
+
~interval 10000 GET /api => @var (polling)
|
|
16
16
|
blocks...
|
|
17
17
|
--- (page separator)
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Backend (full-stack)
|
|
21
|
+
```
|
|
22
|
+
~env DATABASE_URL required
|
|
23
|
+
~db sqlite ./app.db (or postgres $DATABASE_URL)
|
|
24
|
+
~auth jwt $JWT_SECRET expire=7d
|
|
25
|
+
~mail smtp host=smtp.mailgun.com user=$MAIL_USER pass=$MAIL_PASS
|
|
26
|
+
~admin /admin (auto admin panel)
|
|
27
|
+
~middleware cors | rate-limit 100/min | log
|
|
28
|
+
|
|
29
|
+
model User {
|
|
30
|
+
id : uuid : pk auto
|
|
31
|
+
name : text : required
|
|
32
|
+
email : text : required unique
|
|
33
|
+
password : text : required hashed
|
|
34
|
+
plan : enum : starter,pro,enterprise : default=starter
|
|
35
|
+
role : enum : user,admin : default=user
|
|
36
|
+
~soft-delete
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
api POST /api/auth/register {
|
|
40
|
+
~validate name required | email required email | password min=8
|
|
41
|
+
~unique User email $body.email | 409
|
|
42
|
+
~hash password
|
|
43
|
+
insert User($body)
|
|
44
|
+
~mail $inserted.email "Welcome!" "Your account is ready."
|
|
45
|
+
return jwt($inserted) 201
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
api GET /api/users {
|
|
49
|
+
~guard admin
|
|
50
|
+
~query page=1 limit=20
|
|
51
|
+
return User.paginate($page, $limit)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
api DELETE /api/users/:id {
|
|
55
|
+
~guard auth | admin
|
|
56
|
+
delete User($id)
|
|
57
|
+
}
|
|
20
58
|
```
|
|
21
59
|
|
|
22
60
|
### Page declaration
|
|
23
61
|
`%id theme /route`
|
|
24
|
-
-
|
|
25
|
-
- theme: `dark` | `light` | `acid` | `#bg,#text,#accent`
|
|
26
|
-
- route: URL path
|
|
62
|
+
- themes: `dark` | `light` | `acid` | `#bg,#text,#accent`
|
|
27
63
|
|
|
28
|
-
### Global theme
|
|
29
|
-
`~theme accent=#
|
|
64
|
+
### Global theme
|
|
65
|
+
`~theme accent=#7c3aed radius=1.5rem font=Syne bg=#0a0a0a text=#fff surface=#111 navbg=#000 spacing=6rem`
|
|
30
66
|
|
|
31
|
-
### State
|
|
67
|
+
### State & data
|
|
32
68
|
```
|
|
33
|
-
@users = []
|
|
34
|
-
@stats = {}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### Data fetching
|
|
39
|
-
```
|
|
40
|
-
~mount GET /api/path => @var fetch on load, assign to @var
|
|
41
|
-
~mount POST /api/path => @var POST on load
|
|
42
|
-
~interval 10000 GET /api/path => @var poll every 10s
|
|
43
|
-
~mount GET /api/path => @list.push($result) append result
|
|
69
|
+
@users = []
|
|
70
|
+
@stats = {}
|
|
71
|
+
~mount GET /api/users => @users
|
|
72
|
+
~interval 30000 GET /api/stats => @stats
|
|
44
73
|
```
|
|
45
74
|
|
|
46
75
|
### All blocks
|
|
47
76
|
|
|
48
|
-
**
|
|
49
|
-
`nav{Brand>/path:Link>/path:Link}`
|
|
77
|
+
**nav** — `nav{Brand>/path:Link>/path:Link}` (auto mobile hamburger)
|
|
50
78
|
|
|
51
|
-
**
|
|
52
|
-
`hero{Title|Subtitle>/path:CTA>/path:CTA2}`
|
|
53
|
-
`hero{Title|Sub>/path:CTA|img:https://image-url}` (with image, creates split layout)
|
|
79
|
+
**hero** — `hero{Title|Subtitle>/path:CTA>/path:CTA2}` or `hero{Title|Sub>/path:CTA|img:https://url}` (split layout)
|
|
54
80
|
|
|
55
|
-
**
|
|
56
|
-
`stats{@var.field:Label|@var.field:Label|static:Label}`
|
|
81
|
+
**stats** — `stats{@stats.users:Users|@stats.mrr:MRR|99.9%:Uptime}`
|
|
57
82
|
|
|
58
|
-
**
|
|
59
|
-
`row2{icon>Title>Body>/path:Link | icon>Title>Body}`
|
|
60
|
-
`row3{icon>Title>Body | icon>Title>Body | icon>Title>Body}`
|
|
61
|
-
`row4{icon>Title>Body | ...}`
|
|
62
|
-
Icons: bolt leaf map chart lock star heart check alert user car money phone shield fire rocket clock globe gear pin flash eye tag plus minus edit trash search bell home mail
|
|
83
|
+
**rowN** — `row3{rocket>Fast>Zero config.|shield>Secure>SOC2.|chart>Smart>Real-time.} animate:stagger`
|
|
63
84
|
|
|
64
|
-
**
|
|
65
|
-
`sect{Title|Optional body text}` animate:fade-up
|
|
85
|
+
**sect** — `sect{Title|Optional body text}`
|
|
66
86
|
|
|
67
|
-
**
|
|
68
|
-
`table @var { Col:key | Col:key | edit PUT /api/{id} | delete /api/{id} | empty: No data yet. }`
|
|
87
|
+
**table** — `table @users { Name:name | Email:email | Plan:plan | edit PUT /api/users/{id} | delete /api/users/{id} | empty: No data. }`
|
|
69
88
|
|
|
70
|
-
**
|
|
71
|
-
```
|
|
72
|
-
form POST /api/path => @list.push($result) { Label:type:placeholder | Label:type | Label:select:opt1,opt2,opt3 }
|
|
73
|
-
form POST /api/auth/login => redirect /dashboard { Email:email | Password:password }
|
|
74
|
-
form PUT /api/path => @item = $result { Label:text:current | Label:select:a,b,c }
|
|
75
|
-
```
|
|
76
|
-
Field types: `text` `email` `password` `number` `tel` `url` `select` `textarea`
|
|
89
|
+
**form** — `form POST /api/users => @users.push($result) { Name:text:Alice | Email:email | Plan:select:starter,pro,enterprise }`
|
|
77
90
|
|
|
78
|
-
**
|
|
79
|
-
`pricing{Plan>Price>Description>/path:CTA | Plan>Price>Desc>/path:CTA | Plan>Price>Desc>/path:CTA}`
|
|
80
|
-
Middle plan auto-gets "Most popular" badge.
|
|
91
|
+
**form with redirect** — `form POST /api/auth/login => redirect /dashboard { Email:email | Password:password }`
|
|
81
92
|
|
|
82
|
-
**
|
|
83
|
-
`faq{Question > Answer | Question > Answer | Question > Answer}`
|
|
93
|
+
**pricing** — `pricing{Starter>Free>3 projects>/signup:Get started|Pro>$29/mo>Unlimited>/signup:Start trial|Enterprise>Custom>SSO>/contact:Talk}`
|
|
84
94
|
|
|
85
|
-
**
|
|
86
|
-
`testimonial{Full Name, Title @ Company|Quote text without quotes|img:https://avatar-url}`
|
|
95
|
+
**faq** — `faq{How to start?>Sign up free.|Cancel anytime?>Yes, one click.}`
|
|
87
96
|
|
|
88
|
-
**
|
|
89
|
-
`gallery{https://img1 | https://img2 | https://img3}`
|
|
97
|
+
**testimonial** — `testimonial{Alice Chen, CEO @ Acme|"Changed how we ship."|img:https://i.pravatar.cc/64?img=5}`
|
|
90
98
|
|
|
91
|
-
**
|
|
92
|
-
`btn{Label > METHOD /api/path > confirm:Confirmation message}`
|
|
93
|
-
`btn{Export > GET /api/export}`
|
|
94
|
-
`btn{Delete all > DELETE /api/items > confirm:Delete all items?}`
|
|
99
|
+
**gallery** — `gallery{https://img1.jpg | https://img2.jpg | https://img3.jpg}`
|
|
95
100
|
|
|
96
|
-
**
|
|
97
|
-
`select @filterVar { All | Active | Inactive | Pending }`
|
|
101
|
+
**btn** — `btn{Export CSV > GET /api/export}` or `btn{Delete all > DELETE /api/items > confirm:Are you sure?}`
|
|
98
102
|
|
|
99
|
-
**
|
|
100
|
-
`raw{<div style="...">Any HTML, custom components, embeds, iframes</div>}`
|
|
103
|
+
**select** — `select @filterVar { All | Active | Inactive }`
|
|
101
104
|
|
|
102
|
-
**
|
|
103
|
-
`if @var { sect{Only shown when @var is truthy} }`
|
|
105
|
+
**raw** — `raw{<div style="...">Any HTML, embeds, custom components</div>}`
|
|
104
106
|
|
|
105
|
-
**
|
|
106
|
-
`foot{© 2025 AppName>/path:Link>/path:Link}`
|
|
107
|
+
**if** — `if @user { sect{Welcome back!} }`
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
`block{...} animate:animation-name`
|
|
110
|
-
`block{...} class:css-class-name`
|
|
111
|
-
`block{...} animate:stagger class:my-section`
|
|
109
|
+
**foot** — `foot{© 2025 AppName>/privacy:Privacy>/terms:Terms}`
|
|
112
110
|
|
|
111
|
+
### Block modifiers (suffix on any block)
|
|
112
|
+
```
|
|
113
|
+
hero{...} animate:blur-in
|
|
114
|
+
row3{...} animate:stagger class:my-section
|
|
115
|
+
sect{...} animate:fade-up
|
|
116
|
+
```
|
|
113
117
|
Animations: `fade-up` `fade-in` `blur-in` `slide-left` `slide-right` `zoom-in` `stagger`
|
|
114
118
|
|
|
119
|
+
### Multiple pages
|
|
120
|
+
```
|
|
121
|
+
%home dark /
|
|
122
|
+
nav{...}
|
|
123
|
+
hero{...}
|
|
124
|
+
---
|
|
125
|
+
%dashboard dark /dashboard
|
|
126
|
+
@users = []
|
|
127
|
+
~mount GET /api/users => @users
|
|
128
|
+
table @users { ... }
|
|
129
|
+
---
|
|
130
|
+
%login dark /login
|
|
131
|
+
form POST /api/auth/login => redirect /dashboard { ... }
|
|
132
|
+
```
|
|
133
|
+
|
|
115
134
|
---
|
|
116
135
|
|
|
117
136
|
## Complete examples
|
|
118
137
|
|
|
119
|
-
### SaaS
|
|
120
|
-
```
|
|
138
|
+
### SaaS with 4 pages
|
|
139
|
+
```
|
|
121
140
|
~theme accent=#2563eb
|
|
141
|
+
~db sqlite ./app.db
|
|
142
|
+
~auth jwt $JWT_SECRET expire=7d
|
|
143
|
+
~admin /admin
|
|
144
|
+
|
|
145
|
+
model User {
|
|
146
|
+
id : uuid : pk auto
|
|
147
|
+
name : text : required
|
|
148
|
+
email : text : required unique
|
|
149
|
+
password : text : required hashed
|
|
150
|
+
plan : enum : starter,pro,enterprise : default=starter
|
|
151
|
+
role : enum : user,admin : default=user
|
|
152
|
+
~soft-delete
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
api POST /api/auth/register {
|
|
156
|
+
~validate name required | email required email | password min=8
|
|
157
|
+
~unique User email $body.email | 409
|
|
158
|
+
~hash password
|
|
159
|
+
insert User($body)
|
|
160
|
+
return jwt($inserted) 201
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
api POST /api/auth/login {
|
|
164
|
+
$user = User.findBy(email=$body.email)
|
|
165
|
+
~check password $body.password $user.password | 401
|
|
166
|
+
return jwt($user) 200
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
api GET /api/users {
|
|
170
|
+
~guard admin
|
|
171
|
+
return User.paginate(1, 20)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
api GET /api/stats {
|
|
175
|
+
return User.count()
|
|
176
|
+
}
|
|
122
177
|
|
|
123
178
|
%home dark /
|
|
124
179
|
|
|
125
180
|
@stats = {}
|
|
126
181
|
~mount GET /api/stats => @stats
|
|
127
182
|
|
|
128
|
-
nav{
|
|
129
|
-
hero{Ship faster with AI|Zero config. Deploy in seconds
|
|
130
|
-
stats{@stats
|
|
131
|
-
row3{rocket>Deploy instantly>Push to git, live in
|
|
132
|
-
testimonial{Sarah Chen, CEO @ Acme
|
|
133
|
-
pricing{Starter>Free>3 projects
|
|
134
|
-
faq{How
|
|
135
|
-
foot{© 2025
|
|
183
|
+
nav{MySaaS>/pricing:Pricing>/login:Sign in>/signup:Get started}
|
|
184
|
+
hero{Ship faster with AI|Zero config. Deploy in seconds.>/signup:Start free>/demo:View demo} animate:blur-in
|
|
185
|
+
stats{@stats:Users|99.9%:Uptime|$49:Starting price}
|
|
186
|
+
row3{rocket>Deploy instantly>Push to git, live in seconds.|shield>Enterprise ready>SOC2, GDPR, SSO built-in.|chart>Full observability>Real-time errors and performance.} animate:stagger
|
|
187
|
+
testimonial{Sarah Chen, CEO @ Acme|"Cut deployment time by 90%."|img:https://i.pravatar.cc/64?img=47} animate:fade-up
|
|
188
|
+
pricing{Starter>Free>3 projects>/signup:Get started|Pro>$29/mo>Unlimited>/signup:Start trial|Enterprise>Custom>SSO>/contact:Talk}
|
|
189
|
+
faq{How to start?>Sign up free, no credit card.|Cancel anytime?>Yes, one click, no questions.}
|
|
190
|
+
foot{© 2025 MySaaS>/privacy:Privacy>/terms:Terms}
|
|
136
191
|
|
|
137
192
|
---
|
|
138
193
|
|
|
139
194
|
%dashboard dark /dashboard
|
|
140
195
|
|
|
141
|
-
@user = {}
|
|
142
196
|
@users = []
|
|
143
197
|
@stats = {}
|
|
144
|
-
~mount GET /api/me => @user
|
|
145
198
|
~mount GET /api/users => @users
|
|
146
199
|
~mount GET /api/stats => @stats
|
|
147
200
|
~interval 30000 GET /api/stats => @stats
|
|
148
201
|
|
|
149
|
-
nav{
|
|
150
|
-
stats{@stats
|
|
202
|
+
nav{MySaaS>/settings:Settings>/logout:Sign out}
|
|
203
|
+
stats{@stats:Total users|@stats:Active|$0:MRR}
|
|
151
204
|
sect{User database}
|
|
152
|
-
table @users { Name:name | Email:email | Plan:plan | Status:status |
|
|
153
|
-
sect{Add user
|
|
154
|
-
form POST /api/users => @users.push($result) { Full name:text:Alice Johnson | Email:email:alice@
|
|
155
|
-
foot{
|
|
205
|
+
table @users { Name:name | Email:email | Plan:plan | Status:status | edit PUT /api/users/{id} | delete /api/users/{id} | empty: No users yet. }
|
|
206
|
+
sect{Add user}
|
|
207
|
+
form POST /api/users => @users.push($result) { Full name:text:Alice Johnson | Email:email:alice@co.com | Plan:select:starter,pro,enterprise }
|
|
208
|
+
foot{MySaaS Dashboard © 2025}
|
|
156
209
|
|
|
157
210
|
---
|
|
158
211
|
|
|
159
212
|
%login dark /login
|
|
160
213
|
|
|
161
|
-
nav{
|
|
162
|
-
hero{Welcome back|Sign in to
|
|
214
|
+
nav{MySaaS>/signup:Create account}
|
|
215
|
+
hero{Welcome back|Sign in to continue.}
|
|
163
216
|
form POST /api/auth/login => redirect /dashboard { Email:email:you@company.com | Password:password: }
|
|
164
|
-
foot{© 2025
|
|
217
|
+
foot{© 2025 MySaaS>/signup:Create account}
|
|
165
218
|
|
|
166
219
|
---
|
|
167
220
|
|
|
168
221
|
%signup dark /signup
|
|
169
222
|
|
|
170
|
-
nav{
|
|
171
|
-
hero{Start for free|No credit card required.
|
|
172
|
-
form POST /api/auth/register => redirect /dashboard { Full name:text:Alice
|
|
173
|
-
foot{© 2025
|
|
223
|
+
nav{MySaaS>/login:Sign in}
|
|
224
|
+
hero{Start for free|No credit card required.}
|
|
225
|
+
form POST /api/auth/register => redirect /dashboard { Full name:text:Alice | Email:email:alice@co.com | Password:password: }
|
|
226
|
+
foot{© 2025 MySaaS>/login:Already have an account?}
|
|
174
227
|
```
|
|
175
228
|
|
|
176
|
-
###
|
|
177
|
-
```flux
|
|
178
|
-
~theme accent=#10b981 radius=.75rem font=Inter surface=#0d1f1a
|
|
179
|
-
|
|
180
|
-
%products dark /products
|
|
181
|
-
|
|
182
|
-
@products = []
|
|
183
|
-
@search = ""
|
|
184
|
-
~mount GET /api/products => @products
|
|
185
|
-
|
|
186
|
-
nav{MyStore>/products:Products>/orders:Orders>/settings:Settings}
|
|
187
|
-
sect{Product Catalog}
|
|
188
|
-
table @products { Name:name | SKU:sku | Price:price | Stock:stock | Status:status | edit PUT /api/products/{id} | delete /api/products/{id} | empty: No products. Add your first product below. }
|
|
189
|
-
sect{Add Product}
|
|
190
|
-
form POST /api/products => @products.push($result) { Product name:text:iPhone Case | SKU:text:CASE-001 | Price:number:29.99 | Stock:number:100 | Category:select:electronics,clothing,food,other }
|
|
191
|
-
foot{© 2025 MyStore}
|
|
229
|
+
### Landing page with custom theme
|
|
192
230
|
```
|
|
193
|
-
|
|
194
|
-
### Simple landing with image hero
|
|
195
|
-
```flux
|
|
196
231
|
~theme accent=#f59e0b radius=2rem font=Syne bg=#0c0a09 text=#fafaf9 surface=#1c1917
|
|
197
232
|
|
|
198
233
|
%home dark /
|
|
199
234
|
|
|
200
|
-
nav{Acme>/
|
|
201
|
-
hero{We build things that matter|
|
|
202
|
-
row3{globe>Global clients>
|
|
203
|
-
testimonial{Marco Silva, CTO @
|
|
204
|
-
gallery{https://images.unsplash.com/photo-1600880292203
|
|
205
|
-
foot{© 2025 Acme Studio>/privacy:Privacy>/instagram:Instagram
|
|
235
|
+
nav{Acme Studio>/work:Work>/blog:Blog>/contact:Contact}
|
|
236
|
+
hero{We build things that matter|Creative studio based in São Paulo.>/work:View our work>/contact:Get in touch|img:https://images.unsplash.com/photo-1497366216548?w=800} animate:fade-in
|
|
237
|
+
row3{globe>Global clients>Teams in 30+ countries.|star>Award winning>12 design awards.|check>On-time delivery>98% on schedule.} animate:stagger
|
|
238
|
+
testimonial{Marco Silva, CTO @ FinTech BR|"From prototype to production in 6 weeks."|img:https://i.pravatar.cc/64?img=12}
|
|
239
|
+
gallery{https://images.unsplash.com/photo-1600880292203?w=400|https://images.unsplash.com/photo-1522202176988?w=400|https://images.unsplash.com/photo-1497366412874?w=400}
|
|
240
|
+
foot{© 2025 Acme Studio>/privacy:Privacy>/instagram:Instagram}
|
|
206
241
|
```
|
|
207
242
|
|
|
208
243
|
---
|
|
@@ -214,21 +249,29 @@ foot{© 2025 Acme Studio>/privacy:Privacy>/instagram:Instagram>/linkedin:LinkedI
|
|
|
214
249
|
3. For dynamic data, always declare `@var = []` or `@var = {}` and use `~mount`
|
|
215
250
|
4. Tables with data should always have `edit` and `delete` unless readonly
|
|
216
251
|
5. Forms should have `=> @list.push($result)` or `=> redirect /path`
|
|
217
|
-
6. Use real icon names
|
|
252
|
+
6. Use real icon names: bolt rocket shield chart star check globe gear fire money bell mail user
|
|
218
253
|
7. Multiple pages separated by `---`
|
|
219
|
-
8. Add `animate:fade-up` or `animate:stagger` to key sections
|
|
254
|
+
8. Add `animate:fade-up` or `animate:stagger` to key sections
|
|
220
255
|
9. `~theme` always comes before `%` declarations
|
|
221
|
-
10. Never generate explanations — only
|
|
256
|
+
10. Never generate explanations — only aiplang code
|
|
257
|
+
11. For full-stack apps, add `~db`, `~auth`, `model` and `api` blocks before pages
|
|
222
258
|
|
|
223
259
|
---
|
|
224
260
|
|
|
225
|
-
## Running
|
|
261
|
+
## Running
|
|
226
262
|
|
|
227
263
|
```bash
|
|
228
|
-
# Install
|
|
264
|
+
# Install
|
|
229
265
|
npm install -g aiplang
|
|
230
266
|
|
|
231
|
-
#
|
|
232
|
-
aiplang serve # dev
|
|
267
|
+
# Frontend only (static site)
|
|
268
|
+
aiplang serve # dev → localhost:3000
|
|
233
269
|
aiplang build pages/ # compile → dist/
|
|
270
|
+
|
|
271
|
+
# Full-stack (Node.js backend)
|
|
272
|
+
aiplang start app.flux
|
|
273
|
+
|
|
274
|
+
# Go binary (production, v2)
|
|
275
|
+
aiplangd dev app.flux
|
|
276
|
+
aiplangd build app.flux
|
|
234
277
|
```
|
package/bin/aiplang.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs')
|
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const http = require('http')
|
|
7
7
|
|
|
8
|
-
const VERSION = '2.1.
|
|
8
|
+
const VERSION = '2.1.2'
|
|
9
9
|
const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
|
|
10
10
|
const cmd = process.argv[2]
|
|
11
11
|
const args = process.argv.slice(3)
|
|
@@ -53,6 +53,7 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
|
|
|
53
53
|
|
|
54
54
|
GitHub: https://github.com/isacamartin/aiplang
|
|
55
55
|
npm: https://npmjs.com/package/aiplang
|
|
56
|
+
Docs: https://isacamartin.github.io/aiplang
|
|
56
57
|
`)
|
|
57
58
|
process.exit(0)
|
|
58
59
|
}
|
|
@@ -110,10 +111,9 @@ if (cmd==='init') {
|
|
|
110
111
|
const src=path.join(RUNTIME_DIR,f); if(fs.existsSync(src)) fs.copyFileSync(src,path.join(dir,'public',f))
|
|
111
112
|
}
|
|
112
113
|
fs.writeFileSync(path.join(dir,'public','index.html'),`<!DOCTYPE html>
|
|
113
|
-
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${name}</title
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
</script></body></html>`)
|
|
114
|
+
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${name}</title>
|
|
115
|
+
<meta http-equiv="refresh" content="0; url=/">
|
|
116
|
+
</head><body><p>Run <code>npx aiplang serve</code> to start the dev server.</p></body></html>`)
|
|
117
117
|
fs.writeFileSync(path.join(dir,'pages','home.flux'), (TEMPLATES[tplName]||TEMPLATES.default)(name, year))
|
|
118
118
|
fs.writeFileSync(path.join(dir,'package.json'), JSON.stringify({name,version:'0.1.0',scripts:{dev:'npx aiplang serve',build:'npx aiplang build pages/ --out dist/'},devDependencies:{'aiplang':`^${VERSION}`}},null,2))
|
|
119
119
|
fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
|
|
@@ -208,12 +208,12 @@ if (cmd==='serve'||cmd==='dev') {
|
|
|
208
208
|
// ── Dev server (full-stack) ──────────────────────────────────────
|
|
209
209
|
if (cmd === 'start' || cmd === 'run') {
|
|
210
210
|
const aipFile = args[0]
|
|
211
|
-
if (!aipFile || !fs.existsSync(
|
|
211
|
+
if (!aipFile || !fs.existsSync(aipFile)) {
|
|
212
212
|
console.error(`\n ✗ Usage: aiplang start <app.flux>\n`)
|
|
213
213
|
process.exit(1)
|
|
214
214
|
}
|
|
215
215
|
const port = parseInt(process.env.PORT || args[1] || '3000')
|
|
216
|
-
const serverPath = path.join(__dirname, '..', '
|
|
216
|
+
const serverPath = path.join(__dirname, '..', 'server', 'server.js')
|
|
217
217
|
if (!fs.existsSync(serverPath)) {
|
|
218
218
|
console.error(`\n ✗ Full-stack server not found.`)
|
|
219
219
|
console.error(` Install: npm install -g aiplang-server\n`)
|
|
@@ -400,7 +400,7 @@ function renderPage(page, allPages) {
|
|
|
400
400
|
const needsJS=page.queries.length>0||page.blocks.some(b=>['table','list','form','if','btn','select','faq'].includes(b.kind))
|
|
401
401
|
const body=page.blocks.map(b=>applyMods(renderBlock(b,page),b)).join('')
|
|
402
402
|
const config=needsJS?JSON.stringify({id:page.id,theme:page.theme,routes:allPages.map(p=>p.route),state:page.state,queries:page.queries}):''
|
|
403
|
-
const hydrate=needsJS?`\n<script>window.
|
|
403
|
+
const hydrate=needsJS?`\n<script>window.__AIPLANG_PAGE__=${config};</script>\n<script src="./aiplang-hydrate.js" defer></script>`:''
|
|
404
404
|
const customVars=page.customTheme?genCustomThemeVars(page.customTheme):''
|
|
405
405
|
const themeVarCSS=page.themeVars?genThemeVarCSS(page.themeVars):''
|
|
406
406
|
return `<!DOCTYPE html>
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* aiplang-hydrate.js — aiplang Hydration Runtime v2.1
|
|
3
3
|
* Handles: state, queries, table, list, form, if, edit, delete, btn, select
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
(function () {
|
|
7
7
|
'use strict'
|
|
8
8
|
|
|
9
|
-
const cfg = window.
|
|
9
|
+
const cfg = window.__AIPLANG_PAGE__
|
|
10
10
|
if (!cfg) return
|
|
11
11
|
|
|
12
12
|
// ── State ────────────────────────────────────────────────────────
|
|
@@ -64,7 +64,7 @@ async function runQuery(q) {
|
|
|
64
64
|
applyAction(data, q.target, q.action)
|
|
65
65
|
return data
|
|
66
66
|
} catch (e) {
|
|
67
|
-
console.warn('[
|
|
67
|
+
console.warn('[aiplang]', q.method, path, e.message)
|
|
68
68
|
return null
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* aiplang-runtime.js — aiplang Runtime v2.1
|
|
3
3
|
* Reactive state + SPA routing + DOM engine + query engine
|
|
4
4
|
* Zero dependencies. ~28KB unminified.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const AIPLANG = (() => {
|
|
8
8
|
|
|
9
9
|
// ─────────────────────────────────────────────────────────────
|
|
10
10
|
// ICONS
|
|
@@ -165,7 +165,7 @@ class QueryEngine {
|
|
|
165
165
|
this._applyResult(data, q.target, q.action)
|
|
166
166
|
return data
|
|
167
167
|
} catch (e) {
|
|
168
|
-
console.warn('[
|
|
168
|
+
console.warn('[aiplang] query failed:', q.method, path, e.message)
|
|
169
169
|
return null
|
|
170
170
|
}
|
|
171
171
|
}
|
|
@@ -1078,7 +1078,7 @@ function boot(src, container) {
|
|
|
1078
1078
|
|
|
1079
1079
|
const pages = parseFlux(src)
|
|
1080
1080
|
if (!pages.length) {
|
|
1081
|
-
container.textContent = '[
|
|
1081
|
+
container.textContent = '[aiplang] no pages found'
|
|
1082
1082
|
return
|
|
1083
1083
|
}
|
|
1084
1084
|
|
|
@@ -1095,6 +1095,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1095
1095
|
if (script) {
|
|
1096
1096
|
const targetSel = script.getAttribute('target') || '#app'
|
|
1097
1097
|
const container = document.querySelector(targetSel)
|
|
1098
|
-
if (container)
|
|
1098
|
+
if (container) AIPLANG.boot(script.textContent, container)
|
|
1099
1099
|
}
|
|
1100
1100
|
})
|