aiplang 2.3.0 → 2.4.1
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 +6 -6
- package/aiplang-knowledge.md +3 -3
- package/bin/aiplang.js +42 -42
- package/package.json +1 -1
- package/runtime/aiplang-runtime.js +52 -52
- package/server/server.js +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ cd my-app
|
|
|
8
8
|
npx aiplang serve
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Ask Claude to generate a page → paste into `pages/home.
|
|
11
|
+
Ask Claude to generate a page → paste into `pages/home.aiplang` → see it live.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -16,9 +16,9 @@ Ask Claude to generate a page → paste into `pages/home.flux` → see it live.
|
|
|
16
16
|
|
|
17
17
|
**aiplang** is a web language designed to be generated by AI (Claude), not written by humans.
|
|
18
18
|
|
|
19
|
-
A single `.
|
|
19
|
+
A single `.aiplang` file describes a complete app: frontend, backend, database, auth, email, jobs.
|
|
20
20
|
|
|
21
|
-
```
|
|
21
|
+
```aiplang
|
|
22
22
|
~db sqlite ./app.db
|
|
23
23
|
~auth jwt $JWT_SECRET expire=7d
|
|
24
24
|
~admin /admin
|
|
@@ -83,7 +83,7 @@ npx aiplang init --template landing # landing page template
|
|
|
83
83
|
npx aiplang init --template crud # CRUD app template
|
|
84
84
|
npx aiplang serve # dev server + hot reload → localhost:3000
|
|
85
85
|
npx aiplang build pages/ --out dist/ # compile → static HTML
|
|
86
|
-
npx aiplang start app.
|
|
86
|
+
npx aiplang start app.aiplang # full-stack server (Node.js)
|
|
87
87
|
npx aiplang new dashboard # create new page template
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -111,13 +111,13 @@ All blocks accept: animate:fade-up class:my-class | raw{<html>} | foot{text>/pat
|
|
|
111
111
|
|
|
112
112
|
```
|
|
113
113
|
aiplang/
|
|
114
|
-
├── packages/
|
|
114
|
+
├── packages/aiplang-pkg/ ← npm package (aiplang CLI + runtime)
|
|
115
115
|
│ ├── bin/aiplang.js ← CLI: init, serve, build, new, start
|
|
116
116
|
│ ├── runtime/aiplang-hydrate.js← 10KB reactive runtime
|
|
117
117
|
│ ├── server/server.js ← full-stack Node.js server
|
|
118
118
|
│ └── aiplang-knowledge.md ← Claude Project knowledge file
|
|
119
119
|
├── aiplang-go/ ← Go compiler + server (v2)
|
|
120
|
-
│ ├── compiler/compiler.go ← .
|
|
120
|
+
│ ├── compiler/compiler.go ← .aiplang → AST parser
|
|
121
121
|
│ ├── server/server.go ← Go HTTP server
|
|
122
122
|
│ └── cmd/aiplangd/main.go ← binary entrypoint
|
|
123
123
|
├── docs/ ← GitHub Pages (aiplang.io)
|
package/aiplang-knowledge.md
CHANGED
|
@@ -330,9 +330,9 @@ aiplang serve # dev → localhost:3000
|
|
|
330
330
|
aiplang build pages/ # compile → dist/
|
|
331
331
|
|
|
332
332
|
# Full-stack (Node.js backend)
|
|
333
|
-
aiplang start app.
|
|
333
|
+
aiplang start app.aiplang
|
|
334
334
|
|
|
335
335
|
# Go binary (production, v2)
|
|
336
|
-
aiplangd dev app.
|
|
337
|
-
aiplangd build app.
|
|
336
|
+
aiplangd dev app.aiplang
|
|
337
|
+
aiplangd build app.aiplang
|
|
338
338
|
```
|
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.
|
|
8
|
+
const VERSION = '2.4.1'
|
|
9
9
|
const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
|
|
10
10
|
const cmd = process.argv[2]
|
|
11
11
|
const args = process.argv.slice(3)
|
|
@@ -36,7 +36,7 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
|
|
|
36
36
|
Usage:
|
|
37
37
|
npx aiplang init [name] create project (default template)
|
|
38
38
|
npx aiplang init [name] --template <t> use template: saas|landing|crud|dashboard|portfolio|blog
|
|
39
|
-
npx aiplang init [name] --template ./my.
|
|
39
|
+
npx aiplang init [name] --template ./my.aiplang use a local .aiplang file as template
|
|
40
40
|
npx aiplang init [name] --template my-custom use a saved custom template
|
|
41
41
|
npx aiplang serve [dir] dev server + hot reload
|
|
42
42
|
npx aiplang build [dir/file] compile → static HTML
|
|
@@ -44,16 +44,16 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
|
|
|
44
44
|
npx aiplang --version
|
|
45
45
|
|
|
46
46
|
Full-stack:
|
|
47
|
-
npx aiplang start app.
|
|
48
|
-
PORT=8080 aiplang start app.
|
|
47
|
+
npx aiplang start app.aiplang start full-stack server (API + DB + frontend)
|
|
48
|
+
PORT=8080 aiplang start app.aiplang custom port
|
|
49
49
|
|
|
50
50
|
Templates:
|
|
51
51
|
npx aiplang template list list all templates (built-in + custom)
|
|
52
52
|
npx aiplang template save <n> save current project as template
|
|
53
|
-
npx aiplang template save <n> --from <f> save a specific .
|
|
53
|
+
npx aiplang template save <n> --from <f> save a specific .aiplang file as template
|
|
54
54
|
npx aiplang template edit <n> open template in editor
|
|
55
55
|
npx aiplang template show <n> print template source
|
|
56
|
-
npx aiplang template export <n> export template to .
|
|
56
|
+
npx aiplang template export <n> export template to .aiplang file
|
|
57
57
|
npx aiplang template remove <n> delete a custom template
|
|
58
58
|
|
|
59
59
|
Custom template variables:
|
|
@@ -76,7 +76,7 @@ if (cmd==='--version'||cmd==='-v') { console.log(`aiplang v${VERSION}`); process
|
|
|
76
76
|
|
|
77
77
|
// ─────────────────────────────────────────────────────────────────
|
|
78
78
|
// TEMPLATE SYSTEM
|
|
79
|
-
// Custom templates stored at ~/.aiplang/templates/<name>.
|
|
79
|
+
// Custom templates stored at ~/.aiplang/templates/<name>.aiplang
|
|
80
80
|
// ─────────────────────────────────────────────────────────────────
|
|
81
81
|
|
|
82
82
|
const TEMPLATES_DIR = path.join(require('os').homedir(), '.aiplang', 'templates')
|
|
@@ -220,7 +220,7 @@ foot{{{name}}}`,
|
|
|
220
220
|
default: `# {{name}}
|
|
221
221
|
%home dark /
|
|
222
222
|
nav{{{name}}>/login:Sign in}
|
|
223
|
-
hero{Welcome to {{name}}|Edit pages/home.
|
|
223
|
+
hero{Welcome to {{name}}|Edit pages/home.aiplang to get started.>/signup:Get started} animate:fade-up
|
|
224
224
|
row3{rocket>Fast>Renders in under 1ms.|bolt>AI-native>Written by Claude in seconds.|globe>Deploy anywhere>Static files. Any host.}
|
|
225
225
|
foot{© {{year}} {{name}}}`,
|
|
226
226
|
}
|
|
@@ -232,15 +232,15 @@ function applyTemplateVars(src, name, year) {
|
|
|
232
232
|
function getTemplate(tplName, name, year) {
|
|
233
233
|
ensureTemplatesDir()
|
|
234
234
|
|
|
235
|
-
// 1. Local file path: --template ./my-template.
|
|
235
|
+
// 1. Local file path: --template ./my-template.aiplang or --template /abs/path.aiplang
|
|
236
236
|
if (tplName.startsWith('./') || tplName.startsWith('../') || tplName.startsWith('/')) {
|
|
237
237
|
const full = path.resolve(tplName)
|
|
238
238
|
if (!fs.existsSync(full)) { console.error(`\n ✗ Template file not found: ${full}\n`); process.exit(1) }
|
|
239
239
|
return applyTemplateVars(fs.readFileSync(full, 'utf8'), name, year)
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
// 2. User custom template: ~/.aiplang/templates/<name>.
|
|
243
|
-
const customPath = path.join(TEMPLATES_DIR, tplName + '.
|
|
242
|
+
// 2. User custom template: ~/.aiplang/templates/<name>.aiplang
|
|
243
|
+
const customPath = path.join(TEMPLATES_DIR, tplName + '.aiplang')
|
|
244
244
|
if (fs.existsSync(customPath)) {
|
|
245
245
|
return applyTemplateVars(fs.readFileSync(customPath, 'utf8'), name, year)
|
|
246
246
|
}
|
|
@@ -251,7 +251,7 @@ function getTemplate(tplName, name, year) {
|
|
|
251
251
|
|
|
252
252
|
// Not found — show what's available
|
|
253
253
|
const customs = fs.existsSync(TEMPLATES_DIR)
|
|
254
|
-
? fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.
|
|
254
|
+
? fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aiplang')).map(f=>f.replace('.aiplang',''))
|
|
255
255
|
: []
|
|
256
256
|
const all = [...Object.keys(BUILTIN_TEMPLATES).filter(k=>k!=='default'), ...customs]
|
|
257
257
|
console.error(`\n ✗ Template "${tplName}" not found.\n Available: ${all.join(', ')}\n`)
|
|
@@ -261,7 +261,7 @@ function getTemplate(tplName, name, year) {
|
|
|
261
261
|
function listTemplates() {
|
|
262
262
|
ensureTemplatesDir()
|
|
263
263
|
const builtins = Object.keys(BUILTIN_TEMPLATES).filter(k=>k!=='default')
|
|
264
|
-
const customs = fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.
|
|
264
|
+
const customs = fs.readdirSync(TEMPLATES_DIR).filter(f=>f.endsWith('.aiplang')).map(f=>f.replace('.aiplang',''))
|
|
265
265
|
console.log(`\n aiplang templates\n`)
|
|
266
266
|
console.log(` Built-in:`)
|
|
267
267
|
builtins.forEach(t => console.log(` ${t}`))
|
|
@@ -295,18 +295,18 @@ if (cmd === 'template') {
|
|
|
295
295
|
if (!fs.existsSync(fp)) { console.error(`\n ✗ File not found: ${fp}\n`); process.exit(1) }
|
|
296
296
|
src = fs.readFileSync(fp, 'utf8')
|
|
297
297
|
} else {
|
|
298
|
-
// Auto-detect: use pages/ directory or app.
|
|
299
|
-
const sources = ['pages', 'app.
|
|
298
|
+
// Auto-detect: use pages/ directory or app.aiplang
|
|
299
|
+
const sources = ['pages', 'app.aiplang', 'index.aiplang']
|
|
300
300
|
const found = sources.find(s => fs.existsSync(s))
|
|
301
|
-
if (!found) { console.error('\n ✗ No .
|
|
301
|
+
if (!found) { console.error('\n ✗ No .aiplang files found. Use --from <file> to specify source.\n'); process.exit(1) }
|
|
302
302
|
if (fs.statSync(found).isDirectory()) {
|
|
303
|
-
src = fs.readdirSync(found).filter(f=>f.endsWith('.
|
|
303
|
+
src = fs.readdirSync(found).filter(f=>f.endsWith('.aiplang'))
|
|
304
304
|
.map(f => fs.readFileSync(path.join(found,f),'utf8')).join('\n---\n')
|
|
305
305
|
} else {
|
|
306
306
|
src = fs.readFileSync(found, 'utf8')
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
-
const dest = path.join(TEMPLATES_DIR, tname + '.
|
|
309
|
+
const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
|
|
310
310
|
fs.writeFileSync(dest, src)
|
|
311
311
|
console.log(`\n ✓ Template saved: ${tname}\n ${dest}\n\n Use it: aiplang init my-app --template ${tname}\n`)
|
|
312
312
|
process.exit(0)
|
|
@@ -316,7 +316,7 @@ if (cmd === 'template') {
|
|
|
316
316
|
if (sub === 'remove' || sub === 'rm' || sub === 'delete') {
|
|
317
317
|
const tname = args[1]
|
|
318
318
|
if (!tname) { console.error('\n ✗ Usage: aiplang template remove <name>\n'); process.exit(1) }
|
|
319
|
-
const dest = path.join(TEMPLATES_DIR, tname + '.
|
|
319
|
+
const dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
|
|
320
320
|
if (!fs.existsSync(dest)) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
|
|
321
321
|
fs.unlinkSync(dest)
|
|
322
322
|
console.log(`\n ✓ Removed template: ${tname}\n`); process.exit(0)
|
|
@@ -326,7 +326,7 @@ if (cmd === 'template') {
|
|
|
326
326
|
if (sub === 'edit' || sub === 'open') {
|
|
327
327
|
const tname = args[1]
|
|
328
328
|
if (!tname) { console.error('\n ✗ Usage: aiplang template edit <name>\n'); process.exit(1) }
|
|
329
|
-
let dest = path.join(TEMPLATES_DIR, tname + '.
|
|
329
|
+
let dest = path.join(TEMPLATES_DIR, tname + '.aiplang')
|
|
330
330
|
if (!fs.existsSync(dest)) {
|
|
331
331
|
// create from built-in if exists
|
|
332
332
|
const builtin = BUILTIN_TEMPLATES[tname]
|
|
@@ -342,7 +342,7 @@ if (cmd === 'template') {
|
|
|
342
342
|
// aiplang template show <name>
|
|
343
343
|
if (sub === 'show' || sub === 'cat') {
|
|
344
344
|
const tname = args[1] || 'default'
|
|
345
|
-
const customPath = path.join(TEMPLATES_DIR, tname + '.
|
|
345
|
+
const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
|
|
346
346
|
if (fs.existsSync(customPath)) { console.log(fs.readFileSync(customPath,'utf8')); process.exit(0) }
|
|
347
347
|
const builtin = BUILTIN_TEMPLATES[tname]
|
|
348
348
|
if (builtin) { console.log(builtin); process.exit(0) }
|
|
@@ -354,8 +354,8 @@ if (cmd === 'template') {
|
|
|
354
354
|
const tname = args[1]
|
|
355
355
|
if (!tname) { console.error('\n ✗ Usage: aiplang template export <name>\n'); process.exit(1) }
|
|
356
356
|
const outIdx = args.indexOf('--out')
|
|
357
|
-
const outFile = outIdx !== -1 ? args[outIdx+1] : `./${tname}.
|
|
358
|
-
const customPath = path.join(TEMPLATES_DIR, tname + '.
|
|
357
|
+
const outFile = outIdx !== -1 ? args[outIdx+1] : `./${tname}.aiplang`
|
|
358
|
+
const customPath = path.join(TEMPLATES_DIR, tname + '.aiplang')
|
|
359
359
|
const src = fs.existsSync(customPath) ? fs.readFileSync(customPath,'utf8') : BUILTIN_TEMPLATES[tname]
|
|
360
360
|
if (!src) { console.error(`\n ✗ Template "${tname}" not found.\n`); process.exit(1) }
|
|
361
361
|
fs.writeFileSync(outFile, src)
|
|
@@ -384,21 +384,21 @@ if (cmd==='init') {
|
|
|
384
384
|
const isMultiFile = tplSrc.includes('\n---\n')
|
|
385
385
|
|
|
386
386
|
if (isFullStack) {
|
|
387
|
-
// Full-stack project: single app.
|
|
387
|
+
// Full-stack project: single app.aiplang
|
|
388
388
|
fs.mkdirSync(dir, { recursive: true })
|
|
389
|
-
fs.writeFileSync(path.join(dir, 'app.
|
|
389
|
+
fs.writeFileSync(path.join(dir, 'app.aiplang'), tplSrc)
|
|
390
390
|
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
|
|
391
391
|
name, version:'0.1.0',
|
|
392
|
-
scripts: { dev: 'npx aiplang start app.
|
|
392
|
+
scripts: { dev: 'npx aiplang start app.aiplang', start: 'npx aiplang start app.aiplang' },
|
|
393
393
|
devDependencies: { 'aiplang': `^${VERSION}` }
|
|
394
394
|
}, null, 2))
|
|
395
395
|
fs.writeFileSync(path.join(dir, '.env.example'), 'JWT_SECRET=change-me-in-production\n# STRIPE_SECRET_KEY=sk_test_...\n# AWS_ACCESS_KEY_ID=...\n# AWS_SECRET_ACCESS_KEY=...\n# S3_BUCKET=...\n')
|
|
396
396
|
fs.writeFileSync(path.join(dir, '.gitignore'), '*.db\nnode_modules/\ndist/\n.env\nuploads/\n')
|
|
397
|
-
fs.writeFileSync(path.join(dir, 'README.md'), `# ${name}\n\nGenerated with [aiplang](https://npmjs.com/package/aiplang) v${VERSION}\n\n## Run\n\n\`\`\`bash\nnpx aiplang start app.
|
|
397
|
+
fs.writeFileSync(path.join(dir, 'README.md'), `# ${name}\n\nGenerated with [aiplang](https://npmjs.com/package/aiplang) v${VERSION}\n\n## Run\n\n\`\`\`bash\nnpx aiplang start app.aiplang\n\`\`\`\n`)
|
|
398
398
|
const label = tplName !== 'default' ? ` (template: ${tplName})` : ''
|
|
399
|
-
console.log(`\n ✓ Created ${name}/${label}\n\n app.
|
|
399
|
+
console.log(`\n ✓ Created ${name}/${label}\n\n app.aiplang ← full-stack app (backend + frontend)\n\n Next:\n cd ${name} && npx aiplang start app.aiplang\n`)
|
|
400
400
|
} else if (isMultiFile) {
|
|
401
|
-
// Multi-page SSG project: pages/*.
|
|
401
|
+
// Multi-page SSG project: pages/*.aiplang
|
|
402
402
|
fs.mkdirSync(path.join(dir,'pages'), {recursive:true})
|
|
403
403
|
fs.mkdirSync(path.join(dir,'public'), {recursive:true})
|
|
404
404
|
for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
|
|
@@ -408,7 +408,7 @@ if (cmd==='init') {
|
|
|
408
408
|
pageBlocks.forEach((block, i) => {
|
|
409
409
|
const m = block.match(/^%([a-zA-Z0-9_-]+)/m)
|
|
410
410
|
const pageName = m ? m[1] : (i === 0 ? 'home' : `page${i}`)
|
|
411
|
-
fs.writeFileSync(path.join(dir,'pages',`${pageName}.
|
|
411
|
+
fs.writeFileSync(path.join(dir,'pages',`${pageName}.aiplang`), block.trim())
|
|
412
412
|
})
|
|
413
413
|
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))
|
|
414
414
|
fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
|
|
@@ -422,11 +422,11 @@ if (cmd==='init') {
|
|
|
422
422
|
for (const f of ['aiplang-runtime.js','aiplang-hydrate.js']) {
|
|
423
423
|
const src=path.join(RUNTIME_DIR,f); if(fs.existsSync(src)) fs.copyFileSync(src,path.join(dir,'public',f))
|
|
424
424
|
}
|
|
425
|
-
fs.writeFileSync(path.join(dir,'pages','home.
|
|
425
|
+
fs.writeFileSync(path.join(dir,'pages','home.aiplang'), tplSrc)
|
|
426
426
|
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))
|
|
427
427
|
fs.writeFileSync(path.join(dir,'.gitignore'),'dist/\nnode_modules/\n')
|
|
428
428
|
const label = tplName !== 'default' ? ` (template: ${tplName})` : ''
|
|
429
|
-
console.log(`\n ✓ Created ${name}/${label}\n\n pages/home.
|
|
429
|
+
console.log(`\n ✓ Created ${name}/${label}\n\n pages/home.aiplang ← edit this\n\n Next:\n cd ${name} && npx aiplang serve\n`)
|
|
430
430
|
}
|
|
431
431
|
process.exit(0)
|
|
432
432
|
}
|
|
@@ -435,7 +435,7 @@ if (cmd==='init') {
|
|
|
435
435
|
if (cmd==='new') {
|
|
436
436
|
const name=args[0]; if(!name){console.error('\n ✗ Usage: aiplang new <page>\n');process.exit(1)}
|
|
437
437
|
const dir=fs.existsSync('pages')?'pages':'.'
|
|
438
|
-
const file=path.join(dir,`${name}.
|
|
438
|
+
const file=path.join(dir,`${name}.aiplang`)
|
|
439
439
|
if(fs.existsSync(file)){console.error(`\n ✗ ${file} exists.\n`);process.exit(1)}
|
|
440
440
|
const cap=name.charAt(0).toUpperCase()+name.slice(1)
|
|
441
441
|
fs.writeFileSync(file,`# ${name}\n%${name} dark /${name}\n\nnav{AppName>/home:Home}\nhero{${cap}|Description.>/action:Get started}\nfoot{© ${new Date().getFullYear()} AppName}\n`)
|
|
@@ -450,9 +450,9 @@ if (cmd==='build') {
|
|
|
450
450
|
const input=args.filter((a,i)=>!a.startsWith('--')&&i!==outIdx+1)[0]||'pages/'
|
|
451
451
|
const files=[]
|
|
452
452
|
if(fs.existsSync(input)&&fs.statSync(input).isDirectory()){
|
|
453
|
-
fs.readdirSync(input).filter(f=>f.endsWith('.
|
|
454
|
-
} else if(input.endsWith('.
|
|
455
|
-
if(!files.length){console.error(`\n ✗ No .
|
|
453
|
+
fs.readdirSync(input).filter(f=>f.endsWith('.aiplang')).forEach(f=>files.push(path.join(input,f)))
|
|
454
|
+
} else if(input.endsWith('.aiplang')&&fs.existsSync(input)){ files.push(input) }
|
|
455
|
+
if(!files.length){console.error(`\n ✗ No .aiplang files in: ${input}\n`);process.exit(1)}
|
|
456
456
|
const src=files.map(f=>fs.readFileSync(f,'utf8')).join('\n---\n')
|
|
457
457
|
const pages=parseFlux(src)
|
|
458
458
|
if(!pages.length){console.error('\n ✗ No pages found.\n');process.exit(1)}
|
|
@@ -471,7 +471,7 @@ if (cmd==='build') {
|
|
|
471
471
|
}
|
|
472
472
|
const hf=path.join(RUNTIME_DIR,'aiplang-hydrate.js')
|
|
473
473
|
if(fs.existsSync(hf)){const dst=path.join(outDir,'aiplang-hydrate.js');fs.copyFileSync(hf,dst);total+=fs.statSync(dst).size;console.log(` ✓ ${dst.padEnd(40)} ${hSize(fs.statSync(dst).size)}`)}
|
|
474
|
-
if(fs.existsSync('public'))fs.readdirSync('public').filter(f=>!f.endsWith('.
|
|
474
|
+
if(fs.existsSync('public'))fs.readdirSync('public').filter(f=>!f.endsWith('.aiplang')).forEach(f=>fs.copyFileSync(path.join('public',f),path.join(outDir,f)))
|
|
475
475
|
console.log(`\n ${pages.length} page(s) — ${hSize(total)} total\n\n Preview: npx serve ${outDir}\n Deploy: Vercel, Netlify, S3, any static host\n`)
|
|
476
476
|
process.exit(0)
|
|
477
477
|
}
|
|
@@ -480,13 +480,13 @@ if (cmd==='build') {
|
|
|
480
480
|
if (cmd==='serve'||cmd==='dev') {
|
|
481
481
|
const root=path.resolve(args[0]||'.')
|
|
482
482
|
const port=parseInt(process.env.PORT||'3000')
|
|
483
|
-
const MIME={'.html':'text/html;charset=utf-8','.js':'application/javascript','.css':'text/css','.
|
|
483
|
+
const MIME={'.html':'text/html;charset=utf-8','.js':'application/javascript','.css':'text/css','.aiplang':'text/plain','.json':'application/json','.wasm':'application/wasm','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.ico':'image/x-icon'}
|
|
484
484
|
let clients=[]
|
|
485
485
|
const mtimes={}
|
|
486
486
|
setInterval(()=>{
|
|
487
487
|
const pd=path.join(root,'pages')
|
|
488
488
|
if(!fs.existsSync(pd))return
|
|
489
|
-
fs.readdirSync(pd).filter(f=>f.endsWith('.
|
|
489
|
+
fs.readdirSync(pd).filter(f=>f.endsWith('.aiplang')).forEach(f=>{
|
|
490
490
|
const fp=path.join(pd,f),mt=fs.statSync(fp).mtimeMs
|
|
491
491
|
if(mtimes[fp]&&mtimes[fp]!==mt)clients.forEach(c=>{try{c.write('data: reload\n\n')}catch{}})
|
|
492
492
|
mtimes[fp]=mt
|
|
@@ -501,7 +501,7 @@ if (cmd==='serve'||cmd==='dev') {
|
|
|
501
501
|
let p=req.url.split('?')[0];if(p==='/') p='/index.html'
|
|
502
502
|
let fp=null
|
|
503
503
|
for(const c of [path.join(root,'public',p),path.join(root,p)]){if(fs.existsSync(c)&&fs.statSync(c).isFile()){fp=c;break}}
|
|
504
|
-
if(!fp&&p.endsWith('.
|
|
504
|
+
if(!fp&&p.endsWith('.aiplang')){const c=path.join(root,'pages',path.basename(p));if(fs.existsSync(c))fp=c}
|
|
505
505
|
if(!fp){res.writeHead(404);res.end('Not found');return}
|
|
506
506
|
let content=fs.readFileSync(fp)
|
|
507
507
|
if(path.extname(fp)==='.html'){
|
|
@@ -510,7 +510,7 @@ if (cmd==='serve'||cmd==='dev') {
|
|
|
510
510
|
}
|
|
511
511
|
res.writeHead(200,{'Content-Type':MIME[path.extname(fp)]||'application/octet-stream','Access-Control-Allow-Origin':'*'})
|
|
512
512
|
res.end(content)
|
|
513
|
-
}).listen(port,()=>console.log(`\n ✓ aiplang dev server\n\n → http://localhost:${port}\n\n Hot reload ON — edit .
|
|
513
|
+
}).listen(port,()=>console.log(`\n ✓ aiplang dev server\n\n → http://localhost:${port}\n\n Hot reload ON — edit .aiplang files and browser refreshes.\n Ctrl+C to stop.\n`))
|
|
514
514
|
return
|
|
515
515
|
}
|
|
516
516
|
|
|
@@ -518,7 +518,7 @@ if (cmd==='serve'||cmd==='dev') {
|
|
|
518
518
|
if (cmd === 'start' || cmd === 'run') {
|
|
519
519
|
const aipFile = args[0]
|
|
520
520
|
if (!aipFile || !fs.existsSync(aipFile)) {
|
|
521
|
-
console.error(`\n ✗ Usage: aiplang start <app.
|
|
521
|
+
console.error(`\n ✗ Usage: aiplang start <app.aiplang>\n`)
|
|
522
522
|
process.exit(1)
|
|
523
523
|
}
|
|
524
524
|
const port = parseInt(process.env.PORT || args[1] || '3000')
|
package/package.json
CHANGED
|
@@ -456,7 +456,7 @@ class Renderer {
|
|
|
456
456
|
|
|
457
457
|
render(page) {
|
|
458
458
|
this.container.innerHTML = ''
|
|
459
|
-
this.container.className = `
|
|
459
|
+
this.container.className = `aiplang-root aiplang-theme-${page.theme}`
|
|
460
460
|
for (const block of page.blocks) {
|
|
461
461
|
const el = this.renderBlock(block)
|
|
462
462
|
if (el) this.container.appendChild(el)
|
|
@@ -957,108 +957,108 @@ html{scroll-behavior:smooth}
|
|
|
957
957
|
body{font-family:-apple-system,'Segoe UI',system-ui,sans-serif;-webkit-font-smoothing:antialiased}
|
|
958
958
|
a{text-decoration:none;color:inherit}
|
|
959
959
|
input,button,select{font-family:inherit}
|
|
960
|
-
.
|
|
961
|
-
.
|
|
962
|
-
.
|
|
963
|
-
.
|
|
960
|
+
.aiplang-root{min-height:100vh}
|
|
961
|
+
.aiplang-theme-dark{background:#030712;color:#f1f5f9}
|
|
962
|
+
.aiplang-theme-light{background:#fff;color:#0f172a}
|
|
963
|
+
.aiplang-theme-acid{background:#000;color:#a3e635}
|
|
964
964
|
.fx-nav{display:flex;align-items:center;justify-content:space-between;padding:1rem 2.5rem;position:sticky;top:0;z-index:50;backdrop-filter:blur(12px)}
|
|
965
|
-
.
|
|
966
|
-
.
|
|
967
|
-
.
|
|
965
|
+
.aiplang-theme-dark .fx-nav{border-bottom:1px solid #1e293b;background:rgba(3,7,18,.85)}
|
|
966
|
+
.aiplang-theme-light .fx-nav{border-bottom:1px solid #e2e8f0;background:rgba(255,255,255,.85)}
|
|
967
|
+
.aiplang-theme-acid .fx-nav{border-bottom:1px solid #1a2e05;background:rgba(0,0,0,.9)}
|
|
968
968
|
.fx-brand{font-size:1.25rem;font-weight:800;letter-spacing:-.03em}
|
|
969
969
|
.fx-nav-links{display:flex;align-items:center;gap:1.75rem}
|
|
970
970
|
.fx-nav-link{font-size:.875rem;font-weight:500;opacity:.65;transition:opacity .15s;cursor:pointer}
|
|
971
971
|
.fx-nav-link:hover{opacity:1}
|
|
972
|
-
.
|
|
973
|
-
.
|
|
974
|
-
.
|
|
972
|
+
.aiplang-theme-dark .fx-nav-link{color:#cbd5e1}
|
|
973
|
+
.aiplang-theme-light .fx-nav-link{color:#475569}
|
|
974
|
+
.aiplang-theme-acid .fx-nav-link{color:#86efac}
|
|
975
975
|
.fx-hero{display:flex;align-items:center;justify-content:center;min-height:92vh;padding:4rem 1.5rem}
|
|
976
976
|
.fx-hero-inner{max-width:56rem;text-align:center;display:flex;flex-direction:column;align-items:center;gap:1.5rem}
|
|
977
977
|
.fx-title{font-size:clamp(2.5rem,8vw,5.5rem);font-weight:900;letter-spacing:-.04em;line-height:1}
|
|
978
978
|
.fx-sub{font-size:clamp(1rem,2vw,1.25rem);line-height:1.75;max-width:40rem}
|
|
979
|
-
.
|
|
980
|
-
.
|
|
979
|
+
.aiplang-theme-dark .fx-sub{color:#94a3b8}
|
|
980
|
+
.aiplang-theme-light .fx-sub{color:#475569}
|
|
981
981
|
.fx-cta{display:inline-flex;align-items:center;padding:.875rem 2.5rem;border-radius:.75rem;font-weight:700;font-size:1rem;letter-spacing:-.01em;transition:transform .15s,box-shadow .15s;margin:.25rem;cursor:pointer}
|
|
982
982
|
.fx-cta:hover{transform:translateY(-1px)}
|
|
983
|
-
.
|
|
984
|
-
.
|
|
985
|
-
.
|
|
983
|
+
.aiplang-theme-dark .fx-cta{background:#2563eb;color:#fff;box-shadow:0 8px 24px rgba(37,99,235,.35)}
|
|
984
|
+
.aiplang-theme-light .fx-cta{background:#2563eb;color:#fff}
|
|
985
|
+
.aiplang-theme-acid .fx-cta{background:#a3e635;color:#000;font-weight:800}
|
|
986
986
|
.fx-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:3rem;padding:5rem 2.5rem;text-align:center}
|
|
987
987
|
.fx-stat-val{font-size:clamp(2.5rem,5vw,4rem);font-weight:900;letter-spacing:-.04em;line-height:1}
|
|
988
988
|
.fx-stat-lbl{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-top:.5rem}
|
|
989
|
-
.
|
|
990
|
-
.
|
|
989
|
+
.aiplang-theme-dark .fx-stat-lbl{color:#64748b}
|
|
990
|
+
.aiplang-theme-light .fx-stat-lbl{color:#94a3b8}
|
|
991
991
|
.fx-grid{display:grid;gap:1.25rem;padding:1rem 2.5rem 5rem}
|
|
992
992
|
.fx-grid-2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
|
|
993
993
|
.fx-grid-3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
|
|
994
994
|
.fx-grid-4{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
|
|
995
995
|
.fx-card{border-radius:1rem;padding:1.75rem;transition:transform .2s,box-shadow .2s}
|
|
996
996
|
.fx-card:hover{transform:translateY(-2px)}
|
|
997
|
-
.
|
|
998
|
-
.
|
|
999
|
-
.
|
|
1000
|
-
.
|
|
1001
|
-
.
|
|
997
|
+
.aiplang-theme-dark .fx-card{background:#0f172a;border:1px solid #1e293b}
|
|
998
|
+
.aiplang-theme-light .fx-card{background:#f8fafc;border:1px solid #e2e8f0}
|
|
999
|
+
.aiplang-theme-acid .fx-card{background:#0a0f00;border:1px solid #1a2e05}
|
|
1000
|
+
.aiplang-theme-dark .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.5)}
|
|
1001
|
+
.aiplang-theme-light .fx-card:hover{box-shadow:0 20px 40px rgba(0,0,0,.08)}
|
|
1002
1002
|
.fx-icon{font-size:2rem;margin-bottom:1rem}
|
|
1003
1003
|
.fx-card-title{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.5rem}
|
|
1004
1004
|
.fx-card-body{font-size:.875rem;line-height:1.65}
|
|
1005
|
-
.
|
|
1006
|
-
.
|
|
1005
|
+
.aiplang-theme-dark .fx-card-body{color:#64748b}
|
|
1006
|
+
.aiplang-theme-light .fx-card-body{color:#475569}
|
|
1007
1007
|
.fx-card-link{font-size:.8125rem;font-weight:600;display:inline-block;margin-top:1rem;opacity:.6;transition:opacity .15s}
|
|
1008
1008
|
.fx-card-link:hover{opacity:1}
|
|
1009
1009
|
.fx-sect{padding:5rem 2.5rem}
|
|
1010
1010
|
.fx-sect-title{font-size:clamp(1.75rem,4vw,3rem);font-weight:800;letter-spacing:-.04em;margin-bottom:1.5rem;text-align:center}
|
|
1011
1011
|
.fx-sect-body{font-size:1rem;line-height:1.75;text-align:center;max-width:48rem;margin:0 auto}
|
|
1012
|
-
.
|
|
1012
|
+
.aiplang-theme-dark .fx-sect-body{color:#64748b}
|
|
1013
1013
|
.fx-form-wrap{padding:3rem 2.5rem;display:flex;justify-content:center}
|
|
1014
1014
|
.fx-form{width:100%;max-width:28rem;border-radius:1.25rem;padding:2.5rem}
|
|
1015
|
-
.
|
|
1016
|
-
.
|
|
1015
|
+
.aiplang-theme-dark .fx-form{background:#0f172a;border:1px solid #1e293b}
|
|
1016
|
+
.aiplang-theme-light .fx-form{background:#f8fafc;border:1px solid #e2e8f0}
|
|
1017
1017
|
.fx-field{margin-bottom:1.25rem}
|
|
1018
1018
|
.fx-label{display:block;font-size:.8125rem;font-weight:600;margin-bottom:.5rem}
|
|
1019
|
-
.
|
|
1020
|
-
.
|
|
1019
|
+
.aiplang-theme-dark .fx-label{color:#94a3b8}
|
|
1020
|
+
.aiplang-theme-light .fx-label{color:#475569}
|
|
1021
1021
|
.fx-input{width:100%;padding:.75rem 1rem;border-radius:.625rem;font-size:.9375rem;outline:none;transition:box-shadow .15s;background:transparent}
|
|
1022
1022
|
.fx-input:focus{box-shadow:0 0 0 3px rgba(37,99,235,.35)}
|
|
1023
|
-
.
|
|
1024
|
-
.
|
|
1025
|
-
.
|
|
1026
|
-
.
|
|
1023
|
+
.aiplang-theme-dark .fx-input{background:#020617;border:1px solid #1e293b;color:#f1f5f9}
|
|
1024
|
+
.aiplang-theme-dark .fx-input::placeholder{color:#334155}
|
|
1025
|
+
.aiplang-theme-light .fx-input{background:#fff;border:1px solid #cbd5e1;color:#0f172a}
|
|
1026
|
+
.aiplang-theme-acid .fx-input{background:#000;border:1px solid #1a2e05;color:#a3e635}
|
|
1027
1027
|
.fx-btn{width:100%;padding:.875rem 1.5rem;border:none;border-radius:.625rem;font-size:.9375rem;font-weight:700;cursor:pointer;margin-top:.5rem;transition:transform .15s,opacity .15s;letter-spacing:-.01em}
|
|
1028
1028
|
.fx-btn:hover{transform:translateY(-1px)}
|
|
1029
1029
|
.fx-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
|
1030
|
-
.
|
|
1031
|
-
.
|
|
1032
|
-
.
|
|
1030
|
+
.aiplang-theme-dark .fx-btn{background:#2563eb;color:#fff;box-shadow:0 4px 14px rgba(37,99,235,.4)}
|
|
1031
|
+
.aiplang-theme-light .fx-btn{background:#2563eb;color:#fff}
|
|
1032
|
+
.aiplang-theme-acid .fx-btn{background:#a3e635;color:#000;font-weight:800}
|
|
1033
1033
|
.fx-form-msg{font-size:.8125rem;padding:.5rem 0;min-height:1.5rem;text-align:center}
|
|
1034
1034
|
.fx-form-err{color:#f87171}
|
|
1035
1035
|
.fx-form-ok{color:#4ade80}
|
|
1036
1036
|
.fx-table-wrap{overflow-x:auto;padding:0 2.5rem 4rem}
|
|
1037
1037
|
.fx-table{width:100%;border-collapse:collapse;font-size:.875rem}
|
|
1038
1038
|
.fx-th{text-align:left;padding:.875rem 1.25rem;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em}
|
|
1039
|
-
.
|
|
1040
|
-
.
|
|
1039
|
+
.aiplang-theme-dark .fx-th{color:#475569;border-bottom:1px solid #1e293b}
|
|
1040
|
+
.aiplang-theme-light .fx-th{color:#94a3b8;border-bottom:1px solid #e2e8f0}
|
|
1041
1041
|
.fx-tr{transition:background .1s}
|
|
1042
1042
|
.fx-td{padding:.875rem 1.25rem}
|
|
1043
|
-
.
|
|
1044
|
-
.
|
|
1045
|
-
.
|
|
1046
|
-
.
|
|
1043
|
+
.aiplang-theme-dark .fx-tr:hover{background:#0f172a}
|
|
1044
|
+
.aiplang-theme-light .fx-tr:hover{background:#f8fafc}
|
|
1045
|
+
.aiplang-theme-dark .fx-td{border-bottom:1px solid #0f172a}
|
|
1046
|
+
.aiplang-theme-light .fx-td{border-bottom:1px solid #f1f5f9}
|
|
1047
1047
|
.fx-td-empty{padding:2rem 1.25rem;text-align:center;opacity:.4}
|
|
1048
1048
|
.fx-list-wrap{padding:1rem 2.5rem 4rem;display:flex;flex-direction:column;gap:.75rem}
|
|
1049
1049
|
.fx-list-item{border-radius:.75rem;padding:1.25rem 1.5rem}
|
|
1050
|
-
.
|
|
1050
|
+
.aiplang-theme-dark .fx-list-item{background:#0f172a;border:1px solid #1e293b}
|
|
1051
1051
|
.fx-list-field{font-size:.9375rem;line-height:1.5}
|
|
1052
1052
|
.fx-list-link{font-size:.8125rem;font-weight:600;opacity:.6;transition:opacity .15s}
|
|
1053
1053
|
.fx-list-link:hover{opacity:1}
|
|
1054
1054
|
.fx-alert{padding:1rem 2.5rem;font-size:.9375rem;font-weight:500;border-radius:.75rem;margin:1rem 2.5rem}
|
|
1055
|
-
.
|
|
1055
|
+
.aiplang-theme-dark .fx-alert{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#fca5a5}
|
|
1056
1056
|
.fx-if-wrap{display:contents}
|
|
1057
1057
|
.fx-footer{padding:3rem 2.5rem;text-align:center}
|
|
1058
|
-
.
|
|
1059
|
-
.
|
|
1058
|
+
.aiplang-theme-dark .fx-footer{border-top:1px solid #1e293b}
|
|
1059
|
+
.aiplang-theme-light .fx-footer{border-top:1px solid #e2e8f0}
|
|
1060
1060
|
.fx-footer-text{font-size:.8125rem}
|
|
1061
|
-
.
|
|
1061
|
+
.aiplang-theme-dark .fx-footer-text{color:#334155}
|
|
1062
1062
|
.fx-footer-link{font-size:.8125rem;margin:0 .75rem;opacity:.5;transition:opacity .15s}
|
|
1063
1063
|
.fx-footer-link:hover{opacity:1}
|
|
1064
1064
|
`
|
|
@@ -1069,9 +1069,9 @@ input,button,select{font-family:inherit}
|
|
|
1069
1069
|
|
|
1070
1070
|
function boot(src, container) {
|
|
1071
1071
|
// Inject CSS once
|
|
1072
|
-
if (!document.getElementById('
|
|
1072
|
+
if (!document.getElementById('aiplang-css')) {
|
|
1073
1073
|
const style = document.createElement('style')
|
|
1074
|
-
style.id = '
|
|
1074
|
+
style.id = 'aiplang-css'
|
|
1075
1075
|
style.textContent = CSS
|
|
1076
1076
|
document.head.appendChild(style)
|
|
1077
1077
|
}
|
|
@@ -1089,9 +1089,9 @@ return { boot, parseFlux, State, Renderer, Router, QueryEngine }
|
|
|
1089
1089
|
|
|
1090
1090
|
})()
|
|
1091
1091
|
|
|
1092
|
-
// Auto-boot from <script type="text/
|
|
1092
|
+
// Auto-boot from <script type="text/aiplang">
|
|
1093
1093
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1094
|
-
const script = document.querySelector('script[type="text/
|
|
1094
|
+
const script = document.querySelector('script[type="text/aiplang"]')
|
|
1095
1095
|
if (script) {
|
|
1096
1096
|
const targetSel = script.getAttribute('target') || '#app'
|
|
1097
1097
|
const container = document.querySelector(targetSel)
|
package/server/server.js
CHANGED
|
@@ -1137,7 +1137,7 @@ function getMime(filename) {
|
|
|
1137
1137
|
// name: 'my-plugin',
|
|
1138
1138
|
// setup(server, app, utils) {
|
|
1139
1139
|
// // server = AiplangServer instance (.addRoute, .models)
|
|
1140
|
-
// // app = parsed .
|
|
1140
|
+
// // app = parsed .aiplang app definition
|
|
1141
1141
|
// // utils = { uuid, now, emit, on, dispatch, resolveEnv, dbRun, dbAll, dbGet }
|
|
1142
1142
|
// }
|
|
1143
1143
|
// }
|
|
@@ -1364,7 +1364,7 @@ async function startServer(aipFile, port = 3000) {
|
|
|
1364
1364
|
module.exports = { startServer, parseApp, Model, getDB, dispatch, on, emit, sendMail, setupStripe, registerStripeRoutes, setupS3, registerS3Routes, s3Upload, s3Delete, s3PresignedUrl, PLUGIN_UTILS }
|
|
1365
1365
|
if (require.main === module) {
|
|
1366
1366
|
const f=process.argv[2], p=parseInt(process.argv[3]||process.env.PORT||'3000')
|
|
1367
|
-
if (!f) { console.error('Usage: node server.js <app.
|
|
1367
|
+
if (!f) { console.error('Usage: node server.js <app.aiplang> [port]'); process.exit(1) }
|
|
1368
1368
|
startServer(f, p).catch(e=>{console.error(e);process.exit(1)})
|
|
1369
1369
|
}
|
|
1370
1370
|
|