@neuxnet/neux-cli 0.2.3

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.
Files changed (73) hide show
  1. package/README.md +438 -0
  2. package/README.zh-CN.md +554 -0
  3. package/assets/container/assets/index.css +1 -0
  4. package/assets/container/assets/index.js +137 -0
  5. package/assets/container/assets/pageFrame.css +1 -0
  6. package/assets/container/assets/pageFrame.js +56 -0
  7. package/assets/container/assets/service.js +6 -0
  8. package/assets/container/assets/vconsole.js +931 -0
  9. package/assets/container/favicon.ico +0 -0
  10. package/assets/container/images/icon-arrow.png +0 -0
  11. package/assets/container/images/mini-action-white.png +0 -0
  12. package/assets/container/images/mini-action.png +0 -0
  13. package/assets/container/images/mini-arrow-left-white.png +0 -0
  14. package/assets/container/images/mini-arrow-left.jpg +0 -0
  15. package/assets/container/images/mini-arrow-left.png +0 -0
  16. package/assets/container/images/mini-close-white.png +0 -0
  17. package/assets/container/images/mini-close.png +0 -0
  18. package/assets/container/images/more.png +0 -0
  19. package/assets/container/images/search.jpg +0 -0
  20. package/assets/container/index.html +1 -0
  21. package/assets/container/pageFrame.html +1 -0
  22. package/assets/init/tabbar/home-active.png +0 -0
  23. package/assets/init/tabbar/home.png +0 -0
  24. package/assets/init/tabbar/list-active.png +0 -0
  25. package/assets/init/tabbar/list.png +0 -0
  26. package/assets/init/types/neux-api.d.ts +1402 -0
  27. package/package.json +68 -0
  28. package/scripts/generate-init-types.js +210 -0
  29. package/scripts/sync-compiler.js +22 -0
  30. package/scripts/sync-web-container.js +34 -0
  31. package/src/bin/cli.js +646 -0
  32. package/src/core/brand.js +11 -0
  33. package/src/core/compiler.js +305 -0
  34. package/src/core/defaults.js +12 -0
  35. package/src/core/dev.js +15 -0
  36. package/src/core/errors.js +17 -0
  37. package/src/core/fs.js +66 -0
  38. package/src/core/i18n.js +37 -0
  39. package/src/core/init.js +866 -0
  40. package/src/core/lifecycle.js +32 -0
  41. package/src/core/manifest.js +72 -0
  42. package/src/core/pack.js +156 -0
  43. package/src/core/package-info.js +27 -0
  44. package/src/core/preview-server.js +123 -0
  45. package/src/core/project.js +101 -0
  46. package/src/core/prompts.js +71 -0
  47. package/src/core/proxy-security.js +141 -0
  48. package/src/core/proxy.js +156 -0
  49. package/src/core/qr.js +41 -0
  50. package/src/core/terminal-qr.js +72 -0
  51. package/src/core/update.js +278 -0
  52. package/src/core/watch.js +113 -0
  53. package/src/core/web.js +649 -0
  54. package/src/core/zip.js +120 -0
  55. package/src/index.js +20 -0
  56. package/src/providers/service-client.js +149 -0
  57. package/src/providers/service-config.js +264 -0
  58. package/src/providers/service.js +146 -0
  59. package/src/providers/upload.js +112 -0
  60. package/vendor/dimina-compiler/bin/index.cjs +265 -0
  61. package/vendor/dimina-compiler/bin/index.js +263 -0
  62. package/vendor/dimina-compiler/compatibility-B-DoZtUX.cjs +395 -0
  63. package/vendor/dimina-compiler/compatibility-Cl3-DO6V.js +366 -0
  64. package/vendor/dimina-compiler/core/logic-compiler.cjs +378 -0
  65. package/vendor/dimina-compiler/core/logic-compiler.js +374 -0
  66. package/vendor/dimina-compiler/core/style-compiler.cjs +392 -0
  67. package/vendor/dimina-compiler/core/style-compiler.js +377 -0
  68. package/vendor/dimina-compiler/core/view-compiler.cjs +1601 -0
  69. package/vendor/dimina-compiler/core/view-compiler.js +1582 -0
  70. package/vendor/dimina-compiler/index.cjs +762 -0
  71. package/vendor/dimina-compiler/index.js +751 -0
  72. package/vendor/dimina-compiler/sourcemap-BgtIgqkC.cjs +1377 -0
  73. package/vendor/dimina-compiler/sourcemap-CKjhV9h7.js +1135 -0
