@neuxnet/neux-cli 0.2.4 → 0.2.5
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 +10 -0
- package/README.zh-CN.md +10 -0
- package/assets/init/neux-components.code-snippets +756 -0
- package/assets/init/neux-html-data.json +3345 -0
- package/assets/init/schemas/app.schema.json +63 -0
- package/assets/init/schemas/neux-config.schema.json +18 -0
- package/assets/init/schemas/page.schema.json +20 -0
- package/assets/init/schemas/project-config.schema.json +23 -0
- package/assets/init/schemas/project-private-config.schema.json +161 -0
- package/package.json +4 -3
- package/scripts/verify-json-schemas.mjs +40 -0
- package/src/bin/cli.js +12 -0
- package/src/core/compiler.js +11 -0
- package/src/core/init.js +56 -7
- package/src/core/project.js +138 -1
- package/src/index.js +1 -1
package/src/core/project.js
CHANGED
|
@@ -27,9 +27,16 @@ export async function inspectProject(options = {}) {
|
|
|
27
27
|
const appName = options.name || effectiveProjectConfig.projectname || effectiveProjectConfig.projectName || `App ${appId || 'unknown'}`
|
|
28
28
|
const entryPath = options.entryPath || appConfig.entryPagePath || pages[0] || 'pages/index'
|
|
29
29
|
const hasPrivateConfig = await pathExists(projectPrivateConfigPath)
|
|
30
|
+
const diagnostics = await validateProjectConfiguration({
|
|
31
|
+
projectPath,
|
|
32
|
+
miniprogramRoot,
|
|
33
|
+
sourceRoot,
|
|
34
|
+
appConfig,
|
|
35
|
+
pages,
|
|
36
|
+
})
|
|
30
37
|
|
|
31
38
|
return {
|
|
32
|
-
ok:
|
|
39
|
+
ok: diagnostics.every(item => item.severity !== 'error'),
|
|
33
40
|
command: 'inspect',
|
|
34
41
|
projectPath,
|
|
35
42
|
sourceRoot,
|
|
@@ -48,7 +55,137 @@ export async function inspectProject(options = {}) {
|
|
|
48
55
|
condition: effectiveProjectConfig.condition || {},
|
|
49
56
|
neuxCli: effectiveProjectConfig.neuxCli || {},
|
|
50
57
|
cliConfig,
|
|
58
|
+
diagnostics,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function validateProjectConfiguration({ projectPath, miniprogramRoot = '', sourceRoot, appConfig = {}, pages = [] } = {}) {
|
|
63
|
+
const diagnostics = []
|
|
64
|
+
projectPath ||= sourceRoot
|
|
65
|
+
const add = (severity, code, message, pathName, details = {}) => {
|
|
66
|
+
diagnostics.push({ severity, code, message, path: pathName, ...details })
|
|
67
|
+
}
|
|
68
|
+
const pageSet = new Set(pages.map(normalizeProjectPath))
|
|
69
|
+
const entryPagePath = normalizeProjectPath(appConfig.entryPagePath || pages[0])
|
|
70
|
+
|
|
71
|
+
if (!sourceRoot) {
|
|
72
|
+
add('error', 'NEUX_SOURCE_ROOT_INVALID', 'A source root is required for project validation', 'project.config.json')
|
|
73
|
+
return diagnostics
|
|
74
|
+
}
|
|
75
|
+
if (miniprogramRoot && path.isAbsolute(String(miniprogramRoot))) {
|
|
76
|
+
add('error', 'NEUX_MINIPROGRAM_ROOT_INVALID', 'miniprogramRoot must be a relative path', 'project.config.json', { value: miniprogramRoot })
|
|
77
|
+
}
|
|
78
|
+
const normalizedRoot = normalizeProjectPath(miniprogramRoot)
|
|
79
|
+
if (normalizedRoot && !isWithinProjectRoot(projectPath, sourceRoot)) {
|
|
80
|
+
add('error', 'NEUX_MINIPROGRAM_ROOT_OUTSIDE_PROJECT', 'miniprogramRoot resolves outside the project directory', 'project.config.json', { value: miniprogramRoot })
|
|
81
|
+
}
|
|
82
|
+
if (normalizedRoot && !(await pathExists(sourceRoot))) {
|
|
83
|
+
add('error', 'NEUX_MINIPROGRAM_ROOT_NOT_FOUND', `miniprogramRoot does not exist: ${miniprogramRoot}`, 'project.config.json', { value: miniprogramRoot })
|
|
84
|
+
}
|
|
85
|
+
if (appConfig.entryPagePath && !pageSet.has(entryPagePath)) {
|
|
86
|
+
add('error', 'NEUX_ENTRY_PAGE_NOT_DECLARED', `Entry page is not declared in pages: ${appConfig.entryPagePath}`, 'app.json', { value: appConfig.entryPagePath })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const page of pages) {
|
|
90
|
+
const normalized = normalizeProjectPath(page)
|
|
91
|
+
if (!(await hasAnyProjectFile(sourceRoot, normalized, ['.json', '.nxml', '.nxss', '.js', '.ts', '.wxml', '.wxss']))) {
|
|
92
|
+
add('error', 'NEUX_PAGE_NOT_FOUND', `Page does not exist: ${page}`, 'app.json', { value: page })
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
const pageConfigPath = path.join(sourceRoot, `${normalized}.json`)
|
|
96
|
+
const pageConfig = await readJson(pageConfigPath, {})
|
|
97
|
+
await validateUsingComponents(pageConfig.usingComponents, path.dirname(pageConfigPath), sourceRoot, `${page}.json`, add)
|
|
51
98
|
}
|
|
99
|
+
|
|
100
|
+
const tabItems = Array.isArray(appConfig.tabBar?.list) ? appConfig.tabBar.list : []
|
|
101
|
+
if (appConfig.tabBar && !Array.isArray(appConfig.tabBar.list)) {
|
|
102
|
+
add('error', 'NEUX_TABBAR_LIST_INVALID', 'tabBar.list must be an array', 'app.json')
|
|
103
|
+
}
|
|
104
|
+
if (tabItems.length > 5) add('error', 'NEUX_TABBAR_TOO_MANY_ITEMS', 'tabBar.list cannot contain more than 5 items', 'app.json', { count: tabItems.length })
|
|
105
|
+
if (appConfig.tabBar && tabItems.length === 0) add('error', 'NEUX_TABBAR_LIST_EMPTY', 'tabBar.list must contain at least one item', 'app.json')
|
|
106
|
+
for (const [index, item] of tabItems.entries()) {
|
|
107
|
+
const pagePath = normalizeProjectPath(item?.pagePath)
|
|
108
|
+
if (!pagePath) {
|
|
109
|
+
add('error', 'NEUX_TABBAR_PAGE_INVALID', `tabBar.list[${index}].pagePath must be a non-empty string`, 'app.json')
|
|
110
|
+
} else if (!pageSet.has(pagePath)) {
|
|
111
|
+
add('error', 'NEUX_TABBAR_PAGE_NOT_DECLARED', `TabBar page is not declared in pages: ${item.pagePath}`, 'app.json', { value: item.pagePath })
|
|
112
|
+
}
|
|
113
|
+
for (const field of ['iconPath', 'selectedIconPath']) {
|
|
114
|
+
if (item?.[field] && !(await pathExists(path.resolve(sourceRoot, item[field])))) {
|
|
115
|
+
add('error', 'NEUX_ASSET_NOT_FOUND', `TabBar asset does not exist: ${item[field]}`, 'app.json', { value: item[field], field })
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await validateUsingComponents(appConfig.usingComponents, sourceRoot, sourceRoot, 'app.json', add)
|
|
121
|
+
const subPackages = appConfig.subPackages || appConfig.subpackages
|
|
122
|
+
if (Array.isArray(subPackages)) {
|
|
123
|
+
const roots = new Set()
|
|
124
|
+
for (const [index, subPackage] of subPackages.entries()) {
|
|
125
|
+
const root = normalizeProjectPath(subPackage?.root)
|
|
126
|
+
if (!root) {
|
|
127
|
+
add('error', 'NEUX_SUBPACKAGE_ROOT_INVALID', `subPackages[${index}].root must be a non-empty string`, 'app.json')
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
if (roots.has(root)) add('error', 'NEUX_SUBPACKAGE_ROOT_DUPLICATE', `Duplicate subpackage root: ${root}`, 'app.json', { value: root })
|
|
131
|
+
roots.add(root)
|
|
132
|
+
for (const page of Array.isArray(subPackage.pages) ? subPackage.pages : []) {
|
|
133
|
+
const pagePath = normalizeProjectPath(path.posix.join(root, page))
|
|
134
|
+
if (pageSet.has(pagePath)) {
|
|
135
|
+
add('error', 'NEUX_PAGE_IN_MULTIPLE_PACKAGES', `Page is declared in both the main package and a subpackage: ${pagePath}`, 'app.json', { value: pagePath })
|
|
136
|
+
}
|
|
137
|
+
if (!(await hasAnyProjectFile(sourceRoot, pagePath, ['.json', '.nxml', '.nxss', '.js', '.ts', '.wxml', '.wxss']))) {
|
|
138
|
+
add('error', 'NEUX_SUBPACKAGE_PAGE_NOT_FOUND', `Subpackage page does not exist: ${pagePath}`, 'app.json', { value: pagePath })
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const rootEntries = [...roots]
|
|
143
|
+
for (let i = 0; i < rootEntries.length; i += 1) {
|
|
144
|
+
for (let j = i + 1; j < rootEntries.length; j += 1) {
|
|
145
|
+
if (rootEntries[i].startsWith(`${rootEntries[j]}/`) || rootEntries[j].startsWith(`${rootEntries[i]}/`)) {
|
|
146
|
+
add('error', 'NEUX_SUBPACKAGE_ROOT_OVERLAP', `Subpackage roots overlap: ${rootEntries[i]} and ${rootEntries[j]}`, 'app.json')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return diagnostics
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function validateUsingComponents(usingComponents, ownerDir, sourceRoot, configPath, add) {
|
|
155
|
+
if (!usingComponents || typeof usingComponents !== 'object' || Array.isArray(usingComponents)) return
|
|
156
|
+
for (const [name, target] of Object.entries(usingComponents)) {
|
|
157
|
+
if (typeof target !== 'string' || !target.trim()) {
|
|
158
|
+
add('error', 'NEUX_COMPONENT_PATH_INVALID', `Component path must be a non-empty string: ${name}`, configPath, { value: target })
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
if (target.startsWith('plugin://') || target.startsWith('npm:') || (!target.startsWith('.') && !target.startsWith('/'))) continue
|
|
162
|
+
const normalized = target.startsWith('/') ? target.slice(1) : path.relative(sourceRoot, path.resolve(ownerDir, target))
|
|
163
|
+
if (!(await hasAnyProjectFile(sourceRoot, normalizeProjectPath(normalized), ['.json', '.nxml', '.nxss', '.js', '.ts', '.wxml', '.wxss']))) {
|
|
164
|
+
add('error', 'NEUX_COMPONENT_NOT_FOUND', `Component does not exist: ${target}`, configPath, { value: target, component: name })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function hasAnyProjectFile(sourceRoot, basePath, extensions) {
|
|
170
|
+
const candidates = [basePath]
|
|
171
|
+
const basename = path.posix.basename(basePath)
|
|
172
|
+
candidates.push(path.posix.join(basePath, basename))
|
|
173
|
+
for (const candidate of candidates) {
|
|
174
|
+
for (const extension of extensions) {
|
|
175
|
+
if (await pathExists(path.join(sourceRoot, `${candidate}${extension}`))) return true
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeProjectPath(value) {
|
|
182
|
+
return String(value || '').replaceAll('\\', '/').replace(/^\/+|\/+$/g, '')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isWithinProjectRoot(projectPath, sourceRoot) {
|
|
186
|
+
const root = path.resolve(projectPath || sourceRoot)
|
|
187
|
+
const candidate = path.resolve(sourceRoot)
|
|
188
|
+
return candidate === root || candidate.startsWith(`${root}${path.sep}`)
|
|
52
189
|
}
|
|
53
190
|
|
|
54
191
|
function mergeProjectConfig(projectConfig = {}, privateConfig = {}) {
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { buildProject, buildStyleOnlyProject, buildViewOnlyProject, buildLogicOnlyProject } from './core/compiler.js'
|
|
2
2
|
export { DEFAULT_SERVER_URL, initProject } from './core/init.js'
|
|
3
|
-
export { inspectProject } from './core/project.js'
|
|
3
|
+
export { inspectProject, validateProjectConfiguration } from './core/project.js'
|
|
4
4
|
export { packProject } from './core/pack.js'
|
|
5
5
|
export { startPreview } from './core/preview-server.js'
|
|
6
6
|
export { startDev } from './core/dev.js'
|