naider 1.24.5 → 2.0.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 +133 -112
- package/SPEC.naide +8 -6
- package/bin/naide.js +509 -124
- package/bin/postinstall.js +5 -15
- package/examples/model.naide +1 -1
- package/lsp/server.js +210 -101
- package/mcp/server.js +247 -22
- package/package.json +8 -5
- package/src/gen.js +853 -324
- package/src/generator-c.js +34 -14
- package/src/generator-go.js +26 -0
- package/src/generator.js +5 -1
- package/src/index.js +34 -14
- package/src/parser.js +15 -14
- package/src/project-tools.js +785 -0
- package/src/typechecker.js +147 -18
- package/vscode-naide/README.md +4 -6
- package/vscode-naide/extension.js +1 -2
- package/vscode-naide/package-lock.json +2 -2
- package/vscode-naide/package.json +3 -15
- package/SPEC-X.nx +0 -276
- package/assets/nx.ico +0 -0
- package/assets/nx_check.png +0 -0
- package/examples/ai-app.nx +0 -21
- package/examples/async.nx +0 -40
- package/examples/crud.nx +0 -64
- package/examples/fullapp.nx +0 -74
- package/examples/hello.nx +0 -29
- package/examples/server.nx +0 -27
- package/examples/sql-app.nx +0 -23
- package/examples/todo-api.nx +0 -31
- package/src/preprocess.js +0 -308
- package/vscode-naide/language-configuration-nx.json +0 -32
- package/vscode-naide/naide-lang-1.1.0.vsix +0 -0
- package/vscode-naide/syntaxes/naidex.tmLanguage.json +0 -287
package/bin/naide.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { readFileSync, writeFileSync, unlinkSync, watch as fsWatch, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
4
|
-
import { resolve, basename, extname, join, relative } from 'path';
|
|
4
|
+
import { resolve, basename, dirname, extname, join, relative } from 'path';
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
5
6
|
import { compile, compileAsync } from '../src/index.js';
|
|
6
7
|
import { spawn } from 'child_process';
|
|
8
|
+
import { createPackageInstallPlan, getDoctorReport, inspectProject, listPackageAdapters, recordPackageDependency, runPackageInstall, runProjectTests, spawnNativeTool } from '../src/project-tools.js';
|
|
7
9
|
|
|
8
10
|
import { createRequire } from 'module';
|
|
9
11
|
const _require = createRequire(import.meta.url);
|
|
10
12
|
|
|
11
|
-
const NAIDE_VERSION =
|
|
13
|
+
const NAIDE_VERSION = JSON.parse(
|
|
14
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
15
|
+
).version;
|
|
12
16
|
|
|
13
17
|
function crashReport(err, context = {}) {
|
|
14
18
|
const info = [
|
|
@@ -34,6 +38,28 @@ function crashReport(err, context = {}) {
|
|
|
34
38
|
console.error(` └──────────────────────────────────────────────────\n`);
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
function reportRetiredNx(file) {
|
|
42
|
+
console.error(` Error: ${file} uses retired NAIDE-X syntax. Use standard NAIDE syntax in a .naide file.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseNpmPackageSpec(spec) {
|
|
46
|
+
const match = spec.match(/^(?<name>@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._~-]*|[a-z0-9][a-z0-9._~-]*)(?:@(?<version>latest|[A-Za-z][A-Za-z0-9._-]*|[~^]?v?\d+(?:\.\d+){0,2}(?:-[A-Za-z0-9.-]+)?(?:\+[A-Za-z0-9.-]+)?))?$/);
|
|
47
|
+
return match ? { name: match.groups.name, version: match.groups.version } : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function spawnNpm(args, options = {}) {
|
|
51
|
+
const npmCliCandidates = [
|
|
52
|
+
process.env.npm_execpath,
|
|
53
|
+
join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
54
|
+
join(dirname(dirname(process.execPath)), 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
55
|
+
].filter(Boolean).map(path => resolve(path));
|
|
56
|
+
const npmCli = npmCliCandidates.find(path => existsSync(path));
|
|
57
|
+
|
|
58
|
+
if (npmCli) return spawn(process.execPath, [npmCli, ...args], options);
|
|
59
|
+
if (process.platform !== 'win32') return spawn('npm', args, options);
|
|
60
|
+
throw new Error('Could not locate npm-cli.js. Run this command from an npm installation or set npm_execpath.');
|
|
61
|
+
}
|
|
62
|
+
|
|
37
63
|
process.on('uncaughtException', (err) => {
|
|
38
64
|
crashReport(err);
|
|
39
65
|
process.exit(99);
|
|
@@ -107,8 +133,6 @@ const flags = {
|
|
|
107
133
|
tokens: false,
|
|
108
134
|
output: null,
|
|
109
135
|
help: false,
|
|
110
|
-
mode: null,
|
|
111
|
-
mid: false,
|
|
112
136
|
watch: false,
|
|
113
137
|
debug: false,
|
|
114
138
|
check: false,
|
|
@@ -129,8 +153,6 @@ for (let i = 0; i < args.length; i++) {
|
|
|
129
153
|
case '--tokens': flags.tokens = true; flags.run = false; break;
|
|
130
154
|
case '--output': case '-o': flags.output = args[++i]; break;
|
|
131
155
|
case '--help': case '-h': flags.help = true; break;
|
|
132
|
-
case '--x': case '-x': flags.mode = 'x'; break;
|
|
133
|
-
case '--mid': flags.mid = true; flags.run = false; break;
|
|
134
156
|
case '--watch': case '-w': flags.watch = true; break;
|
|
135
157
|
case '--debug': case '-d': flags.debug = true; break;
|
|
136
158
|
case '--check': flags.check = true; flags.run = false; break;
|
|
@@ -143,6 +165,65 @@ for (let i = 0; i < args.length; i++) {
|
|
|
143
165
|
}
|
|
144
166
|
}
|
|
145
167
|
|
|
168
|
+
if (files[0] === 'doctor') {
|
|
169
|
+
const directory = files[1] && !files[1].startsWith('--') ? files[1] : '.';
|
|
170
|
+
const report = await getDoctorReport(resolve(directory));
|
|
171
|
+
if (files.includes('--json')) {
|
|
172
|
+
console.log(JSON.stringify(report, null, 2));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(`\n NAIDE doctor — ${report.project.directory}`);
|
|
175
|
+
console.log(` Node.js ${report.node.version}${report.node.supported ? '' : ' (unsupported; NAIDE requires >=18)'}`);
|
|
176
|
+
console.log(` Platform: ${report.platform}`);
|
|
177
|
+
console.log(` Project manifests: ${report.project.manifests.join(', ') || 'none found'}`);
|
|
178
|
+
for (const tool of report.tools) {
|
|
179
|
+
console.log(` ${tool.available ? 'OK' : '--'} ${tool.executable}${tool.version ? ` (${tool.version})` : ''}`);
|
|
180
|
+
}
|
|
181
|
+
for (const check of report.project.checks) console.log(` OK ${check}`);
|
|
182
|
+
for (const warning of report.project.warnings) console.log(` WARN ${warning}`);
|
|
183
|
+
for (const error of report.project.errors) console.error(` ERROR ${error}`);
|
|
184
|
+
console.log(`\n ${report.project.ok ? 'Project checks passed.' : 'Project checks found errors.'}`);
|
|
185
|
+
}
|
|
186
|
+
process.exit(report.node.supported && report.project.ok ? 0 : 1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (files[0] === 'verify') {
|
|
190
|
+
const directory = resolve(files[1] && !files[1].startsWith('--') ? files[1] : '.');
|
|
191
|
+
const report = inspectProject(directory);
|
|
192
|
+
if (files.includes('--json')) {
|
|
193
|
+
console.log(JSON.stringify(report, null, 2));
|
|
194
|
+
} else {
|
|
195
|
+
console.log(`\n NAIDE verify — ${report.directory}`);
|
|
196
|
+
for (const check of report.checks) console.log(` OK ${check}`);
|
|
197
|
+
for (const warning of report.warnings) console.log(` WARN ${warning}`);
|
|
198
|
+
for (const error of report.errors) console.error(` ERROR ${error}`);
|
|
199
|
+
console.log(`\n ${report.ok ? 'Project verification passed.' : 'Project verification failed.'}`);
|
|
200
|
+
}
|
|
201
|
+
process.exit(report.ok ? 0 : 1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (files[0] === 'test') {
|
|
205
|
+
const directory = files[1] && !files[1].startsWith('--') ? files[1] : '.';
|
|
206
|
+
let report;
|
|
207
|
+
try {
|
|
208
|
+
report = await runProjectTests(resolve(directory));
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error(` Could not inspect test project: ${error.message}`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
if (report.plans.length === 0) {
|
|
214
|
+
console.error(' No configured native test suite found. Add a test script or a supported test project.');
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
for (const outcome of report.outcomes) {
|
|
218
|
+
console.log(`\n > ${outcome.label}`);
|
|
219
|
+
if (outcome.stdout) process.stdout.write(outcome.stdout);
|
|
220
|
+
if (outcome.stderr) process.stderr.write(outcome.stderr);
|
|
221
|
+
if (outcome.error) console.error(` ${outcome.error}`);
|
|
222
|
+
console.log(` ${outcome.code === 0 ? 'PASS' : 'FAIL'} ${outcome.label}`);
|
|
223
|
+
}
|
|
224
|
+
process.exit(report.ok ? 0 : 1);
|
|
225
|
+
}
|
|
226
|
+
|
|
146
227
|
// ── Code Generator (~~) ──
|
|
147
228
|
if (files[0] === 'gen' || files[0] === '~~') {
|
|
148
229
|
const instruction = files.slice(1).join(' ');
|
|
@@ -221,11 +302,11 @@ if (files[0] === 'gen' || files[0] === '~~') {
|
|
|
221
302
|
|
|
222
303
|
const { generate, generateProject, updateProject, writeToFile } = await import('../src/gen.js');
|
|
223
304
|
|
|
224
|
-
// Write mode: inject into existing .naide
|
|
225
|
-
const targetFile = instruction.match(/\S+\.
|
|
305
|
+
// Write mode: inject into existing .naide file
|
|
306
|
+
const targetFile = instruction.match(/\S+\.naide$/)?.[0];
|
|
226
307
|
if (targetFile || flags.write) {
|
|
227
308
|
const file = flags.write || targetFile;
|
|
228
|
-
const cleanInstruction = targetFile ? instruction.replace(/\s+\S+\.
|
|
309
|
+
const cleanInstruction = targetFile ? instruction.replace(/\s+\S+\.naide$/, '') : instruction;
|
|
229
310
|
const mode = flags.replace ? 'replace' : 'append';
|
|
230
311
|
const result = writeToFile(resolve(file), cleanInstruction, { mode });
|
|
231
312
|
const a = result.analysis;
|
|
@@ -271,8 +352,11 @@ if (files[0] === 'gen' || files[0] === '~~') {
|
|
|
271
352
|
}
|
|
272
353
|
|
|
273
354
|
const isGame = result.analysis && result.analysis.gameType;
|
|
355
|
+
const isApp = result.analysis && result.analysis.appType;
|
|
274
356
|
if (isGame && !flags.output && !isUpdate) {
|
|
275
357
|
dir = result.analysis.gameType + '-game';
|
|
358
|
+
} else if (isApp && !flags.output && !isUpdate) {
|
|
359
|
+
dir = result.analysis.appType + '-app';
|
|
276
360
|
} else {
|
|
277
361
|
const primaryName = (result.entities[0] || 'app').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
278
362
|
if (!flags.output && !isUpdate) dir = primaryName + '-app';
|
|
@@ -424,7 +508,7 @@ if (flags.expand && files.length > 0) {
|
|
|
424
508
|
if (files[0] === 'vscode') {
|
|
425
509
|
const os = await import('os');
|
|
426
510
|
const extSrc = new URL('../vscode-naide', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1');
|
|
427
|
-
const extName = 'irxk.naide-lang-
|
|
511
|
+
const extName = 'irxk.naide-lang-2.0.1';
|
|
428
512
|
const extDir = join(os.homedir(), '.vscode', 'extensions', extName);
|
|
429
513
|
|
|
430
514
|
if (existsSync(extDir)) {
|
|
@@ -457,7 +541,7 @@ if (files[0] === 'vscode') {
|
|
|
457
541
|
`);
|
|
458
542
|
} catch (e) {
|
|
459
543
|
console.error(` Error: ${e.message}`);
|
|
460
|
-
console.log('\n Manual install: copy vscode-naide/ to ~/.vscode/extensions/irxk.naide-lang-
|
|
544
|
+
console.log('\n Manual install: copy vscode-naide/ to ~/.vscode/extensions/irxk.naide-lang-2.0.1/');
|
|
461
545
|
process.exit(1);
|
|
462
546
|
}
|
|
463
547
|
process.exit(0);
|
|
@@ -474,7 +558,7 @@ if (files[0] === 'repl' || (files.length === 0 && !flags.help)) {
|
|
|
474
558
|
const { createInterface } = await import('readline');
|
|
475
559
|
const { transpile } = await import('../src/index.js');
|
|
476
560
|
|
|
477
|
-
console.log(`\n NAIDE REPL
|
|
561
|
+
console.log(`\n NAIDE REPL v${NAIDE_VERSION} — type NAIDE code, see JavaScript output`);
|
|
478
562
|
console.log(` Type .exit to quit, .eval to toggle eval mode\n`);
|
|
479
563
|
|
|
480
564
|
const rl = createInterface({
|
|
@@ -609,12 +693,38 @@ if (files[0] === 'init') {
|
|
|
609
693
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
610
694
|
const name = basename(dir === process.cwd() ? dir : dir);
|
|
611
695
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
696
|
+
const packagePath = resolve(dir, 'package.json');
|
|
697
|
+
const defaultScripts = {
|
|
698
|
+
start: 'node -r dotenv/config ./node_modules/naider/bin/naide.js app.naide',
|
|
699
|
+
dev: 'node -r dotenv/config ./node_modules/naider/bin/naide.js -w app.naide',
|
|
700
|
+
check: 'naide check app.naide',
|
|
701
|
+
test: 'node --test',
|
|
702
|
+
build: 'naide --emit app.naide -o dist/app.mjs',
|
|
703
|
+
};
|
|
704
|
+
const projectPackage = existsSync(packagePath)
|
|
705
|
+
? JSON.parse(readFileSync(packagePath, 'utf8'))
|
|
706
|
+
: { name, version: '1.0.0', type: 'module' };
|
|
707
|
+
let packageChanged = false;
|
|
708
|
+
projectPackage.scripts ||= {};
|
|
709
|
+
for (const [script, command] of Object.entries(defaultScripts)) {
|
|
710
|
+
if (!projectPackage.scripts[script]) {
|
|
711
|
+
projectPackage.scripts[script] = command;
|
|
712
|
+
packageChanged = true;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
projectPackage.dependencies ||= {};
|
|
716
|
+
for (const [dependency, version] of Object.entries({
|
|
717
|
+
naider: `^${NAIDE_VERSION}`,
|
|
718
|
+
express: '^4.21.2',
|
|
719
|
+
dotenv: '^16.4.7',
|
|
720
|
+
})) {
|
|
721
|
+
if (!projectPackage.dependencies[dependency] && !projectPackage.devDependencies?.[dependency]) {
|
|
722
|
+
projectPackage.dependencies[dependency] = version;
|
|
723
|
+
packageChanged = true;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (packageChanged || !existsSync(packagePath)) {
|
|
727
|
+
writeFileSync(packagePath, JSON.stringify(projectPackage, null, 2) + '\n');
|
|
618
728
|
}
|
|
619
729
|
|
|
620
730
|
if (!existsSync(resolve(dir, 'app.naide'))) {
|
|
@@ -622,32 +732,279 @@ if (files[0] === 'init') {
|
|
|
622
732
|
|
|
623
733
|
env:
|
|
624
734
|
PORT int default(3000)
|
|
735
|
+
JWT_SECRET str required
|
|
736
|
+
|
|
737
|
+
schema User:
|
|
738
|
+
id auto
|
|
739
|
+
name str required min(1) max(80)
|
|
740
|
+
email str required email max(254)
|
|
741
|
+
password str required min(12) max(128)
|
|
625
742
|
|
|
626
743
|
schema Item:
|
|
627
744
|
id auto
|
|
745
|
+
ownerId str required
|
|
628
746
|
name str required min(1) max(100)
|
|
629
747
|
done bool default(false)
|
|
630
748
|
|
|
631
749
|
server app port PORT:
|
|
632
|
-
cors "
|
|
633
|
-
|
|
750
|
+
cors ["http://localhost:3000", "http://localhost:5173"]
|
|
751
|
+
limit "/api/*" 60 "1m"
|
|
752
|
+
limit "/api/auth/*" 10 "1m"
|
|
753
|
+
auth JWT_SECRET:
|
|
754
|
+
protect "/api/*"
|
|
755
|
+
public "/api/auth/register"
|
|
756
|
+
public "/api/auth/login"
|
|
757
|
+
public "/api/health"
|
|
634
758
|
static "/public"
|
|
635
|
-
crud "/api/items" Item
|
|
636
759
|
|
|
637
760
|
get "/":
|
|
638
|
-
ret
|
|
761
|
+
ret {name: "NAIDE API", health: "/api/health"}
|
|
639
762
|
|
|
640
763
|
get "/api/health":
|
|
641
|
-
ret {status: "ok"
|
|
764
|
+
ret {status: "ok"}
|
|
765
|
+
|
|
766
|
+
post "/api/auth/register" (req, res):
|
|
767
|
+
if typeof req.body.name != "string" or typeof req.body.email != "string" or typeof req.body.password != "string":
|
|
768
|
+
ret.status 400 {error: "name, email, and password are required"}
|
|
769
|
+
if req.body.name.length < 1 or req.body.name.length > 80 or req.body.email.length > 254:
|
|
770
|
+
ret.status 400 {error: "name or email is too long"}
|
|
771
|
+
if req.body.password.length < 12 or req.body.password.length > 128:
|
|
772
|
+
ret.status 400 {error: "password must be between 12 and 128 characters"}
|
|
773
|
+
str email = req.body.email.toLowerCase().trim()
|
|
774
|
+
if UserStore.where({email})[0]:
|
|
775
|
+
ret.status 409 {error: "An account with that email already exists"}
|
|
776
|
+
str hashed = hash(req.body.password)
|
|
777
|
+
user = UserStore.create({name: req.body.name.trim(), email, password: hashed})
|
|
778
|
+
if user.error:
|
|
779
|
+
ret.status 400 {errors: user.error}
|
|
780
|
+
token = auth.sign({id: user.id})
|
|
781
|
+
ret.status 201 {token, user: {id: user.id, name: user.name, email: user.email}}
|
|
782
|
+
|
|
783
|
+
post "/api/auth/login" (req, res):
|
|
784
|
+
if typeof req.body.email != "string" or typeof req.body.password != "string":
|
|
785
|
+
ret.status 401 {error: "Invalid credentials"}
|
|
786
|
+
if req.body.password.length > 128:
|
|
787
|
+
ret.status 401 {error: "Invalid credentials"}
|
|
788
|
+
str email = req.body.email.toLowerCase().trim()
|
|
789
|
+
user = UserStore.where({email})[0]
|
|
790
|
+
if not user:
|
|
791
|
+
ret.status 401 {error: "Invalid credentials"}
|
|
792
|
+
if not verify(req.body.password, user.password):
|
|
793
|
+
ret.status 401 {error: "Invalid credentials"}
|
|
794
|
+
token = auth.sign({id: user.id})
|
|
795
|
+
ret {token}
|
|
796
|
+
|
|
797
|
+
get "/api/items" (req, res):
|
|
798
|
+
items = ItemStore.where({ownerId: req.user.id})
|
|
799
|
+
ret {data: items, total: items.length}
|
|
800
|
+
|
|
801
|
+
post "/api/items" (req, res):
|
|
802
|
+
if typeof req.body.name != "string" or req.body.name.length < 1 or req.body.name.length > 100:
|
|
803
|
+
ret.status 400 {error: "name must be between 1 and 100 characters"}
|
|
804
|
+
item = ItemStore.create({ownerId: req.user.id, name: req.body.name.trim(), done: false})
|
|
805
|
+
if item.error:
|
|
806
|
+
ret.status 400 {errors: item.error}
|
|
807
|
+
ret.status 201 item
|
|
808
|
+
|
|
809
|
+
get "/api/items/:id" (req, res):
|
|
810
|
+
item = ItemStore.where({id: req.params.id, ownerId: req.user.id})[0]
|
|
811
|
+
if not item:
|
|
812
|
+
ret.status 404 {error: "Item not found"}
|
|
813
|
+
ret item
|
|
814
|
+
|
|
815
|
+
put "/api/items/:id" (req, res):
|
|
816
|
+
item = ItemStore.where({id: req.params.id, ownerId: req.user.id})[0]
|
|
817
|
+
if not item:
|
|
818
|
+
ret.status 404 {error: "Item not found"}
|
|
819
|
+
str name = item.name
|
|
820
|
+
bool done = item.done
|
|
821
|
+
if req.body.name != undefined:
|
|
822
|
+
if typeof req.body.name != "string" or req.body.name.length < 1 or req.body.name.length > 100:
|
|
823
|
+
ret.status 400 {error: "name must be between 1 and 100 characters"}
|
|
824
|
+
name = req.body.name.trim()
|
|
825
|
+
if req.body.done != undefined:
|
|
826
|
+
if typeof req.body.done != "boolean":
|
|
827
|
+
ret.status 400 {error: "done must be a boolean"}
|
|
828
|
+
done = req.body.done
|
|
829
|
+
updated = ItemStore.update(item.id, {name, done})
|
|
830
|
+
ret updated
|
|
831
|
+
|
|
832
|
+
del "/api/items/:id" (req, res):
|
|
833
|
+
item = ItemStore.where({id: req.params.id, ownerId: req.user.id})[0]
|
|
834
|
+
if not item:
|
|
835
|
+
ret.status 404 {error: "Item not found"}
|
|
836
|
+
bool deleted = ItemStore.delete(item.id)
|
|
837
|
+
ret {deleted}
|
|
838
|
+
`);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (!existsSync(resolve(dir, '.env'))) {
|
|
842
|
+
writeFileSync(resolve(dir, '.env'), `JWT_SECRET=${randomBytes(32).toString('hex')}\n`, { mode: 0o600 });
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const gitignorePath = resolve(dir, '.gitignore');
|
|
846
|
+
const ignoredPaths = ['.env', 'data/', 'dist/', 'node_modules/'];
|
|
847
|
+
const existingGitignore = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';
|
|
848
|
+
const missingIgnoredPaths = ignoredPaths.filter(path => !existingGitignore.split(/\r?\n/).includes(path));
|
|
849
|
+
if (missingIgnoredPaths.length > 0) {
|
|
850
|
+
const separator = existingGitignore && !existingGitignore.endsWith('\n') ? '\n' : '';
|
|
851
|
+
writeFileSync(gitignorePath, `${existingGitignore}${separator}${missingIgnoredPaths.join('\n')}\n`);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (!existsSync(resolve(dir, 'README.md'))) {
|
|
855
|
+
writeFileSync(resolve(dir, 'README.md'), `# ${name}
|
|
856
|
+
|
|
857
|
+
A small NAIDE API starter with registration, login, and an items API scoped to the authenticated user.
|
|
858
|
+
|
|
859
|
+
## Start
|
|
860
|
+
|
|
861
|
+
\`\`\`sh
|
|
862
|
+
npm install
|
|
863
|
+
npm run dev
|
|
864
|
+
\`\`\`
|
|
865
|
+
|
|
866
|
+
The initializer creates a random \`JWT_SECRET\` in \`.env\`. Keep that file private; it is excluded by \`.gitignore\`. The development server loads it automatically.
|
|
867
|
+
|
|
868
|
+
## API
|
|
869
|
+
|
|
870
|
+
- \`GET /api/health\` — public health check
|
|
871
|
+
- \`POST /api/auth/register\` — create an account (password: 12–128 characters)
|
|
872
|
+
- \`POST /api/auth/login\` — receive a bearer token
|
|
873
|
+
- \`/api/items\` — protected CRUD API; send \`Authorization: Bearer <token>\`
|
|
874
|
+
|
|
875
|
+
The default CORS allowlist is limited to local development origins. Change it deliberately when connecting a deployed frontend. The generated in-memory/file-backed starter and single-process rate limiter are for local development, not production deployment; use a production database, shared rate limiting, HTTPS, and an operational secret store before exposing it publicly.
|
|
876
|
+
|
|
877
|
+
## Checks
|
|
878
|
+
|
|
879
|
+
\`\`\`sh
|
|
880
|
+
npm run check
|
|
881
|
+
npm test
|
|
882
|
+
\`\`\`
|
|
883
|
+
`);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const testDir = resolve(dir, 'test');
|
|
887
|
+
if (!existsSync(testDir)) mkdirSync(testDir, { recursive: true });
|
|
888
|
+
const starterTestPath = resolve(testDir, 'api.test.js');
|
|
889
|
+
if (!existsSync(starterTestPath)) {
|
|
890
|
+
writeFileSync(starterTestPath, `import assert from 'node:assert/strict';
|
|
891
|
+
import { spawn } from 'node:child_process';
|
|
892
|
+
import { createServer } from 'node:net';
|
|
893
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
894
|
+
import { test } from 'node:test';
|
|
895
|
+
|
|
896
|
+
test('starter API separates each authenticated user’s items', async () => {
|
|
897
|
+
const portProbe = createServer();
|
|
898
|
+
await new Promise((resolve, reject) => {
|
|
899
|
+
portProbe.once('error', reject);
|
|
900
|
+
portProbe.listen(0, '127.0.0.1', resolve);
|
|
901
|
+
});
|
|
902
|
+
const { port } = portProbe.address();
|
|
903
|
+
await new Promise((resolve, reject) => portProbe.close(error => error ? reject(error) : resolve()));
|
|
904
|
+
|
|
905
|
+
const child = spawn(process.execPath, [
|
|
906
|
+
'-r', 'dotenv/config',
|
|
907
|
+
'./node_modules/naider/bin/naide.js',
|
|
908
|
+
'app.naide',
|
|
909
|
+
], {
|
|
910
|
+
cwd: process.cwd(),
|
|
911
|
+
env: { ...process.env, PORT: String(port) },
|
|
912
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
913
|
+
});
|
|
914
|
+
let output = '';
|
|
915
|
+
child.stdout.setEncoding('utf8').on('data', chunk => { output += chunk; });
|
|
916
|
+
child.stderr.setEncoding('utf8').on('data', chunk => { output += chunk; });
|
|
917
|
+
|
|
918
|
+
try {
|
|
919
|
+
const baseUrl = \`http://127.0.0.1:\${port}\`;
|
|
920
|
+
let health;
|
|
921
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
922
|
+
if (child.exitCode !== null) throw new Error(\`Starter server exited early:\\n\${output}\`);
|
|
923
|
+
try {
|
|
924
|
+
health = await fetch(\`\${baseUrl}/api/health\`);
|
|
925
|
+
break;
|
|
926
|
+
} catch {
|
|
927
|
+
await delay(100);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
assert.ok(health, \`Starter server did not become ready:\\n\${output}\`);
|
|
931
|
+
assert.equal(health.status, 200);
|
|
932
|
+
assert.deepEqual(await health.json(), { status: 'ok' });
|
|
933
|
+
|
|
934
|
+
const unauthenticated = await fetch(\`\${baseUrl}/api/items\`);
|
|
935
|
+
assert.equal(unauthenticated.status, 401);
|
|
936
|
+
|
|
937
|
+
const email = \`test-\${Date.now()}@example.com\`;
|
|
938
|
+
const registration = await fetch(\`\${baseUrl}/api/auth/register\`, {
|
|
939
|
+
method: 'POST',
|
|
940
|
+
headers: { 'Content-Type': 'application/json' },
|
|
941
|
+
body: JSON.stringify({
|
|
942
|
+
name: 'Test User',
|
|
943
|
+
email,
|
|
944
|
+
password: 'correct-horse-battery-staple',
|
|
945
|
+
}),
|
|
946
|
+
});
|
|
947
|
+
assert.equal(registration.status, 201, await registration.text());
|
|
948
|
+
const account = await registration.json();
|
|
949
|
+
assert.ok(account.token);
|
|
950
|
+
assert.equal(account.user.password, undefined);
|
|
951
|
+
|
|
952
|
+
const created = await fetch(\`\${baseUrl}/api/items\`, {
|
|
953
|
+
method: 'POST',
|
|
954
|
+
headers: {
|
|
955
|
+
Authorization: \`Bearer \${account.token}\`,
|
|
956
|
+
'Content-Type': 'application/json',
|
|
957
|
+
},
|
|
958
|
+
body: JSON.stringify({ name: 'Private item' }),
|
|
959
|
+
});
|
|
960
|
+
assert.equal(created.status, 201);
|
|
961
|
+
const privateItem = await created.json();
|
|
962
|
+
|
|
963
|
+
const items = await fetch(\`\${baseUrl}/api/items\`, {
|
|
964
|
+
headers: { Authorization: \`Bearer \${account.token}\` },
|
|
965
|
+
});
|
|
966
|
+
assert.equal(items.status, 200);
|
|
967
|
+
assert.equal((await items.json()).data[0].id, privateItem.id);
|
|
968
|
+
|
|
969
|
+
const otherRegistration = await fetch(\`\${baseUrl}/api/auth/register\`, {
|
|
970
|
+
method: 'POST',
|
|
971
|
+
headers: { 'Content-Type': 'application/json' },
|
|
972
|
+
body: JSON.stringify({
|
|
973
|
+
name: 'Other User',
|
|
974
|
+
email: \`other-\${Date.now()}@example.com\`,
|
|
975
|
+
password: 'another-correct-horse-battery',
|
|
976
|
+
}),
|
|
977
|
+
});
|
|
978
|
+
assert.equal(otherRegistration.status, 201);
|
|
979
|
+
const otherAccount = await otherRegistration.json();
|
|
980
|
+
|
|
981
|
+
const otherItems = await fetch(\`\${baseUrl}/api/items\`, {
|
|
982
|
+
headers: { Authorization: \`Bearer \${otherAccount.token}\` },
|
|
983
|
+
});
|
|
984
|
+
assert.deepEqual((await otherItems.json()).data, []);
|
|
985
|
+
|
|
986
|
+
const hiddenItem = await fetch(\`\${baseUrl}/api/items/\${privateItem.id}\`, {
|
|
987
|
+
headers: { Authorization: \`Bearer \${otherAccount.token}\` },
|
|
988
|
+
});
|
|
989
|
+
assert.equal(hiddenItem.status, 404);
|
|
990
|
+
} finally {
|
|
991
|
+
child.kill();
|
|
992
|
+
if (child.exitCode === null) await new Promise(resolve => child.once('exit', resolve));
|
|
993
|
+
}
|
|
994
|
+
});
|
|
642
995
|
`);
|
|
643
996
|
}
|
|
644
997
|
|
|
645
998
|
if (!existsSync(resolve(dir, 'public'))) mkdirSync(resolve(dir, 'public'), { recursive: true });
|
|
999
|
+
if (!existsSync(resolve(dir, 'dist'))) mkdirSync(resolve(dir, 'dist'), { recursive: true });
|
|
646
1000
|
|
|
647
1001
|
console.log(`\n NAIDE project initialized!
|
|
648
1002
|
|
|
649
1003
|
${dir === process.cwd() ? '' : ` cd ${basename(dir)}\n`} npm install
|
|
1004
|
+
npm run check
|
|
650
1005
|
npm run dev
|
|
1006
|
+
|
|
1007
|
+
A random JWT secret and a local-development API test were created.
|
|
651
1008
|
`);
|
|
652
1009
|
process.exit(0);
|
|
653
1010
|
}
|
|
@@ -663,31 +1020,34 @@ if (files[0] === 'build') {
|
|
|
663
1020
|
if (statSync(full).isDirectory()) {
|
|
664
1021
|
if (entry === 'node_modules' || entry === '.git' || entry === 'dist') continue;
|
|
665
1022
|
found.push(...walkDir(full));
|
|
666
|
-
} else if (entry.endsWith('.naide')
|
|
1023
|
+
} else if (entry.endsWith('.naide')) {
|
|
667
1024
|
found.push(full);
|
|
1025
|
+
} else if (entry.endsWith('.nx')) {
|
|
1026
|
+
retiredFiles.push(full);
|
|
668
1027
|
}
|
|
669
1028
|
}
|
|
670
1029
|
return found;
|
|
671
1030
|
}
|
|
672
1031
|
|
|
1032
|
+
const retiredFiles = [];
|
|
673
1033
|
const sourceFiles = walkDir(dir);
|
|
1034
|
+
for (const file of retiredFiles) reportRetiredNx(relative(dir, file));
|
|
674
1035
|
if (sourceFiles.length === 0) {
|
|
675
|
-
console.log(' No .naide
|
|
676
|
-
process.exit(0);
|
|
1036
|
+
console.log(' No .naide files found.');
|
|
1037
|
+
process.exit(retiredFiles.length > 0 ? 1 : 0);
|
|
677
1038
|
}
|
|
678
1039
|
|
|
679
1040
|
console.log(`\n NAIDE build — ${sourceFiles.length} file(s)\n`);
|
|
680
|
-
let errors =
|
|
1041
|
+
let errors = retiredFiles.length;
|
|
681
1042
|
const schemaMap = {};
|
|
682
1043
|
const routeMap = {};
|
|
683
1044
|
|
|
684
1045
|
for (const srcFile of sourceFiles) {
|
|
685
1046
|
const rel = relative(dir, srcFile);
|
|
686
|
-
const mode = srcFile.endsWith('.nx') ? 'x' : 'naide';
|
|
687
1047
|
try {
|
|
688
1048
|
const source = readFileSync(srcFile, 'utf-8');
|
|
689
|
-
const { js, ast } = compile(source
|
|
690
|
-
const outName = rel.replace(/\.
|
|
1049
|
+
const { js, ast } = compile(source);
|
|
1050
|
+
const outName = rel.replace(/\.naide$/, '.mjs');
|
|
691
1051
|
const outPath = outDir ? join(outDir, outName) : join(dir, outName);
|
|
692
1052
|
const outDirPath = resolve(outPath, '..');
|
|
693
1053
|
if (!existsSync(outDirPath)) mkdirSync(outDirPath, { recursive: true });
|
|
@@ -742,81 +1102,26 @@ if (files[0] === 'build') {
|
|
|
742
1102
|
process.exit(errors > 0 ? 1 : 0);
|
|
743
1103
|
}
|
|
744
1104
|
|
|
745
|
-
// ── Convert (NX ↔ NAIDE) ──
|
|
746
|
-
if (files[0] === 'convert') {
|
|
747
|
-
const targets = files.slice(1);
|
|
748
|
-
if (targets.length === 0) {
|
|
749
|
-
console.log(' Usage: naide convert <file.nx|file.naide>');
|
|
750
|
-
console.log(' .nx → .naide Expand NX shorthand to readable NAIDE');
|
|
751
|
-
console.log(' .naide → .nx Compress NAIDE to AI-optimized NX');
|
|
752
|
-
process.exit(0);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
for (const file of targets) {
|
|
756
|
-
const filePath = resolve(file);
|
|
757
|
-
const ext = extname(file);
|
|
758
|
-
try {
|
|
759
|
-
const source = readFileSync(filePath, 'utf-8');
|
|
760
|
-
|
|
761
|
-
if (ext === '.nx') {
|
|
762
|
-
const { preprocess } = await import('../src/preprocess.js');
|
|
763
|
-
const naide = preprocess(source);
|
|
764
|
-
const outPath = filePath.replace(/\.nx$/, '.naide');
|
|
765
|
-
writeFileSync(outPath, naide);
|
|
766
|
-
console.log(` ${file} → ${basename(outPath)}`);
|
|
767
|
-
} else if (ext === '.naide') {
|
|
768
|
-
const lines = source.split('\n');
|
|
769
|
-
const nxLines = [];
|
|
770
|
-
for (const line of lines) {
|
|
771
|
-
let nx = line;
|
|
772
|
-
nx = nx.replace(/^(\s*)#\s?(.*)/, '$1-- $2');
|
|
773
|
-
nx = nx.replace(/^(\s*)fn\.async\s+/, '$1~f ');
|
|
774
|
-
nx = nx.replace(/^(\s*)fn\s+/, '$1f ');
|
|
775
|
-
nx = nx.replace(/^(\s*)use\s+\{(.+?)\}\s+from\s+/, '$1<{$2}');
|
|
776
|
-
nx = nx.replace(/^(\s*)use\s+/, '$1<');
|
|
777
|
-
nx = nx.replace(/\bret\b/, '>');
|
|
778
|
-
nx = nx.replace(/\beach\s+(\w+)\s+in\s+/, '@$1<');
|
|
779
|
-
nx = nx.replace(/\bawait\s+/, '~');
|
|
780
|
-
nx = nx.replace(/\bstr\b/g, 's');
|
|
781
|
-
nx = nx.replace(/\bint\b/g, 'i');
|
|
782
|
-
nx = nx.replace(/\bnum\b/g, 'n');
|
|
783
|
-
nx = nx.replace(/\bbool\b/g, 'b');
|
|
784
|
-
nx = nx.replace(/\blist\b/g, 'l');
|
|
785
|
-
nx = nx.replace(/\bmap\b/g, 'm');
|
|
786
|
-
nx = nx.replace(/\bany\b/g, 'a');
|
|
787
|
-
nx = nx.replace(/\bconsole\.log\b/, 'log');
|
|
788
|
-
nxLines.push(nx);
|
|
789
|
-
}
|
|
790
|
-
const outPath = filePath.replace(/\.naide$/, '.nx');
|
|
791
|
-
writeFileSync(outPath, nxLines.join('\n'));
|
|
792
|
-
console.log(` ${file} → ${basename(outPath)}`);
|
|
793
|
-
} else {
|
|
794
|
-
console.error(` Unsupported: ${file} (use .naide or .nx)`);
|
|
795
|
-
}
|
|
796
|
-
} catch (e) {
|
|
797
|
-
console.error(` Error: ${file} — ${e.message}`);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
process.exit(0);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
1105
|
// ── Type Check ──
|
|
804
1106
|
if (files[0] === 'check') {
|
|
805
1107
|
const targets = files.slice(1);
|
|
806
1108
|
if (targets.length === 0) {
|
|
807
|
-
console.log(' Usage: naide check <file.naide
|
|
1109
|
+
console.log(' Usage: naide check <file.naide> [files...]');
|
|
808
1110
|
process.exit(0);
|
|
809
1111
|
}
|
|
810
1112
|
|
|
811
1113
|
let totalErrors = 0;
|
|
812
1114
|
let totalWarnings = 0;
|
|
813
1115
|
for (const file of targets) {
|
|
1116
|
+
if (extname(file) === '.nx') {
|
|
1117
|
+
reportRetiredNx(file);
|
|
1118
|
+
totalErrors++;
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
814
1121
|
const filePath = resolve(file);
|
|
815
1122
|
try {
|
|
816
1123
|
const source = readFileSync(filePath, 'utf-8');
|
|
817
|
-
const
|
|
818
|
-
const mode = ext === '.nx' ? 'x' : 'naide';
|
|
819
|
-
const result = compile(source, { mode, typeCheck: true });
|
|
1124
|
+
const result = compile(source, { typeCheck: true });
|
|
820
1125
|
const { typeErrors } = result;
|
|
821
1126
|
|
|
822
1127
|
if (typeErrors) {
|
|
@@ -854,6 +1159,8 @@ if (files[0] === 'pkg') {
|
|
|
854
1159
|
Usage:
|
|
855
1160
|
naide pkg init Create naide.pkg.json manifest
|
|
856
1161
|
naide pkg install <name> Install a NAIDE package from npm
|
|
1162
|
+
naide pkg add <manager> <spec> Install through a native package manager
|
|
1163
|
+
naide pkg managers List supported package ecosystems
|
|
857
1164
|
naide pkg publish Publish current package to npm
|
|
858
1165
|
naide pkg list List installed NAIDE packages
|
|
859
1166
|
`);
|
|
@@ -862,6 +1169,57 @@ if (files[0] === 'pkg') {
|
|
|
862
1169
|
|
|
863
1170
|
const pkgManifestPath = resolve('naide.pkg.json');
|
|
864
1171
|
|
|
1172
|
+
if (subcmd === 'managers') {
|
|
1173
|
+
console.log('\n Native package ecosystems:\n');
|
|
1174
|
+
for (const adapter of listPackageAdapters()) {
|
|
1175
|
+
console.log(` ${adapter.name.padEnd(10)} ${adapter.description}`);
|
|
1176
|
+
}
|
|
1177
|
+
console.log('\n Packages run in their native runtimes. Calling one from NAIDE requires an explicit bridge or adapter.\n');
|
|
1178
|
+
process.exit(0);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
if (subcmd === 'add') {
|
|
1182
|
+
const manager = files[2];
|
|
1183
|
+
const spec = files[3];
|
|
1184
|
+
if (!manager || !spec) {
|
|
1185
|
+
console.error(' Usage: naide pkg add <manager> <package[@version]>');
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
let plan;
|
|
1190
|
+
try {
|
|
1191
|
+
plan = createPackageInstallPlan(manager, spec);
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
console.error(` Error: ${error.message}`);
|
|
1194
|
+
process.exit(1);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
console.log(` Installing ${spec} via ${manager}...`);
|
|
1198
|
+
try {
|
|
1199
|
+
const code = await runPackageInstall(plan, {
|
|
1200
|
+
directory: process.cwd(),
|
|
1201
|
+
spawnCommand: (packagePlan, cwd) => packagePlan.manager === 'npm'
|
|
1202
|
+
? spawnNpm(packagePlan.args, { cwd, stdio: 'inherit' })
|
|
1203
|
+
: spawnNativeTool(packagePlan, cwd, { stdio: 'inherit' }),
|
|
1204
|
+
});
|
|
1205
|
+
if (code === 0) {
|
|
1206
|
+
try {
|
|
1207
|
+
recordPackageDependency(process.cwd(), plan);
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
console.error(` Error: ${spec} was installed, but package metadata could not be saved: ${error.message}`);
|
|
1210
|
+
process.exit(1);
|
|
1211
|
+
}
|
|
1212
|
+
console.log(`\n Installed ${spec} via ${manager}.`);
|
|
1213
|
+
console.log(` Recorded dependency in ${manager === 'pip' ? 'naide.pkg.json and requirements.txt' : 'naide.pkg.json'}.`);
|
|
1214
|
+
if (plan.note) console.log(` Note: ${plan.note}`);
|
|
1215
|
+
}
|
|
1216
|
+
process.exit(code);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
console.error(` Error: could not start ${plan.executable}: ${error.message}`);
|
|
1219
|
+
process.exit(1);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
865
1223
|
if (subcmd === 'init') {
|
|
866
1224
|
if (existsSync(pkgManifestPath)) {
|
|
867
1225
|
console.log(' naide.pkg.json already exists.');
|
|
@@ -880,6 +1238,7 @@ if (files[0] === 'pkg') {
|
|
|
880
1238
|
keywords: ['naide', 'naide-plugin'],
|
|
881
1239
|
exports: {},
|
|
882
1240
|
dependencies: {},
|
|
1241
|
+
languageDependencies: {},
|
|
883
1242
|
};
|
|
884
1243
|
writeFileSync(pkgManifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
885
1244
|
console.log(`\n Created naide.pkg.json\n`);
|
|
@@ -892,25 +1251,43 @@ if (files[0] === 'pkg') {
|
|
|
892
1251
|
console.log(' Usage: naide pkg install <package-name>');
|
|
893
1252
|
process.exit(1);
|
|
894
1253
|
}
|
|
1254
|
+
const packageSpec = parseNpmPackageSpec(pkgName);
|
|
1255
|
+
if (!packageSpec) {
|
|
1256
|
+
console.error(' Error: expected a package name, optional scope, and optional tag or version.');
|
|
1257
|
+
process.exit(1);
|
|
1258
|
+
}
|
|
895
1259
|
|
|
896
1260
|
console.log(` Installing ${pkgName}...`);
|
|
897
|
-
|
|
1261
|
+
let child;
|
|
1262
|
+
try {
|
|
1263
|
+
child = spawnNpm(['install', '--', pkgName], { stdio: 'inherit' });
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
console.error(` Error: ${error.message}`);
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
child.once('error', (error) => {
|
|
1269
|
+
console.error(` Error: failed to start npm: ${error.message}`);
|
|
1270
|
+
process.exit(1);
|
|
1271
|
+
});
|
|
898
1272
|
child.on('close', (code) => {
|
|
899
1273
|
if (code === 0) {
|
|
900
1274
|
if (existsSync(pkgManifestPath)) {
|
|
901
1275
|
try {
|
|
902
1276
|
const manifest = JSON.parse(readFileSync(pkgManifestPath, 'utf-8'));
|
|
903
|
-
|
|
1277
|
+
manifest.dependencies ||= {};
|
|
1278
|
+
const npmPkg = resolve('node_modules', packageSpec.name, 'package.json');
|
|
904
1279
|
if (existsSync(npmPkg)) {
|
|
905
1280
|
const ver = JSON.parse(readFileSync(npmPkg, 'utf-8')).version;
|
|
906
|
-
manifest.dependencies[
|
|
1281
|
+
manifest.dependencies[packageSpec.name] = `^${ver}`;
|
|
907
1282
|
writeFileSync(pkgManifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
908
1283
|
}
|
|
909
|
-
} catch {
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
console.error(` Warning: package installed, but naide.pkg.json could not be updated: ${error.message}`);
|
|
1286
|
+
}
|
|
910
1287
|
}
|
|
911
1288
|
console.log(`\n Installed ${pkgName}`);
|
|
912
1289
|
}
|
|
913
|
-
process.exit(code);
|
|
1290
|
+
process.exit(code ?? 1);
|
|
914
1291
|
});
|
|
915
1292
|
await new Promise(() => {});
|
|
916
1293
|
}
|
|
@@ -931,15 +1308,25 @@ if (files[0] === 'pkg') {
|
|
|
931
1308
|
type: 'module',
|
|
932
1309
|
main: manifest.main,
|
|
933
1310
|
keywords: manifest.keywords,
|
|
934
|
-
files: ['*.naide', '
|
|
1311
|
+
files: ['*.naide', 'src/', 'naide.pkg.json'],
|
|
935
1312
|
}, null, 2) + '\n');
|
|
936
1313
|
}
|
|
937
1314
|
|
|
938
1315
|
console.log(` Publishing ${manifest.name}@${manifest.version}...`);
|
|
939
|
-
|
|
1316
|
+
let child;
|
|
1317
|
+
try {
|
|
1318
|
+
child = spawnNpm(['publish', '--access', 'public'], { stdio: 'inherit' });
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
console.error(` Error: ${error.message}`);
|
|
1321
|
+
process.exit(1);
|
|
1322
|
+
}
|
|
1323
|
+
child.once('error', (error) => {
|
|
1324
|
+
console.error(` Error: failed to start npm: ${error.message}`);
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
});
|
|
940
1327
|
child.on('close', (code) => {
|
|
941
1328
|
if (code === 0) console.log(`\n Published ${manifest.name}@${manifest.version}`);
|
|
942
|
-
process.exit(code);
|
|
1329
|
+
process.exit(code ?? 1);
|
|
943
1330
|
});
|
|
944
1331
|
await new Promise(() => {});
|
|
945
1332
|
}
|
|
@@ -951,13 +1338,18 @@ if (files[0] === 'pkg') {
|
|
|
951
1338
|
}
|
|
952
1339
|
const manifest = JSON.parse(readFileSync(pkgManifestPath, 'utf-8'));
|
|
953
1340
|
const deps = Object.entries(manifest.dependencies || {});
|
|
954
|
-
|
|
1341
|
+
const languageDeps = Object.entries(manifest.languageDependencies || {})
|
|
1342
|
+
.flatMap(([manager, packages]) => Object.entries(packages).map(([name, spec]) => [name, spec, manager]));
|
|
1343
|
+
if (deps.length === 0 && languageDeps.length === 0) {
|
|
955
1344
|
console.log(' No NAIDE packages installed.');
|
|
956
1345
|
} else {
|
|
957
1346
|
console.log('\n NAIDE packages:\n');
|
|
958
1347
|
for (const [name, ver] of deps) {
|
|
959
1348
|
console.log(` ${name} ${ver}`);
|
|
960
1349
|
}
|
|
1350
|
+
for (const [name, spec, manager] of languageDeps) {
|
|
1351
|
+
console.log(` ${name} ${spec} (${manager})`);
|
|
1352
|
+
}
|
|
961
1353
|
console.log('');
|
|
962
1354
|
}
|
|
963
1355
|
process.exit(0);
|
|
@@ -985,7 +1377,6 @@ CMD ["node", "dist/app.mjs"]
|
|
|
985
1377
|
const dockerignoreContent = `node_modules
|
|
986
1378
|
.git
|
|
987
1379
|
*.naide
|
|
988
|
-
*.nx
|
|
989
1380
|
.naide_tmp_*
|
|
990
1381
|
dist
|
|
991
1382
|
`;
|
|
@@ -1022,12 +1413,11 @@ dist
|
|
|
1022
1413
|
if (flags.help) {
|
|
1023
1414
|
console.log(`
|
|
1024
1415
|
NAIDE - Node AI Development Environment
|
|
1025
|
-
|
|
1416
|
+
One readable syntax for people and tools
|
|
1026
1417
|
|
|
1027
1418
|
Usage:
|
|
1028
1419
|
naide Start interactive REPL
|
|
1029
1420
|
naide <file.naide> Run a NAIDE file
|
|
1030
|
-
naide <file.nx> Run a NAIDE-X file (auto-detected)
|
|
1031
1421
|
naide ~~ "instruction" Generate NAIDE code from instruction (no AI)
|
|
1032
1422
|
naide gen "instruction" Same as ~~ (alias)
|
|
1033
1423
|
naide init [dir] Create a new NAIDE project
|
|
@@ -1037,15 +1427,17 @@ if (flags.help) {
|
|
|
1037
1427
|
naide lsp Start language server (LSP)
|
|
1038
1428
|
naide vscode Install VS Code extension
|
|
1039
1429
|
naide deploy [dir] Generate Dockerfile for deployment
|
|
1040
|
-
naide
|
|
1430
|
+
naide doctor [dir] Diagnose local tools and project setup
|
|
1431
|
+
naide verify [dir] Check project manifests and setup
|
|
1432
|
+
naide test [dir] Run detected native project test suites
|
|
1041
1433
|
naide fmt <files...> Format NAIDE files
|
|
1042
1434
|
naide pkg init Create naide.pkg.json manifest
|
|
1043
1435
|
naide pkg install <name> Install a NAIDE package
|
|
1436
|
+
naide pkg add <manager> <spec> Install with a native package manager
|
|
1437
|
+
naide pkg managers List supported package ecosystems
|
|
1044
1438
|
naide pkg publish Publish package to npm
|
|
1045
1439
|
naide pkg list List NAIDE dependencies
|
|
1046
1440
|
naide --emit <file> Output generated JavaScript
|
|
1047
|
-
naide --mid <file.nx> Output intermediate NAIDE v1 (debug)
|
|
1048
|
-
naide -x <file.naide> Force NAIDE-X mode
|
|
1049
1441
|
naide -w <file.naide> Watch mode (auto-restart on changes)
|
|
1050
1442
|
naide -d <file> Debug mode (Node.js inspector)
|
|
1051
1443
|
|
|
@@ -1072,19 +1464,13 @@ if (flags.help) {
|
|
|
1072
1464
|
php PHP
|
|
1073
1465
|
ruby Ruby (Sinatra)
|
|
1074
1466
|
|
|
1075
|
-
Modes:
|
|
1076
|
-
.naide Standard NAIDE (~40% fewer tokens than JS)
|
|
1077
|
-
.nx NAIDE-X extreme (~80% fewer tokens than JS)
|
|
1078
|
-
|
|
1079
1467
|
Options:
|
|
1080
1468
|
-e, --emit Print generated JavaScript
|
|
1081
1469
|
-o, --output Write generated JavaScript to file
|
|
1082
|
-
-x Force NAIDE-X mode
|
|
1083
1470
|
-w, --watch Watch mode: restart on file changes
|
|
1084
1471
|
-d, --debug Start with Node.js debugger (--inspect-brk)
|
|
1085
1472
|
-t, --target Compile target: node (default), bun, python/py
|
|
1086
1473
|
--check Type-check files without running
|
|
1087
|
-
--mid Show intermediate NAIDE v1 (X mode only)
|
|
1088
1474
|
--expand Show ~~ directive expansion (debug)
|
|
1089
1475
|
--ast Print AST
|
|
1090
1476
|
--tokens Print tokens
|
|
@@ -1094,6 +1480,7 @@ if (flags.help) {
|
|
|
1094
1480
|
Intent server api rest bot cli page test database ai crud auth ws mail graphql game
|
|
1095
1481
|
Composite todo blog chat shop fullstack board
|
|
1096
1482
|
Game tapping quiz memory snake typing reaction breakout dodge color pong
|
|
1483
|
+
App calculator timer counter pomodoro clock drawing notes
|
|
1097
1484
|
Platform discord slack telegram line
|
|
1098
1485
|
JP Intent サーバー 認証 ログイン 会員 CRUD 管理 データベース ボット テスト ページ ゲーム
|
|
1099
1486
|
JP Entity ユーザー 商品 記事 タスク 注文 コメント イベント 問い合わせ 掲示板 決済
|
|
@@ -1115,15 +1502,13 @@ for (const file of files) {
|
|
|
1115
1502
|
}
|
|
1116
1503
|
|
|
1117
1504
|
const ext = extname(file);
|
|
1118
|
-
|
|
1505
|
+
if (ext === '.nx') {
|
|
1506
|
+
reportRetiredNx(file);
|
|
1507
|
+
process.exitCode = 1;
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1119
1510
|
|
|
1120
1511
|
try {
|
|
1121
|
-
if (flags.mid && mode === 'x') {
|
|
1122
|
-
const { preprocess } = await import('../src/preprocess.js');
|
|
1123
|
-
console.log(preprocess(source));
|
|
1124
|
-
continue;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
1512
|
const runtimePath = flags.emit || flags.output ? 'naider/runtime' : runtimeUrl;
|
|
1128
1513
|
|
|
1129
1514
|
if (flags.watch) {
|
|
@@ -1133,7 +1518,7 @@ for (const file of files) {
|
|
|
1133
1518
|
function start() {
|
|
1134
1519
|
try {
|
|
1135
1520
|
source = readFileSync(filePath, 'utf-8');
|
|
1136
|
-
const result = compile(source, {
|
|
1521
|
+
const result = compile(source, { runtimePath });
|
|
1137
1522
|
writeFileSync(tempFile, result.js, 'utf-8');
|
|
1138
1523
|
child = spawn(process.execPath, [tempFile], { stdio: 'inherit' });
|
|
1139
1524
|
child.on('error', (err) => console.error(`[NAIDE] Process error: ${err.message}`));
|
|
@@ -1176,8 +1561,8 @@ for (const file of files) {
|
|
|
1176
1561
|
|
|
1177
1562
|
const useAsync = flags.target !== 'node';
|
|
1178
1563
|
const result = useAsync
|
|
1179
|
-
? await compileAsync(source, {
|
|
1180
|
-
: compile(source, {
|
|
1564
|
+
? await compileAsync(source, { runtimePath, sourceFile: file, typeCheck: flags.check, target: flags.target })
|
|
1565
|
+
: compile(source, { runtimePath, sourceFile: file, typeCheck: flags.check });
|
|
1181
1566
|
|
|
1182
1567
|
if (flags.check) {
|
|
1183
1568
|
const { typeErrors } = result;
|
|
@@ -1224,7 +1609,7 @@ for (const file of files) {
|
|
|
1224
1609
|
const tempFile = resolve(`.naide_tmp_${basename(file, ext)}.mjs`);
|
|
1225
1610
|
writeFileSync(tempFile, outputCode, 'utf-8');
|
|
1226
1611
|
console.log(`[NAIDE] Running with Bun — ${file}`);
|
|
1227
|
-
const bunChild = spawn('bun', ['run', tempFile], { stdio: 'inherit'
|
|
1612
|
+
const bunChild = spawn('bun', ['run', tempFile], { stdio: 'inherit' });
|
|
1228
1613
|
bunChild.on('close', (code) => {
|
|
1229
1614
|
try { unlinkSync(tempFile); } catch {}
|
|
1230
1615
|
process.exit(code || 0);
|