@@ -0,0 +1,866 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { DiminaCliError } from './errors.js'
5
+ import { pathExists, resolvePath, writeJson } from './fs.js'
6
+ import { getCliVersion } from './package-info.js'
7
+ import { saveCliKey } from '../providers/service-config.js'
8
+ import { promptLines, shouldPrompt } from './prompts.js'
9
+
10
+ const DEFAULT_APP_ID = 'touristappid'
11
+ const DEFAULT_LIB_VERSION = '3.3.3'
12
+ export const DEFAULT_SERVER_URL = 'https://demo-c.paas.superapp.neuvision.cn'
13
+ const CLI_PACKAGE_NAME = '@neuxnet/neux-cli'
14
+ const TEMPLATE_ASSET_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../assets/init')
15
+ const DEFAULT_DOCS_BASE_URL = `${DEFAULT_SERVER_URL}/miniappdoc`
16
+
17
+ export async function initProject(options = {}) {
18
+ const targetArg = options._?.[0] || options.project || '.'
19
+ const projectPath = resolvePath(targetArg)
20
+ await assertWritableProjectDir(projectPath, !!options.force)
21
+
22
+ const interactive = shouldPrompt(options)
23
+ const prompts = interactive ? await promptInitValues(options, projectPath) : {}
24
+ const projectName = normalizeProjectName(options.name || prompts.name, projectPath)
25
+ const appId = options.appId || prompts.appId || DEFAULT_APP_ID
26
+ const serverUrl = options.serverUrl || prompts.serverUrl || DEFAULT_SERVER_URL
27
+ const cliKey = options.key || prompts.cliKey
28
+ const cliVersion = await getCliVersion()
29
+ const files = createTemplateFiles({ appId, projectName, serverUrl, cliVersion })
30
+
31
+ for (const file of files) {
32
+ const filePath = path.join(projectPath, file.path)
33
+ if (file.kind === 'json') await writeJson(filePath, file.content)
34
+ else if (file.kind === 'merge-json') await mergeJsonFile(filePath, file.content)
35
+ else if (file.kind === 'asset') {
36
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
37
+ const sourcePath = path.join(TEMPLATE_ASSET_DIR, file.source)
38
+ if (file.source === 'types/neux-api.d.ts') {
39
+ const docsBaseUrl = `${String(serverUrl || DEFAULT_SERVER_URL).replace(/\/+$/, '')}/miniappdoc`
40
+ const content = await fs.readFile(sourcePath, 'utf8')
41
+ await fs.writeFile(filePath, content.replaceAll(DEFAULT_DOCS_BASE_URL, docsBaseUrl), 'utf8')
42
+ }
43
+ else await fs.copyFile(sourcePath, filePath)
44
+ }
45
+ else {
46
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
47
+ await fs.writeFile(filePath, file.content, 'utf8')
48
+ }
49
+ }
50
+
51
+ if (cliKey) await saveCliKey({ appId, key: cliKey, configPath: options.config })
52
+
53
+ return {
54
+ ok: true,
55
+ command: 'init',
56
+ projectPath,
57
+ outputPath: projectPath,
58
+ appId,
59
+ name: projectName,
60
+ serverUrl,
61
+ cliKeyConfigured: !!cliKey,
62
+ files: files.map(file => file.path),
63
+ }
64
+ }
65
+
66
+ async function promptInitValues(options, projectPath) {
67
+ const prompts = []
68
+ if (!options.name) prompts.push({ name: 'name', label: `Project name (${path.basename(projectPath) || 'neux-miniapp'}): ` })
69
+ if (!options.appId) prompts.push({ name: 'appId', label: `App ID (${DEFAULT_APP_ID}): ` })
70
+ if (!options.serverUrl) prompts.push({ name: 'serverUrl', label: `Server URL (${DEFAULT_SERVER_URL}): ` })
71
+ if (!options.key) prompts.push({ name: 'cliKey', label: 'CLI Key (optional): ', secret: true })
72
+ const values = prompts.length > 0 ? await promptLines(prompts) : {}
73
+ return {
74
+ name: String(values.name || '').trim() || path.basename(projectPath),
75
+ appId: String(values.appId || '').trim() || DEFAULT_APP_ID,
76
+ serverUrl: String(values.serverUrl || '').trim() || DEFAULT_SERVER_URL,
77
+ cliKey: String(values.cliKey || '').trim(),
78
+ }
79
+ }
80
+
81
+ function normalizeProjectName(name, projectPath) {
82
+ const value = String(name || '').trim()
83
+ if (value) return value
84
+ const basename = path.basename(projectPath)
85
+ return basename && basename !== '.' ? basename : 'neux-miniapp'
86
+ }
87
+
88
+ function normalizePackageName(name) {
89
+ const normalized = String(name || '')
90
+ .trim()
91
+ .toLowerCase()
92
+ .replace(/[^a-z0-9._-]+/g, '-')
93
+ .replace(/^[._-]+|[._-]+$/g, '')
94
+ .replace(/-{2,}/g, '-')
95
+ return normalized || 'neux-miniapp'
96
+ }
97
+
98
+ async function assertWritableProjectDir(projectPath, force) {
99
+ if (!(await pathExists(projectPath))) return
100
+
101
+ const stat = await fs.stat(projectPath)
102
+ if (!stat.isDirectory()) {
103
+ throw new DiminaCliError('NEUX_CLI_INIT_TARGET_INVALID', `Init target is not a directory: ${projectPath}`, {
104
+ projectPath,
105
+ })
106
+ }
107
+
108
+ const entries = await fs.readdir(projectPath)
109
+ if (entries.length > 0 && !force) {
110
+ throw new DiminaCliError('NEUX_CLI_INIT_TARGET_NOT_EMPTY', `Init target is not empty: ${projectPath}`, {
111
+ projectPath,
112
+ suggestions: [
113
+ 'Choose an empty directory.',
114
+ 'Pass --force to write the starter files into the existing directory.',
115
+ ],
116
+ })
117
+ }
118
+ }
119
+
120
+ async function mergeJsonFile(filePath, generated) {
121
+ let existing = {}
122
+ try {
123
+ existing = JSON.parse(await fs.readFile(filePath, 'utf8'))
124
+ }
125
+ catch (error) {
126
+ if (error.code !== 'ENOENT') throw error
127
+ }
128
+
129
+ const merged = {
130
+ ...(generated && typeof generated === 'object' ? generated : {}),
131
+ ...(existing && typeof existing === 'object' ? existing : {}),
132
+ }
133
+ if (generated?.['files.associations'] || existing?.['files.associations']) {
134
+ merged['files.associations'] = {
135
+ ...(generated['files.associations'] || {}),
136
+ ...(existing['files.associations'] || {}),
137
+ }
138
+ }
139
+ if (filePath.endsWith('neux.code-snippets')) {
140
+ Object.assign(merged, generated || {}, existing || {})
141
+ }
142
+ await writeJson(filePath, merged)
143
+ }
144
+
145
+ function createTemplateFiles({ appId, projectName, serverUrl = DEFAULT_SERVER_URL, cliVersion }) {
146
+ return [
147
+ {
148
+ path: 'project.config.json',
149
+ kind: 'json',
150
+ content: {
151
+ description: 'Neux mini program project.',
152
+ setting: {
153
+ urlCheck: true,
154
+ es6: true,
155
+ postcss: true,
156
+ minified: true,
157
+ newFeature: true,
158
+ babelSetting: {
159
+ ignore: [],
160
+ disablePlugins: [],
161
+ outputPath: '',
162
+ },
163
+ },
164
+ compileType: 'miniprogram',
165
+ libVersion: DEFAULT_LIB_VERSION,
166
+ appid: appId,
167
+ projectname: projectName,
168
+ miniprogramRoot: '',
169
+ versionName: '1.0.0',
170
+ versionCode: 1,
171
+ neuxCli: {
172
+ serverUrl,
173
+ },
174
+ condition: {},
175
+ packOptions: {
176
+ ignore: [],
177
+ include: [],
178
+ },
179
+ editorSetting: {
180
+ tabIndent: 'insertSpaces',
181
+ tabSize: 2,
182
+ },
183
+ },
184
+ },
185
+ {
186
+ path: 'package.json',
187
+ kind: 'json',
188
+ content: {
189
+ name: normalizePackageName(projectName),
190
+ version: '1.0.0',
191
+ private: true,
192
+ scripts: {
193
+ dev: 'neux dev',
194
+ build: 'neux build',
195
+ debug: 'neux debug',
196
+ upload: 'neux upload --preview',
197
+ submit: 'neux submit',
198
+ },
199
+ devDependencies: {
200
+ [CLI_PACKAGE_NAME]: `^${cliVersion}`,
201
+ },
202
+ },
203
+ },
204
+ {
205
+ path: 'app.json',
206
+ kind: 'json',
207
+ content: {
208
+ pages: ['pages/index/index', 'pages/list/list'],
209
+ window: {
210
+ navigationBarTitleText: projectName,
211
+ navigationBarBackgroundColor: '#ffffff',
212
+ navigationBarTextStyle: 'black',
213
+ backgroundColor: '#f7f8fa',
214
+ backgroundTextStyle: 'light',
215
+ },
216
+ tabBar: {
217
+ color: '#64748b',
218
+ selectedColor: '#1aad19',
219
+ backgroundColor: '#ffffff',
220
+ borderStyle: 'black',
221
+ list: [
222
+ {
223
+ pagePath: 'pages/index/index',
224
+ iconPath: 'assets/tabbar/home.png',
225
+ selectedIconPath: 'assets/tabbar/home-active.png',
226
+ text: 'Home',
227
+ },
228
+ {
229
+ pagePath: 'pages/list/list',
230
+ iconPath: 'assets/tabbar/list.png',
231
+ selectedIconPath: 'assets/tabbar/list-active.png',
232
+ text: 'List',
233
+ },
234
+ ],
235
+ },
236
+ },
237
+ },
238
+ {
239
+ path: 'app.js',
240
+ content: 'App({})\n',
241
+ },
242
+ {
243
+ path: 'AGENTS.md',
244
+ content: createAgentsDocument(),
245
+ },
246
+ {
247
+ path: 'llms.txt',
248
+ content: createLlmsDocument(),
249
+ },
250
+ {
251
+ path: 'mcp.example.json',
252
+ kind: 'json',
253
+ content: createMcpExample(),
254
+ },
255
+ {
256
+ path: '.vscode/settings.json',
257
+ kind: 'merge-json',
258
+ content: {
259
+ 'files.associations': {
260
+ '*.nxml': 'html',
261
+ '*.nxss': 'css',
262
+ },
263
+ },
264
+ },
265
+ {
266
+ path: '.vscode/neux.code-snippets',
267
+ kind: 'merge-json',
268
+ content: createNeuxCodeSnippets(),
269
+ },
270
+ {
271
+ path: 'jsconfig.json',
272
+ kind: 'json',
273
+ content: {
274
+ compilerOptions: {
275
+ allowJs: true,
276
+ checkJs: false,
277
+ noEmit: true,
278
+ target: 'ES2020',
279
+ },
280
+ include: ['**/*.js', '**/*.ts', 'types/**/*.d.ts'],
281
+ },
282
+ },
283
+ { path: 'types/neux-api.d.ts', kind: 'asset', source: 'types/neux-api.d.ts' },
284
+ { path: 'assets/tabbar/home.png', kind: 'asset', source: 'tabbar/home.png' },
285
+ { path: 'assets/tabbar/home-active.png', kind: 'asset', source: 'tabbar/home-active.png' },
286
+ { path: 'assets/tabbar/list.png', kind: 'asset', source: 'tabbar/list.png' },
287
+ { path: 'assets/tabbar/list-active.png', kind: 'asset', source: 'tabbar/list-active.png' },
288
+ {
289
+ path: 'app.nxss',
290
+ content: [
291
+ 'page {',
292
+ ' min-height: 100vh;',
293
+ ' background: #f7f8fa;',
294
+ ' color: #1f2933;',
295
+ ' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;',
296
+ '}',
297
+ '',
298
+ ].join('\n'),
299
+ },
300
+ {
301
+ path: 'pages/index/index.json',
302
+ kind: 'json',
303
+ content: {
304
+ navigationBarTitleText: projectName,
305
+ },
306
+ },
307
+ {
308
+ path: 'pages/index/index.js',
309
+ content: [
310
+ 'Page({',
311
+ ' data: {',
312
+ ` title: '${escapeJsString(projectName)}',`,
313
+ ' count: 0,',
314
+ ' },',
315
+ ' increase() {',
316
+ ' this.setData({',
317
+ ' count: this.data.count + 1,',
318
+ ' })',
319
+ ' },',
320
+ '})',
321
+ '',
322
+ ].join('\n'),
323
+ },
324
+ {
325
+ path: 'pages/index/index.nxml',
326
+ content: [
327
+ '<view class="page">',
328
+ ' <view class="card">',
329
+ ' <text class="eyebrow">Welcome</text>',
330
+ ' <text class="title">{{title}}</text>',
331
+ ' <text class="desc">A simple setData counter.</text>',
332
+ ' <view class="counter">',
333
+ ' <text class="count">{{count}}</text>',
334
+ ' <text class="count-label">clicks</text>',
335
+ ' </view>',
336
+ ' <button class="primary-button" bindtap="increase">Add one</button>',
337
+ ' </view>',
338
+ '</view>',
339
+ '',
340
+ ].join('\n'),
341
+ },
342
+ {
343
+ path: 'pages/index/index.nxss',
344
+ content: [
345
+ '.page {',
346
+ ' min-height: 100vh;',
347
+ ' box-sizing: border-box;',
348
+ ' padding: 48rpx 32rpx;',
349
+ '}',
350
+ '',
351
+ '.card {',
352
+ ' padding: 40rpx 32rpx;',
353
+ ' border-radius: 16rpx;',
354
+ ' background: #ffffff;',
355
+ ' box-shadow: 0 12rpx 36rpx rgba(15, 23, 42, 0.08);',
356
+ '}',
357
+ '',
358
+ '.title {',
359
+ ' display: block;',
360
+ ' font-size: 40rpx;',
361
+ ' font-weight: 600;',
362
+ '}',
363
+ '',
364
+ '.eyebrow {',
365
+ ' display: block;',
366
+ ' color: #1aad19;',
367
+ ' font-size: 24rpx;',
368
+ ' font-weight: 600;',
369
+ ' text-transform: uppercase;',
370
+ '}',
371
+ '',
372
+ '.desc {',
373
+ ' display: block;',
374
+ ' margin-top: 16rpx;',
375
+ ' color: #64748b;',
376
+ ' font-size: 28rpx;',
377
+ '}',
378
+ '',
379
+ '.counter {',
380
+ ' display: flex;',
381
+ ' align-items: baseline;',
382
+ ' margin-top: 48rpx;',
383
+ '}',
384
+ '',
385
+ '.count {',
386
+ ' color: #0f172a;',
387
+ ' font-size: 88rpx;',
388
+ ' font-weight: 700;',
389
+ ' line-height: 1;',
390
+ '}',
391
+ '',
392
+ '.count-label {',
393
+ ' margin-left: 16rpx;',
394
+ ' color: #64748b;',
395
+ ' font-size: 28rpx;',
396
+ '}',
397
+ '',
398
+ '.primary-button {',
399
+ ' margin-top: 40rpx;',
400
+ ' border-radius: 12rpx;',
401
+ ' background: #1aad19;',
402
+ ' color: #ffffff;',
403
+ ' font-size: 30rpx;',
404
+ '}',
405
+ '',
406
+ ].join('\n'),
407
+ },
408
+ {
409
+ path: 'pages/list/list.json',
410
+ kind: 'json',
411
+ content: {
412
+ navigationBarTitleText: 'List',
413
+ },
414
+ },
415
+ {
416
+ path: 'pages/list/list.js',
417
+ content: [
418
+ 'Page({',
419
+ ' data: {',
420
+ ' items: [',
421
+ " { id: 1, name: 'Learn nxml', detail: 'Build a page with template syntax.' },",
422
+ " { id: 2, name: 'Use setData', detail: 'Update data from an event handler.' },",
423
+ " { id: 3, name: 'Try hot update', detail: 'Change code and see the preview update.' },",
424
+ ' ],',
425
+ ' },',
426
+ '})',
427
+ '',
428
+ ].join('\n'),
429
+ },
430
+ {
431
+ path: 'pages/list/list.nxml',
432
+ content: [
433
+ '<view class="page">',
434
+ ' <view class="header">',
435
+ ' <text class="title">List example</text>',
436
+ ' <text class="desc">This page uses the nx:for directive.</text>',
437
+ ' </view>',
438
+ ' <view class="list">',
439
+ ' <view class="list-item" nx:for="{{items}}" nx:key="id">',
440
+ ' <view class="item-index">{{index + 1}}</view>',
441
+ ' <view class="item-content">',
442
+ ' <text class="item-name">{{item.name}}</text>',
443
+ ' <text class="item-detail">{{item.detail}}</text>',
444
+ ' </view>',
445
+ ' </view>',
446
+ ' </view>',
447
+ '</view>',
448
+ '',
449
+ ].join('\n'),
450
+ },
451
+ {
452
+ path: 'pages/list/list.nxss',
453
+ content: [
454
+ '.page {',
455
+ ' min-height: 100vh;',
456
+ ' box-sizing: border-box;',
457
+ ' padding: 48rpx 32rpx;',
458
+ '}',
459
+ '',
460
+ '.header {',
461
+ ' padding: 24rpx 8rpx 32rpx;',
462
+ '}',
463
+ '',
464
+ '.title {',
465
+ ' display: block;',
466
+ ' color: #0f172a;',
467
+ ' font-size: 44rpx;',
468
+ ' font-weight: 700;',
469
+ '}',
470
+ '',
471
+ '.desc {',
472
+ ' display: block;',
473
+ ' margin-top: 16rpx;',
474
+ ' color: #64748b;',
475
+ ' font-size: 28rpx;',
476
+ '}',
477
+ '',
478
+ '.list-item {',
479
+ ' display: flex;',
480
+ ' align-items: center;',
481
+ ' margin-bottom: 20rpx;',
482
+ ' padding: 28rpx 24rpx;',
483
+ ' border-radius: 16rpx;',
484
+ ' background: #ffffff;',
485
+ '}',
486
+ '',
487
+ '.item-index {',
488
+ ' display: flex;',
489
+ ' align-items: center;',
490
+ ' justify-content: center;',
491
+ ' width: 56rpx;',
492
+ ' height: 56rpx;',
493
+ ' border-radius: 50%;',
494
+ ' background: #1aad19;',
495
+ ' color: #ffffff;',
496
+ ' font-weight: 700;',
497
+ '}',
498
+ '',
499
+ '.item-content {',
500
+ ' flex: 1;',
501
+ ' margin-left: 20rpx;',
502
+ '}',
503
+ '',
504
+ '.item-name, .item-detail {',
505
+ ' display: block;',
506
+ '}',
507
+ '',
508
+ '.item-name {',
509
+ ' color: #0f172a;',
510
+ ' font-size: 30rpx;',
511
+ ' font-weight: 600;',
512
+ '}',
513
+ '',
514
+ '.item-detail {',
515
+ ' margin-top: 8rpx;',
516
+ ' color: #64748b;',
517
+ ' font-size: 24rpx;',
518
+ '}',
519
+ '',
520
+ ].join('\n'),
521
+ },
522
+ ]
523
+ }
524
+
525
+ function escapeJsString(value) {
526
+ return String(value).replaceAll('\\', '\\\\').replaceAll("'", "\\'")
527
+ }
528
+
529
+ function createAgentsDocument() {
530
+ const ai = createAiDocumentationLinks(DEFAULT_SERVER_URL)
531
+ return `# AI Development Standard for Neux Mini Apps
532
+
533
+ This file defines a reusable development standard for AI coding agents. It is intentionally independent of any project, application, App ID, page, business domain, or deployment environment.
534
+
535
+ ## Role
536
+
537
+ Act as a careful Neux mini app engineer. Before changing code, understand the existing project structure, configuration, compiler behavior, and runtime constraints. Make the smallest complete change that satisfies the request.
538
+
539
+ ## Official Documentation for AI Tools
540
+
541
+ Use the official documentation as the source of truth for framework behavior, APIs, components, SDK integration, and CLI workflows:
542
+
543
+ - AI guide: ${ai.guide}
544
+ - LLM index: ${ai.llms}
545
+ - Full LLM export: ${ai.llmsFull}
546
+ - MCP endpoint: ${ai.mcp}
547
+ - Agent Skill index: ${ai.skillsIndex}
548
+ - Neux documentation Skill: ${ai.skill}
549
+
550
+ Prefer the official documentation over assumptions or undocumented runtime behavior. Use the MCP endpoint only when the AI tool supports MCP. Do not commit credentials or modify global AI tool configuration automatically.
551
+
552
+ ## Working Process
553
+
554
+ 1. Inspect the repository, current branch, working tree, relevant documentation, and existing tests.
555
+ 2. Identify the correct layer for the change: project code, compiler, runtime, container, SDK, or host integration.
556
+ 3. Reproduce the problem or create a minimal test case before making a broad change.
557
+ 4. Follow existing patterns and public contracts. Avoid speculative APIs and unrelated refactors.
558
+ 5. Implement the change with clear names, stable behavior, and short comments only where the intent is not obvious.
559
+ 6. Verify the original workflow and nearby regression paths.
560
+ 7. Report what changed, what was verified, and any remaining limitations.
561
+
562
+ ## Source and Runtime Rules
563
+
564
+ - Use the project's configured source format. For Neux projects, view templates use \`.nxml\` and styles use \`.nxss\`.
565
+ - Keep application and page behavior in source JavaScript or TypeScript, not generated output.
566
+ - Update rendered state through the framework's data update API, such as \`setData\`, instead of directly mutating displayed state.
567
+ - Use the project's documented template directives and preserve stable list identity with a key when rendering collections.
568
+ - Treat configuration files as contracts. Validate route, component, asset, and package references before changing them.
569
+ - Preserve service/render, compiler/runtime, and host/SDK boundaries. Do not solve a framework problem with a page-specific workaround.
570
+ - Do not rely on fixed delays, random identifiers, or environment-specific paths to hide lifecycle or timing problems.
571
+
572
+ ## Code Change Rules
573
+
574
+ - Read surrounding code before editing and preserve unrelated user or collaborator changes.
575
+ - Prefer structured parsers and existing helpers over ad hoc text replacement.
576
+ - Keep generated directories such as \`dist\` disposable; never edit generated output as the source of truth.
577
+ - Do not add dependencies when the existing toolchain is sufficient.
578
+ - Do not introduce secrets, tokens, private URLs, or machine-specific paths into source files.
579
+ - Keep public interfaces backward compatible unless the task explicitly requires a breaking change.
580
+ - Update focused tests and documentation when a public behavior or workflow changes.
581
+
582
+ ## Commands
583
+
584
+ \`\`\`bash
585
+ npm run dev
586
+ npm run build
587
+ npm run debug
588
+ \`\`\`
589
+
590
+ Use \`npm run dev\` for local Web preview and hot update. If multiple development servers are needed, give each process a different port and output directory:
591
+
592
+ \`\`\`bash
593
+ neux dev --port 7721 --out dist/dev-7721
594
+ \`\`\`
595
+
596
+ ## Verification
597
+
598
+ After changing code:
599
+
600
+ 1. Run \`npm run dev\`.
601
+ 2. Check the affected workflow in the Web preview or target host.
602
+ 3. Modify the same source file more than once when verifying hot update behavior.
603
+ 4. Run the focused tests and \`npm run build\` before delivery.
604
+
605
+ Read \`llms.txt\` for official AI documentation entry points. \`mcp.example.json\` contains the public Neux documentation MCP endpoint and can be copied into an MCP-compatible client. Never commit credentials or modify global AI tool configuration automatically.
606
+ `
607
+ }
608
+
609
+ function createLlmsDocument() {
610
+ const ai = createAiDocumentationLinks(DEFAULT_SERVER_URL)
611
+ return `# Mini App AI Development Context
612
+
613
+ > General development context for Neux mini apps. This file does not describe a specific application, App ID, business domain, or page implementation.
614
+
615
+ ## Official AI Documentation
616
+
617
+ - AI guide: ${ai.guide}
618
+ - LLM index: ${ai.llms}
619
+ - Full LLM export: ${ai.llmsFull}
620
+ - MCP endpoint: ${ai.mcp}
621
+ - Agent Skill index: ${ai.skillsIndex}
622
+ - Neux documentation Skill: ${ai.skill}
623
+
624
+ These published resources are the source of truth for framework, API, component, SDK, and CLI questions.
625
+
626
+ ## Development Guidelines
627
+
628
+ - Use \`nxml\` for view templates and \`nxss\` for stylesheets.
629
+ - Use page or component JavaScript for behavior and state.
630
+ - Use \`this.setData({ ... })\` when changing data rendered by the view.
631
+ - Use \`nx:for\` with \`nx:key\` for list rendering when supported by the project compiler.
632
+ - Keep configuration, source code, assets, and generated output separate.
633
+ - Do not edit the generated \`dist\` directory by hand.
634
+ - Inspect the existing project configuration before changing routes, components, or runtime settings.
635
+
636
+ ## Development
637
+
638
+ \`\`\`bash
639
+ npm install
640
+ npm run dev
641
+ \`\`\`
642
+
643
+ ## Build
644
+
645
+ \`\`\`bash
646
+ npm run build
647
+ \`\`\`
648
+
649
+ Use the project files as a starting point and organize pages, components, and application logic according to the actual project requirements.
650
+ `
651
+ }
652
+
653
+ function createAiDocumentationLinks(serverUrl) {
654
+ const baseUrl = String(serverUrl || DEFAULT_SERVER_URL).replace(/\/+$/, '')
655
+ const docsBaseUrl = `${baseUrl}/miniappdoc`
656
+ return {
657
+ guide: `${docsBaseUrl}/en/ai`,
658
+ llms: `${docsBaseUrl}/llms.txt`,
659
+ llmsFull: `${docsBaseUrl}/llms-full.txt`,
660
+ mcp: `${docsBaseUrl}/mcp`,
661
+ skillsIndex: `${docsBaseUrl}/.well-known/skills/index.json`,
662
+ skill: `${docsBaseUrl}/.well-known/skills/neux-docs/SKILL.md`,
663
+ }
664
+ }
665
+
666
+ function createNeuxCodeSnippets() {
667
+ return {
668
+ 'Neux Page': {
669
+ prefix: 'nxpage',
670
+ scope: 'javascript,typescript',
671
+ include: ['**/*.js', '**/*.ts'],
672
+ body: [
673
+ 'Page({',
674
+ ' data: {',
675
+ ' ${1:key}: ${2:value},',
676
+ ' },',
677
+ ' onLoad(options) {',
678
+ ' ${0}',
679
+ ' },',
680
+ '})',
681
+ ],
682
+ description: 'Create a Neux page definition',
683
+ },
684
+ 'Neux Component': {
685
+ prefix: 'nxcomponent',
686
+ scope: 'javascript,typescript',
687
+ include: ['**/*.js', '**/*.ts'],
688
+ body: [
689
+ 'Component({',
690
+ ' properties: {',
691
+ ' ${1:value}: {',
692
+ ' type: ${2:String},',
693
+ ' value: ${3:undefined},',
694
+ ' },',
695
+ ' },',
696
+ ' data: {},',
697
+ ' methods: {',
698
+ ' ${0}',
699
+ ' },',
700
+ '})',
701
+ ],
702
+ description: 'Create a Neux component definition',
703
+ },
704
+ 'setData Update': {
705
+ prefix: 'nxset',
706
+ scope: 'javascript,typescript',
707
+ include: ['**/*.js', '**/*.ts'],
708
+ body: ['this.setData({', ' ${1:key}: ${2:value},', '})$0'],
709
+ description: 'Update rendered data with setData',
710
+ },
711
+ 'Event Handler': {
712
+ prefix: 'nxevent',
713
+ scope: 'javascript,typescript',
714
+ include: ['**/*.js', '**/*.ts'],
715
+ body: ['${1:handleTap}(event) {', ' ${0}', '}'],
716
+ description: 'Create a page or component event handler',
717
+ },
718
+ 'Network Request': {
719
+ prefix: 'nxrequest',
720
+ scope: 'javascript,typescript',
721
+ include: ['**/*.js', '**/*.ts'],
722
+ body: [
723
+ 'wx.request({',
724
+ " url: '${1:https://example.com/api}',",
725
+ ' method: ${2:\'GET\'},',
726
+ ' data: ${3:{}},',
727
+ ' success(result) {',
728
+ ' ${4}',
729
+ ' },',
730
+ ' fail(error) {',
731
+ ' ${0}',
732
+ ' },',
733
+ '})',
734
+ ],
735
+ description: 'Create a wx.request call',
736
+ },
737
+ 'Toast': {
738
+ prefix: 'nxtoast',
739
+ scope: 'javascript,typescript',
740
+ include: ['**/*.js', '**/*.ts'],
741
+ body: ["nx.showToast({", " title: '${1:Done}',", ' icon: ${2:\'success\'},', '})$0'],
742
+ description: 'Show a Neux toast',
743
+ },
744
+ 'List Rendering': {
745
+ prefix: 'nxfor',
746
+ scope: 'html',
747
+ include: ['**/*.nxml'],
748
+ body: [
749
+ '<view nx:for="{{${1:items}}}" nx:key="${2:id}">',
750
+ ' <text>{{${3:item}.${4:name}}}</text>',
751
+ '</view>$0',
752
+ ],
753
+ description: 'Render a list with nx:for and nx:key',
754
+ },
755
+ 'Conditional Rendering': {
756
+ prefix: 'nxif',
757
+ scope: 'html',
758
+ include: ['**/*.nxml'],
759
+ body: ['<view nx:if="{{${1:visible}}}">', ' ${0}', '</view>'],
760
+ description: 'Render a view conditionally',
761
+ },
762
+ 'Tap Button': {
763
+ prefix: 'nxbutton',
764
+ scope: 'html',
765
+ include: ['**/*.nxml'],
766
+ body: ['<button bindtap="${1:handleTap}">${2:Button}</button>$0'],
767
+ description: 'Insert a button with a tap handler',
768
+ },
769
+ 'Page View': {
770
+ prefix: 'nxview',
771
+ scope: 'html',
772
+ include: ['**/*.nxml'],
773
+ body: ['<view class="${1:page}">', ' ${0}', '</view>'],
774
+ description: 'Insert a page view container',
775
+ },
776
+ 'NXML Image': {
777
+ prefix: 'nximage',
778
+ scope: 'html',
779
+ include: ['**/*.nxml'],
780
+ body: ['<image src="{{${1:imageUrl}}}" mode="${2:aspectFit}" />$0'],
781
+ description: 'Insert an image with a bound source',
782
+ },
783
+ 'NXML Text': {
784
+ prefix: 'nxtext',
785
+ scope: 'html',
786
+ include: ['**/*.nxml'],
787
+ body: ['<text class="${1:text}">{{${2:value}}}</text>$0'],
788
+ description: 'Insert a text binding',
789
+ },
790
+ 'NXML Input': {
791
+ prefix: 'nxinput',
792
+ scope: 'html',
793
+ include: ['**/*.nxml'],
794
+ body: ['<input value="{{${1:value}}}" placeholder="${2:Enter text}" bindinput="${3:onInput}" />$0'],
795
+ description: 'Insert a data-bound input',
796
+ },
797
+ 'NXML Include': {
798
+ prefix: 'nxinclude',
799
+ scope: 'html',
800
+ include: ['**/*.nxml'],
801
+ body: ['<include src="${1:components/header/header.nxml}" />$0'],
802
+ description: 'Include another NXML template',
803
+ },
804
+ 'NXML Template': {
805
+ prefix: 'nxtemplate',
806
+ scope: 'html',
807
+ include: ['**/*.nxml'],
808
+ body: ['<template name="${1:card}">', ' ${0}', '</template>'],
809
+ description: 'Define a reusable NXML template',
810
+ },
811
+ 'NXML Template Use': {
812
+ prefix: 'nxtemplateuse',
813
+ scope: 'html',
814
+ include: ['**/*.nxml'],
815
+ body: ['<template is="${1:card}" data="{{${2:item}}}" />$0'],
816
+ description: 'Use a reusable NXML template',
817
+ },
818
+ 'NXML Scroll View': {
819
+ prefix: 'nxscroll',
820
+ scope: 'html',
821
+ include: ['**/*.nxml'],
822
+ body: ['<scroll-view scroll-y class="${1:scroll-view}">', ' ${0}', '</scroll-view>'],
823
+ description: 'Insert a vertical scroll view',
824
+ },
825
+ 'Style Rule': {
826
+ prefix: 'nxstyle',
827
+ scope: 'css',
828
+ include: ['**/*.nxss'],
829
+ body: ['.${1:class-name} {', ' ${2:display}: ${3:block};', '}$0'],
830
+ description: 'Insert an nxss style rule',
831
+ },
832
+ 'Flex Layout': {
833
+ prefix: 'nxflex',
834
+ scope: 'css',
835
+ include: ['**/*.nxss'],
836
+ body: ['display: flex;', 'align-items: ${1:center};', 'justify-content: ${2:center};$0'],
837
+ description: 'Insert common flex layout properties',
838
+ },
839
+ 'Grid Layout': {
840
+ prefix: 'nxgrid',
841
+ scope: 'css',
842
+ include: ['**/*.nxss'],
843
+ body: ['display: grid;', 'grid-template-columns: repeat(${1:2}, 1fr);', 'gap: ${2:16rpx};$0'],
844
+ description: 'Insert a common grid layout',
845
+ },
846
+ 'Text Ellipsis': {
847
+ prefix: 'nxellipsis',
848
+ scope: 'css',
849
+ include: ['**/*.nxss'],
850
+ body: ['overflow: hidden;', 'text-overflow: ellipsis;', 'white-space: nowrap;$0'],
851
+ description: 'Insert single-line text ellipsis styles',
852
+ },
853
+ }
854
+ }
855
+
856
+ function createMcpExample() {
857
+ const ai = createAiDocumentationLinks(DEFAULT_SERVER_URL)
858
+ return {
859
+ '$schema': 'https://modelcontextprotocol.io/schema/config.json',
860
+ mcpServers: {
861
+ 'neux-docs': {
862
+ url: ai.mcp,
863
+ },
864
+ },
865
+ }
866
+ }