naider 1.21.0 → 1.21.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/bin/naide.js CHANGED
@@ -8,7 +8,7 @@ import { spawn } from 'child_process';
8
8
  import { createRequire } from 'module';
9
9
  const _require = createRequire(import.meta.url);
10
10
 
11
- const NAIDE_VERSION = '1.21.0';
11
+ const NAIDE_VERSION = '1.21.1';
12
12
 
13
13
  function crashReport(err, context = {}) {
14
14
  const info = [
@@ -171,7 +171,13 @@ if (files[0] === 'gen' || files[0] === '~~') {
171
171
  Project Mode (--project):
172
172
  naide ~~ "e-commerce app with auth" --project
173
173
  # Creates: app.naide, package.json, .env, .gitignore,
174
- # Dockerfile, .dockerignore, README.md
174
+ # Dockerfile, .dockerignore, README.md, openapi.json,
175
+ # admin.html, client.mjs
176
+
177
+ Update existing project (re-run --project in same dir):
178
+ naide ~~ "add users with auth" --project -o my-app
179
+ naide ~~ "add products" --project -o my-app
180
+ # Merges new entities, regenerates all supporting files
175
181
 
176
182
  File Write Mode:
177
183
  naide ~~ "add auth" app.naide # merge into existing file
@@ -213,7 +219,7 @@ if (files[0] === 'gen' || files[0] === '~~') {
213
219
  process.exit(0);
214
220
  }
215
221
 
216
- const { generate, generateProject, writeToFile } = await import('../src/gen.js');
222
+ const { generate, generateProject, updateProject, writeToFile } = await import('../src/gen.js');
217
223
 
218
224
  // Write mode: inject into existing .naide/.nx file
219
225
  const targetFile = instruction.match(/\S+\.(?:naide|nx)$/)?.[0];
@@ -235,32 +241,63 @@ if (files[0] === 'gen' || files[0] === '~~') {
235
241
  process.exit(0);
236
242
  }
237
243
 
238
- // Project mode: generate full project directory
244
+ // Project mode: generate or update project directory
239
245
  if (flags.project) {
240
- const result = generateProject(instruction);
241
- const primaryName = (result.entities[0] || 'app').toLowerCase().replace(/[^a-z0-9]/g, '');
242
- const dir = flags.output || (primaryName + '-app');
246
+ const isCurrentDir = instruction === '.' || instruction === './';
247
+ let dir, result;
248
+
249
+ if (isCurrentDir) {
250
+ // naide gen . --project → update current directory
251
+ dir = '.';
252
+ const outDir = resolve(dir);
253
+ if (!existsSync(resolve(outDir, 'app.naide'))) {
254
+ process.stderr.write(' Error: no app.naide found in current directory.\n');
255
+ process.stderr.write(' Use naide ~~ "instruction" --project to create a new project.\n');
256
+ process.exit(1);
257
+ }
258
+ process.stderr.write(' Error: use naide ~~ "instruction" --project in an existing project dir to update.\n');
259
+ process.exit(1);
260
+ }
261
+
262
+ const primaryGuess = instruction.split(/\s+/)[0].toLowerCase().replace(/[^a-z0-9]/g, '') || 'app';
263
+ dir = flags.output || (primaryGuess + '-app');
243
264
  const outDir = resolve(dir);
244
- if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
265
+ const isUpdate = existsSync(resolve(outDir, 'app.naide'));
266
+
267
+ if (isUpdate) {
268
+ result = updateProject(outDir, instruction);
269
+ } else {
270
+ result = generateProject(instruction);
271
+ }
272
+
273
+ const primaryName = (result.entities[0] || 'app').toLowerCase().replace(/[^a-z0-9]/g, '');
274
+ if (!flags.output && !isUpdate) dir = primaryName + '-app';
275
+ const finalDir = resolve(dir);
276
+ if (!existsSync(finalDir)) mkdirSync(finalDir, { recursive: true });
245
277
  for (const f of result.files) {
246
- const fp = resolve(outDir, f.path);
278
+ const fp = resolve(finalDir, f.path);
247
279
  writeFileSync(fp, f.content, 'utf-8');
248
280
  }
249
281
  const a = result.analysis;
250
- process.stderr.write('\n ── NAIDE Agent ──────────────────────────────────────────\n');
282
+ const mode = isUpdate ? 'Updated' : 'Created';
283
+ process.stderr.write(`\n ── NAIDE Agent (${mode}) ────────────────────────────────\n`);
251
284
  process.stderr.write(` Input: "${a.instruction}"\n`);
252
285
  if (a.entities.length > 0) {
253
286
  process.stderr.write(` Entities: ${a.entities.map(e => `${e.name} (${e.fieldCount} fields)`).join(', ')}\n`);
254
287
  }
255
- if (a.relationships.length > 0) {
288
+ if (a.relationships && a.relationships.length > 0) {
256
289
  for (const r of a.relationships) process.stderr.write(` Relation: ${r.parent} 1→N ${r.child} (${r.fk})\n`);
257
290
  }
291
+ if (a.added && a.added.length > 0) {
292
+ process.stderr.write(` Added: ${a.added.join(', ')}\n`);
293
+ }
258
294
  process.stderr.write(` Plan:\n`);
259
295
  for (const step of a.plan) process.stderr.write(` + ${step}\n`);
260
296
  process.stderr.write(' ────────────────────────────────────────────────────────\n\n');
261
- console.log(` Project created: ${dir}/`);
297
+ console.log(` Project ${isUpdate ? 'updated' : 'created'}: ${dir}/`);
262
298
  for (const f of result.files) console.log(` ${f.path}`);
263
- console.log(`\n cd ${dir} && npm install && npm run dev\n`);
299
+ if (!isUpdate) console.log(`\n cd ${dir} && npm install && npm run dev\n`);
300
+ else console.log(`\n Files regenerated. Run: npm run dev\n`);
264
301
  process.exit(0);
265
302
  }
266
303
 
package/mcp/server.js CHANGED
@@ -192,7 +192,7 @@ async function handleMessage(msg) {
192
192
  respond(msg.id, {
193
193
  protocolVersion: '2024-11-05',
194
194
  capabilities: { tools: {} },
195
- serverInfo: { name: 'naide-mcp', version: '1.21.0' }
195
+ serverInfo: { name: 'naide-mcp', version: '1.21.1' }
196
196
  });
197
197
  break;
198
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naider",
3
- "version": "1.21.0",
3
+ "version": "1.21.1",
4
4
  "description": "NAIDE - Simpler than Python, compiles to 15 targets. AI-specialized language with 35+ built-in functions, syntax sugar (unless/until/repeat/swap/is/isnt), and 47 features. Targets: Node.js, Python, TypeScript, C, C++, Java, Go, Rust, PHP, Ruby, Kotlin, Swift, Dart, C#, Bun.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/gen.js CHANGED
@@ -1479,6 +1479,200 @@ CMD ["node", "dist/app.mjs"]
1479
1479
  return { ...result, files };
1480
1480
  }
1481
1481
 
1482
+ // ── Project Update Mode ───────────────────────────────────
1483
+
1484
+ export function updateProject(projectDir, instruction, options = {}) {
1485
+ const appPath = projectDir + '/app.naide';
1486
+ const existing = _existsFS(appPath) ? _readFS(appPath, 'utf-8') : '';
1487
+
1488
+ const existingState = existing ? parseExistingProject(existing) : { entities: [], intents: [], port: 3000 };
1489
+ const newResult = generate(instruction, options);
1490
+ const newEntities = (newResult.entities || []).filter(e => !existingState.entities.includes(e));
1491
+
1492
+ const allEntities = [...existingState.entities, ...newEntities];
1493
+ const combinedIntents = [...new Set([...existingState.intents, ...newResult.intents])];
1494
+ const extras = [];
1495
+ if (combinedIntents.includes('auth')) extras.push('auth');
1496
+ if (combinedIntents.includes('websocket')) extras.push('websocket');
1497
+ if (combinedIntents.includes('database')) extras.push('database');
1498
+ const fullInstruction = 'REST API for ' + allEntities.join(' and ') + (extras.length ? ' with ' + extras.join(' and ') : '');
1499
+
1500
+ const combined = generate(fullInstruction, options);
1501
+ const finalCode = combined.code;
1502
+
1503
+ const files = [];
1504
+ files.push({ path: 'app.naide', content: finalCode });
1505
+
1506
+ const intents = new Set(combined.intents);
1507
+ const entities = combined.entities || allEntities;
1508
+ const relationships = combined.relationships || detectRelationships(entities);
1509
+
1510
+ const primaryName = (entities[0] || 'app').toLowerCase();
1511
+ const projectName = safeName(primaryName) + '-app';
1512
+ const port = existingState.port || combined.port || 3000;
1513
+
1514
+ const deps = {};
1515
+ if (intents.has('server')) deps.express = '^4.21.0';
1516
+ if (intents.has('websocket')) deps.ws = '^8.18.0';
1517
+ if (intents.has('auth')) {
1518
+ deps.jsonwebtoken = '^9.0.2';
1519
+ deps.bcryptjs = '^2.4.3';
1520
+ }
1521
+ if (intents.has('mail')) deps.nodemailer = '^6.9.0';
1522
+ if (entities.length > 0) deps.naider = '^1.21.0';
1523
+
1524
+ const existingPkgPath = projectDir + '/package.json';
1525
+ let pkg;
1526
+ if (_existsFS(existingPkgPath)) {
1527
+ pkg = JSON.parse(_readFS(existingPkgPath, 'utf-8'));
1528
+ pkg.dependencies = { ...pkg.dependencies, ...deps };
1529
+ } else {
1530
+ pkg = {
1531
+ name: projectName, version: '1.0.0', type: 'module',
1532
+ scripts: { dev: 'naide app.naide', build: 'naide app.naide --emit -o dist/app.mjs',
1533
+ start: 'node dist/app.mjs', test: 'naide test app.naide', seed: 'naide app.naide --run-seed' },
1534
+ dependencies: deps,
1535
+ };
1536
+ }
1537
+ files.push({ path: 'package.json', content: JSON.stringify(pkg, null, 2) + '\n' });
1538
+
1539
+ const envL = [];
1540
+ envL.push(`PORT=${port}`);
1541
+ if (intents.has('auth')) envL.push('JWT_SECRET=change-me-in-production');
1542
+ if (intents.has('bot')) envL.push(`${(options.platform || 'DISCORD').toUpperCase()}_TOKEN=your-bot-token`);
1543
+ if (intents.has('mail')) { envL.push('SMTP_HOST=smtp.example.com'); envL.push('SMTP_USER=user@example.com'); envL.push('SMTP_PASS=password'); }
1544
+ if (intents.has('ai')) envL.push('OPENAI_API_KEY=your-api-key');
1545
+ const existingEnvPath = projectDir + '/.env';
1546
+ if (_existsFS(existingEnvPath)) {
1547
+ const existingEnv = _readFS(existingEnvPath, 'utf-8');
1548
+ const existingKeys = new Set(existingEnv.split('\n').map(l => l.split('=')[0]).filter(Boolean));
1549
+ const newEnvLines = envL.filter(l => !existingKeys.has(l.split('=')[0]));
1550
+ if (newEnvLines.length > 0) {
1551
+ files.push({ path: '.env', content: existingEnv.trimEnd() + '\n' + newEnvLines.join('\n') + '\n' });
1552
+ }
1553
+ } else {
1554
+ files.push({ path: '.env', content: envL.join('\n') + '\n' });
1555
+ }
1556
+
1557
+ files.push({ path: '.gitignore', content: 'node_modules/\ndist/\ndata/\n.env\n*.mjs\n' });
1558
+
1559
+ if (intents.has('server')) {
1560
+ files.push({ path: 'Dockerfile', content: `FROM node:22-slim\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --omit=dev\nRUN npm install -g naider\nCOPY . .\nRUN naide app.naide --emit -o dist/app.mjs\nEXPOSE ${port}\nCMD ["node", "dist/app.mjs"]\n` });
1561
+ files.push({ path: '.dockerignore', content: 'node_modules\ndist\ndata\n.env\n.git\n' });
1562
+ }
1563
+
1564
+ const readmeLines = [`# ${projectName}\n`];
1565
+ readmeLines.push(`Generated by NAIDE Agent Coder (~~)\n`);
1566
+ readmeLines.push(`## Entities\n`);
1567
+ for (const e of entities) {
1568
+ const preset = SCHEMA_PRESETS[e.toLowerCase()];
1569
+ const fc = preset ? preset.length : 3;
1570
+ readmeLines.push(`- **${e}** (${fc} fields)`);
1571
+ }
1572
+ if (relationships.length > 0) {
1573
+ readmeLines.push(`\n## Relationships\n`);
1574
+ for (const r of relationships) readmeLines.push(`- ${r.parent} → ${r.child} (${r.fk})`);
1575
+ }
1576
+ readmeLines.push(`\n## Quick Start\n`);
1577
+ readmeLines.push('```bash\nnpm install\nnpm run dev\n```\n');
1578
+ if (intents.has('server')) {
1579
+ readmeLines.push(`## API Endpoints\n`);
1580
+ readmeLines.push(`- \`GET /api/health\` — Health check`);
1581
+ for (const e of entities) {
1582
+ const ep = pluralize(e.toLowerCase());
1583
+ readmeLines.push(`- \`GET /api/${ep}?page=1&limit=20&search=\` — List ${ep} (paginated)`);
1584
+ readmeLines.push(`- \`POST /api/${ep}\` — Create ${e.toLowerCase()}`);
1585
+ readmeLines.push(`- \`GET /api/${ep}/:id\` — Get ${e.toLowerCase()}`);
1586
+ readmeLines.push(`- \`PUT /api/${ep}/:id\` — Update ${e.toLowerCase()}`);
1587
+ readmeLines.push(`- \`DELETE /api/${ep}/:id\` — Delete ${e.toLowerCase()}`);
1588
+ readmeLines.push(`- \`DELETE /api/${ep}/:id/soft\` — Soft delete`);
1589
+ readmeLines.push(`- \`PUT /api/${ep}/:id/restore\` — Restore`);
1590
+ readmeLines.push(`- \`POST /api/${ep}/batch\` — Batch create`);
1591
+ readmeLines.push(`- \`DELETE /api/${ep}/batch\` — Batch delete`);
1592
+ }
1593
+ for (const r of relationships) {
1594
+ readmeLines.push(`- \`GET /api/${pluralize(r.parent.toLowerCase())}/:id/${pluralize(r.child.toLowerCase())}\` — ${r.child}s by ${r.parent}`);
1595
+ }
1596
+ readmeLines.push(`- \`POST /api/upload\` — File upload`);
1597
+ readmeLines.push(`- \`POST /api/webhooks\` — Register webhook`);
1598
+ if (intents.has('auth')) {
1599
+ readmeLines.push(`- \`POST /api/auth/register\` — Register`);
1600
+ readmeLines.push(`- \`POST /api/auth/login\` — Login`);
1601
+ readmeLines.push(`- \`GET /api/auth/me\` — Current user`);
1602
+ readmeLines.push(`- \`GET /api/admin/stats\` — Admin stats`);
1603
+ }
1604
+ }
1605
+ if (intents.has('server')) {
1606
+ readmeLines.push(`\n## Docker\n`);
1607
+ readmeLines.push('```bash');
1608
+ readmeLines.push(`docker build -t ${projectName} .`);
1609
+ readmeLines.push(`docker run -p ${port}:${port} --env-file .env ${projectName}`);
1610
+ readmeLines.push('```');
1611
+ }
1612
+ files.push({ path: 'README.md', content: readmeLines.join('\n') + '\n' });
1613
+
1614
+ const entityObjs = entities.map(name => {
1615
+ const preset = SCHEMA_PRESETS[name.toLowerCase()];
1616
+ const fields = preset ? preset.map(f => ({ name: f[0], type: f[1], modifiers: f.slice(2) })) : defaultFields(name);
1617
+ if (!fields.some(f => f.name === 'deleted')) fields.push({ name: 'deleted', type: 'bool', modifiers: [] });
1618
+ return { name, fields };
1619
+ });
1620
+
1621
+ if (intents.has('server') && entityObjs.length > 0) {
1622
+ files.push({ path: 'openapi.json', content: JSON.stringify(openApiSpec(entityObjs, intents, { port }, relationships), null, 2) + '\n' });
1623
+ files.push({ path: 'admin.html', content: adminPage(entityObjs, intents) });
1624
+ }
1625
+ if (intents.has('server')) {
1626
+ files.push({ path: 'client.mjs', content: apiClientCode(entityObjs, intents, port) });
1627
+ }
1628
+
1629
+ const added = existing
1630
+ ? entities.filter(e => !existing.includes(`schema ${e}`))
1631
+ : entities;
1632
+ const analysis = {
1633
+ instruction,
1634
+ entities: entityObjs.map(e => ({ name: e.name, fieldCount: e.fields.length })),
1635
+ relationships,
1636
+ plan: [],
1637
+ added,
1638
+ };
1639
+ if (added.length > 0) analysis.plan.push(`Added: ${added.join(', ')}`);
1640
+ if (existing) analysis.plan.push('Merged with existing app.naide');
1641
+ analysis.plan.push(`Total entities: ${entities.length}`);
1642
+ analysis.plan.push(`Supporting files regenerated`);
1643
+
1644
+ return {
1645
+ code: finalCode, valid: combined.valid, fixed: combined.fixed, repairs: combined.repairs,
1646
+ intents: [...intents], entities, relationships, port,
1647
+ files, analysis, action: existing ? 'updated' : 'created',
1648
+ };
1649
+ }
1650
+
1651
+ function parseExistingProject(code) {
1652
+ const lines = code.split('\n');
1653
+ const entities = [];
1654
+ const intents = new Set();
1655
+ let port = 3000;
1656
+
1657
+ for (const line of lines) {
1658
+ const sm = line.match(/^schema\s+(\w+)\s*:/);
1659
+ if (sm && sm[1] !== 'AuditLog') {
1660
+ entities.push(sm[1]);
1661
+ intents.add('schema');
1662
+ }
1663
+ if (line.match(/^server\s/)) intents.add('server');
1664
+ if (line.match(/\bcrud\b/)) intents.add('crud');
1665
+ if (line.match(/\bauth\b/)) intents.add('auth');
1666
+ if (line.match(/\bdb\b/)) intents.add('database');
1667
+ if (line.match(/\bws\b/)) intents.add('websocket');
1668
+ if (line.match(/\bbot\b/)) intents.add('bot');
1669
+ const pm = line.match(/port\s+(\d+)/);
1670
+ if (pm) port = parseInt(pm[1], 10);
1671
+ }
1672
+
1673
+ return { entities, intents: [...intents], port };
1674
+ }
1675
+
1482
1676
  // ── File Write Mode ────────────────────────────────────────
1483
1677
 
1484
1678
  export function writeToFile(filePath, instruction, options = {}) {
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ import { Parser } from './parser.js';
3
3
  import { Generator } from './generator.js';
4
4
  import { preprocess } from './preprocess.js';
5
5
  import { TypeChecker } from './typechecker.js';
6
- import { generate, expandDirectives } from './gen.js';
6
+ import { generate, generateProject, updateProject, expandDirectives } from './gen.js';
7
7
 
8
8
  async function getGenerator(target, options) {
9
9
  switch (target) {
@@ -162,4 +162,4 @@ export function transpile(source, opts) {
162
162
  return compile(source, opts).js;
163
163
  }
164
164
 
165
- export { preprocess, generate };
165
+ export { preprocess, generate, generateProject, updateProject };