@ohos-cpf/3rdloop 0.0.1 → 0.0.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.
- package/README.md +245 -425
- package/bin/3rdloop.mjs +46 -46
- package/lib/cli.js +781 -740
- package/lib/config-cmd.js +452 -0
- package/lib/config.js +318 -317
- package/lib/doctor.js +113 -113
- package/lib/exit-codes.js +30 -30
- package/lib/opencode.js +155 -155
- package/lib/orch.js +426 -426
- package/lib/queries.js +263 -263
- package/lib/step.js +325 -325
- package/lib/ui.js +222 -222
- package/lib/update.js +280 -0
- package/lib/workflow.js +308 -290
- package/lib/workflows.js +131 -131
- package/package.json +50 -50
- package/vendor/Server/Agent/SkillSelector/README.md +343 -343
- package/vendor/Server/Agent/SkillSelector/core/Agent.js +207 -207
- package/vendor/Server/Agent/SkillSelector/index.js +14 -14
- package/vendor/Server/Agent/SkillSelector/llm/llmClient.js +313 -313
- package/vendor/Server/Agent/SkillSelector/llm/prompts.js +161 -161
- package/vendor/Server/Agent/SkillSelector/llm/reactSteps.js +144 -144
- package/vendor/Server/Agent/SkillSelector/matcher/fuzzyMatcher.js +433 -433
- package/vendor/Server/Agent/SkillSelector/scanner/skillScanner.js +382 -382
- package/vendor/Server/Agent/SkillSelector/utils/actionWords.js +59 -59
- package/vendor/Server/Brain/AnalysisStrategies.js +422 -422
- package/vendor/Server/Brain/Brain.js +686 -686
- package/vendor/Server/Brain/DecompositionStrategies.js +621 -621
- package/vendor/Server/Brain/PlanPersistence.js +196 -196
- package/vendor/Server/Brain/SkillExecutor.js +251 -251
- package/vendor/Server/Brain/index.js +11 -11
- package/vendor/Server/CLI/cli.js +275 -243
- package/vendor/Server/CLI/deveco-code/index.js +71 -0
- package/vendor/Server/CLI/deveco-code.js +5 -0
- package/vendor/Server/CLI/opencode/index.js +817 -804
- package/vendor/Server/CLI/opencode.js +5 -5
- package/vendor/Server/DbUse/MessageArchive.js +61 -61
- package/vendor/Server/DbUse/StorageManager.js +107 -107
- package/vendor/Server/DbUse/adapters/BaseAdapter.js +94 -94
- package/vendor/Server/DbUse/adapters/FileAdapter.js +138 -138
- package/vendor/Server/DbUse/adapters/JsonAdapter.js +138 -138
- package/vendor/Server/DbUse/adapters/JsonlAdapter.js +114 -114
- package/vendor/Server/DbUse/adapters/SqliteAdapter.js +157 -157
- package/vendor/Server/DbUse/index.js +160 -160
- package/vendor/Server/DbUse/stores/SessionStore.js +279 -279
- package/vendor/Server/DbUse/stores/TaskExecutionStore.js +133 -133
- package/vendor/Server/DbUse/stores/TaskRegistryStore.js +262 -262
- package/vendor/Server/DbUse/stores/TaskStepStore.js +279 -279
- package/vendor/Server/FlexRunner/FlexRunner.js +1759 -1759
- package/vendor/Server/FlexRunner/index.js +12 -12
- package/vendor/Server/Knowledge/KnowledgeImporter.js +523 -523
- package/vendor/Server/Knowledge/KnowledgeManager.js +341 -341
- package/vendor/Server/Knowledge/index.js +33 -33
- package/vendor/Server/LoopEngine/LoopEngine.js +1133 -1133
- package/vendor/Server/LoopEngine/index.js +11 -11
- package/vendor/Server/Orchestrator/Orchestrator.js +0 -3
- package/vendor/Server/Orchestrator/StepNode.js +312 -312
- package/vendor/Server/Orchestrator/index.js +13 -13
- package/vendor/Server/Routes/controllers/BrainController.js +241 -241
- package/vendor/Server/Routes/controllers/FlexRunnerController.js +1125 -1125
- package/vendor/Server/Routes/controllers/HealthController.js +44 -44
- package/vendor/Server/Routes/controllers/KnowledgeImportController.js +355 -355
- package/vendor/Server/Routes/controllers/LoopEngineController.js +788 -788
- package/vendor/Server/Routes/controllers/StatsController.js +57 -57
- package/vendor/Server/Routes/controllers/StreamController.js +67 -67
- package/vendor/Server/Routes/library/logger.js +179 -179
- package/vendor/Server/Routes/library/zipArchive.js +172 -172
- package/vendor/Server/Routes/router/Router.js +204 -204
- package/vendor/Server/Routes/routes/brain.js +47 -47
- package/vendor/Server/Routes/routes/flexrunner.js +72 -72
- package/vendor/Server/Routes/routes/health.js +28 -28
- package/vendor/Server/Routes/routes/index.js +57 -57
- package/vendor/Server/Routes/routes/knowledge.js +55 -55
- package/vendor/Server/Routes/routes/loopengine.js +58 -58
- package/vendor/Server/Routes/routes/orchestrator.js +61 -61
- package/vendor/Server/Routes/routes/registry.js +97 -97
- package/vendor/Server/Routes/routes/stats.js +23 -23
- package/vendor/Server/Routes/routes/stream.js +30 -30
- package/vendor/Server/Routes/server.js +203 -202
- package/vendor/Server/Routes/sse/SseHub.js +308 -308
- package/vendor/Server/Skills/acceptance-check/SKILL.md +191 -174
- package/vendor/Server/Skills/acceptance-check/references/PHASE1_ARTIFACT_COLLECTION.md +52 -52
- package/vendor/Server/Skills/acceptance-check/references/PHASE2_CRITERIA_EVALUATION.md +77 -77
- package/vendor/Server/Skills/acceptance-check/references/PHASE2_EVIDENCE_VERIFICATION.md +70 -0
- package/vendor/Server/Skills/arkts-code-check/SKILL.md +320 -313
- package/vendor/Server/Skills/arkts-code-check/references/CODE_CHECK_GUIDE.md +210 -159
- package/vendor/Server/Skills/arkts-code-check/references/FILE_TYPE_GUIDE.md +28 -28
- package/vendor/Server/Skills/arkts-code-check/references/PHASE1_INFO_COLLECTION.md +233 -222
- package/vendor/Server/Skills/arkts-code-check/references/REPORT_TEMPLATE.md +219 -219
- package/vendor/Server/Skills/arkts-library-compile-validate/SKILL.md +328 -328
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/build.ps1 +93 -93
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/code-linter.cjs +432 -432
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/migrate.cjs +851 -851
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/ohpm-switch.cjs +788 -788
- package/vendor/Server/Skills/arkts-library-demo-code/SKILL.md +291 -291
- package/vendor/Server/Skills/arkts-library-demo-code/references/API_SIGNATURE_CHECKS.md +68 -68
- package/vendor/Server/Skills/arkts-library-demo-code/references/BUILD_VALIDATION.md +184 -184
- package/vendor/Server/Skills/arkts-library-demo-code/references/CODE_STANDARDS.md +337 -337
- package/vendor/Server/Skills/arkts-library-demo-code/references/EXAMPLES.md +400 -400
- package/vendor/Server/Skills/arkts-library-demo-code/references/SYSTEM_API_LOOKUP.md +98 -98
- package/vendor/Server/Skills/arkts-library-demo-code/references/TEMPLATE.md +544 -544
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/build.ps1 +93 -93
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/code-linter.cjs +432 -432
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/migrate.cjs +851 -851
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/ohpm-switch.cjs +788 -788
- package/vendor/Server/Skills/arkts-library-demo-coverage/SKILL.md +174 -174
- package/vendor/Server/Skills/arkts-library-demo-scenario/SKILL.md +321 -321
- package/vendor/Server/Skills/arkts-library-demo-scenario/references/EXAMPLES.md +100 -100
- package/vendor/Server/Skills/arkts-library-demo-scenario/references/TEMPLATE.md +185 -185
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/SKILL.md +233 -233
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/assets/REPORT_TEMPLATE.md +102 -102
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/CHECK_DIMENSIONS.md +89 -89
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/COMPLIANCE_CHECKLIST.md +62 -62
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/KB_QUERY_GUIDE.md +107 -107
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/MCP_TOOL_DISCOVERY.md +124 -124
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/REPORT_TEMPLATE.md +90 -90
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/SCORING_FORMULA.md +57 -57
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/scripts/validate-skill.cjs +259 -259
- package/vendor/Server/Skills/arkts-library-interface-spec/SKILL.md +97 -97
- package/vendor/Server/Skills/arkts-library-memory-leak-check/SKILL.md +391 -391
- package/vendor/Server/Skills/arkts-library-source-clone/SKILL.md +113 -113
- package/vendor/Server/Skills/arkts-library-stability-check/SKILL.md +312 -312
- package/vendor/Server/Skills/arkts-library-stability-check/assets/REPORT_TEMPLATE.md +154 -154
- package/vendor/Server/Skills/arkts-library-stability-check/references/CHECKLIST.md +165 -165
- package/vendor/Server/Skills/arkts-library-stability-check/references/CODE_EXAMPLES.md +308 -308
- package/vendor/Server/Skills/arkts-library-stability-check/references/SCORING_RULES.md +90 -90
- package/vendor/Server/Skills/arkts-library-stability-check/scripts/validate-skill.cjs +282 -282
- package/vendor/Server/Skills/arkts-library-test-coverage-check/SKILL.md +360 -360
- package/vendor/Server/Skills/arkts-library-test-coverage-check/assets/REPORT_TEMPLATE.md +145 -145
- package/vendor/Server/Skills/arkts-library-test-coverage-check/references/CHECKLIST.md +252 -252
- package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +114 -114
- package/vendor/Server/Skills/arkts-library-test-coverage-check/scripts/validate-skill.cjs +262 -262
- package/vendor/Server/Skills/arkts-library-xts-code/SKILL.md +455 -455
- package/vendor/Server/Skills/arkts-library-xts-code/assets/report-template.md +60 -60
- package/vendor/Server/Skills/arkts-library-xts-code/references/ACCESS_MODIFIER_PRECHECK.md +76 -76
- package/vendor/Server/Skills/arkts-library-xts-code/references/BUILD_VERIFICATION.md +200 -200
- package/vendor/Server/Skills/arkts-library-xts-code/references/CODE_STANDARDS.md +518 -518
- package/vendor/Server/Skills/arkts-library-xts-code/references/SUBAGENT_PROMPT_TEMPLATE.md +276 -276
- package/vendor/Server/Skills/arkts-library-xts-code/references/SYSTEM_API_LOOKUP.md +92 -92
- package/vendor/Server/Skills/arkts-library-xts-code/references/XTS_TEMPLATE.md +767 -767
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/build.ps1 +93 -93
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/code-linter.cjs +432 -432
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/migrate.cjs +851 -851
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/ohpm-switch.cjs +788 -788
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/precheck-access-modifiers.cjs +221 -221
- package/vendor/Server/Skills/arkts-library-xts-code-fix/SKILL.md +228 -228
- package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +97 -97
- package/vendor/Server/Skills/arkts-library-xts-doc/SKILL.md +194 -194
- package/vendor/Server/Skills/arkts-library-xts-doc/references/EXAMPLES.md +575 -575
- package/vendor/Server/Skills/arkts-library-xts-doc/references/TEMPLATE.md +574 -574
- package/vendor/Server/Skills/arkts-library-xts-doc/references/TESTABILITY_GUIDE.md +182 -182
- package/vendor/Server/Skills/arkts-library-xts-execution/SKILL.md +490 -490
- package/vendor/Server/Skills/arkts-library-xts-execution/references/error-fixing-examples.md +217 -217
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/build.ps1 +93 -93
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/code-linter.cjs +432 -432
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/migrate.cjs +851 -851
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/ohpm-switch.cjs +788 -788
- package/vendor/Server/Skills/flutter-code-check/SKILL.md +299 -299
- package/vendor/Server/Skills/flutter-code-check/assets/OUTPUT_TEMPLATE.md +311 -311
- package/vendor/Server/Skills/flutter-code-check/references/CHECKLIST.md +190 -190
- package/vendor/Server/Skills/flutter-code-check/references/PLATFORMS.md +133 -133
- package/vendor/Server/Skills/flutter-code-check/scripts/validate-skill.cjs +259 -259
- package/vendor/Server/Skills/flutter-interface/README.md +75 -75
- package/vendor/Server/Skills/flutter-interface/SKILL.md +291 -291
- package/vendor/Server/Skills/flutter-interface/references/EXAMPLES.md +336 -336
- package/vendor/Server/Skills/flutter-interface/references/GIT_CLONE_GUIDE.md +190 -190
- package/vendor/Server/Skills/flutter-interface/references/SCRIPT_REFERENCE.md +258 -258
- package/vendor/Server/Skills/flutter-interface/references/TEMPLATE.md +254 -254
- package/vendor/Server/Skills/flutter-library-consistency-check/SKILL.md +517 -517
- package/vendor/Server/Skills/flutter-library-consistency-check/references/CHECKLIST.md +381 -381
- package/vendor/Server/Skills/flutter-library-consistency-check/references/FUNCTION_COMPARISON_GUIDE.md +341 -341
- package/vendor/Server/Skills/flutter-library-consistency-check/references/PLATFORM_LIMITATION_GUIDE.md +71 -71
- package/vendor/Server/Skills/flutter-library-documentation-quality-check/SKILL.md +518 -518
- package/vendor/Server/Skills/flutter-library-documentation-quality-check/references/CHECKLIST.md +406 -406
- package/vendor/Server/Skills/flutter-library-memory-leak-check/SKILL.md +652 -652
- package/vendor/Server/Skills/flutter-library-memory-leak-check/references/CHECKLIST.md +396 -396
- package/vendor/Server/Skills/flutter-library-stability-check/SKILL.md +744 -744
- package/vendor/Server/Skills/flutter-library-stability-check/references/CHECKLIST.md +720 -720
- package/vendor/Server/Skills/flutter-library-test-coverage-check/SKILL.md +414 -414
- package/vendor/Server/Skills/flutter-library-test-coverage-check/assets/REPORT_TEMPLATE.md +150 -150
- package/vendor/Server/Skills/flutter-library-test-coverage-check/references/CHECKLIST.md +304 -304
- package/vendor/Server/Skills/flutter-library-test-coverage-check/references/SCORING_RULES.md +265 -265
- package/vendor/Server/Skills/flutter-library-test-coverage-check/references/SCRIPT_USAGE.md +286 -286
- package/vendor/Server/Skills/knowledge-import/SKILL.md +383 -383
- package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +165 -165
- package/vendor/Server/Skills/knowledge-import/references/SKILL_LOCATOR_GUIDE.md +91 -91
- package/vendor/Server/Skills/knowledge-import/references/SKILL_OPTIMIZATION_GUIDE.md +127 -127
- package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +453 -301
- package/vendor/Server/Skills/ohos-lib-pr-push/assets/PR_CHECKLIST.md +151 -0
- package/vendor/Server/Skills/ohos-lib-pr-push/assets/PR_ISSUE_TEMPLATE.md +257 -105
- package/vendor/Server/Skills/ohos-lib-pr-push/references/GITCODE_API.md +191 -127
- package/vendor/Server/Skills/ohos-lib-pr-push/scripts/gitcode-api.cjs +317 -208
- package/vendor/Server/Skills/rn-code-check/SKILL.md +320 -320
- package/vendor/Server/Skills/rn-code-check/references/API-GUIDE.md +234 -234
- package/vendor/Server/Skills/rn-code-check/references/CHECKLIST.md +188 -188
- package/vendor/Server/Skills/rn-code-check/references/REPORT-TEMPLATE.md +246 -246
- package/vendor/Server/Skills/rn-lib-add-example/SKILL.md +198 -198
- package/vendor/Server/Skills/rn-lib-add-example/references/APP_RENAME_GUIDE.md +40 -40
- package/vendor/Server/Skills/rn-lib-add-example/references/SHELL_ADAPTATION_CHECKLIST.md +78 -78
- package/vendor/Server/Skills/rn-lib-add-example/references/TEMPLATE_REPO_GUIDE.md +70 -70
- package/vendor/Server/Skills/rn-lib-add-example/references/VERIFICATION_GUIDE.md +47 -47
- package/vendor/Server/Skills/rn-lib-add-example/scripts/create-example.cjs +252 -252
- package/vendor/Server/Skills/rn-lib-update-doc/SKILL.md +176 -176
- package/vendor/Server/Skills/rn-lib-update-doc/config.json +5 -5
- package/vendor/Server/Skills/rn-lib-update-doc/references/changelog-template.md +35 -35
- package/vendor/Server/Skills/rn-lib-update-doc/references/format-diff.md +137 -137
- package/vendor/Server/Skills/rn-lib-update-doc/references/link-registry.json +33 -33
- package/vendor/Server/Skills/rn-lib-update-doc/references/model-0.4.2-overview.md +40 -40
- package/vendor/Server/Skills/rn-lib-update-doc/references/model-0.4.2.md +429 -429
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/detect-readme-format.cjs +105 -105
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/find-usage-doc.cjs +90 -90
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/link-verify.cjs +213 -213
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-changelog.cjs +73 -73
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-package-meta.cjs +258 -258
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-readme-links.cjs +341 -341
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/resolve-usage-docs.cjs +67 -67
- package/vendor/Server/Skills/rn-library-consistency-check/SKILL.md +299 -299
- package/vendor/Server/Skills/rn-library-consistency-check/assets/REPORT_TEMPLATE.md +293 -293
- package/vendor/Server/Skills/rn-library-consistency-check/references/ARKTS_COMPONENT_CHECK_GUIDE.md +857 -857
- package/vendor/Server/Skills/rn-library-consistency-check/references/ARKTS_TURBOMODULE_CHECK_GUIDE.md +856 -856
- package/vendor/Server/Skills/rn-library-consistency-check/references/CHECKLIST.md +157 -157
- package/vendor/Server/Skills/rn-library-consistency-check/references/CPP_COMPONENT_CHECK_GUIDE.md +801 -801
- package/vendor/Server/Skills/rn-library-consistency-check/references/CPP_TURBOMODULE_CHECK_GUIDE.md +683 -683
- package/vendor/Server/Skills/rn-library-consistency-check/references/FUNCTION_COMPARISON_GUIDE.md +233 -233
- package/vendor/Server/Skills/rn-library-consistency-check/references/NATIVE_COMPONENT_IDENTIFICATION.md +1373 -1373
- package/vendor/Server/Skills/rn-library-consistency-check/references/SCORING_STANDARD.md +220 -220
- package/vendor/Server/Skills/rn-library-consistency-check/scripts/validate-skill.py +245 -245
- package/vendor/Server/Skills/rn-library-documentation-quality-check/SKILL.md +263 -263
- package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/MODEL.md +414 -414
- package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/MODEL_en.md +413 -413
- package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/REPORT_TEMPLATE.md +311 -311
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/CHECK_CHECKLIST.md +355 -355
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/CROSS_VALIDATION_GUIDE.md +115 -115
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/DOC_COLLECTION_GUIDE.md +139 -139
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/EXAMPLES.md +586 -586
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/GOTCHAS.md +57 -57
- package/vendor/Server/Skills/rn-library-documentation-quality-check/scripts/validate-skill.py +259 -259
- package/vendor/Server/Skills/rn-library-interface-analyzer/SKILL.md +301 -301
- package/vendor/Server/Skills/rn-library-interface-analyzer/assets/REPORT_TEMPLATE.md +148 -148
- package/vendor/Server/Skills/rn-library-interface-analyzer/references/DOC_ANNOTATION_GUIDE.md +118 -118
- package/vendor/Server/Skills/rn-library-interface-analyzer/references/SCRIPT_USAGE_GUIDE.md +168 -168
- package/vendor/Server/Skills/rn-library-interface-analyzer/references/URL_PARSING_RULES.md +176 -176
- package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +274 -274
- package/vendor/Server/Skills/rn-library-issue-generalizer/assets/case-template.md +45 -45
- package/vendor/Server/Skills/rn-library-issue-generalizer/assets/rule-template.md +25 -25
- package/vendor/Server/Skills/rn-library-issue-generalizer/references/PR_FETCH_GUIDE.md +61 -61
- package/vendor/Server/Skills/rn-library-issue-generalizer/references/RULE_TEMPLATE_GUIDE.md +165 -165
- package/vendor/Server/Skills/rn-library-issue-generalizer/scripts/fetch-pr-info.py +239 -239
- package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +263 -263
- package/vendor/Server/Skills/rn-library-known-issue-check/assets/REPORT_TEMPLATE.md +96 -96
- package/vendor/Server/Skills/rn-library-known-issue-check/references/COMPLIANCE_CHECKLIST.md +57 -57
- package/vendor/Server/Skills/rn-library-known-issue-check/references/INSPECTION_GUIDE.md +99 -99
- package/vendor/Server/Skills/rn-library-known-issue-check/references/PRESCREEN_GUIDE.md +64 -64
- package/vendor/Server/Skills/rn-library-known-issue-check/references/SOURCE_PREP_GUIDE.md +74 -74
- package/vendor/Server/Skills/rn-library-known-issue-check/scripts/validate-known-issue-report.py +164 -164
- package/vendor/Server/Skills/rn-library-memory-leak-check/SKILL.md +356 -339
- package/vendor/Server/Skills/rn-library-memory-leak-check/assets/REPORT_TEMPLATE.md +85 -83
- package/vendor/Server/Skills/rn-library-memory-leak-check/references/CHECKLIST.md +905 -859
- package/vendor/Server/Skills/rn-library-memory-leak-check/references/HARMONY_CODE_PATTERNS.md +124 -122
- package/vendor/Server/Skills/rn-library-memory-leak-check/scripts/scan_harmony_code.py +287 -279
- package/vendor/Server/Skills/rn-library-stability-check/SKILL.md +273 -273
- package/vendor/Server/Skills/rn-library-stability-check/assets/REPORT_TEMPLATE.md +129 -129
- package/vendor/Server/Skills/rn-library-stability-check/references/CHECKLIST.md +581 -581
- package/vendor/Server/Skills/rn-library-stability-check/references/SCORING_RULES.md +138 -138
- package/vendor/Server/Skills/rn-library-stability-check/scripts/scan-harmony-code.py +168 -168
- package/vendor/Server/Skills/rn-library-test-coverage-check/SKILL.md +526 -506
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/CHECKLIST.md +321 -280
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/DEMO_LOCATION.md +221 -221
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/REPORT_TEMPLATE.md +220 -209
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/SCRIPT_USAGE.md +325 -325
- package/vendor/Server/Skills/rn-ohos-xts-generator/SKILL.md +279 -279
- package/vendor/Server/Skills/rn-ohos-xts-generator/assets/TEST_FILE_TEMPLATES.md +404 -404
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/BUILD_TROUBLESHOOTING.md +185 -185
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/BUILD_WORKFLOW.md +265 -265
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/COMMON_CONFIG.md +189 -189
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/CONFIG_GUIDE.md +203 -203
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/COVERAGE_ANALYZER.md +147 -147
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/DESIGN_DOC_GUIDE.md +148 -148
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/GENERATION_FLOW.md +165 -165
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/GENERATION_STRATEGY.md +195 -195
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/HARMONY_SCANNER.md +249 -249
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/JEST_FRAMEWORK.md +235 -235
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/PLATFORM_COMPAT.md +161 -161
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/RN_TEST_PATTERNS.md +413 -413
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/SOURCE_PARSER.md +235 -235
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/TEST_CONVENTIONS.md +245 -245
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/TROUBLESHOOTING.md +282 -282
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/USAGE_GUIDE.md +251 -251
- package/vendor/Server/Skills/rnoh-cptf-api-test/SKILL.md +469 -469
- package/vendor/Server/Skills/rnoh-cptf-api-test/assets/CPTF_TEST_TEMPLATE.md +310 -310
- package/vendor/Server/Skills/rnoh-cptf-api-test/assets/DEMO_PAGE_TEMPLATE.md +303 -303
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/CPTF_TEST_GENERATION_GUIDE.md +421 -421
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/DEMO_GENERATION_GUIDE.md +358 -358
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/QUALITY_CHECKLIST.md +114 -114
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/SPEC_PARSING_GUIDE.md +274 -274
- package/vendor/Server/Skills/rnoh-cptf-api-test/scripts/validate-rnoh-cptf-api-test.sh +185 -185
- package/vendor/Server/Skills/rnoh-cptf-demo-test/SKILL.md +191 -191
- package/vendor/Server/Skills/rnoh-cptf-demo-test/assets/TEST_TEMPLATE.md +216 -216
- package/vendor/Server/Skills/rnoh-cptf-demo-test/references/CPTF_SETUP.md +107 -107
- package/vendor/Server/Skills/rnoh-cptf-demo-test/references/DEMO_ANALYSIS.md +120 -120
- package/vendor/Server/Skills/rnoh-cptf-demo-test/scripts/validate-rnoh-cptf-test.sh +108 -108
- package/vendor/Server/Skills/rnoh-cptf-test-run/SKILL.md +237 -237
- package/vendor/Server/Skills/rnoh-cptf-test-run/references/DEBUG_PLAYBOOK.md +261 -261
- package/vendor/Server/Skills/rnoh-cptf-test-run/scripts/validate-run-env.sh +112 -112
- package/vendor/Server/Skills/rnoh-lib-code-adapt/SKILL.md +497 -497
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/adapt-checklist.md +270 -270
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/api-migration-checklist.md +164 -164
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/common-compile-errors.md +556 -556
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/deveco-cli-tools.md +459 -459
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/edge-cases.md +65 -65
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/har-tgz-build.md +512 -512
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/version-compat-matrix.md +132 -132
- package/vendor/Server/Skills/rnoh-lib-code-adapt/scripts/auto-integrate.sh +424 -424
- package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +137 -137
- package/vendor/Server/Skills/rnoh-lib-demo-create/SKILL.md +306 -306
- package/vendor/Server/Skills/rnoh-lib-demo-create/assets/DEMO_PAGE_TEMPLATE.md +485 -485
- package/vendor/Server/Skills/rnoh-lib-demo-create/assets/INDEX_TEMPLATE.md +172 -172
- package/vendor/Server/Skills/rnoh-lib-demo-create/references/DEMO_CODE_RULES.md +422 -422
- package/vendor/Server/Skills/rnoh-lib-demo-create/references/QUALITY_CHECKLIST.md +95 -95
- package/vendor/Server/Skills/rnoh-lib-demo-create/references/SOURCE_EXPLORATION.md +169 -169
- package/vendor/Server/Skills/rnoh-lib-demo-doc/SKILL.md +554 -554
- package/vendor/Server/Skills/rnoh-lib-demo-doc/assets/DEMO_DOC_TEMPLATE.md +106 -106
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/COVERAGE_INCREMENTAL.md +216 -216
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/DEMO_DESIGN_RULES.md +413 -413
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/OUTPUT_FORMAT.md +269 -269
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/QUALITY_CHECKLIST.md +98 -98
- package/vendor/Server/Skills/rnoh-lib-func-test/SKILL.md +337 -337
- package/vendor/Server/Skills/rnoh-lib-rnt-search/SKILL.md +722 -722
- package/vendor/Server/Skills/rnoh-lib-rnt-search/assets/PORT_REPORT_TEMPLATE.md +219 -219
- package/vendor/Server/Skills/rnoh-lib-rnt-search/references/DEMO_PORTING_GUIDE.md +345 -345
- package/vendor/Server/Skills/rnoh-lib-rnt-search/references/PORT_VERIFICATION.md +269 -269
- package/vendor/Server/Skills/rnoh-lib-rnt-search/references/RNT_PROJECT_STRUCTURE.md +173 -173
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/analyze-demo-deps.cjs +267 -267
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/detect-rnoh-version.cjs +174 -174
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/find-rnt-demo.sh +111 -111
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/port-demo.sh +186 -186
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/read-lib-meta.cjs +150 -150
- package/vendor/Server/Skills/rnoh-lib-test-align/SKILL.md +296 -296
- package/vendor/Server/Skills/rnoh-lib-test-align/references/jest-troubleshooting.md +371 -371
- package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +212 -212
- package/vendor/Server/Skills/rnoh-lib-test-align/references/runtime-troubleshooting.md +345 -345
- package/vendor/Server/Skills/rnoh-lib-test-align/references/unit-test-template.md +223 -223
- package/vendor/Server/Skills/rnoh-lib-test-align/scripts/port-test-demo.sh +1053 -1053
- package/vendor/Server/Skills/rnoh-lib-version-diff/SKILL.md +295 -255
- package/vendor/Server/Skills/rnoh-lib-version-diff/references/OHOS_ADAPTATION.md +82 -77
- package/vendor/Server/Skills/rnoh-lib-version-diff/references/OHOS_API_LOOKUP.md +224 -224
- package/vendor/Server/Skills/rnoh-lib-version-diff/references/script-reference.md +175 -174
- package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/detect-upstream-version.js +967 -967
- package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/generate-diff.js +1798 -1798
- package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/package.json +5 -5
- package/vendor/Server/Skills/rnoh-lib-version-diff/templates/report-template.md +180 -137
- package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +145 -145
- package/vendor/Server/Skills/skill-generator/SKILL.md +365 -365
- package/vendor/Server/Skills/skill-generator/assets/SKILL_TEMPLATE.md +183 -183
- package/vendor/Server/Skills/skill-generator/references/COMPLIANCE_CHECKLIST.md +82 -82
- package/vendor/Server/Skills/skill-generator/references/KB_QUERY_GUIDE.md +110 -110
- package/vendor/Server/Skills/skill-generator/references/MCP_TOOL_DISCOVERY.md +122 -122
- package/vendor/Server/Skills/skill-generator/references/SKILL_SPEC.md +121 -121
- package/vendor/Server/Skills/skill-generator/scripts/validate-skill.cjs +259 -259
- package/vendor/Server/Skills/task-analysis/SKILL.md +234 -234
- package/vendor/Server/Skills/task-analysis/assets/OUTPUT_EXAMPLES.md +160 -160
- package/vendor/Server/Skills/task-analysis/references/ACCEPTANCE_CRITERIA_GUIDE.md +190 -190
- package/vendor/Server/Skills/task-analysis/references/ANALYSIS_SCHEMA.md +224 -224
- package/vendor/Server/Skills/task-analysis/references/INTEGRATION_CONTEXT.md +162 -162
- package/vendor/Server/Skills/task-analysis/references/LESSONS_LEARNED.md +55 -55
- package/vendor/Server/Skills/task-analysis/references/REPLAN_CONTEXT_GUIDE.md +126 -126
- package/vendor/Server/Skills/task-analysis/scripts/validate-analysis-output.sh +256 -256
- package/vendor/Server/Skills/task-decomposition/SKILL.md +528 -528
- package/vendor/Server/Skills/task-decomposition/assets/OUTPUT_EXAMPLES.md +451 -451
- package/vendor/Server/Skills/task-decomposition/references/ACCEPTANCE_CRITERIA_GUIDE.md +179 -179
- package/vendor/Server/Skills/task-decomposition/references/DEPENDENCY_GRAPH_GUIDE.md +152 -152
- package/vendor/Server/Skills/task-decomposition/references/INTEGRATION_CONTEXT.md +175 -175
- package/vendor/Server/Skills/task-decomposition/references/REPLAN_CONTEXT_GUIDE.md +195 -195
- package/vendor/Server/Skills/task-decomposition/references/TASK_DECOMPOSITION_TEMPLATES.md +226 -226
- package/vendor/Server/Skills/task-decomposition/scripts/list-available-skills.cjs +263 -263
- package/vendor/Server/Skills/task-decomposition/scripts/skill-blacklist.json +29 -29
- package/vendor/Server/Skills/task-decomposition/scripts/validate-decomposition-output.cjs +376 -376
- package/vendor/Server/Skills/task-decomposition/scripts/validate-param-consistency.cjs +259 -259
- package/vendor/Server/Skills/task-decomposition/scripts/validate-specified-skills.cjs +216 -216
- package/vendor/Server/Skills/task-decomposition/scripts/validate-step-graph.cjs +424 -424
- package/vendor/Server/Skills/tpc-cpp-check/SKILL.md +427 -427
- package/vendor/Server/Skills/tpc-cpp-check/references/CHECKLIST.md +214 -214
- package/vendor/Server/Skills/tpc-cpp-check/references/HPKBUILD_GUIDE.md +235 -235
- package/vendor/Server/Skills/tpc-cpp-check/references/REPORT_TEMPLATE.md +250 -250
- package/vendor/Server/TestCheck/TestCheck.js +775 -775
- package/vendor/Server/TestCheck/index.js +11 -11
- package/vendor/Server/library/McpToolGuide.js +280 -280
- package/vendor/Server/library/StatsService.js +126 -126
- package/vendor/Server/library/cstTime.js +16 -16
- package/vendor/VERSION +3 -3
- package/workflows/pr-check.mjs +105 -105
- package/workflows/pr-push.mjs +193 -0
- package/vendor/Server/Agent/SkillSelector/types.js +0 -63
|
@@ -1,967 +1,967 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* detect-upstream-version.js
|
|
4
|
-
*
|
|
5
|
-
* 从 RN 鸿蒙三方库 git 仓库链接中,分析出鸿蒙化仓库是基于上游的哪个版本进行的鸿蒙化,
|
|
6
|
-
* 同时查询 npm registry 获取该上游包的最新发布版本。
|
|
7
|
-
*
|
|
8
|
-
* 原理:
|
|
9
|
-
* 鸿蒙化仓库的 package.json 中通常包含:
|
|
10
|
-
* - "name": "@react-native-ohos/<pkg-name>" (鸿蒙化后的包名)
|
|
11
|
-
* - "harmony": { "alias": "<upstream-pkg-name>" } (上游 npm 包名别名)
|
|
12
|
-
* - "dependencies" / "devDependencies" 中有 "<upstream-pkg-name>": "<version>"
|
|
13
|
-
* 本脚本从仓库 URL 中推断 npm 包名,然后在 package.json 的依赖列表中
|
|
14
|
-
* 找到同名的依赖项,输出其版本号。之后通过 npm registry API 查询该包的
|
|
15
|
-
* dist-tags.latest,获取当前最新版本。
|
|
16
|
-
*
|
|
17
|
-
* 用法:
|
|
18
|
-
* node detect-upstream-version.js <git-repo-url>
|
|
19
|
-
*
|
|
20
|
-
* 示例:
|
|
21
|
-
* node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native/blob/br_rnoh0.77
|
|
22
|
-
* # 输出: 鸿蒙化版本 7.2.2 + npm 最新版本
|
|
23
|
-
*
|
|
24
|
-
* node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native
|
|
25
|
-
* # 输出: 鸿蒙化版本 7.2.2 + npm 最新版本
|
|
26
|
-
*
|
|
27
|
-
* node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native/tree/br_rnoh0.77
|
|
28
|
-
* # 输出: 鸿蒙化版本 7.2.2 + npm 最新版本
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
'use strict';
|
|
32
|
-
|
|
33
|
-
const https = require('https');
|
|
34
|
-
const http = require('http');
|
|
35
|
-
const { URL } = require('url');
|
|
36
|
-
const { execFileSync } = require('child_process');
|
|
37
|
-
const os = require('os');
|
|
38
|
-
const path = require('path');
|
|
39
|
-
const fs = require('fs');
|
|
40
|
-
|
|
41
|
-
// ── 颜色输出 ─────────────────────────────────────────────
|
|
42
|
-
const COLORS = {
|
|
43
|
-
RED: '\x1b[0;31m',
|
|
44
|
-
GREEN: '\x1b[0;32m',
|
|
45
|
-
YELLOW: '\x1b[1;33m',
|
|
46
|
-
BLUE: '\x1b[0;34m',
|
|
47
|
-
CYAN: '\x1b[0;36m',
|
|
48
|
-
NC: '\x1b[0m',
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
function info(msg) { console.log(`${COLORS.BLUE}[INFO]${COLORS.NC} ${msg}`); }
|
|
52
|
-
function ok(msg) { console.log(`${COLORS.GREEN}[OK]${COLORS.NC} ${msg}`); }
|
|
53
|
-
function warn(msg) { console.log(`${COLORS.YELLOW}[WARN]${COLORS.NC} ${msg}`); }
|
|
54
|
-
function error(msg) { console.error(`${COLORS.RED}[ERROR]${COLORS.NC} ${msg}`); }
|
|
55
|
-
|
|
56
|
-
// ── URL 解析 ─────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 从 git 仓库 URL 中解析出平台、owner、repo、分支和路径格式。
|
|
60
|
-
*
|
|
61
|
-
* 支持的 URL 格式:
|
|
62
|
-
* https://gitcode.com/<owner>/<repo>
|
|
63
|
-
* https://gitcode.com/<owner>/<repo>/blob/<branch>
|
|
64
|
-
* https://gitcode.com/<owner>/<repo>/tree/<branch>
|
|
65
|
-
* https://gitcode.com/<owner>/<repo>/blob/<branch>/<path>
|
|
66
|
-
* https://gitcode.com/<owner>/<repo>/tree/<branch>/<path>
|
|
67
|
-
* https://github.com/<owner>/<repo>
|
|
68
|
-
* https://github.com/<owner>/<repo>/tree/<branch>
|
|
69
|
-
* https://github.com/<owner>/<repo>/blob/<branch>/<path>
|
|
70
|
-
* https://github.com/<owner>/<repo>.git
|
|
71
|
-
* git@gitcode.com:<owner>/<repo>.git
|
|
72
|
-
*
|
|
73
|
-
* @param {string} rawUrl 原始 URL
|
|
74
|
-
* @returns {{platform: string, owner: string, repo: string, branch: string, host: string}}
|
|
75
|
-
*/
|
|
76
|
-
function parseGitUrl(rawUrl) {
|
|
77
|
-
let urlStr = rawUrl.trim();
|
|
78
|
-
|
|
79
|
-
// 处理 SSH 格式: git@host:owner/repo.git
|
|
80
|
-
if (urlStr.startsWith('git@')) {
|
|
81
|
-
const match = urlStr.match(/^git@([^:]+):(.+)$/);
|
|
82
|
-
if (match) {
|
|
83
|
-
const host = match[1];
|
|
84
|
-
const path = match[2].replace(/\.git$/, '');
|
|
85
|
-
const parts = path.split('/').filter(Boolean);
|
|
86
|
-
if (parts.length >= 2) {
|
|
87
|
-
return {
|
|
88
|
-
platform: detectPlatform(host),
|
|
89
|
-
host: host,
|
|
90
|
-
owner: parts[0],
|
|
91
|
-
repo: parts[1],
|
|
92
|
-
branch: '',
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
throw new Error(`无法解析 SSH URL: ${rawUrl}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 去除查询参数
|
|
100
|
-
const queryIdx = urlStr.indexOf('?');
|
|
101
|
-
if (queryIdx >= 0) {
|
|
102
|
-
urlStr = urlStr.substring(0, queryIdx);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 去除锚点
|
|
106
|
-
const hashIdx = urlStr.indexOf('#');
|
|
107
|
-
if (hashIdx >= 0) {
|
|
108
|
-
urlStr = urlStr.substring(0, hashIdx);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const parsed = new URL(urlStr);
|
|
112
|
-
const host = parsed.hostname;
|
|
113
|
-
const pathParts = parsed.pathname.split('/').filter(Boolean);
|
|
114
|
-
|
|
115
|
-
if (pathParts.length < 2) {
|
|
116
|
-
throw new Error(`URL 路径不完整,无法提取 owner/repo: ${rawUrl}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const owner = pathParts[0];
|
|
120
|
-
let repo = pathParts[1];
|
|
121
|
-
|
|
122
|
-
// 去除 .git 后缀
|
|
123
|
-
repo = repo.replace(/\.git$/, '');
|
|
124
|
-
|
|
125
|
-
// 解析分支: /blob/<branch> 或 /tree/<branch>
|
|
126
|
-
let branch = '';
|
|
127
|
-
if (pathParts.length >= 4 && (pathParts[2] === 'blob' || pathParts[2] === 'tree')) {
|
|
128
|
-
branch = pathParts[3];
|
|
129
|
-
} else if (pathParts.length >= 3 && (pathParts[2] === 'blob' || pathParts[2] === 'tree')) {
|
|
130
|
-
branch = pathParts[3] || '';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
platform: detectPlatform(host),
|
|
135
|
-
host: host,
|
|
136
|
-
owner: owner,
|
|
137
|
-
repo: repo,
|
|
138
|
-
branch: branch,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 根据主机名检测平台类型。
|
|
144
|
-
* @param {string} host
|
|
145
|
-
* @returns {string} 'gitcode' | 'github' | 'gitee' | 'other'
|
|
146
|
-
*/
|
|
147
|
-
function detectPlatform(host) {
|
|
148
|
-
if (host.includes('gitcode.com')) return 'gitcode';
|
|
149
|
-
if (host.includes('github.com')) return 'github';
|
|
150
|
-
if (host.includes('gitee.com')) return 'gitee';
|
|
151
|
-
return 'other';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ── 仓库名 → npm 包名推断 ───────────────────────────────
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 从仓库名中推断上游 npm 包名。
|
|
158
|
-
*
|
|
159
|
-
* 鸿蒙化仓库名通常遵循以下模式:
|
|
160
|
-
* rntpc_<upstream-pkg-name> → rntpc_lottie-turbo-react-native → lottie-turbo-react-native
|
|
161
|
-
* rntpc_<upstream-pkg-name>-turbo → (特殊处理)
|
|
162
|
-
* react-native-ohos-<name> → react-native-<name>
|
|
163
|
-
* <upstream-pkg-name> → 直接使用
|
|
164
|
-
*
|
|
165
|
-
* 推断策略:
|
|
166
|
-
* 1. 去除前缀 "rntpc_"
|
|
167
|
-
* 2. 去除前缀 "react-native-ohos-"
|
|
168
|
-
* 3. 如果结果以 "-turbo" 结尾且去掉后仍合理,尝试去掉
|
|
169
|
-
* 4. 其他情况直接使用仓库名
|
|
170
|
-
*
|
|
171
|
-
* @param {string} repoName 仓库名
|
|
172
|
-
* @returns {string} 推断的上游 npm 包名
|
|
173
|
-
*/
|
|
174
|
-
function inferPackageName(repoName) {
|
|
175
|
-
let name = repoName;
|
|
176
|
-
|
|
177
|
-
// 去除常见前缀
|
|
178
|
-
if (name.startsWith('rntpc_')) {
|
|
179
|
-
name = name.substring('rntpc_'.length);
|
|
180
|
-
} else if (name.startsWith('react-native-ohos-')) {
|
|
181
|
-
name = name.substring('react-native-ohos-'.length);
|
|
182
|
-
name = 'react-native-' + name;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return name;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ── HTTP 请求 ────────────────────────────────────────────
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* 发送 HTTP/HTTPS 请求并返回响应体 (字符串)。
|
|
192
|
-
* @param {string} url 请求 URL
|
|
193
|
-
* @param {number} maxRedirects 最大重定向次数
|
|
194
|
-
* @returns {Promise<string>} 响应体
|
|
195
|
-
*/
|
|
196
|
-
function fetchText(url, maxRedirects = 5) {
|
|
197
|
-
return new Promise((resolve, reject) => {
|
|
198
|
-
const parsed = new URL(url);
|
|
199
|
-
const lib = parsed.protocol === 'https:' ? https : http;
|
|
200
|
-
|
|
201
|
-
const options = {
|
|
202
|
-
hostname: parsed.hostname,
|
|
203
|
-
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
204
|
-
path: parsed.pathname + parsed.search,
|
|
205
|
-
method: 'GET',
|
|
206
|
-
headers: {
|
|
207
|
-
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
208
|
-
'Accept': 'text/plain, application/json, text/html, */*',
|
|
209
|
-
'Accept-Encoding': 'identity',
|
|
210
|
-
},
|
|
211
|
-
timeout: 30000,
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const req = lib.request(options, (res) => {
|
|
215
|
-
// 处理重定向
|
|
216
|
-
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
217
|
-
if (maxRedirects <= 0) {
|
|
218
|
-
reject(new Error('重定向次数超限'));
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
const redirectUrl = res.headers.location.startsWith('http')
|
|
222
|
-
? res.headers.location
|
|
223
|
-
: new URL(res.headers.location, url).href;
|
|
224
|
-
fetchText(redirectUrl, maxRedirects - 1).then(resolve).catch(reject);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (res.statusCode !== 200) {
|
|
229
|
-
reject(new Error(`HTTP ${res.statusCode}: ${url}`));
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
let data = '';
|
|
234
|
-
res.setEncoding('utf-8');
|
|
235
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
236
|
-
res.on('end', () => resolve(data));
|
|
237
|
-
res.on('error', reject);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
req.on('error', reject);
|
|
241
|
-
req.on('timeout', () => {
|
|
242
|
-
req.destroy(new Error('请求超时'));
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
req.end();
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ── Raw 文件 URL 构造 ───────────────────────────────────
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* 构造获取 package.json 原始内容的 URL。
|
|
253
|
-
*
|
|
254
|
-
* 不同平台的 raw 文件 URL 格式:
|
|
255
|
-
* - gitcode: https://raw.gitcode.com/<owner>/<repo>/raw/<branch>/package.json
|
|
256
|
-
* - github: https://raw.githubusercontent.com/<owner>/<repo>/<branch>/package.json
|
|
257
|
-
* - gitee: https://gitee.com/<owner>/<repo>/raw/<branch>/package.json
|
|
258
|
-
*
|
|
259
|
-
* @param {{platform: string, host: string, owner: string, repo: string, branch: string}} repoInfo
|
|
260
|
-
* @returns {string} raw package.json URL
|
|
261
|
-
*/
|
|
262
|
-
function buildRawPackageJsonUrl(repoInfo) {
|
|
263
|
-
const { platform, owner, repo, branch } = repoInfo;
|
|
264
|
-
const br = branch || 'master';
|
|
265
|
-
|
|
266
|
-
switch (platform) {
|
|
267
|
-
case 'github':
|
|
268
|
-
return `https://raw.githubusercontent.com/${owner}/${repo}/${br}/package.json`;
|
|
269
|
-
case 'gitcode':
|
|
270
|
-
return `https://raw.gitcode.com/${owner}/${repo}/raw/${br}/package.json`;
|
|
271
|
-
case 'gitee':
|
|
272
|
-
return `https://gitee.com/${owner}/${repo}/raw/${br}/package.json`;
|
|
273
|
-
default:
|
|
274
|
-
// 通用格式: 尝试 gitcode 风格
|
|
275
|
-
return `https://${repoInfo.host}/${owner}/${repo}/raw/${br}/package.json`;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* 获取 package.json 内容。
|
|
281
|
-
* 先尝试 raw URL,失败后尝试 API。
|
|
282
|
-
*
|
|
283
|
-
* @param {{platform: string, host: string, owner: string, repo: string, branch: string}} repoInfo
|
|
284
|
-
* @returns {Promise<object>} 解析后的 package.json 对象
|
|
285
|
-
*/
|
|
286
|
-
async function fetchPackageJson(repoInfo) {
|
|
287
|
-
const rawUrl = buildRawPackageJsonUrl(repoInfo);
|
|
288
|
-
info(`正在获取 package.json: ${rawUrl}`);
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
const content = await fetchText(rawUrl);
|
|
292
|
-
const pkg = JSON.parse(content);
|
|
293
|
-
ok('成功获取并解析 package.json');
|
|
294
|
-
return pkg;
|
|
295
|
-
} catch (err) {
|
|
296
|
-
warn(`Raw URL 获取失败: ${err.message}`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// 回退: gitcode API v5 (返回 base64 编码的文件内容)
|
|
300
|
-
if (repoInfo.platform === 'gitcode') {
|
|
301
|
-
const apiUrl = `https://gitcode.com/api/v5/repos/${repoInfo.owner}/${repoInfo.repo}/contents/package.json?ref=${repoInfo.branch || 'master'}`;
|
|
302
|
-
info(`尝试从 gitcode API 获取: ${apiUrl}`);
|
|
303
|
-
try {
|
|
304
|
-
const content = await fetchText(apiUrl);
|
|
305
|
-
const data = JSON.parse(content);
|
|
306
|
-
if (data.content) {
|
|
307
|
-
const decoded = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
308
|
-
const pkg = JSON.parse(decoded);
|
|
309
|
-
ok('从 gitcode API 成功获取 package.json');
|
|
310
|
-
return pkg;
|
|
311
|
-
}
|
|
312
|
-
} catch (err) {
|
|
313
|
-
warn(`gitcode API 获取失败: ${err.message}`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// 回退: GitHub API
|
|
318
|
-
if (repoInfo.platform === 'github') {
|
|
319
|
-
const apiUrl = `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/contents/package.json?ref=${repoInfo.branch || 'master'}`;
|
|
320
|
-
info(`尝试从 GitHub API 获取: ${apiUrl}`);
|
|
321
|
-
try {
|
|
322
|
-
const content = await fetchText(apiUrl);
|
|
323
|
-
const data = JSON.parse(content);
|
|
324
|
-
if (data.content && data.encoding === 'base64') {
|
|
325
|
-
const decoded = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
326
|
-
const pkg = JSON.parse(decoded);
|
|
327
|
-
ok('从 GitHub API 成功获取 package.json');
|
|
328
|
-
return pkg;
|
|
329
|
-
}
|
|
330
|
-
} catch (err) {
|
|
331
|
-
warn(`GitHub API 获取失败: ${err.message}`);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// 回退: Gitee API
|
|
336
|
-
if (repoInfo.platform === 'gitee') {
|
|
337
|
-
const apiUrl = `https://gitee.com/api/v5/repos/${repoInfo.owner}/${repoInfo.repo}/contents/package.json?ref=${repoInfo.branch || 'master'}`;
|
|
338
|
-
info(`尝试从 Gitee API 获取: ${apiUrl}`);
|
|
339
|
-
try {
|
|
340
|
-
const content = await fetchText(apiUrl);
|
|
341
|
-
const data = JSON.parse(content);
|
|
342
|
-
if (data.content) {
|
|
343
|
-
const decoded = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
344
|
-
const pkg = JSON.parse(decoded);
|
|
345
|
-
ok('从 Gitee API 成功获取 package.json');
|
|
346
|
-
return pkg;
|
|
347
|
-
}
|
|
348
|
-
} catch (err) {
|
|
349
|
-
warn(`Gitee API 获取失败: ${err.message}`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// 最终回退: git clone --depth=1
|
|
354
|
-
info('尝试通过 git clone 获取...');
|
|
355
|
-
try {
|
|
356
|
-
const pkg = await fetchPackageJsonViaClone(repoInfo);
|
|
357
|
-
if (pkg) {
|
|
358
|
-
ok('通过 git clone 成功获取 package.json');
|
|
359
|
-
return pkg;
|
|
360
|
-
}
|
|
361
|
-
} catch (err) {
|
|
362
|
-
warn(`git clone 获取失败: ${err.message}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
throw new Error('无法获取 package.json,请检查仓库地址和分支是否正确');
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* 通过 git clone --depth=1 获取 package.json。
|
|
370
|
-
* 这是最可靠但最慢的回退方案。
|
|
371
|
-
*
|
|
372
|
-
* @param {{platform: string, host: string, owner: string, repo: string, branch: string}} repoInfo
|
|
373
|
-
* @returns {Promise<object|null>} 解析后的 package.json 对象
|
|
374
|
-
*/
|
|
375
|
-
async function fetchPackageJsonViaClone(repoInfo) {
|
|
376
|
-
const cloneUrl = `https://${repoInfo.host}/${repoInfo.owner}/${repoInfo.repo}.git`;
|
|
377
|
-
const branch = repoInfo.branch || 'master';
|
|
378
|
-
const tmpDir = path.join(os.tmpdir(), `detect-upstream-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
379
|
-
|
|
380
|
-
return new Promise((resolve, reject) => {
|
|
381
|
-
try {
|
|
382
|
-
execFileSync('git', [
|
|
383
|
-
'clone', '--depth=1',
|
|
384
|
-
'--branch', branch,
|
|
385
|
-
cloneUrl,
|
|
386
|
-
tmpDir,
|
|
387
|
-
], {
|
|
388
|
-
timeout: 60000,
|
|
389
|
-
stdio: 'pipe',
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const pkgPath = path.join(tmpDir, 'package.json');
|
|
393
|
-
if (!fs.existsSync(pkgPath)) {
|
|
394
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
395
|
-
resolve(null);
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const content = fs.readFileSync(pkgPath, 'utf-8');
|
|
400
|
-
const pkg = JSON.parse(content);
|
|
401
|
-
|
|
402
|
-
// 清理临时目录
|
|
403
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
404
|
-
resolve(pkg);
|
|
405
|
-
} catch (err) {
|
|
406
|
-
// 清理临时目录
|
|
407
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) {}
|
|
408
|
-
reject(err);
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// ── 版本号匹配 ───────────────────────────────────────────
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* 在 package.json 中查找与仓库名匹配的上游依赖版本号。
|
|
417
|
-
*
|
|
418
|
-
* 匹配策略 (按优先级):
|
|
419
|
-
* 1. 使用 harmony.alias 字段的值作为包名,在 dependencies 中查找
|
|
420
|
-
* 2. 使用从仓库名推断的包名,在 dependencies 中查找
|
|
421
|
-
* 3. 使用 harmony.alias 在 devDependencies 中查找
|
|
422
|
-
* 4. 使用推断的包名在 devDependencies 中查找
|
|
423
|
-
* 5. 使用 package.json 的 name 字段去掉 @react-native-ohos/ 前缀后在依赖中查找
|
|
424
|
-
*
|
|
425
|
-
* @param {object} pkg package.json 对象
|
|
426
|
-
* @param {string} repoName 仓库名
|
|
427
|
-
* @returns {{version: string, matchedField: string, matchedSection: string, packageName: string}}
|
|
428
|
-
*/
|
|
429
|
-
function findUpstreamVersion(pkg, repoName) {
|
|
430
|
-
const inferredPkgName = inferPackageName(repoName);
|
|
431
|
-
const harmonyAlias = pkg.harmony && pkg.harmony.alias ? pkg.harmony.alias : '';
|
|
432
|
-
const pkgName = pkg.name || '';
|
|
433
|
-
|
|
434
|
-
// 从 @react-native-ohos/xxx 中提取 xxx
|
|
435
|
-
const ohosPkgName = pkgName.startsWith('@react-native-ohos/')
|
|
436
|
-
? pkgName.substring('@react-native-ohos/'.length)
|
|
437
|
-
: pkgName;
|
|
438
|
-
|
|
439
|
-
// 候选包名列表 (按优先级排序,去重)
|
|
440
|
-
const candidateNames = [];
|
|
441
|
-
const addCandidate = (name) => {
|
|
442
|
-
if (name && !candidateNames.includes(name)) {
|
|
443
|
-
candidateNames.push(name);
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
addCandidate(harmonyAlias);
|
|
447
|
-
addCandidate(inferredPkgName);
|
|
448
|
-
addCandidate(ohosPkgName);
|
|
449
|
-
|
|
450
|
-
// 搜索的依赖区域 (按优先级排序)
|
|
451
|
-
const sections = [
|
|
452
|
-
{ key: 'dependencies', label: 'dependencies' },
|
|
453
|
-
{ key: 'devDependencies', label: 'devDependencies' },
|
|
454
|
-
{ key: 'peerDependencies', label: 'peerDependencies' },
|
|
455
|
-
];
|
|
456
|
-
|
|
457
|
-
// 遍历候选包名 × 依赖区域
|
|
458
|
-
for (const candidateName of candidateNames) {
|
|
459
|
-
for (const section of sections) {
|
|
460
|
-
const deps = pkg[section.key];
|
|
461
|
-
if (!deps || typeof deps !== 'object') continue;
|
|
462
|
-
|
|
463
|
-
// 精确匹配
|
|
464
|
-
if (deps.hasOwnProperty(candidateName)) {
|
|
465
|
-
return {
|
|
466
|
-
version: deps[candidateName],
|
|
467
|
-
matchedField: candidateName,
|
|
468
|
-
matchedSection: section.label,
|
|
469
|
-
packageName: candidateName,
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// 模糊匹配: 如果候选名包含某些关键词,尝试在依赖中反向查找
|
|
476
|
-
// 例如仓库名 rntpc_lottie-turbo-react-native 推断出 lottie-turbo-react-native
|
|
477
|
-
// 但实际依赖名可能是 lottie-react-native
|
|
478
|
-
for (const section of sections) {
|
|
479
|
-
const deps = pkg[section.key];
|
|
480
|
-
if (!deps || typeof deps !== 'object') continue;
|
|
481
|
-
|
|
482
|
-
for (const depName of Object.keys(deps)) {
|
|
483
|
-
// 跳过 @react-native-ohos/ 前缀的包 (那是鸿蒙化包自身)
|
|
484
|
-
if (depName.startsWith('@react-native-ohos/')) continue;
|
|
485
|
-
// 跳过 react/react-native 核心包
|
|
486
|
-
if (['react', 'react-native', 'react-native-windows'].includes(depName)) continue;
|
|
487
|
-
|
|
488
|
-
// 检查候选名是否与依赖名有足够相似度
|
|
489
|
-
for (const candidateName of candidateNames) {
|
|
490
|
-
if (isPackageMatch(candidateName, depName)) {
|
|
491
|
-
return {
|
|
492
|
-
version: deps[depName],
|
|
493
|
-
matchedField: depName,
|
|
494
|
-
matchedSection: section.label,
|
|
495
|
-
packageName: depName,
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return null;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* 判断两个包名是否匹配 (模糊匹配)。
|
|
507
|
-
*
|
|
508
|
-
* 规则:
|
|
509
|
-
* 1. 精确匹配
|
|
510
|
-
* 2. 一个包含另一个 (如 lottie-turbo-react-native 包含 lottie-react-native)
|
|
511
|
-
* 3. 去除 turbo 后缀后匹配
|
|
512
|
-
* 4. 共享相同的关键词组合 (如都包含 lottie 和 react-native)
|
|
513
|
-
*
|
|
514
|
-
* @param {string} candidate 候选包名
|
|
515
|
-
* @param {string} depName 依赖中的包名
|
|
516
|
-
* @returns {boolean}
|
|
517
|
-
*/
|
|
518
|
-
function isPackageMatch(candidate, depName) {
|
|
519
|
-
if (candidate === depName) return true;
|
|
520
|
-
if (!candidate || !depName) return false;
|
|
521
|
-
|
|
522
|
-
// 去除 turbo 后缀
|
|
523
|
-
const normalize = (s) => s.replace(/-turbo$/i, '').replace(/turbo-/gi, '');
|
|
524
|
-
if (normalize(candidate) === normalize(depName)) return true;
|
|
525
|
-
|
|
526
|
-
// 一个包含另一个
|
|
527
|
-
if (candidate.includes(depName) || depName.includes(candidate)) return true;
|
|
528
|
-
|
|
529
|
-
// 提取关键词 (以 - 分割),检查交集
|
|
530
|
-
const candidateKeywords = candidate.split('-').filter(k => k.length > 2);
|
|
531
|
-
const depKeywords = depName.split('-').filter(k => k.length > 2);
|
|
532
|
-
const intersection = candidateKeywords.filter(k => depKeywords.includes(k));
|
|
533
|
-
|
|
534
|
-
// 如果有足够的关键词交集 (至少2个且覆盖了依赖名的大部分关键词)
|
|
535
|
-
if (intersection.length >= 2 && intersection.length >= depKeywords.length - 1) {
|
|
536
|
-
return true;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return false;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// ── npm Registry 查询 ───────────────────────────────────
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* 从 npm registry 查询包的最新版本号。
|
|
546
|
-
*
|
|
547
|
-
* 使用 npm registry API:
|
|
548
|
-
* GET https://registry.npmjs.org/<package-name>
|
|
549
|
-
* 返回的 JSON 中 dist-tags.latest 包含最新版本号。
|
|
550
|
-
*
|
|
551
|
-
* 对于 scoped 包 (@scope/pkg),需要将 / 编码为 %2F。
|
|
552
|
-
*
|
|
553
|
-
* 查询策略 (按优先级):
|
|
554
|
-
* 1. npm registry packument API (dist-tags.latest)
|
|
555
|
-
* 2. npm registry /latest 端点
|
|
556
|
-
* 3. npm view 命令 (尊重用户本地 npm 配置)
|
|
557
|
-
*
|
|
558
|
-
* @param {string} packageName npm 包名
|
|
559
|
-
* @returns {Promise<{latest: string, allTags: object, recentVersions: string[]} | null>} 版本信息
|
|
560
|
-
*/
|
|
561
|
-
async function fetchLatestNpmVersion(packageName) {
|
|
562
|
-
// 对 scoped 包名进行编码: @scope/pkg → @scope%2Fpkg
|
|
563
|
-
const encodedName = packageName.replace('/', '%2F');
|
|
564
|
-
|
|
565
|
-
// 策略 1: packument API
|
|
566
|
-
const registryUrl = `https://registry.npmjs.org/${encodedName}`;
|
|
567
|
-
info(`正在查询 npm registry: ${registryUrl}`);
|
|
568
|
-
|
|
569
|
-
try {
|
|
570
|
-
const content = await fetchText(registryUrl);
|
|
571
|
-
const data = JSON.parse(content);
|
|
572
|
-
|
|
573
|
-
const distTags = data['dist-tags'] || data.distTags;
|
|
574
|
-
if (distTags && distTags.latest) {
|
|
575
|
-
// 从 versions 字段提取所有版本号,排序后取最近 10 个
|
|
576
|
-
const allVersions = data.versions ? Object.keys(data.versions) : [];
|
|
577
|
-
const recentVersions = sortVersionsDesc(allVersions).slice(0, 10);
|
|
578
|
-
ok(`npm 最新版本: ${distTags.latest}`);
|
|
579
|
-
if (recentVersions.length > 0) {
|
|
580
|
-
info(`最近 ${recentVersions.length} 个版本: ${recentVersions.join(', ')}`);
|
|
581
|
-
}
|
|
582
|
-
return {
|
|
583
|
-
latest: distTags.latest,
|
|
584
|
-
allTags: distTags,
|
|
585
|
-
recentVersions: recentVersions,
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// 某些简化的响应可能直接包含 version 字段
|
|
590
|
-
if (data.version) {
|
|
591
|
-
ok(`npm 最新版本: ${data.version}`);
|
|
592
|
-
return {
|
|
593
|
-
latest: data.version,
|
|
594
|
-
allTags: {},
|
|
595
|
-
recentVersions: [],
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
warn('npm registry 响应中未找到 dist-tags.latest');
|
|
600
|
-
} catch (err) {
|
|
601
|
-
warn(`npm registry 查询失败: ${err.message}`);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// 策略 2: /latest 端点
|
|
605
|
-
const latestUrl = `https://registry.npmjs.org/${encodedName}/latest`;
|
|
606
|
-
info(`尝试从 /latest 端点获取: ${latestUrl}`);
|
|
607
|
-
try {
|
|
608
|
-
const content = await fetchText(latestUrl);
|
|
609
|
-
const data = JSON.parse(content);
|
|
610
|
-
if (data.version) {
|
|
611
|
-
ok(`npm 最新版本 (via /latest): ${data.version}`);
|
|
612
|
-
return {
|
|
613
|
-
latest: data.version,
|
|
614
|
-
allTags: {},
|
|
615
|
-
recentVersions: [],
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
} catch (err) {
|
|
619
|
-
warn(`/latest 端点查询失败: ${err.message}`);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// 策略 3: npm view 命令 (尊重用户本地 .npmrc 配置,如镜像源)
|
|
623
|
-
const npmResult = await fetchLatestViaNpmCommand(packageName);
|
|
624
|
-
if (npmResult) {
|
|
625
|
-
return npmResult;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* 通过 npm view 命令获取最新版本号和最近版本列表。
|
|
633
|
-
*
|
|
634
|
-
* 此方式会读取用户本地的 .npmrc 配置,
|
|
635
|
-
* 因此能正确处理使用了镜像源 (如 npmmirror.com) 的场景。
|
|
636
|
-
*
|
|
637
|
-
* @param {string} packageName npm 包名
|
|
638
|
-
* @returns {Promise<{latest: string, allTags: object, recentVersions: string[]} | null>}
|
|
639
|
-
*/
|
|
640
|
-
async function fetchLatestViaNpmCommand(packageName) {
|
|
641
|
-
info('尝试通过 npm view 命令获取...');
|
|
642
|
-
try {
|
|
643
|
-
// 获取最新版本
|
|
644
|
-
const versionOutput = execFileSync('npm', [
|
|
645
|
-
'view', packageName, 'version',
|
|
646
|
-
], {
|
|
647
|
-
timeout: 30000,
|
|
648
|
-
encoding: 'utf-8',
|
|
649
|
-
stdio: 'pipe',
|
|
650
|
-
}).trim();
|
|
651
|
-
|
|
652
|
-
// 获取所有版本列表
|
|
653
|
-
let recentVersions = [];
|
|
654
|
-
try {
|
|
655
|
-
const versionsOutput = execFileSync('npm', [
|
|
656
|
-
'view', packageName, 'versions', '--json',
|
|
657
|
-
], {
|
|
658
|
-
timeout: 30000,
|
|
659
|
-
encoding: 'utf-8',
|
|
660
|
-
stdio: 'pipe',
|
|
661
|
-
}).trim();
|
|
662
|
-
|
|
663
|
-
const allVersions = JSON.parse(versionsOutput);
|
|
664
|
-
if (Array.isArray(allVersions)) {
|
|
665
|
-
recentVersions = sortVersionsDesc(allVersions).slice(0, 10);
|
|
666
|
-
}
|
|
667
|
-
} catch (e) {
|
|
668
|
-
// versions 获取失败不影响主流程
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
if (versionOutput) {
|
|
672
|
-
// npm view 可能返回多行 (废弃包警告等),取最后一行作为版本号
|
|
673
|
-
const lines = versionOutput.split('\n').filter(l => l.trim());
|
|
674
|
-
const version = lines[lines.length - 1].trim();
|
|
675
|
-
ok(`npm 最新版本 (via npm view): ${version}`);
|
|
676
|
-
if (recentVersions.length > 0) {
|
|
677
|
-
info(`最近 ${recentVersions.length} 个版本: ${recentVersions.join(', ')}`);
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
latest: version,
|
|
681
|
-
allTags: {},
|
|
682
|
-
recentVersions: recentVersions,
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
} catch (err) {
|
|
686
|
-
warn(`npm view 命令失败: ${err.message}`);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
return null;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* 解析语义化版本号为结构化对象。
|
|
694
|
-
*
|
|
695
|
-
* @param {string} version 版本号 (可能含 ^ ~ 等前缀)
|
|
696
|
-
* @returns {{major: number, minor: number, patch: number, prerelease: string}}
|
|
697
|
-
*/
|
|
698
|
-
function parseSemver(version) {
|
|
699
|
-
const clean = version.replace(/^[\^~>=<\s]+/, '');
|
|
700
|
-
const [mainPart, prePart] = clean.split('-');
|
|
701
|
-
const parts = mainPart.split('.');
|
|
702
|
-
return {
|
|
703
|
-
major: parseInt(parts[0] || '0', 10),
|
|
704
|
-
minor: parseInt(parts[1] || '0', 10),
|
|
705
|
-
patch: parseInt(parts[2] || '0', 10),
|
|
706
|
-
prerelease: prePart || '',
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
/**
|
|
711
|
-
* 比较两个语义化版本号的大小 (含 prerelease 优先级)。
|
|
712
|
-
*
|
|
713
|
-
* 遵循 semver 规范:
|
|
714
|
-
* - major.minor.patch 数值比较
|
|
715
|
-
* - 无 prerelease > 有 prerelease (如 7.4.0 > 7.4.0-rc1)
|
|
716
|
-
* - prerelease 部分按标识符逐段比较 (数字用数值,字母用 ASCII)
|
|
717
|
-
*
|
|
718
|
-
* @param {string} v1 版本号1
|
|
719
|
-
* @param {string} v2 版本号2
|
|
720
|
-
* @returns {number} -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
|
|
721
|
-
*/
|
|
722
|
-
function compareSemver(v1, v2) {
|
|
723
|
-
const p1 = parseSemver(v1);
|
|
724
|
-
const p2 = parseSemver(v2);
|
|
725
|
-
|
|
726
|
-
if (p1.major !== p2.major) return p1.major < p2.major ? -1 : 1;
|
|
727
|
-
if (p1.minor !== p2.minor) return p1.minor < p2.minor ? -1 : 1;
|
|
728
|
-
if (p1.patch !== p2.patch) return p1.patch < p2.patch ? -1 : 1;
|
|
729
|
-
|
|
730
|
-
// 无 prerelease > 有 prerelease
|
|
731
|
-
if (!p1.prerelease && p2.prerelease) return 1;
|
|
732
|
-
if (p1.prerelease && !p2.prerelease) return -1;
|
|
733
|
-
if (!p1.prerelease && !p2.prerelease) return 0;
|
|
734
|
-
|
|
735
|
-
// 两者都有 prerelease,逐段比较
|
|
736
|
-
const pre1Parts = p1.prerelease.split('.');
|
|
737
|
-
const pre2Parts = p2.prerelease.split('.');
|
|
738
|
-
const maxLen = Math.max(pre1Parts.length, pre2Parts.length);
|
|
739
|
-
for (let i = 0; i < maxLen; i++) {
|
|
740
|
-
const a = pre1Parts[i];
|
|
741
|
-
const b = pre2Parts[i];
|
|
742
|
-
if (!a) return -1;
|
|
743
|
-
if (!b) return 1;
|
|
744
|
-
const aNum = parseInt(a, 10);
|
|
745
|
-
const bNum = parseInt(b, 10);
|
|
746
|
-
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
747
|
-
if (aNum !== bNum) return aNum < bNum ? -1 : 1;
|
|
748
|
-
} else {
|
|
749
|
-
if (a !== b) return a < b ? -1 : 1;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
return 0;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* 将版本号数组按语义化版本降序排序 (最新在前)。
|
|
757
|
-
*
|
|
758
|
-
* @param {string[]} versions 版本号数组
|
|
759
|
-
* @returns {string[]} 排序后的数组 (新数组,不修改原数组)
|
|
760
|
-
*/
|
|
761
|
-
function sortVersionsDesc(versions) {
|
|
762
|
-
return [...versions].sort((a, b) => compareSemver(b, a));
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* 比较两个语义化版本号的大小。
|
|
767
|
-
*
|
|
768
|
-
* 兼容包装: 使用 compareSemver 进行完整比较。
|
|
769
|
-
*
|
|
770
|
-
* @param {string} v1 版本号1 (可能含 ^ ~ 等前缀)
|
|
771
|
-
* @param {string} v2 版本号2
|
|
772
|
-
* @returns {number} -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
|
|
773
|
-
*/
|
|
774
|
-
function compareVersions(v1, v2) {
|
|
775
|
-
return compareSemver(v1, v2);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* 根据版本比较结果生成状态描述。
|
|
780
|
-
*
|
|
781
|
-
* @param {string} currentVersion 当前鸿蒙化使用的版本
|
|
782
|
-
* @param {string} latestVersion npm 最新版本
|
|
783
|
-
* @returns {{status: string, color: string, detail: string}}
|
|
784
|
-
*/
|
|
785
|
-
function getVersionStatus(currentVersion, latestVersion) {
|
|
786
|
-
const cmp = compareVersions(currentVersion, latestVersion);
|
|
787
|
-
const cleanCurrent = currentVersion.replace(/^[\^~>=<\s]+/, '');
|
|
788
|
-
|
|
789
|
-
if (cmp === 0) {
|
|
790
|
-
return {
|
|
791
|
-
status: '最新',
|
|
792
|
-
color: COLORS.GREEN,
|
|
793
|
-
detail: `鸿蒙化版本与 npm 最新版本一致 (${cleanCurrent})`,
|
|
794
|
-
};
|
|
795
|
-
} else if (cmp < 0) {
|
|
796
|
-
// 计算落后的大版本差距
|
|
797
|
-
const curParts = cleanCurrent.split('.').map(n => parseInt(n, 10));
|
|
798
|
-
const latParts = latestVersion.split('.').map(n => parseInt(n, 10));
|
|
799
|
-
const majorDiff = (latParts[0] || 0) - (curParts[0] || 0);
|
|
800
|
-
const minorDiff = (latParts[1] || 0) - (curParts[1] || 0);
|
|
801
|
-
const patchDiff = (latParts[2] || 0) - (curParts[2] || 0);
|
|
802
|
-
|
|
803
|
-
let gapDesc;
|
|
804
|
-
if (majorDiff > 0) {
|
|
805
|
-
gapDesc = `落后 ${majorDiff} 个大版本`;
|
|
806
|
-
} else if (minorDiff > 0) {
|
|
807
|
-
gapDesc = `落后 ${minorDiff} 个小版本`;
|
|
808
|
-
} else {
|
|
809
|
-
gapDesc = `落后 ${patchDiff} 个补丁版本`;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
return {
|
|
813
|
-
status: '落后',
|
|
814
|
-
color: COLORS.YELLOW,
|
|
815
|
-
detail: `鸿蒙化版本落后于 npm 最新版本 (${gapDesc})`,
|
|
816
|
-
};
|
|
817
|
-
} else {
|
|
818
|
-
return {
|
|
819
|
-
status: '超前',
|
|
820
|
-
color: COLORS.CYAN,
|
|
821
|
-
detail: `鸿蒙化版本高于 npm 最新版本 (可能使用了 beta/next 等预发布版本)`,
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// ── 主流程 ───────────────────────────────────────────────
|
|
827
|
-
|
|
828
|
-
async function main() {
|
|
829
|
-
const args = process.argv.slice(2);
|
|
830
|
-
|
|
831
|
-
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
832
|
-
console.log(`
|
|
833
|
-
detect-upstream-version.js
|
|
834
|
-
从 RN 鸿蒙三方库 git 仓库链接中分析上游版本号
|
|
835
|
-
|
|
836
|
-
用法:
|
|
837
|
-
node detect-upstream-version.js <git-repo-url>
|
|
838
|
-
|
|
839
|
-
示例:
|
|
840
|
-
node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native/blob/br_rnoh0.77
|
|
841
|
-
node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native
|
|
842
|
-
node detect-upstream-version.js https://github.com/react-native-oh-library/lottie-react-native/tree/main
|
|
843
|
-
|
|
844
|
-
输出说明 (普通模式):
|
|
845
|
-
鸿蒙化版本 - 鸿蒙化仓库 package.json 中记录的上游依赖版本
|
|
846
|
-
npm最新版本 - 通过 npm registry API 查询到的 dist-tags.latest 版本
|
|
847
|
-
最近版本 - npm registry 中按语义化版本排序的最近 10 个版本号
|
|
848
|
-
版本状态 - up-to-date/behind/ahead,表示鸿蒙化版本相对最新版本的状态
|
|
849
|
-
|
|
850
|
-
输出说明 (quiet 模式):
|
|
851
|
-
格式: <鸿蒙化版本> <npm最新版本> <状态> <最近10个版本(逗号分隔)>
|
|
852
|
-
状态: up-to-date | behind | ahead | unknown
|
|
853
|
-
示例: 7.2.2 7.4.0 behind 7.4.0,7.3.1,7.2.2,7.2.1,...
|
|
854
|
-
|
|
855
|
-
选项:
|
|
856
|
-
-h, --help 显示帮助信息
|
|
857
|
-
-v, --verbose 显示详细输出
|
|
858
|
-
-q, --quiet 只输出关键信息 (适合脚本调用)
|
|
859
|
-
`);
|
|
860
|
-
process.exit(0);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
const verbose = args.includes('-v') || args.includes('--verbose');
|
|
864
|
-
const quiet = args.includes('-q') || args.includes('--quiet');
|
|
865
|
-
const urlArg = args.find(a => !a.startsWith('-'));
|
|
866
|
-
|
|
867
|
-
if (!urlArg) {
|
|
868
|
-
error('请提供 git 仓库 URL');
|
|
869
|
-
process.exit(1);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
try {
|
|
873
|
-
// 1. 解析 URL
|
|
874
|
-
const repoInfo = parseGitUrl(urlArg);
|
|
875
|
-
if (verbose) {
|
|
876
|
-
info(`平台: ${repoInfo.platform}`);
|
|
877
|
-
info(`Owner: ${repoInfo.owner}`);
|
|
878
|
-
info(`仓库: ${repoInfo.repo}`);
|
|
879
|
-
info(`分支: ${repoInfo.branch || '(默认)'}`);
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// 2. 获取 package.json
|
|
883
|
-
const pkg = await fetchPackageJson(repoInfo);
|
|
884
|
-
|
|
885
|
-
if (verbose) {
|
|
886
|
-
info(`包名: ${pkg.name || '(无)'}`);
|
|
887
|
-
if (pkg.harmony && pkg.harmony.alias) {
|
|
888
|
-
info(`Harmony alias: ${pkg.harmony.alias}`);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// 3. 查找上游版本号
|
|
893
|
-
const result = findUpstreamVersion(pkg, repoInfo.repo);
|
|
894
|
-
|
|
895
|
-
if (result) {
|
|
896
|
-
const cleanVersion = result.version.replace(/^[\^~>=<\s]+/, '');
|
|
897
|
-
|
|
898
|
-
// 4. 查询 npm registry 获取最新版本和最近版本列表
|
|
899
|
-
const npmInfo = await fetchLatestNpmVersion(result.matchedField);
|
|
900
|
-
|
|
901
|
-
if (quiet) {
|
|
902
|
-
// 只输出关键信息: 当前版本 最新版本 状态 最近10个版本
|
|
903
|
-
if (npmInfo) {
|
|
904
|
-
const cmp = compareVersions(cleanVersion, npmInfo.latest);
|
|
905
|
-
const status = cmp === 0 ? 'up-to-date' : (cmp < 0 ? 'behind' : 'ahead');
|
|
906
|
-
const recentStr = npmInfo.recentVersions && npmInfo.recentVersions.length > 0
|
|
907
|
-
? npmInfo.recentVersions.join(',')
|
|
908
|
-
: 'N/A';
|
|
909
|
-
console.log(`${cleanVersion} ${npmInfo.latest} ${status} ${recentStr}`);
|
|
910
|
-
} else {
|
|
911
|
-
console.log(`${cleanVersion} UNKNOWN unknown N/A`);
|
|
912
|
-
}
|
|
913
|
-
} else {
|
|
914
|
-
ok(`找到上游版本:`);
|
|
915
|
-
console.log(` 仓库名: ${repoInfo.repo}`);
|
|
916
|
-
console.log(` 匹配包名: ${result.matchedField}`);
|
|
917
|
-
console.log(` 匹配区域: ${result.matchedSection}`);
|
|
918
|
-
console.log(` 鸿蒙化版本: ${result.version}`);
|
|
919
|
-
console.log(` 纯版本号: ${cleanVersion}`);
|
|
920
|
-
|
|
921
|
-
if (npmInfo) {
|
|
922
|
-
console.log(` npm最新版本: ${npmInfo.latest}`);
|
|
923
|
-
|
|
924
|
-
// 显示其他 dist-tags (如果有)
|
|
925
|
-
if (npmInfo.allTags && Object.keys(npmInfo.allTags).length > 1) {
|
|
926
|
-
const otherTags = Object.entries(npmInfo.allTags)
|
|
927
|
-
.filter(([k]) => k !== 'latest')
|
|
928
|
-
.map(([k, v]) => `${k}: ${v}`)
|
|
929
|
-
.join(', ');
|
|
930
|
-
if (otherTags) {
|
|
931
|
-
console.log(` 其他标签: ${otherTags}`);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// 显示最近 10 个版本
|
|
936
|
-
if (npmInfo.recentVersions && npmInfo.recentVersions.length > 0) {
|
|
937
|
-
console.log(` 最近版本: ${npmInfo.recentVersions.join(', ')}`);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// 版本状态分析
|
|
941
|
-
const versionStatus = getVersionStatus(cleanVersion, npmInfo.latest);
|
|
942
|
-
console.log(` 版本状态: ${versionStatus.color}${versionStatus.status}${COLORS.NC} - ${versionStatus.detail}`);
|
|
943
|
-
} else {
|
|
944
|
-
console.log(` npm最新版本: ${COLORS.YELLOW}查询失败${COLORS.NC}`);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
} else {
|
|
948
|
-
if (quiet) {
|
|
949
|
-
console.error('UNKNOWN');
|
|
950
|
-
} else {
|
|
951
|
-
warn('未找到匹配的上游版本号');
|
|
952
|
-
if (verbose && pkg.dependencies) {
|
|
953
|
-
info('dependencies 中的依赖:');
|
|
954
|
-
for (const [name, version] of Object.entries(pkg.dependencies)) {
|
|
955
|
-
console.log(` ${name}: ${version}`);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
process.exit(2);
|
|
960
|
-
}
|
|
961
|
-
} catch (err) {
|
|
962
|
-
error(err.message);
|
|
963
|
-
process.exit(1);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* detect-upstream-version.js
|
|
4
|
+
*
|
|
5
|
+
* 从 RN 鸿蒙三方库 git 仓库链接中,分析出鸿蒙化仓库是基于上游的哪个版本进行的鸿蒙化,
|
|
6
|
+
* 同时查询 npm registry 获取该上游包的最新发布版本。
|
|
7
|
+
*
|
|
8
|
+
* 原理:
|
|
9
|
+
* 鸿蒙化仓库的 package.json 中通常包含:
|
|
10
|
+
* - "name": "@react-native-ohos/<pkg-name>" (鸿蒙化后的包名)
|
|
11
|
+
* - "harmony": { "alias": "<upstream-pkg-name>" } (上游 npm 包名别名)
|
|
12
|
+
* - "dependencies" / "devDependencies" 中有 "<upstream-pkg-name>": "<version>"
|
|
13
|
+
* 本脚本从仓库 URL 中推断 npm 包名,然后在 package.json 的依赖列表中
|
|
14
|
+
* 找到同名的依赖项,输出其版本号。之后通过 npm registry API 查询该包的
|
|
15
|
+
* dist-tags.latest,获取当前最新版本。
|
|
16
|
+
*
|
|
17
|
+
* 用法:
|
|
18
|
+
* node detect-upstream-version.js <git-repo-url>
|
|
19
|
+
*
|
|
20
|
+
* 示例:
|
|
21
|
+
* node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native/blob/br_rnoh0.77
|
|
22
|
+
* # 输出: 鸿蒙化版本 7.2.2 + npm 最新版本
|
|
23
|
+
*
|
|
24
|
+
* node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native
|
|
25
|
+
* # 输出: 鸿蒙化版本 7.2.2 + npm 最新版本
|
|
26
|
+
*
|
|
27
|
+
* node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native/tree/br_rnoh0.77
|
|
28
|
+
* # 输出: 鸿蒙化版本 7.2.2 + npm 最新版本
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const https = require('https');
|
|
34
|
+
const http = require('http');
|
|
35
|
+
const { URL } = require('url');
|
|
36
|
+
const { execFileSync } = require('child_process');
|
|
37
|
+
const os = require('os');
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const fs = require('fs');
|
|
40
|
+
|
|
41
|
+
// ── 颜色输出 ─────────────────────────────────────────────
|
|
42
|
+
const COLORS = {
|
|
43
|
+
RED: '\x1b[0;31m',
|
|
44
|
+
GREEN: '\x1b[0;32m',
|
|
45
|
+
YELLOW: '\x1b[1;33m',
|
|
46
|
+
BLUE: '\x1b[0;34m',
|
|
47
|
+
CYAN: '\x1b[0;36m',
|
|
48
|
+
NC: '\x1b[0m',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function info(msg) { console.log(`${COLORS.BLUE}[INFO]${COLORS.NC} ${msg}`); }
|
|
52
|
+
function ok(msg) { console.log(`${COLORS.GREEN}[OK]${COLORS.NC} ${msg}`); }
|
|
53
|
+
function warn(msg) { console.log(`${COLORS.YELLOW}[WARN]${COLORS.NC} ${msg}`); }
|
|
54
|
+
function error(msg) { console.error(`${COLORS.RED}[ERROR]${COLORS.NC} ${msg}`); }
|
|
55
|
+
|
|
56
|
+
// ── URL 解析 ─────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 从 git 仓库 URL 中解析出平台、owner、repo、分支和路径格式。
|
|
60
|
+
*
|
|
61
|
+
* 支持的 URL 格式:
|
|
62
|
+
* https://gitcode.com/<owner>/<repo>
|
|
63
|
+
* https://gitcode.com/<owner>/<repo>/blob/<branch>
|
|
64
|
+
* https://gitcode.com/<owner>/<repo>/tree/<branch>
|
|
65
|
+
* https://gitcode.com/<owner>/<repo>/blob/<branch>/<path>
|
|
66
|
+
* https://gitcode.com/<owner>/<repo>/tree/<branch>/<path>
|
|
67
|
+
* https://github.com/<owner>/<repo>
|
|
68
|
+
* https://github.com/<owner>/<repo>/tree/<branch>
|
|
69
|
+
* https://github.com/<owner>/<repo>/blob/<branch>/<path>
|
|
70
|
+
* https://github.com/<owner>/<repo>.git
|
|
71
|
+
* git@gitcode.com:<owner>/<repo>.git
|
|
72
|
+
*
|
|
73
|
+
* @param {string} rawUrl 原始 URL
|
|
74
|
+
* @returns {{platform: string, owner: string, repo: string, branch: string, host: string}}
|
|
75
|
+
*/
|
|
76
|
+
function parseGitUrl(rawUrl) {
|
|
77
|
+
let urlStr = rawUrl.trim();
|
|
78
|
+
|
|
79
|
+
// 处理 SSH 格式: git@host:owner/repo.git
|
|
80
|
+
if (urlStr.startsWith('git@')) {
|
|
81
|
+
const match = urlStr.match(/^git@([^:]+):(.+)$/);
|
|
82
|
+
if (match) {
|
|
83
|
+
const host = match[1];
|
|
84
|
+
const path = match[2].replace(/\.git$/, '');
|
|
85
|
+
const parts = path.split('/').filter(Boolean);
|
|
86
|
+
if (parts.length >= 2) {
|
|
87
|
+
return {
|
|
88
|
+
platform: detectPlatform(host),
|
|
89
|
+
host: host,
|
|
90
|
+
owner: parts[0],
|
|
91
|
+
repo: parts[1],
|
|
92
|
+
branch: '',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`无法解析 SSH URL: ${rawUrl}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 去除查询参数
|
|
100
|
+
const queryIdx = urlStr.indexOf('?');
|
|
101
|
+
if (queryIdx >= 0) {
|
|
102
|
+
urlStr = urlStr.substring(0, queryIdx);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 去除锚点
|
|
106
|
+
const hashIdx = urlStr.indexOf('#');
|
|
107
|
+
if (hashIdx >= 0) {
|
|
108
|
+
urlStr = urlStr.substring(0, hashIdx);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parsed = new URL(urlStr);
|
|
112
|
+
const host = parsed.hostname;
|
|
113
|
+
const pathParts = parsed.pathname.split('/').filter(Boolean);
|
|
114
|
+
|
|
115
|
+
if (pathParts.length < 2) {
|
|
116
|
+
throw new Error(`URL 路径不完整,无法提取 owner/repo: ${rawUrl}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const owner = pathParts[0];
|
|
120
|
+
let repo = pathParts[1];
|
|
121
|
+
|
|
122
|
+
// 去除 .git 后缀
|
|
123
|
+
repo = repo.replace(/\.git$/, '');
|
|
124
|
+
|
|
125
|
+
// 解析分支: /blob/<branch> 或 /tree/<branch>
|
|
126
|
+
let branch = '';
|
|
127
|
+
if (pathParts.length >= 4 && (pathParts[2] === 'blob' || pathParts[2] === 'tree')) {
|
|
128
|
+
branch = pathParts[3];
|
|
129
|
+
} else if (pathParts.length >= 3 && (pathParts[2] === 'blob' || pathParts[2] === 'tree')) {
|
|
130
|
+
branch = pathParts[3] || '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
platform: detectPlatform(host),
|
|
135
|
+
host: host,
|
|
136
|
+
owner: owner,
|
|
137
|
+
repo: repo,
|
|
138
|
+
branch: branch,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 根据主机名检测平台类型。
|
|
144
|
+
* @param {string} host
|
|
145
|
+
* @returns {string} 'gitcode' | 'github' | 'gitee' | 'other'
|
|
146
|
+
*/
|
|
147
|
+
function detectPlatform(host) {
|
|
148
|
+
if (host.includes('gitcode.com')) return 'gitcode';
|
|
149
|
+
if (host.includes('github.com')) return 'github';
|
|
150
|
+
if (host.includes('gitee.com')) return 'gitee';
|
|
151
|
+
return 'other';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── 仓库名 → npm 包名推断 ───────────────────────────────
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 从仓库名中推断上游 npm 包名。
|
|
158
|
+
*
|
|
159
|
+
* 鸿蒙化仓库名通常遵循以下模式:
|
|
160
|
+
* rntpc_<upstream-pkg-name> → rntpc_lottie-turbo-react-native → lottie-turbo-react-native
|
|
161
|
+
* rntpc_<upstream-pkg-name>-turbo → (特殊处理)
|
|
162
|
+
* react-native-ohos-<name> → react-native-<name>
|
|
163
|
+
* <upstream-pkg-name> → 直接使用
|
|
164
|
+
*
|
|
165
|
+
* 推断策略:
|
|
166
|
+
* 1. 去除前缀 "rntpc_"
|
|
167
|
+
* 2. 去除前缀 "react-native-ohos-"
|
|
168
|
+
* 3. 如果结果以 "-turbo" 结尾且去掉后仍合理,尝试去掉
|
|
169
|
+
* 4. 其他情况直接使用仓库名
|
|
170
|
+
*
|
|
171
|
+
* @param {string} repoName 仓库名
|
|
172
|
+
* @returns {string} 推断的上游 npm 包名
|
|
173
|
+
*/
|
|
174
|
+
function inferPackageName(repoName) {
|
|
175
|
+
let name = repoName;
|
|
176
|
+
|
|
177
|
+
// 去除常见前缀
|
|
178
|
+
if (name.startsWith('rntpc_')) {
|
|
179
|
+
name = name.substring('rntpc_'.length);
|
|
180
|
+
} else if (name.startsWith('react-native-ohos-')) {
|
|
181
|
+
name = name.substring('react-native-ohos-'.length);
|
|
182
|
+
name = 'react-native-' + name;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return name;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── HTTP 请求 ────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 发送 HTTP/HTTPS 请求并返回响应体 (字符串)。
|
|
192
|
+
* @param {string} url 请求 URL
|
|
193
|
+
* @param {number} maxRedirects 最大重定向次数
|
|
194
|
+
* @returns {Promise<string>} 响应体
|
|
195
|
+
*/
|
|
196
|
+
function fetchText(url, maxRedirects = 5) {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
const parsed = new URL(url);
|
|
199
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
200
|
+
|
|
201
|
+
const options = {
|
|
202
|
+
hostname: parsed.hostname,
|
|
203
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
204
|
+
path: parsed.pathname + parsed.search,
|
|
205
|
+
method: 'GET',
|
|
206
|
+
headers: {
|
|
207
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
208
|
+
'Accept': 'text/plain, application/json, text/html, */*',
|
|
209
|
+
'Accept-Encoding': 'identity',
|
|
210
|
+
},
|
|
211
|
+
timeout: 30000,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const req = lib.request(options, (res) => {
|
|
215
|
+
// 处理重定向
|
|
216
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
217
|
+
if (maxRedirects <= 0) {
|
|
218
|
+
reject(new Error('重定向次数超限'));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const redirectUrl = res.headers.location.startsWith('http')
|
|
222
|
+
? res.headers.location
|
|
223
|
+
: new URL(res.headers.location, url).href;
|
|
224
|
+
fetchText(redirectUrl, maxRedirects - 1).then(resolve).catch(reject);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (res.statusCode !== 200) {
|
|
229
|
+
reject(new Error(`HTTP ${res.statusCode}: ${url}`));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let data = '';
|
|
234
|
+
res.setEncoding('utf-8');
|
|
235
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
236
|
+
res.on('end', () => resolve(data));
|
|
237
|
+
res.on('error', reject);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
req.on('error', reject);
|
|
241
|
+
req.on('timeout', () => {
|
|
242
|
+
req.destroy(new Error('请求超时'));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
req.end();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Raw 文件 URL 构造 ───────────────────────────────────
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 构造获取 package.json 原始内容的 URL。
|
|
253
|
+
*
|
|
254
|
+
* 不同平台的 raw 文件 URL 格式:
|
|
255
|
+
* - gitcode: https://raw.gitcode.com/<owner>/<repo>/raw/<branch>/package.json
|
|
256
|
+
* - github: https://raw.githubusercontent.com/<owner>/<repo>/<branch>/package.json
|
|
257
|
+
* - gitee: https://gitee.com/<owner>/<repo>/raw/<branch>/package.json
|
|
258
|
+
*
|
|
259
|
+
* @param {{platform: string, host: string, owner: string, repo: string, branch: string}} repoInfo
|
|
260
|
+
* @returns {string} raw package.json URL
|
|
261
|
+
*/
|
|
262
|
+
function buildRawPackageJsonUrl(repoInfo) {
|
|
263
|
+
const { platform, owner, repo, branch } = repoInfo;
|
|
264
|
+
const br = branch || 'master';
|
|
265
|
+
|
|
266
|
+
switch (platform) {
|
|
267
|
+
case 'github':
|
|
268
|
+
return `https://raw.githubusercontent.com/${owner}/${repo}/${br}/package.json`;
|
|
269
|
+
case 'gitcode':
|
|
270
|
+
return `https://raw.gitcode.com/${owner}/${repo}/raw/${br}/package.json`;
|
|
271
|
+
case 'gitee':
|
|
272
|
+
return `https://gitee.com/${owner}/${repo}/raw/${br}/package.json`;
|
|
273
|
+
default:
|
|
274
|
+
// 通用格式: 尝试 gitcode 风格
|
|
275
|
+
return `https://${repoInfo.host}/${owner}/${repo}/raw/${br}/package.json`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 获取 package.json 内容。
|
|
281
|
+
* 先尝试 raw URL,失败后尝试 API。
|
|
282
|
+
*
|
|
283
|
+
* @param {{platform: string, host: string, owner: string, repo: string, branch: string}} repoInfo
|
|
284
|
+
* @returns {Promise<object>} 解析后的 package.json 对象
|
|
285
|
+
*/
|
|
286
|
+
async function fetchPackageJson(repoInfo) {
|
|
287
|
+
const rawUrl = buildRawPackageJsonUrl(repoInfo);
|
|
288
|
+
info(`正在获取 package.json: ${rawUrl}`);
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const content = await fetchText(rawUrl);
|
|
292
|
+
const pkg = JSON.parse(content);
|
|
293
|
+
ok('成功获取并解析 package.json');
|
|
294
|
+
return pkg;
|
|
295
|
+
} catch (err) {
|
|
296
|
+
warn(`Raw URL 获取失败: ${err.message}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 回退: gitcode API v5 (返回 base64 编码的文件内容)
|
|
300
|
+
if (repoInfo.platform === 'gitcode') {
|
|
301
|
+
const apiUrl = `https://gitcode.com/api/v5/repos/${repoInfo.owner}/${repoInfo.repo}/contents/package.json?ref=${repoInfo.branch || 'master'}`;
|
|
302
|
+
info(`尝试从 gitcode API 获取: ${apiUrl}`);
|
|
303
|
+
try {
|
|
304
|
+
const content = await fetchText(apiUrl);
|
|
305
|
+
const data = JSON.parse(content);
|
|
306
|
+
if (data.content) {
|
|
307
|
+
const decoded = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
308
|
+
const pkg = JSON.parse(decoded);
|
|
309
|
+
ok('从 gitcode API 成功获取 package.json');
|
|
310
|
+
return pkg;
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
warn(`gitcode API 获取失败: ${err.message}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 回退: GitHub API
|
|
318
|
+
if (repoInfo.platform === 'github') {
|
|
319
|
+
const apiUrl = `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/contents/package.json?ref=${repoInfo.branch || 'master'}`;
|
|
320
|
+
info(`尝试从 GitHub API 获取: ${apiUrl}`);
|
|
321
|
+
try {
|
|
322
|
+
const content = await fetchText(apiUrl);
|
|
323
|
+
const data = JSON.parse(content);
|
|
324
|
+
if (data.content && data.encoding === 'base64') {
|
|
325
|
+
const decoded = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
326
|
+
const pkg = JSON.parse(decoded);
|
|
327
|
+
ok('从 GitHub API 成功获取 package.json');
|
|
328
|
+
return pkg;
|
|
329
|
+
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
warn(`GitHub API 获取失败: ${err.message}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 回退: Gitee API
|
|
336
|
+
if (repoInfo.platform === 'gitee') {
|
|
337
|
+
const apiUrl = `https://gitee.com/api/v5/repos/${repoInfo.owner}/${repoInfo.repo}/contents/package.json?ref=${repoInfo.branch || 'master'}`;
|
|
338
|
+
info(`尝试从 Gitee API 获取: ${apiUrl}`);
|
|
339
|
+
try {
|
|
340
|
+
const content = await fetchText(apiUrl);
|
|
341
|
+
const data = JSON.parse(content);
|
|
342
|
+
if (data.content) {
|
|
343
|
+
const decoded = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
344
|
+
const pkg = JSON.parse(decoded);
|
|
345
|
+
ok('从 Gitee API 成功获取 package.json');
|
|
346
|
+
return pkg;
|
|
347
|
+
}
|
|
348
|
+
} catch (err) {
|
|
349
|
+
warn(`Gitee API 获取失败: ${err.message}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 最终回退: git clone --depth=1
|
|
354
|
+
info('尝试通过 git clone 获取...');
|
|
355
|
+
try {
|
|
356
|
+
const pkg = await fetchPackageJsonViaClone(repoInfo);
|
|
357
|
+
if (pkg) {
|
|
358
|
+
ok('通过 git clone 成功获取 package.json');
|
|
359
|
+
return pkg;
|
|
360
|
+
}
|
|
361
|
+
} catch (err) {
|
|
362
|
+
warn(`git clone 获取失败: ${err.message}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
throw new Error('无法获取 package.json,请检查仓库地址和分支是否正确');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 通过 git clone --depth=1 获取 package.json。
|
|
370
|
+
* 这是最可靠但最慢的回退方案。
|
|
371
|
+
*
|
|
372
|
+
* @param {{platform: string, host: string, owner: string, repo: string, branch: string}} repoInfo
|
|
373
|
+
* @returns {Promise<object|null>} 解析后的 package.json 对象
|
|
374
|
+
*/
|
|
375
|
+
async function fetchPackageJsonViaClone(repoInfo) {
|
|
376
|
+
const cloneUrl = `https://${repoInfo.host}/${repoInfo.owner}/${repoInfo.repo}.git`;
|
|
377
|
+
const branch = repoInfo.branch || 'master';
|
|
378
|
+
const tmpDir = path.join(os.tmpdir(), `detect-upstream-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
379
|
+
|
|
380
|
+
return new Promise((resolve, reject) => {
|
|
381
|
+
try {
|
|
382
|
+
execFileSync('git', [
|
|
383
|
+
'clone', '--depth=1',
|
|
384
|
+
'--branch', branch,
|
|
385
|
+
cloneUrl,
|
|
386
|
+
tmpDir,
|
|
387
|
+
], {
|
|
388
|
+
timeout: 60000,
|
|
389
|
+
stdio: 'pipe',
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const pkgPath = path.join(tmpDir, 'package.json');
|
|
393
|
+
if (!fs.existsSync(pkgPath)) {
|
|
394
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
395
|
+
resolve(null);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const content = fs.readFileSync(pkgPath, 'utf-8');
|
|
400
|
+
const pkg = JSON.parse(content);
|
|
401
|
+
|
|
402
|
+
// 清理临时目录
|
|
403
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
404
|
+
resolve(pkg);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
// 清理临时目录
|
|
407
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) {}
|
|
408
|
+
reject(err);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ── 版本号匹配 ───────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* 在 package.json 中查找与仓库名匹配的上游依赖版本号。
|
|
417
|
+
*
|
|
418
|
+
* 匹配策略 (按优先级):
|
|
419
|
+
* 1. 使用 harmony.alias 字段的值作为包名,在 dependencies 中查找
|
|
420
|
+
* 2. 使用从仓库名推断的包名,在 dependencies 中查找
|
|
421
|
+
* 3. 使用 harmony.alias 在 devDependencies 中查找
|
|
422
|
+
* 4. 使用推断的包名在 devDependencies 中查找
|
|
423
|
+
* 5. 使用 package.json 的 name 字段去掉 @react-native-ohos/ 前缀后在依赖中查找
|
|
424
|
+
*
|
|
425
|
+
* @param {object} pkg package.json 对象
|
|
426
|
+
* @param {string} repoName 仓库名
|
|
427
|
+
* @returns {{version: string, matchedField: string, matchedSection: string, packageName: string}}
|
|
428
|
+
*/
|
|
429
|
+
function findUpstreamVersion(pkg, repoName) {
|
|
430
|
+
const inferredPkgName = inferPackageName(repoName);
|
|
431
|
+
const harmonyAlias = pkg.harmony && pkg.harmony.alias ? pkg.harmony.alias : '';
|
|
432
|
+
const pkgName = pkg.name || '';
|
|
433
|
+
|
|
434
|
+
// 从 @react-native-ohos/xxx 中提取 xxx
|
|
435
|
+
const ohosPkgName = pkgName.startsWith('@react-native-ohos/')
|
|
436
|
+
? pkgName.substring('@react-native-ohos/'.length)
|
|
437
|
+
: pkgName;
|
|
438
|
+
|
|
439
|
+
// 候选包名列表 (按优先级排序,去重)
|
|
440
|
+
const candidateNames = [];
|
|
441
|
+
const addCandidate = (name) => {
|
|
442
|
+
if (name && !candidateNames.includes(name)) {
|
|
443
|
+
candidateNames.push(name);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
addCandidate(harmonyAlias);
|
|
447
|
+
addCandidate(inferredPkgName);
|
|
448
|
+
addCandidate(ohosPkgName);
|
|
449
|
+
|
|
450
|
+
// 搜索的依赖区域 (按优先级排序)
|
|
451
|
+
const sections = [
|
|
452
|
+
{ key: 'dependencies', label: 'dependencies' },
|
|
453
|
+
{ key: 'devDependencies', label: 'devDependencies' },
|
|
454
|
+
{ key: 'peerDependencies', label: 'peerDependencies' },
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
// 遍历候选包名 × 依赖区域
|
|
458
|
+
for (const candidateName of candidateNames) {
|
|
459
|
+
for (const section of sections) {
|
|
460
|
+
const deps = pkg[section.key];
|
|
461
|
+
if (!deps || typeof deps !== 'object') continue;
|
|
462
|
+
|
|
463
|
+
// 精确匹配
|
|
464
|
+
if (deps.hasOwnProperty(candidateName)) {
|
|
465
|
+
return {
|
|
466
|
+
version: deps[candidateName],
|
|
467
|
+
matchedField: candidateName,
|
|
468
|
+
matchedSection: section.label,
|
|
469
|
+
packageName: candidateName,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 模糊匹配: 如果候选名包含某些关键词,尝试在依赖中反向查找
|
|
476
|
+
// 例如仓库名 rntpc_lottie-turbo-react-native 推断出 lottie-turbo-react-native
|
|
477
|
+
// 但实际依赖名可能是 lottie-react-native
|
|
478
|
+
for (const section of sections) {
|
|
479
|
+
const deps = pkg[section.key];
|
|
480
|
+
if (!deps || typeof deps !== 'object') continue;
|
|
481
|
+
|
|
482
|
+
for (const depName of Object.keys(deps)) {
|
|
483
|
+
// 跳过 @react-native-ohos/ 前缀的包 (那是鸿蒙化包自身)
|
|
484
|
+
if (depName.startsWith('@react-native-ohos/')) continue;
|
|
485
|
+
// 跳过 react/react-native 核心包
|
|
486
|
+
if (['react', 'react-native', 'react-native-windows'].includes(depName)) continue;
|
|
487
|
+
|
|
488
|
+
// 检查候选名是否与依赖名有足够相似度
|
|
489
|
+
for (const candidateName of candidateNames) {
|
|
490
|
+
if (isPackageMatch(candidateName, depName)) {
|
|
491
|
+
return {
|
|
492
|
+
version: deps[depName],
|
|
493
|
+
matchedField: depName,
|
|
494
|
+
matchedSection: section.label,
|
|
495
|
+
packageName: depName,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* 判断两个包名是否匹配 (模糊匹配)。
|
|
507
|
+
*
|
|
508
|
+
* 规则:
|
|
509
|
+
* 1. 精确匹配
|
|
510
|
+
* 2. 一个包含另一个 (如 lottie-turbo-react-native 包含 lottie-react-native)
|
|
511
|
+
* 3. 去除 turbo 后缀后匹配
|
|
512
|
+
* 4. 共享相同的关键词组合 (如都包含 lottie 和 react-native)
|
|
513
|
+
*
|
|
514
|
+
* @param {string} candidate 候选包名
|
|
515
|
+
* @param {string} depName 依赖中的包名
|
|
516
|
+
* @returns {boolean}
|
|
517
|
+
*/
|
|
518
|
+
function isPackageMatch(candidate, depName) {
|
|
519
|
+
if (candidate === depName) return true;
|
|
520
|
+
if (!candidate || !depName) return false;
|
|
521
|
+
|
|
522
|
+
// 去除 turbo 后缀
|
|
523
|
+
const normalize = (s) => s.replace(/-turbo$/i, '').replace(/turbo-/gi, '');
|
|
524
|
+
if (normalize(candidate) === normalize(depName)) return true;
|
|
525
|
+
|
|
526
|
+
// 一个包含另一个
|
|
527
|
+
if (candidate.includes(depName) || depName.includes(candidate)) return true;
|
|
528
|
+
|
|
529
|
+
// 提取关键词 (以 - 分割),检查交集
|
|
530
|
+
const candidateKeywords = candidate.split('-').filter(k => k.length > 2);
|
|
531
|
+
const depKeywords = depName.split('-').filter(k => k.length > 2);
|
|
532
|
+
const intersection = candidateKeywords.filter(k => depKeywords.includes(k));
|
|
533
|
+
|
|
534
|
+
// 如果有足够的关键词交集 (至少2个且覆盖了依赖名的大部分关键词)
|
|
535
|
+
if (intersection.length >= 2 && intersection.length >= depKeywords.length - 1) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ── npm Registry 查询 ───────────────────────────────────
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* 从 npm registry 查询包的最新版本号。
|
|
546
|
+
*
|
|
547
|
+
* 使用 npm registry API:
|
|
548
|
+
* GET https://registry.npmjs.org/<package-name>
|
|
549
|
+
* 返回的 JSON 中 dist-tags.latest 包含最新版本号。
|
|
550
|
+
*
|
|
551
|
+
* 对于 scoped 包 (@scope/pkg),需要将 / 编码为 %2F。
|
|
552
|
+
*
|
|
553
|
+
* 查询策略 (按优先级):
|
|
554
|
+
* 1. npm registry packument API (dist-tags.latest)
|
|
555
|
+
* 2. npm registry /latest 端点
|
|
556
|
+
* 3. npm view 命令 (尊重用户本地 npm 配置)
|
|
557
|
+
*
|
|
558
|
+
* @param {string} packageName npm 包名
|
|
559
|
+
* @returns {Promise<{latest: string, allTags: object, recentVersions: string[]} | null>} 版本信息
|
|
560
|
+
*/
|
|
561
|
+
async function fetchLatestNpmVersion(packageName) {
|
|
562
|
+
// 对 scoped 包名进行编码: @scope/pkg → @scope%2Fpkg
|
|
563
|
+
const encodedName = packageName.replace('/', '%2F');
|
|
564
|
+
|
|
565
|
+
// 策略 1: packument API
|
|
566
|
+
const registryUrl = `https://registry.npmjs.org/${encodedName}`;
|
|
567
|
+
info(`正在查询 npm registry: ${registryUrl}`);
|
|
568
|
+
|
|
569
|
+
try {
|
|
570
|
+
const content = await fetchText(registryUrl);
|
|
571
|
+
const data = JSON.parse(content);
|
|
572
|
+
|
|
573
|
+
const distTags = data['dist-tags'] || data.distTags;
|
|
574
|
+
if (distTags && distTags.latest) {
|
|
575
|
+
// 从 versions 字段提取所有版本号,排序后取最近 10 个
|
|
576
|
+
const allVersions = data.versions ? Object.keys(data.versions) : [];
|
|
577
|
+
const recentVersions = sortVersionsDesc(allVersions).slice(0, 10);
|
|
578
|
+
ok(`npm 最新版本: ${distTags.latest}`);
|
|
579
|
+
if (recentVersions.length > 0) {
|
|
580
|
+
info(`最近 ${recentVersions.length} 个版本: ${recentVersions.join(', ')}`);
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
latest: distTags.latest,
|
|
584
|
+
allTags: distTags,
|
|
585
|
+
recentVersions: recentVersions,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 某些简化的响应可能直接包含 version 字段
|
|
590
|
+
if (data.version) {
|
|
591
|
+
ok(`npm 最新版本: ${data.version}`);
|
|
592
|
+
return {
|
|
593
|
+
latest: data.version,
|
|
594
|
+
allTags: {},
|
|
595
|
+
recentVersions: [],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
warn('npm registry 响应中未找到 dist-tags.latest');
|
|
600
|
+
} catch (err) {
|
|
601
|
+
warn(`npm registry 查询失败: ${err.message}`);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 策略 2: /latest 端点
|
|
605
|
+
const latestUrl = `https://registry.npmjs.org/${encodedName}/latest`;
|
|
606
|
+
info(`尝试从 /latest 端点获取: ${latestUrl}`);
|
|
607
|
+
try {
|
|
608
|
+
const content = await fetchText(latestUrl);
|
|
609
|
+
const data = JSON.parse(content);
|
|
610
|
+
if (data.version) {
|
|
611
|
+
ok(`npm 最新版本 (via /latest): ${data.version}`);
|
|
612
|
+
return {
|
|
613
|
+
latest: data.version,
|
|
614
|
+
allTags: {},
|
|
615
|
+
recentVersions: [],
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
} catch (err) {
|
|
619
|
+
warn(`/latest 端点查询失败: ${err.message}`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// 策略 3: npm view 命令 (尊重用户本地 .npmrc 配置,如镜像源)
|
|
623
|
+
const npmResult = await fetchLatestViaNpmCommand(packageName);
|
|
624
|
+
if (npmResult) {
|
|
625
|
+
return npmResult;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* 通过 npm view 命令获取最新版本号和最近版本列表。
|
|
633
|
+
*
|
|
634
|
+
* 此方式会读取用户本地的 .npmrc 配置,
|
|
635
|
+
* 因此能正确处理使用了镜像源 (如 npmmirror.com) 的场景。
|
|
636
|
+
*
|
|
637
|
+
* @param {string} packageName npm 包名
|
|
638
|
+
* @returns {Promise<{latest: string, allTags: object, recentVersions: string[]} | null>}
|
|
639
|
+
*/
|
|
640
|
+
async function fetchLatestViaNpmCommand(packageName) {
|
|
641
|
+
info('尝试通过 npm view 命令获取...');
|
|
642
|
+
try {
|
|
643
|
+
// 获取最新版本
|
|
644
|
+
const versionOutput = execFileSync('npm', [
|
|
645
|
+
'view', packageName, 'version',
|
|
646
|
+
], {
|
|
647
|
+
timeout: 30000,
|
|
648
|
+
encoding: 'utf-8',
|
|
649
|
+
stdio: 'pipe',
|
|
650
|
+
}).trim();
|
|
651
|
+
|
|
652
|
+
// 获取所有版本列表
|
|
653
|
+
let recentVersions = [];
|
|
654
|
+
try {
|
|
655
|
+
const versionsOutput = execFileSync('npm', [
|
|
656
|
+
'view', packageName, 'versions', '--json',
|
|
657
|
+
], {
|
|
658
|
+
timeout: 30000,
|
|
659
|
+
encoding: 'utf-8',
|
|
660
|
+
stdio: 'pipe',
|
|
661
|
+
}).trim();
|
|
662
|
+
|
|
663
|
+
const allVersions = JSON.parse(versionsOutput);
|
|
664
|
+
if (Array.isArray(allVersions)) {
|
|
665
|
+
recentVersions = sortVersionsDesc(allVersions).slice(0, 10);
|
|
666
|
+
}
|
|
667
|
+
} catch (e) {
|
|
668
|
+
// versions 获取失败不影响主流程
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (versionOutput) {
|
|
672
|
+
// npm view 可能返回多行 (废弃包警告等),取最后一行作为版本号
|
|
673
|
+
const lines = versionOutput.split('\n').filter(l => l.trim());
|
|
674
|
+
const version = lines[lines.length - 1].trim();
|
|
675
|
+
ok(`npm 最新版本 (via npm view): ${version}`);
|
|
676
|
+
if (recentVersions.length > 0) {
|
|
677
|
+
info(`最近 ${recentVersions.length} 个版本: ${recentVersions.join(', ')}`);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
latest: version,
|
|
681
|
+
allTags: {},
|
|
682
|
+
recentVersions: recentVersions,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
} catch (err) {
|
|
686
|
+
warn(`npm view 命令失败: ${err.message}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* 解析语义化版本号为结构化对象。
|
|
694
|
+
*
|
|
695
|
+
* @param {string} version 版本号 (可能含 ^ ~ 等前缀)
|
|
696
|
+
* @returns {{major: number, minor: number, patch: number, prerelease: string}}
|
|
697
|
+
*/
|
|
698
|
+
function parseSemver(version) {
|
|
699
|
+
const clean = version.replace(/^[\^~>=<\s]+/, '');
|
|
700
|
+
const [mainPart, prePart] = clean.split('-');
|
|
701
|
+
const parts = mainPart.split('.');
|
|
702
|
+
return {
|
|
703
|
+
major: parseInt(parts[0] || '0', 10),
|
|
704
|
+
minor: parseInt(parts[1] || '0', 10),
|
|
705
|
+
patch: parseInt(parts[2] || '0', 10),
|
|
706
|
+
prerelease: prePart || '',
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* 比较两个语义化版本号的大小 (含 prerelease 优先级)。
|
|
712
|
+
*
|
|
713
|
+
* 遵循 semver 规范:
|
|
714
|
+
* - major.minor.patch 数值比较
|
|
715
|
+
* - 无 prerelease > 有 prerelease (如 7.4.0 > 7.4.0-rc1)
|
|
716
|
+
* - prerelease 部分按标识符逐段比较 (数字用数值,字母用 ASCII)
|
|
717
|
+
*
|
|
718
|
+
* @param {string} v1 版本号1
|
|
719
|
+
* @param {string} v2 版本号2
|
|
720
|
+
* @returns {number} -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
|
|
721
|
+
*/
|
|
722
|
+
function compareSemver(v1, v2) {
|
|
723
|
+
const p1 = parseSemver(v1);
|
|
724
|
+
const p2 = parseSemver(v2);
|
|
725
|
+
|
|
726
|
+
if (p1.major !== p2.major) return p1.major < p2.major ? -1 : 1;
|
|
727
|
+
if (p1.minor !== p2.minor) return p1.minor < p2.minor ? -1 : 1;
|
|
728
|
+
if (p1.patch !== p2.patch) return p1.patch < p2.patch ? -1 : 1;
|
|
729
|
+
|
|
730
|
+
// 无 prerelease > 有 prerelease
|
|
731
|
+
if (!p1.prerelease && p2.prerelease) return 1;
|
|
732
|
+
if (p1.prerelease && !p2.prerelease) return -1;
|
|
733
|
+
if (!p1.prerelease && !p2.prerelease) return 0;
|
|
734
|
+
|
|
735
|
+
// 两者都有 prerelease,逐段比较
|
|
736
|
+
const pre1Parts = p1.prerelease.split('.');
|
|
737
|
+
const pre2Parts = p2.prerelease.split('.');
|
|
738
|
+
const maxLen = Math.max(pre1Parts.length, pre2Parts.length);
|
|
739
|
+
for (let i = 0; i < maxLen; i++) {
|
|
740
|
+
const a = pre1Parts[i];
|
|
741
|
+
const b = pre2Parts[i];
|
|
742
|
+
if (!a) return -1;
|
|
743
|
+
if (!b) return 1;
|
|
744
|
+
const aNum = parseInt(a, 10);
|
|
745
|
+
const bNum = parseInt(b, 10);
|
|
746
|
+
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
747
|
+
if (aNum !== bNum) return aNum < bNum ? -1 : 1;
|
|
748
|
+
} else {
|
|
749
|
+
if (a !== b) return a < b ? -1 : 1;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return 0;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* 将版本号数组按语义化版本降序排序 (最新在前)。
|
|
757
|
+
*
|
|
758
|
+
* @param {string[]} versions 版本号数组
|
|
759
|
+
* @returns {string[]} 排序后的数组 (新数组,不修改原数组)
|
|
760
|
+
*/
|
|
761
|
+
function sortVersionsDesc(versions) {
|
|
762
|
+
return [...versions].sort((a, b) => compareSemver(b, a));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* 比较两个语义化版本号的大小。
|
|
767
|
+
*
|
|
768
|
+
* 兼容包装: 使用 compareSemver 进行完整比较。
|
|
769
|
+
*
|
|
770
|
+
* @param {string} v1 版本号1 (可能含 ^ ~ 等前缀)
|
|
771
|
+
* @param {string} v2 版本号2
|
|
772
|
+
* @returns {number} -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
|
|
773
|
+
*/
|
|
774
|
+
function compareVersions(v1, v2) {
|
|
775
|
+
return compareSemver(v1, v2);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* 根据版本比较结果生成状态描述。
|
|
780
|
+
*
|
|
781
|
+
* @param {string} currentVersion 当前鸿蒙化使用的版本
|
|
782
|
+
* @param {string} latestVersion npm 最新版本
|
|
783
|
+
* @returns {{status: string, color: string, detail: string}}
|
|
784
|
+
*/
|
|
785
|
+
function getVersionStatus(currentVersion, latestVersion) {
|
|
786
|
+
const cmp = compareVersions(currentVersion, latestVersion);
|
|
787
|
+
const cleanCurrent = currentVersion.replace(/^[\^~>=<\s]+/, '');
|
|
788
|
+
|
|
789
|
+
if (cmp === 0) {
|
|
790
|
+
return {
|
|
791
|
+
status: '最新',
|
|
792
|
+
color: COLORS.GREEN,
|
|
793
|
+
detail: `鸿蒙化版本与 npm 最新版本一致 (${cleanCurrent})`,
|
|
794
|
+
};
|
|
795
|
+
} else if (cmp < 0) {
|
|
796
|
+
// 计算落后的大版本差距
|
|
797
|
+
const curParts = cleanCurrent.split('.').map(n => parseInt(n, 10));
|
|
798
|
+
const latParts = latestVersion.split('.').map(n => parseInt(n, 10));
|
|
799
|
+
const majorDiff = (latParts[0] || 0) - (curParts[0] || 0);
|
|
800
|
+
const minorDiff = (latParts[1] || 0) - (curParts[1] || 0);
|
|
801
|
+
const patchDiff = (latParts[2] || 0) - (curParts[2] || 0);
|
|
802
|
+
|
|
803
|
+
let gapDesc;
|
|
804
|
+
if (majorDiff > 0) {
|
|
805
|
+
gapDesc = `落后 ${majorDiff} 个大版本`;
|
|
806
|
+
} else if (minorDiff > 0) {
|
|
807
|
+
gapDesc = `落后 ${minorDiff} 个小版本`;
|
|
808
|
+
} else {
|
|
809
|
+
gapDesc = `落后 ${patchDiff} 个补丁版本`;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return {
|
|
813
|
+
status: '落后',
|
|
814
|
+
color: COLORS.YELLOW,
|
|
815
|
+
detail: `鸿蒙化版本落后于 npm 最新版本 (${gapDesc})`,
|
|
816
|
+
};
|
|
817
|
+
} else {
|
|
818
|
+
return {
|
|
819
|
+
status: '超前',
|
|
820
|
+
color: COLORS.CYAN,
|
|
821
|
+
detail: `鸿蒙化版本高于 npm 最新版本 (可能使用了 beta/next 等预发布版本)`,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ── 主流程 ───────────────────────────────────────────────
|
|
827
|
+
|
|
828
|
+
async function main() {
|
|
829
|
+
const args = process.argv.slice(2);
|
|
830
|
+
|
|
831
|
+
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
832
|
+
console.log(`
|
|
833
|
+
detect-upstream-version.js
|
|
834
|
+
从 RN 鸿蒙三方库 git 仓库链接中分析上游版本号
|
|
835
|
+
|
|
836
|
+
用法:
|
|
837
|
+
node detect-upstream-version.js <git-repo-url>
|
|
838
|
+
|
|
839
|
+
示例:
|
|
840
|
+
node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native/blob/br_rnoh0.77
|
|
841
|
+
node detect-upstream-version.js https://gitcode.com/CPF-RN/rntpc_lottie-turbo-react-native
|
|
842
|
+
node detect-upstream-version.js https://github.com/react-native-oh-library/lottie-react-native/tree/main
|
|
843
|
+
|
|
844
|
+
输出说明 (普通模式):
|
|
845
|
+
鸿蒙化版本 - 鸿蒙化仓库 package.json 中记录的上游依赖版本
|
|
846
|
+
npm最新版本 - 通过 npm registry API 查询到的 dist-tags.latest 版本
|
|
847
|
+
最近版本 - npm registry 中按语义化版本排序的最近 10 个版本号
|
|
848
|
+
版本状态 - up-to-date/behind/ahead,表示鸿蒙化版本相对最新版本的状态
|
|
849
|
+
|
|
850
|
+
输出说明 (quiet 模式):
|
|
851
|
+
格式: <鸿蒙化版本> <npm最新版本> <状态> <最近10个版本(逗号分隔)>
|
|
852
|
+
状态: up-to-date | behind | ahead | unknown
|
|
853
|
+
示例: 7.2.2 7.4.0 behind 7.4.0,7.3.1,7.2.2,7.2.1,...
|
|
854
|
+
|
|
855
|
+
选项:
|
|
856
|
+
-h, --help 显示帮助信息
|
|
857
|
+
-v, --verbose 显示详细输出
|
|
858
|
+
-q, --quiet 只输出关键信息 (适合脚本调用)
|
|
859
|
+
`);
|
|
860
|
+
process.exit(0);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const verbose = args.includes('-v') || args.includes('--verbose');
|
|
864
|
+
const quiet = args.includes('-q') || args.includes('--quiet');
|
|
865
|
+
const urlArg = args.find(a => !a.startsWith('-'));
|
|
866
|
+
|
|
867
|
+
if (!urlArg) {
|
|
868
|
+
error('请提供 git 仓库 URL');
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
try {
|
|
873
|
+
// 1. 解析 URL
|
|
874
|
+
const repoInfo = parseGitUrl(urlArg);
|
|
875
|
+
if (verbose) {
|
|
876
|
+
info(`平台: ${repoInfo.platform}`);
|
|
877
|
+
info(`Owner: ${repoInfo.owner}`);
|
|
878
|
+
info(`仓库: ${repoInfo.repo}`);
|
|
879
|
+
info(`分支: ${repoInfo.branch || '(默认)'}`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// 2. 获取 package.json
|
|
883
|
+
const pkg = await fetchPackageJson(repoInfo);
|
|
884
|
+
|
|
885
|
+
if (verbose) {
|
|
886
|
+
info(`包名: ${pkg.name || '(无)'}`);
|
|
887
|
+
if (pkg.harmony && pkg.harmony.alias) {
|
|
888
|
+
info(`Harmony alias: ${pkg.harmony.alias}`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// 3. 查找上游版本号
|
|
893
|
+
const result = findUpstreamVersion(pkg, repoInfo.repo);
|
|
894
|
+
|
|
895
|
+
if (result) {
|
|
896
|
+
const cleanVersion = result.version.replace(/^[\^~>=<\s]+/, '');
|
|
897
|
+
|
|
898
|
+
// 4. 查询 npm registry 获取最新版本和最近版本列表
|
|
899
|
+
const npmInfo = await fetchLatestNpmVersion(result.matchedField);
|
|
900
|
+
|
|
901
|
+
if (quiet) {
|
|
902
|
+
// 只输出关键信息: 当前版本 最新版本 状态 最近10个版本
|
|
903
|
+
if (npmInfo) {
|
|
904
|
+
const cmp = compareVersions(cleanVersion, npmInfo.latest);
|
|
905
|
+
const status = cmp === 0 ? 'up-to-date' : (cmp < 0 ? 'behind' : 'ahead');
|
|
906
|
+
const recentStr = npmInfo.recentVersions && npmInfo.recentVersions.length > 0
|
|
907
|
+
? npmInfo.recentVersions.join(',')
|
|
908
|
+
: 'N/A';
|
|
909
|
+
console.log(`${cleanVersion} ${npmInfo.latest} ${status} ${recentStr}`);
|
|
910
|
+
} else {
|
|
911
|
+
console.log(`${cleanVersion} UNKNOWN unknown N/A`);
|
|
912
|
+
}
|
|
913
|
+
} else {
|
|
914
|
+
ok(`找到上游版本:`);
|
|
915
|
+
console.log(` 仓库名: ${repoInfo.repo}`);
|
|
916
|
+
console.log(` 匹配包名: ${result.matchedField}`);
|
|
917
|
+
console.log(` 匹配区域: ${result.matchedSection}`);
|
|
918
|
+
console.log(` 鸿蒙化版本: ${result.version}`);
|
|
919
|
+
console.log(` 纯版本号: ${cleanVersion}`);
|
|
920
|
+
|
|
921
|
+
if (npmInfo) {
|
|
922
|
+
console.log(` npm最新版本: ${npmInfo.latest}`);
|
|
923
|
+
|
|
924
|
+
// 显示其他 dist-tags (如果有)
|
|
925
|
+
if (npmInfo.allTags && Object.keys(npmInfo.allTags).length > 1) {
|
|
926
|
+
const otherTags = Object.entries(npmInfo.allTags)
|
|
927
|
+
.filter(([k]) => k !== 'latest')
|
|
928
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
929
|
+
.join(', ');
|
|
930
|
+
if (otherTags) {
|
|
931
|
+
console.log(` 其他标签: ${otherTags}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// 显示最近 10 个版本
|
|
936
|
+
if (npmInfo.recentVersions && npmInfo.recentVersions.length > 0) {
|
|
937
|
+
console.log(` 最近版本: ${npmInfo.recentVersions.join(', ')}`);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// 版本状态分析
|
|
941
|
+
const versionStatus = getVersionStatus(cleanVersion, npmInfo.latest);
|
|
942
|
+
console.log(` 版本状态: ${versionStatus.color}${versionStatus.status}${COLORS.NC} - ${versionStatus.detail}`);
|
|
943
|
+
} else {
|
|
944
|
+
console.log(` npm最新版本: ${COLORS.YELLOW}查询失败${COLORS.NC}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
} else {
|
|
948
|
+
if (quiet) {
|
|
949
|
+
console.error('UNKNOWN');
|
|
950
|
+
} else {
|
|
951
|
+
warn('未找到匹配的上游版本号');
|
|
952
|
+
if (verbose && pkg.dependencies) {
|
|
953
|
+
info('dependencies 中的依赖:');
|
|
954
|
+
for (const [name, version] of Object.entries(pkg.dependencies)) {
|
|
955
|
+
console.log(` ${name}: ${version}`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
process.exit(2);
|
|
960
|
+
}
|
|
961
|
+
} catch (err) {
|
|
962
|
+
error(err.message);
|
|
963
|
+
process.exit(1);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
main();
|