naider 1.11.0 → 1.12.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/README.md +102 -15
- package/SPEC.naide +74 -0
- package/lsp/server.js +15 -1
- package/package.json +16 -3
- package/src/generator-bun.js +89 -0
- package/src/generator-python.js +441 -2
- package/src/generator.js +425 -4
- package/src/parser.js +200 -2
- package/src/tokens.js +12 -0
- package/vscode-naide/syntaxes/naide.tmLanguage.json +1 -1
package/src/generator.js
CHANGED
|
@@ -143,6 +143,11 @@ export class Generator {
|
|
|
143
143
|
case 'ReturnRedirect': return this.visitReturnRedirect(node);
|
|
144
144
|
case 'ReturnDownload': return this.visitReturnDownload(node);
|
|
145
145
|
case 'PromptDecl': return this.visitPrompt(node);
|
|
146
|
+
case 'Page': return this.visitPage(node);
|
|
147
|
+
case 'CliApp': return this.visitCli(node);
|
|
148
|
+
case 'MailConfig': return this.visitMail(node);
|
|
149
|
+
case 'DesktopApp': return this.visitDesktop(node);
|
|
150
|
+
case 'Screen': return this.visitScreen(node);
|
|
146
151
|
default:
|
|
147
152
|
this.emit(`/* unknown: ${node.type} */`);
|
|
148
153
|
}
|
|
@@ -400,6 +405,8 @@ export class Generator {
|
|
|
400
405
|
this.visitOpenapi(node.name, child);
|
|
401
406
|
} else if (child.type === 'QueueDecl') {
|
|
402
407
|
this.visitQueue(child);
|
|
408
|
+
} else if (child.type === 'GraphqlDecl') {
|
|
409
|
+
this.visitGraphql(node.name, child);
|
|
403
410
|
} else {
|
|
404
411
|
this.visitStatement(child);
|
|
405
412
|
}
|
|
@@ -428,8 +435,21 @@ export class Generator {
|
|
|
428
435
|
}
|
|
429
436
|
|
|
430
437
|
visitBot(node) {
|
|
431
|
-
const tokenExpr = node.token ? this.expr(node.token) : 'process.env.
|
|
438
|
+
const tokenExpr = node.token ? this.expr(node.token) : 'process.env.BOT_TOKEN';
|
|
439
|
+
const botType = this.rawBotType(node.botType);
|
|
432
440
|
|
|
441
|
+
if (botType === 'slack') return this.visitSlackBot(node, tokenExpr);
|
|
442
|
+
if (botType === 'telegram') return this.visitTelegramBot(node, tokenExpr);
|
|
443
|
+
if (botType === 'line') return this.visitLineBot(node, tokenExpr);
|
|
444
|
+
return this.visitDiscordBot(node, tokenExpr);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
rawBotType(bt) {
|
|
448
|
+
if (!bt) return 'discord';
|
|
449
|
+
return bt.raw || bt.parts?.map(p => p.value).join('') || 'discord';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
visitDiscordBot(node, tokenExpr) {
|
|
433
453
|
this.emit(`import { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder } from 'discord.js';`);
|
|
434
454
|
this.emitRaw('');
|
|
435
455
|
this.emit(`const ${node.name} = new Client({`);
|
|
@@ -503,6 +523,120 @@ export class Generator {
|
|
|
503
523
|
this.emitRaw('');
|
|
504
524
|
}
|
|
505
525
|
|
|
526
|
+
visitSlackBot(node, tokenExpr) {
|
|
527
|
+
this.emit(`import pkg from '@slack/bolt';`);
|
|
528
|
+
this.emit(`const { App: SlackApp } = pkg;`);
|
|
529
|
+
this.emitRaw('');
|
|
530
|
+
this.emit(`const ${node.name} = new SlackApp({`);
|
|
531
|
+
this.indent++;
|
|
532
|
+
this.emit(`token: ${tokenExpr},`);
|
|
533
|
+
this.emit(`signingSecret: process.env.SLACK_SIGNING_SECRET,`);
|
|
534
|
+
this.indent--;
|
|
535
|
+
this.emit(`});`);
|
|
536
|
+
this.emitRaw('');
|
|
537
|
+
|
|
538
|
+
for (const handler of node.handlers) {
|
|
539
|
+
if (handler.type === 'BotEvent') {
|
|
540
|
+
const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
|
|
541
|
+
const params = handler.params.length > 0 ? handler.params.join(', ') : '';
|
|
542
|
+
const needsAsync = this.bodyUsesAwait(handler.body);
|
|
543
|
+
const asyncPrefix = needsAsync ? 'async ' : '';
|
|
544
|
+
if (evtName === 'message') {
|
|
545
|
+
this.emit(`${node.name}.message(${asyncPrefix}({ message, say${params ? ', ' + params : ''} }) => {`);
|
|
546
|
+
} else {
|
|
547
|
+
this.emit(`${node.name}.event('${evtName}', ${asyncPrefix}({ event${params ? ', ' + params : ''} }) => {`);
|
|
548
|
+
}
|
|
549
|
+
this.indent++;
|
|
550
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
551
|
+
this.indent--;
|
|
552
|
+
this.emit(`});`);
|
|
553
|
+
this.emitRaw('');
|
|
554
|
+
} else if (handler.type === 'BotSlashCmd') {
|
|
555
|
+
const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
|
|
556
|
+
this.emit(`${node.name}.command('/${cmdName}', async ({ command, ack, respond }) => {`);
|
|
557
|
+
this.indent++;
|
|
558
|
+
this.emit(`await ack();`);
|
|
559
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
560
|
+
this.indent--;
|
|
561
|
+
this.emit(`});`);
|
|
562
|
+
this.emitRaw('');
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
this.emit(`(async () => { await ${node.name}.start(3000); console.log('Slack bot running'); })();`);
|
|
567
|
+
this.emitRaw('');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
visitTelegramBot(node, tokenExpr) {
|
|
571
|
+
this.emit(`import TelegramBot from 'node-telegram-bot-api';`);
|
|
572
|
+
this.emitRaw('');
|
|
573
|
+
this.emit(`const ${node.name} = new TelegramBot(${tokenExpr}, { polling: true });`);
|
|
574
|
+
this.emitRaw('');
|
|
575
|
+
|
|
576
|
+
for (const handler of node.handlers) {
|
|
577
|
+
if (handler.type === 'BotEvent') {
|
|
578
|
+
const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
|
|
579
|
+
const params = handler.params.length > 0 ? handler.params.join(', ') : 'msg';
|
|
580
|
+
const needsAsync = this.bodyUsesAwait(handler.body);
|
|
581
|
+
const asyncPrefix = needsAsync ? 'async ' : '';
|
|
582
|
+
this.emit(`${node.name}.on('${evtName}', ${asyncPrefix}(${params}) => {`);
|
|
583
|
+
this.indent++;
|
|
584
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
585
|
+
this.indent--;
|
|
586
|
+
this.emit(`});`);
|
|
587
|
+
this.emitRaw('');
|
|
588
|
+
} else if (handler.type === 'BotSlashCmd') {
|
|
589
|
+
const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
|
|
590
|
+
const param = handler.params.length > 0 ? handler.params[0] : 'msg';
|
|
591
|
+
this.emit(`${node.name}.onText(/\\/${cmdName}/, async (${param}) => {`);
|
|
592
|
+
this.indent++;
|
|
593
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
594
|
+
this.indent--;
|
|
595
|
+
this.emit(`});`);
|
|
596
|
+
this.emitRaw('');
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
visitLineBot(node, tokenExpr) {
|
|
602
|
+
this.emit(`import line from '@line/bot-sdk';`);
|
|
603
|
+
this.emit(`import express from 'express';`);
|
|
604
|
+
this.emitRaw('');
|
|
605
|
+
this.emit(`const __lineConfig = { channelAccessToken: ${tokenExpr}, channelSecret: process.env.LINE_CHANNEL_SECRET };`);
|
|
606
|
+
this.emit(`const ${node.name} = new line.messagingApi.MessagingApiClient({ channelAccessToken: ${tokenExpr} });`);
|
|
607
|
+
this.emit(`const __lineApp = express();`);
|
|
608
|
+
this.emitRaw('');
|
|
609
|
+
|
|
610
|
+
this.emit(`__lineApp.post('/webhook', line.middleware(__lineConfig), (req, res) => {`);
|
|
611
|
+
this.indent++;
|
|
612
|
+
this.emit(`Promise.all(req.body.events.map(__handleLineEvent)).then(r => res.json(r));`);
|
|
613
|
+
this.indent--;
|
|
614
|
+
this.emit(`});`);
|
|
615
|
+
this.emitRaw('');
|
|
616
|
+
|
|
617
|
+
this.emit(`async function __handleLineEvent(event) {`);
|
|
618
|
+
this.indent++;
|
|
619
|
+
for (const handler of node.handlers) {
|
|
620
|
+
if (handler.type === 'BotEvent') {
|
|
621
|
+
const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
|
|
622
|
+
if (evtName === 'message') {
|
|
623
|
+
this.emit(`if (event.type === 'message') {`);
|
|
624
|
+
} else {
|
|
625
|
+
this.emit(`if (event.type === '${evtName}') {`);
|
|
626
|
+
}
|
|
627
|
+
this.indent++;
|
|
628
|
+
for (const stmt of handler.body) this.visitStatement(stmt);
|
|
629
|
+
this.indent--;
|
|
630
|
+
this.emit(`}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
this.indent--;
|
|
634
|
+
this.emit(`}`);
|
|
635
|
+
this.emitRaw('');
|
|
636
|
+
this.emit(`__lineApp.listen(3000, () => console.log('LINE bot running on port 3000'));`);
|
|
637
|
+
this.emitRaw('');
|
|
638
|
+
}
|
|
639
|
+
|
|
506
640
|
visitRoute(appName, route) {
|
|
507
641
|
const method = route.method === 'del' ? 'delete' : route.method;
|
|
508
642
|
const path = this.stringValue(route.path);
|
|
@@ -1161,13 +1295,24 @@ export class Generator {
|
|
|
1161
1295
|
}
|
|
1162
1296
|
|
|
1163
1297
|
visitEvery(node) {
|
|
1164
|
-
this.runtimeImports.add('scheduleEvery');
|
|
1165
|
-
|
|
1166
1298
|
const interval = this.expr(node.interval);
|
|
1167
1299
|
const needsAsync = this.bodyUsesAwait(node.body);
|
|
1168
1300
|
const asyncPrefix = needsAsync ? 'async ' : '';
|
|
1169
1301
|
|
|
1170
|
-
|
|
1302
|
+
let raw = '';
|
|
1303
|
+
if (node.interval.type === 'String' && node.interval.value) {
|
|
1304
|
+
raw = node.interval.value.raw || node.interval.value.parts?.map(p => p.value).join('') || '';
|
|
1305
|
+
}
|
|
1306
|
+
const isCron = /^[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+/.test(raw.trim());
|
|
1307
|
+
|
|
1308
|
+
if (isCron) {
|
|
1309
|
+
this.emit(`import cron from 'node-cron';`);
|
|
1310
|
+
this.emitRaw('');
|
|
1311
|
+
this.emit(`cron.schedule(${interval}, ${asyncPrefix}() => {`);
|
|
1312
|
+
} else {
|
|
1313
|
+
this.runtimeImports.add('scheduleEvery');
|
|
1314
|
+
this.emit(`scheduleEvery(${interval}, ${asyncPrefix}() => {`);
|
|
1315
|
+
}
|
|
1171
1316
|
this.indent++;
|
|
1172
1317
|
for (const stmt of node.body) this.visitStatement(stmt);
|
|
1173
1318
|
this.indent--;
|
|
@@ -1190,6 +1335,282 @@ export class Generator {
|
|
|
1190
1335
|
this.emitRaw('');
|
|
1191
1336
|
}
|
|
1192
1337
|
|
|
1338
|
+
// ===== Page (Frontend HTML) =====
|
|
1339
|
+
|
|
1340
|
+
visitPage(node) {
|
|
1341
|
+
const filename = this.rawPageString(node.filename);
|
|
1342
|
+
this.emit(`import { writeFileSync } from 'fs';`);
|
|
1343
|
+
this.emitRaw('');
|
|
1344
|
+
|
|
1345
|
+
const headParts = [];
|
|
1346
|
+
const bodyParts = [];
|
|
1347
|
+
let pageTitle = 'NAIDE Page';
|
|
1348
|
+
|
|
1349
|
+
for (const el of node.elements) {
|
|
1350
|
+
this.classifyPageElement(el, headParts, bodyParts, (t) => { pageTitle = t; });
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
this.emit(`writeFileSync(${JSON.stringify(filename)}, \`<!DOCTYPE html>`);
|
|
1354
|
+
this.emit(`<html lang="en">`);
|
|
1355
|
+
this.emit(`<head>`);
|
|
1356
|
+
this.emit(`<meta charset="UTF-8">`);
|
|
1357
|
+
this.emit(`<meta name="viewport" content="width=device-width, initial-scale=1.0">`);
|
|
1358
|
+
this.emit(`<title>\${${JSON.stringify(pageTitle)}}</title>`);
|
|
1359
|
+
for (const h of headParts) this.emit(h);
|
|
1360
|
+
this.emit(`</head>`);
|
|
1361
|
+
this.emit(`<body>`);
|
|
1362
|
+
for (const b of bodyParts) this.emit(b);
|
|
1363
|
+
this.emit(`</body>`);
|
|
1364
|
+
this.emit(`</html>\`);`);
|
|
1365
|
+
this.emitRaw('');
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
classifyPageElement(el, head, body, setTitle) {
|
|
1369
|
+
const tag = el.tag;
|
|
1370
|
+
const arg0 = el.args[0] ? this.rawPageString(el.args[0]) : '';
|
|
1371
|
+
const arg1 = el.args[1] ? this.rawPageString(el.args[1]) : '';
|
|
1372
|
+
|
|
1373
|
+
if (tag === 'title') { setTitle(arg0); return; }
|
|
1374
|
+
if (tag === 'style') { head.push(`<link rel="stylesheet" href="${arg0}">`); return; }
|
|
1375
|
+
if (tag === 'meta') { head.push(`<meta name="${arg0}" content="${arg1}">`); return; }
|
|
1376
|
+
if (tag === 'script') { body.push(`<script src="${arg0}"></script>`); return; }
|
|
1377
|
+
if (tag === 'link') { head.push(`<link rel="stylesheet" href="${arg0}">`); return; }
|
|
1378
|
+
if (tag === 'img') { body.push(`<img src="${arg0}" alt="${arg1}">`); return; }
|
|
1379
|
+
if (tag === 'a') { body.push(`<a href="${arg0}">${arg1}</a>`); return; }
|
|
1380
|
+
if (tag === 'input') { body.push(`<input type="${arg0}" name="${arg1}" placeholder="${arg1}">`); return; }
|
|
1381
|
+
|
|
1382
|
+
if (el.children && el.children.length > 0) {
|
|
1383
|
+
const cls = arg0 ? ` class="${arg0}"` : '';
|
|
1384
|
+
body.push(`<${tag}${cls}>`);
|
|
1385
|
+
for (const child of el.children) this.classifyPageElement(child, head, body, setTitle);
|
|
1386
|
+
body.push(`</${tag}>`);
|
|
1387
|
+
} else {
|
|
1388
|
+
body.push(`<${tag}>${arg0}</${tag}>`);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
rawPageString(strData) {
|
|
1393
|
+
if (!strData) return '';
|
|
1394
|
+
if (strData.raw !== null && strData.raw !== undefined) return strData.raw;
|
|
1395
|
+
if (strData.parts) return strData.parts.map(p => p.value).join('');
|
|
1396
|
+
return '';
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// ===== CLI App =====
|
|
1400
|
+
|
|
1401
|
+
visitCli(node) {
|
|
1402
|
+
const name = node.name;
|
|
1403
|
+
const desc = node.description ? this.rawPageString(node.description) : name;
|
|
1404
|
+
|
|
1405
|
+
this.emit(`const __argv = process.argv.slice(2);`);
|
|
1406
|
+
this.emit(`const args = {};`);
|
|
1407
|
+
this.emitRaw('');
|
|
1408
|
+
|
|
1409
|
+
this.emit(`if (__argv.includes('--help') || __argv.includes('-h')) {`);
|
|
1410
|
+
this.indent++;
|
|
1411
|
+
this.emit(`console.log(${JSON.stringify(desc)});`);
|
|
1412
|
+
this.emit(`console.log('Options:');`);
|
|
1413
|
+
for (const arg of node.args) {
|
|
1414
|
+
const argName = this.rawPageString(arg.name);
|
|
1415
|
+
const argDesc = arg.description ? this.rawPageString(arg.description) : '';
|
|
1416
|
+
this.emit(`console.log(' --${argName} <${arg.type}> ${argDesc}');`);
|
|
1417
|
+
}
|
|
1418
|
+
for (const flag of node.flags) {
|
|
1419
|
+
const s = this.rawPageString(flag.short);
|
|
1420
|
+
const l = this.rawPageString(flag.long);
|
|
1421
|
+
const d = flag.description ? this.rawPageString(flag.description) : '';
|
|
1422
|
+
this.emit(`console.log(' -${s}, --${l} ${d}');`);
|
|
1423
|
+
}
|
|
1424
|
+
this.emit(`process.exit(0);`);
|
|
1425
|
+
this.indent--;
|
|
1426
|
+
this.emit(`}`);
|
|
1427
|
+
this.emitRaw('');
|
|
1428
|
+
|
|
1429
|
+
this.emit(`for (let __i = 0; __i < __argv.length; __i++) {`);
|
|
1430
|
+
this.indent++;
|
|
1431
|
+
for (const arg of node.args) {
|
|
1432
|
+
const argName = this.rawPageString(arg.name);
|
|
1433
|
+
const coerce = arg.type === 'int' ? 'parseInt(__argv[++__i])' :
|
|
1434
|
+
arg.type === 'num' ? 'parseFloat(__argv[++__i])' :
|
|
1435
|
+
arg.type === 'bool' ? 'true' : '__argv[++__i]';
|
|
1436
|
+
this.emit(`if (__argv[__i] === '--${argName}') { args.${argName} = ${coerce}; continue; }`);
|
|
1437
|
+
}
|
|
1438
|
+
for (const flag of node.flags) {
|
|
1439
|
+
const s = this.rawPageString(flag.short);
|
|
1440
|
+
const l = this.rawPageString(flag.long);
|
|
1441
|
+
this.emit(`if (__argv[__i] === '-${s}' || __argv[__i] === '--${l}') { args.${l} = true; continue; }`);
|
|
1442
|
+
}
|
|
1443
|
+
this.indent--;
|
|
1444
|
+
this.emit(`}`);
|
|
1445
|
+
this.emitRaw('');
|
|
1446
|
+
|
|
1447
|
+
if (node.run) {
|
|
1448
|
+
const runParam = node.run.params.length > 0 ? node.run.params[0] : 'args';
|
|
1449
|
+
if (runParam !== 'args') {
|
|
1450
|
+
this.emit(`const ${runParam} = args;`);
|
|
1451
|
+
}
|
|
1452
|
+
for (const stmt of node.run.body) this.visitStatement(stmt);
|
|
1453
|
+
}
|
|
1454
|
+
this.emitRaw('');
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// ===== Mail =====
|
|
1458
|
+
|
|
1459
|
+
visitMail(node) {
|
|
1460
|
+
const host = this.generateString(node.host);
|
|
1461
|
+
const port = this.expr(node.port);
|
|
1462
|
+
const user = node.user ? this.expr(node.user) : 'process.env.MAIL_USER';
|
|
1463
|
+
const pass = node.pass ? this.expr(node.pass) : 'process.env.MAIL_PASS';
|
|
1464
|
+
|
|
1465
|
+
this.emit(`import nodemailer from 'nodemailer';`);
|
|
1466
|
+
this.emitRaw('');
|
|
1467
|
+
this.emit(`const mail = nodemailer.createTransport({`);
|
|
1468
|
+
this.indent++;
|
|
1469
|
+
this.emit(`host: ${host},`);
|
|
1470
|
+
this.emit(`port: ${port},`);
|
|
1471
|
+
this.emit(`secure: ${port} === 465,`);
|
|
1472
|
+
this.emit(`auth: { user: ${user}, pass: ${pass} },`);
|
|
1473
|
+
this.indent--;
|
|
1474
|
+
this.emit(`});`);
|
|
1475
|
+
this.emitRaw('');
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// ===== GraphQL =====
|
|
1479
|
+
|
|
1480
|
+
visitGraphql(appName, node) {
|
|
1481
|
+
const path = this.stringValue(node.path);
|
|
1482
|
+
|
|
1483
|
+
this.emit(`import { buildSchema } from 'graphql';`);
|
|
1484
|
+
this.emit(`import { createHandler } from 'graphql-http/lib/use/express';`);
|
|
1485
|
+
this.emitRaw('');
|
|
1486
|
+
|
|
1487
|
+
const schemaNames = [...this.schemas.keys()];
|
|
1488
|
+
if (schemaNames.length > 0) {
|
|
1489
|
+
let schemaDef = '';
|
|
1490
|
+
for (const name of schemaNames) {
|
|
1491
|
+
const schema = this.schemas.get(name);
|
|
1492
|
+
const fields = schema.fields.map(f => {
|
|
1493
|
+
const gqlType = this.toGraphqlType(f.type);
|
|
1494
|
+
return ` ${f.name}: ${gqlType}`;
|
|
1495
|
+
}).join('\\n');
|
|
1496
|
+
schemaDef += `type ${name} {\\n${fields}\\n}\\n`;
|
|
1497
|
+
}
|
|
1498
|
+
schemaDef += `type Query {\\n`;
|
|
1499
|
+
for (const name of schemaNames) {
|
|
1500
|
+
schemaDef += ` ${name.toLowerCase()}s: [${name}]\\n`;
|
|
1501
|
+
schemaDef += ` ${name.toLowerCase()}(id: ID): ${name}\\n`;
|
|
1502
|
+
}
|
|
1503
|
+
schemaDef += `}`;
|
|
1504
|
+
|
|
1505
|
+
this.emit(`const __graphqlSchema = buildSchema(\`${schemaDef}\`);`);
|
|
1506
|
+
this.emit(`const __graphqlRoot = {`);
|
|
1507
|
+
this.indent++;
|
|
1508
|
+
for (const name of schemaNames) {
|
|
1509
|
+
this.emit(`${name.toLowerCase()}s: () => ${name}Store.getAll(),`);
|
|
1510
|
+
this.emit(`${name.toLowerCase()}: ({ id }) => ${name}Store.getById(id),`);
|
|
1511
|
+
}
|
|
1512
|
+
this.indent--;
|
|
1513
|
+
this.emit(`};`);
|
|
1514
|
+
} else {
|
|
1515
|
+
this.emit(`const __graphqlSchema = buildSchema(\`type Query { hello: String }\`);`);
|
|
1516
|
+
this.emit(`const __graphqlRoot = { hello: () => 'Hello from NAIDE GraphQL' };`);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
this.emit(`${appName}.all(${path}, createHandler({ schema: __graphqlSchema, rootValue: __graphqlRoot }));`);
|
|
1520
|
+
this.emitRaw('');
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
toGraphqlType(naideType) {
|
|
1524
|
+
const map = { 'str': 'String', 'int': 'Int', 'num': 'Float', 'bool': 'Boolean', 'auto': 'ID', 'timestamp': 'String', 'enum': 'String' };
|
|
1525
|
+
return map[naideType] || 'String';
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// ===== Desktop (Electron) =====
|
|
1529
|
+
|
|
1530
|
+
visitDesktop(node) {
|
|
1531
|
+
const title = node.title ? this.rawPageString(node.title) : node.name;
|
|
1532
|
+
const width = node.width ? this.expr(node.width) : '800';
|
|
1533
|
+
const height = node.height ? this.expr(node.height) : '600';
|
|
1534
|
+
const load = node.load ? this.expr(node.load) : '"index.html"';
|
|
1535
|
+
|
|
1536
|
+
this.emit(`import { app, BrowserWindow } from 'electron';`);
|
|
1537
|
+
this.emitRaw('');
|
|
1538
|
+
this.emit(`function createWindow() {`);
|
|
1539
|
+
this.indent++;
|
|
1540
|
+
this.emit(`const ${node.name} = new BrowserWindow({`);
|
|
1541
|
+
this.indent++;
|
|
1542
|
+
this.emit(`width: ${width},`);
|
|
1543
|
+
this.emit(`height: ${height},`);
|
|
1544
|
+
this.emit(`title: ${JSON.stringify(title)},`);
|
|
1545
|
+
this.emit(`webPreferences: { nodeIntegration: true, contextIsolation: false },`);
|
|
1546
|
+
this.indent--;
|
|
1547
|
+
this.emit(`});`);
|
|
1548
|
+
this.emit(`${node.name}.loadFile(${load});`);
|
|
1549
|
+
this.indent--;
|
|
1550
|
+
this.emit(`}`);
|
|
1551
|
+
this.emitRaw('');
|
|
1552
|
+
this.emit(`app.whenReady().then(createWindow);`);
|
|
1553
|
+
this.emit(`app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });`);
|
|
1554
|
+
this.emit(`app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });`);
|
|
1555
|
+
this.emitRaw('');
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// ===== Screen (React Native) =====
|
|
1559
|
+
|
|
1560
|
+
visitScreen(node) {
|
|
1561
|
+
const imports = new Set(['View', 'StyleSheet']);
|
|
1562
|
+
const elements = [];
|
|
1563
|
+
|
|
1564
|
+
for (const el of node.elements) {
|
|
1565
|
+
const tag = el.tag;
|
|
1566
|
+
const arg0 = el.args[0] ? this.rawPageString(el.args[0]) : '';
|
|
1567
|
+
if (tag === 'text') { imports.add('Text'); elements.push({ jsx: `<Text>${arg0}</Text>` }); }
|
|
1568
|
+
else if (tag === 'button') { imports.add('Button'); elements.push({ jsx: `<Button title="${arg0}" onPress={() => { ${this.screenBodyToJs(el.body)} }} />` }); }
|
|
1569
|
+
else if (tag === 'input') { imports.add('TextInput'); elements.push({ jsx: `<TextInput placeholder="${arg0}" style={styles.input} />` }); }
|
|
1570
|
+
else if (tag === 'image') { imports.add('Image'); elements.push({ jsx: `<Image source={require('${arg0}')} style={styles.image} />` }); }
|
|
1571
|
+
else { imports.add('Text'); elements.push({ jsx: `<Text>${arg0}</Text>` }); }
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
this.emit(`import React from 'react';`);
|
|
1575
|
+
this.emit(`import { ${[...imports].join(', ')} } from 'react-native';`);
|
|
1576
|
+
this.emitRaw('');
|
|
1577
|
+
this.emit(`export function ${node.name}() {`);
|
|
1578
|
+
this.indent++;
|
|
1579
|
+
this.emit(`return (`);
|
|
1580
|
+
this.indent++;
|
|
1581
|
+
this.emit(`<View style={styles.container}>`);
|
|
1582
|
+
this.indent++;
|
|
1583
|
+
for (const el of elements) this.emit(el.jsx);
|
|
1584
|
+
this.indent--;
|
|
1585
|
+
this.emit(`</View>`);
|
|
1586
|
+
this.indent--;
|
|
1587
|
+
this.emit(`);`);
|
|
1588
|
+
this.indent--;
|
|
1589
|
+
this.emit(`}`);
|
|
1590
|
+
this.emitRaw('');
|
|
1591
|
+
this.emit(`const styles = StyleSheet.create({`);
|
|
1592
|
+
this.indent++;
|
|
1593
|
+
this.emit(`container: { flex: 1, padding: 16, justifyContent: 'center' },`);
|
|
1594
|
+
this.emit(`input: { borderWidth: 1, borderColor: '#ccc', padding: 8, marginVertical: 4, borderRadius: 4 },`);
|
|
1595
|
+
this.emit(`image: { width: 200, height: 200, alignSelf: 'center' },`);
|
|
1596
|
+
this.indent--;
|
|
1597
|
+
this.emit(`});`);
|
|
1598
|
+
this.emitRaw('');
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
screenBodyToJs(body) {
|
|
1602
|
+
if (!body || body.length === 0) return '';
|
|
1603
|
+
const saved = this.output;
|
|
1604
|
+
const savedIndent = this.indent;
|
|
1605
|
+
this.output = [];
|
|
1606
|
+
this.indent = 0;
|
|
1607
|
+
for (const stmt of body) this.visitStatement(stmt);
|
|
1608
|
+
const result = this.output.join(' ');
|
|
1609
|
+
this.output = saved;
|
|
1610
|
+
this.indent = savedIndent;
|
|
1611
|
+
return result;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1193
1614
|
// ===== Expression generation =====
|
|
1194
1615
|
|
|
1195
1616
|
expr(node) {
|