flu-cli 2.0.6 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +17 -4
- package/config/dev.config.js +11 -11
- package/config/templates.js +10 -10
- package/index.js +554 -102
- package/lib/commands/add.js +365 -266
- package/lib/commands/assets.js +77 -78
- package/lib/commands/cache.js +29 -52
- package/lib/commands/completion.js +13 -11
- package/lib/commands/config.js +150 -44
- package/lib/commands/init-ai-base.js +89 -0
- package/lib/commands/newClack.js +269 -178
- package/lib/commands/snippets.js +58 -43
- package/lib/commands/template.js +98 -58
- package/lib/commands/templates.js +101 -57
- package/lib/commands/upload.js +313 -0
- package/lib/commands/vnext-options.js +206 -0
- package/lib/generators/model_generator.js +91 -88
- package/lib/generators/page_generator.js +100 -93
- package/lib/generators/service_generator.js +44 -39
- package/lib/generators/viewmodel_generator.js +25 -29
- package/lib/generators/widget_generator.js +30 -35
- package/lib/templates/templateCopier.js +14 -15
- package/lib/templates/templateManager.js +22 -21
- package/lib/utils/config.js +37 -20
- package/lib/utils/flutterHelper.js +2 -2
- package/lib/utils/i18n.js +3 -3
- package/lib/utils/index_updater.js +22 -23
- package/lib/utils/json-output.js +59 -0
- package/lib/utils/logger.js +17 -17
- package/lib/utils/project_detector.js +66 -66
- package/lib/utils/snippet_loader.js +21 -19
- package/lib/utils/string_helper.js +13 -13
- package/lib/utils/templateSelectorEnquirer.js +94 -108
- package/locales/en-US.json +1 -1
- package/locales/zh-CN.json +2 -2
- package/package.json +60 -57
- package/scripts/smoke-vnext-generate.mjs +1934 -0
- package/scripts/smoke-vnext-params.mjs +92 -0
- package/CLI.md +0 -513
- package/release.sh +0 -529
- package/scripts/e2e-state-tests.js +0 -116
- package/scripts/sync-base-to-templates.js +0 -108
- package/scripts/workspace-clone-all.sh +0 -101
- package/scripts/workspace-status-all.sh +0 -112
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import {
|
|
2
|
+
intro,
|
|
3
|
+
outro,
|
|
4
|
+
select,
|
|
5
|
+
text,
|
|
6
|
+
confirm,
|
|
7
|
+
spinner,
|
|
8
|
+
note,
|
|
9
|
+
isCancel,
|
|
10
|
+
cancel,
|
|
11
|
+
} from '@clack/prompts'
|
|
12
|
+
import chalk from 'chalk'
|
|
13
|
+
import fs from 'fs-extra'
|
|
14
|
+
import path from 'path'
|
|
15
|
+
import {
|
|
16
|
+
ConfigValidator,
|
|
17
|
+
AndroidBuilder,
|
|
18
|
+
IOSBuilder,
|
|
19
|
+
PgyerUploader,
|
|
20
|
+
HuaweiUploader,
|
|
21
|
+
XiaomiUploader,
|
|
22
|
+
AppleStoreUploader,
|
|
23
|
+
TestFlightUploader,
|
|
24
|
+
AdHocUploader,
|
|
25
|
+
UploadManager,
|
|
26
|
+
} from 'flu-cli-core'
|
|
27
|
+
|
|
28
|
+
import { logger } from '../utils/logger.js'
|
|
29
|
+
|
|
30
|
+
const CONFIG_FILE = 'flu_release.yaml'
|
|
31
|
+
|
|
32
|
+
export async function uploadCommand(options) {
|
|
33
|
+
console.clear()
|
|
34
|
+
intro(chalk.bgCyan(' FLU UPLOAD '))
|
|
35
|
+
|
|
36
|
+
const cwd = process.cwd()
|
|
37
|
+
const configPath = path.join(cwd, CONFIG_FILE)
|
|
38
|
+
|
|
39
|
+
// 1. Check Config
|
|
40
|
+
if (!fs.existsSync(configPath)) {
|
|
41
|
+
note(`Config file not found: ${CONFIG_FILE}`, 'Info')
|
|
42
|
+
const shouldInit = await confirm({
|
|
43
|
+
message: 'Do you want to initialize a configuration file?',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (isCancel(shouldInit) || !shouldInit) {
|
|
47
|
+
cancel('Operation cancelled.')
|
|
48
|
+
return process.exit(0)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await initConfig(configPath)
|
|
52
|
+
outro('Configuration file created. Please edit it and run upload again.')
|
|
53
|
+
return process.exit(0)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Validate Config
|
|
57
|
+
const s = spinner()
|
|
58
|
+
s.start('Validating configuration...')
|
|
59
|
+
const config = ConfigValidator.loadAndValidate(configPath)
|
|
60
|
+
|
|
61
|
+
if (!config) {
|
|
62
|
+
s.stop(chalk.red('Configuration invalid.'))
|
|
63
|
+
cancel('Please check your verification logs above.')
|
|
64
|
+
return process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
s.stop('Configuration valid.')
|
|
67
|
+
|
|
68
|
+
// 3. Runtime Specs (Version, Changelog)
|
|
69
|
+
const pubspecPath = path.join(cwd, 'pubspec.yaml')
|
|
70
|
+
let currentVersion = '1.0.0'
|
|
71
|
+
let appMetadata = {
|
|
72
|
+
version: currentVersion,
|
|
73
|
+
changelog: '',
|
|
74
|
+
buildNumber: '1',
|
|
75
|
+
platform: [],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (fs.existsSync(pubspecPath)) {
|
|
79
|
+
const pubspec = fs.readFileSync(pubspecPath, 'utf8')
|
|
80
|
+
const match = pubspec.match(/version:\s+([\d\.]+)(\+(\d+))?/)
|
|
81
|
+
if (match) {
|
|
82
|
+
currentVersion = match[1]
|
|
83
|
+
appMetadata.buildNumber = match[3] || '1'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Version Bump (Only if building)
|
|
88
|
+
if (!options.onlyUpload) {
|
|
89
|
+
const nextVersion = await handleVersionBump(currentVersion, pubspecPath)
|
|
90
|
+
if (!nextVersion) return process.exit(0)
|
|
91
|
+
appMetadata.version = nextVersion
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Changelog
|
|
95
|
+
if (options.desc) {
|
|
96
|
+
appMetadata.changelog = options.desc
|
|
97
|
+
} else {
|
|
98
|
+
const desc = await text({
|
|
99
|
+
message: 'Update Description (Changelog)',
|
|
100
|
+
placeholder: 'Fixed bugs...',
|
|
101
|
+
initialValue: '',
|
|
102
|
+
})
|
|
103
|
+
if (isCancel(desc)) return process.exit(0)
|
|
104
|
+
appMetadata.changelog = desc
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 4. Dispatch Modes
|
|
108
|
+
if (options.onlyBuild) {
|
|
109
|
+
await runBuildPhase(config, cwd, appMetadata)
|
|
110
|
+
outro('Build Only Completed.')
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (options.onlyUpload) {
|
|
115
|
+
await runUploadPhase(config, cwd, appMetadata, options.file)
|
|
116
|
+
outro('Upload Only Completed.')
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Default: Build + Upload
|
|
121
|
+
const artifactPaths = await runBuildPhase(config, cwd, appMetadata)
|
|
122
|
+
// Find artifact for upload?
|
|
123
|
+
// runBuildPhase returns paths.
|
|
124
|
+
// Assuming single artifact for now or handle array.
|
|
125
|
+
// For now, let's assume one platform build or similar.
|
|
126
|
+
// Or we handle upload platform by platform.
|
|
127
|
+
|
|
128
|
+
if (artifactPaths.length > 0) {
|
|
129
|
+
// TODO: Pass correct file to each uploader?
|
|
130
|
+
// Simple logic: Upload the file generated.
|
|
131
|
+
// If Android & iOS both built, we have 2 files.
|
|
132
|
+
// Uploader needs to know which one.
|
|
133
|
+
// "AppMetadata" could hold a map? Or we do upload inside build loop?
|
|
134
|
+
// Better: passing file path to runUploadPhase.
|
|
135
|
+
|
|
136
|
+
// Let's refine runUploadPhase to accept specific files if available.
|
|
137
|
+
// Or runUploadPhase finds them.
|
|
138
|
+
|
|
139
|
+
// Actually, Pgyer/Huawei might take APK or IPA.
|
|
140
|
+
// If we have both, we probably upload both?
|
|
141
|
+
// Pgyer takes everything. Huawei takes APK. AppStore takes IPA.
|
|
142
|
+
// I will simplify for Phase 1: Support Android mostly or sequential.
|
|
143
|
+
|
|
144
|
+
// If "platform" is filtered, execute accordingly.
|
|
145
|
+
|
|
146
|
+
for (const buildRes of artifactPaths) {
|
|
147
|
+
await runUploadPhase(config, cwd, appMetadata, buildRes.artifactPath)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
outro('All operations completed.')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function handleVersionBump(currentVersion, pubspecPath) {
|
|
155
|
+
const bumpType = await select({
|
|
156
|
+
message: `Current Version: ${currentVersion}. Upgrade?`,
|
|
157
|
+
options: [
|
|
158
|
+
{ value: 'patch', label: 'Patch (x.x.+1)' },
|
|
159
|
+
{ value: 'minor', label: 'Minor (x.+1.0)' },
|
|
160
|
+
{ value: 'major', label: 'Major (+1.0.0)' },
|
|
161
|
+
{ value: 'manual', label: 'Manual Input' },
|
|
162
|
+
{ value: 'skip', label: 'Skip' },
|
|
163
|
+
],
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
if (isCancel(bumpType)) return null
|
|
167
|
+
if (bumpType === 'skip') return currentVersion
|
|
168
|
+
|
|
169
|
+
// Logic to increment version... (Simplified for now)
|
|
170
|
+
// We update pubspec file.
|
|
171
|
+
// TODO: implement actual semver logic or replace string.
|
|
172
|
+
|
|
173
|
+
return currentVersion // Placeholder
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runBuildPhase(config, cwd, metadata) {
|
|
177
|
+
const s = spinner()
|
|
178
|
+
s.start('Building...')
|
|
179
|
+
|
|
180
|
+
const results = []
|
|
181
|
+
|
|
182
|
+
// Android
|
|
183
|
+
if (config.build?.android) {
|
|
184
|
+
const res = await AndroidBuilder.build(config.build.android, cwd)
|
|
185
|
+
if (res.success) {
|
|
186
|
+
note(`Android Artifact: ${res.artifactPath}`, 'SUCCESS')
|
|
187
|
+
results.push(res)
|
|
188
|
+
} else {
|
|
189
|
+
s.stop(chalk.red('Android Build Failed'))
|
|
190
|
+
// Continue or exit?
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// iOS
|
|
195
|
+
if (config.build?.ios) {
|
|
196
|
+
const res = await IOSBuilder.build(config.build.ios, cwd)
|
|
197
|
+
if (res.success) {
|
|
198
|
+
note(`iOS Artifact: ${res.artifactPath}`, 'SUCCESS')
|
|
199
|
+
results.push(res)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
s.stop(`Build Phase Completed. Generated ${results.length} artifacts.`)
|
|
204
|
+
return results
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function runUploadPhase(config, cwd, metadata, explicitFile) {
|
|
208
|
+
let file = explicitFile
|
|
209
|
+
if (!file) {
|
|
210
|
+
note(`No file provided for upload (Auto-find logic pending)`, 'WARN')
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create upload manager for parallel uploads
|
|
215
|
+
const manager = new UploadManager({
|
|
216
|
+
maxConcurrent: 3,
|
|
217
|
+
continueOnError: true,
|
|
218
|
+
verbose: false,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const s = spinner()
|
|
222
|
+
s.start('Uploading to all enabled platforms...')
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Execute parallel uploads
|
|
226
|
+
const report = await manager.uploadToMultiplePlatforms(file, config, metadata)
|
|
227
|
+
|
|
228
|
+
s.stop('Upload phase completed.')
|
|
229
|
+
|
|
230
|
+
// Display results
|
|
231
|
+
if (report.successCount === report.totalUploads) {
|
|
232
|
+
note(`✅ All platforms uploaded successfully!`, 'SUCCESS')
|
|
233
|
+
} else if (report.failureCount === report.totalUploads) {
|
|
234
|
+
note(`❌ Failed to upload to any platform`, 'ERROR')
|
|
235
|
+
} else {
|
|
236
|
+
note(`⚠️ Partial success: ${report.successCount}/${report.totalUploads} platforms`, 'WARN')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Ask if user wants to retry failed platforms
|
|
240
|
+
if (report.failureCount > 0) {
|
|
241
|
+
const shouldRetry = await confirm({
|
|
242
|
+
message: `Retry ${report.failureCount} failed platform(s)?`,
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
if (!isCancel(shouldRetry) && shouldRetry) {
|
|
246
|
+
const retryReport = await manager.retryFailedPlatforms(
|
|
247
|
+
report.failurePlatforms,
|
|
248
|
+
file,
|
|
249
|
+
config,
|
|
250
|
+
metadata,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
// Update summary
|
|
254
|
+
note(
|
|
255
|
+
`After retry: ${retryReport.successCount}/${retryReport.totalUploads} success`,
|
|
256
|
+
retryReport.failureCount === 0 ? 'SUCCESS' : 'WARN',
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return report
|
|
262
|
+
} catch (error) {
|
|
263
|
+
s.stop(chalk.red('Upload failed'))
|
|
264
|
+
logger.error(`Upload error: ${error.message}`)
|
|
265
|
+
throw error
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function initConfig(path) {
|
|
270
|
+
const template = `appId: com.example.app
|
|
271
|
+
version: 1.0.0
|
|
272
|
+
|
|
273
|
+
build:
|
|
274
|
+
android:
|
|
275
|
+
type: apk
|
|
276
|
+
flavor: production
|
|
277
|
+
ios:
|
|
278
|
+
scheme: Runner
|
|
279
|
+
exportMethod: ad-hoc
|
|
280
|
+
|
|
281
|
+
publish:
|
|
282
|
+
pgyer:
|
|
283
|
+
enable: true
|
|
284
|
+
apiKey: \${PGYER_API_KEY}
|
|
285
|
+
|
|
286
|
+
huawei:
|
|
287
|
+
enable: false
|
|
288
|
+
appId: ""
|
|
289
|
+
releaseType: draft
|
|
290
|
+
auth:
|
|
291
|
+
clientId: \${HUAWEI_CLIENT_ID}
|
|
292
|
+
clientSecret: \${HUAWEI_CLIENT_SECRET}
|
|
293
|
+
|
|
294
|
+
app_store:
|
|
295
|
+
enable: false
|
|
296
|
+
appId: ""
|
|
297
|
+
auth:
|
|
298
|
+
# CLI Upload (Required)
|
|
299
|
+
appleId: \${APPLE_ID}
|
|
300
|
+
appPassword: \${APPLE_APP_PASSWORD}
|
|
301
|
+
# API (Optional, for changelog updates)
|
|
302
|
+
issuerId: ""
|
|
303
|
+
keyId: ""
|
|
304
|
+
privateKeyPath: ""
|
|
305
|
+
|
|
306
|
+
xiaomi:
|
|
307
|
+
enable: false
|
|
308
|
+
auth:
|
|
309
|
+
appId: \${XIAOMI_APP_ID}
|
|
310
|
+
appKey: \${XIAOMI_APP_KEY}
|
|
311
|
+
`
|
|
312
|
+
await fs.writeFile(path, template)
|
|
313
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
export function buildVNextProjectOptions(options = {}) {
|
|
2
|
+
const helpers = parseHelpers(options.helpers)
|
|
3
|
+
const capabilities = {}
|
|
4
|
+
const examples = parseExamples(options.examples)
|
|
5
|
+
const composition = parseComposition(options)
|
|
6
|
+
|
|
7
|
+
if (options.auth === true) {
|
|
8
|
+
capabilities.auth = { enabled: true }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const serialization = parseSerialization(options)
|
|
12
|
+
if (serialization) {
|
|
13
|
+
capabilities.serialization = serialization
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = {}
|
|
17
|
+
if (Object.keys(capabilities).length > 0) result.capabilities = capabilities
|
|
18
|
+
if (Object.keys(helpers).length > 0) result.helpers = helpers
|
|
19
|
+
if (examples.length > 0) result.examples = examples
|
|
20
|
+
if (composition) result.composition = composition
|
|
21
|
+
if (composition?.architectureMode) result.architectureMode = composition.architectureMode
|
|
22
|
+
if (composition?.enableMixinOptions !== undefined) {
|
|
23
|
+
result.enableMixinOptions = composition.enableMixinOptions
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const platforms = parseList(options.platforms).map(normalizePlatformName)
|
|
27
|
+
if (platforms.length > 0) result.platforms = platforms
|
|
28
|
+
|
|
29
|
+
const flutterSdk = parseFlutterSdk(options)
|
|
30
|
+
if (flutterSdk) result.flutterSdk = flutterSdk
|
|
31
|
+
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseComposition(options) {
|
|
36
|
+
const rawProfile = normalizeRawCompositionProfile(
|
|
37
|
+
options.compositionProfile || options.composition,
|
|
38
|
+
)
|
|
39
|
+
const profile = normalizeCompositionProfile(rawProfile)
|
|
40
|
+
const architectureMode = normalizeArchitectureMode(options.architectureMode, profile)
|
|
41
|
+
const enableMixinOptions = parseEnableMixinOptions(options.enableMixinOptions, rawProfile)
|
|
42
|
+
|
|
43
|
+
if (!profile && !architectureMode && enableMixinOptions === undefined) return null
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
profile: profile || 'base',
|
|
47
|
+
architectureMode: architectureMode || 'mvvm',
|
|
48
|
+
...(enableMixinOptions !== undefined ? { enableMixinOptions } : {}),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseHelpers(value) {
|
|
53
|
+
const helperNames = parseList(value)
|
|
54
|
+
const helpers = {}
|
|
55
|
+
|
|
56
|
+
for (const helperName of helperNames) {
|
|
57
|
+
const normalized = normalizeHelperName(helperName)
|
|
58
|
+
if (normalized) helpers[normalized] = true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return helpers
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseExamples(value) {
|
|
65
|
+
return parseList(value)
|
|
66
|
+
.map(normalizeExampleName)
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.filter((item, index, array) => array.indexOf(item) === index)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseSerialization(options) {
|
|
72
|
+
if (!options.serialization) return null
|
|
73
|
+
|
|
74
|
+
const strategy = String(options.serialization).trim()
|
|
75
|
+
const normalized = strategy.toLowerCase()
|
|
76
|
+
if (!normalized || ['false', 'off', 'none', 'no'].includes(normalized)) {
|
|
77
|
+
return { enabled: false, strategy: 'manual', buildRunner: false }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
enabled: true,
|
|
82
|
+
strategy: normalized,
|
|
83
|
+
buildRunner:
|
|
84
|
+
options.serializationBuildRunner === true ||
|
|
85
|
+
normalized === 'json_serializable' ||
|
|
86
|
+
normalized === 'freezed',
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseFlutterSdk(options) {
|
|
91
|
+
const mode = options.flutterSdk
|
|
92
|
+
const version = options.flutterSdkVersion
|
|
93
|
+
const flutterBin = options.flutterBin
|
|
94
|
+
|
|
95
|
+
if (!mode && !version && !flutterBin) return null
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
mode: mode || (flutterBin ? 'custom' : 'system'),
|
|
99
|
+
version,
|
|
100
|
+
flutterBin,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseEnableMixinOptions(value, profile) {
|
|
105
|
+
if (value === undefined) {
|
|
106
|
+
return typeof profile === 'string' ? profile.endsWith('mixins') : undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof value === 'boolean') return value
|
|
110
|
+
|
|
111
|
+
const normalized = String(value).trim().toLowerCase()
|
|
112
|
+
if (['true', '1', 'yes', 'on', 'enabled'].includes(normalized)) return true
|
|
113
|
+
if (['false', '0', 'no', 'off', 'disabled'].includes(normalized)) return false
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseList(value) {
|
|
118
|
+
if (!value) return []
|
|
119
|
+
|
|
120
|
+
const values = Array.isArray(value) ? value : [value]
|
|
121
|
+
return values
|
|
122
|
+
.flatMap((item) => String(item).split(','))
|
|
123
|
+
.map((item) => item.trim())
|
|
124
|
+
.filter(Boolean)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeHelperName(value) {
|
|
128
|
+
const normalized = String(value).trim().toLowerCase().replace(/[-_]/g, '')
|
|
129
|
+
|
|
130
|
+
const map = {
|
|
131
|
+
payment: 'payment',
|
|
132
|
+
pay: 'payment',
|
|
133
|
+
webview: 'webview',
|
|
134
|
+
web: 'webview',
|
|
135
|
+
permission: 'permission',
|
|
136
|
+
permissions: 'permission',
|
|
137
|
+
imagepicker: 'imagePicker',
|
|
138
|
+
image: 'imagePicker',
|
|
139
|
+
imagepick: 'imagePicker',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return map[normalized]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizePlatformName(value) {
|
|
146
|
+
const normalized = String(value).trim().toLowerCase()
|
|
147
|
+
if (normalized === 'harmonyos') return 'harmony'
|
|
148
|
+
return normalized
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeArchitectureMode(value, profile) {
|
|
152
|
+
const normalized = String(value || '')
|
|
153
|
+
.trim()
|
|
154
|
+
.toLowerCase()
|
|
155
|
+
if (normalized === 'native') return 'native'
|
|
156
|
+
if (normalized === 'mvvm') return 'mvvm'
|
|
157
|
+
return 'mvvm'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeRawCompositionProfile(value) {
|
|
161
|
+
const normalized = String(value || '')
|
|
162
|
+
.trim()
|
|
163
|
+
.toLowerCase()
|
|
164
|
+
if (['base', 'base_mixins', 'pure_mixins', 'pure'].includes(normalized)) {
|
|
165
|
+
return normalized
|
|
166
|
+
}
|
|
167
|
+
return ''
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeCompositionProfile(value) {
|
|
171
|
+
return value
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normalizeExampleName(value) {
|
|
175
|
+
const normalized = String(value)
|
|
176
|
+
.trim()
|
|
177
|
+
.toLowerCase()
|
|
178
|
+
.replace(/[-_\s]/g, '')
|
|
179
|
+
|
|
180
|
+
const map = {
|
|
181
|
+
// 默认用户列表入口已属于模板基座;旧 commonListHome 入参只做兼容清理。
|
|
182
|
+
commonlisthome: null,
|
|
183
|
+
commonlist: null,
|
|
184
|
+
listhome: null,
|
|
185
|
+
networkrequest: 'networkGallery',
|
|
186
|
+
request: 'networkGallery',
|
|
187
|
+
networkexamples: 'networkGallery',
|
|
188
|
+
networkgallery: 'networkGallery',
|
|
189
|
+
gallery: 'networkGallery',
|
|
190
|
+
webviewbasic: 'webviewBasic',
|
|
191
|
+
webview: 'webviewBasic',
|
|
192
|
+
web: 'webviewBasic',
|
|
193
|
+
permissionbasic: 'permissionBasic',
|
|
194
|
+
permission: 'permissionBasic',
|
|
195
|
+
permissions: 'permissionBasic',
|
|
196
|
+
imagepickerbasic: 'imagePickerBasic',
|
|
197
|
+
imagepicker: 'imagePickerBasic',
|
|
198
|
+
imagepick: 'imagePickerBasic',
|
|
199
|
+
image: 'imagePickerBasic',
|
|
200
|
+
paymentshell: 'paymentShell',
|
|
201
|
+
payment: 'paymentShell',
|
|
202
|
+
pay: 'paymentShell',
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return map[normalized]
|
|
206
|
+
}
|