@thomasminh1995/depverdict 0.6.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/bin/depverdict.js +7 -0
- package/bin/upgradelens.js +7 -0
- package/docs/GR-01-Semantic-Grounding-Failure-Analysis.md +309 -0
- package/docs/GR-02-Versioned-Action-Evaluation-Criteria.md +187 -0
- package/docs/GR-03-Extractive-Contract-Safety-Experiment.md +242 -0
- package/docs/GR-04-Versioned-Production-Extractive-Contract.md +154 -0
- package/docs/IA-01-Repository-Usage-Discovery.md +122 -0
- package/docs/IA-02-Repository-Impact-Analysis.md +118 -0
- package/docs/IA-03-Repository-Impact-Evidence.md +160 -0
- package/docs/IA-04-CLI-Orchestration.md +235 -0
- package/docs/IA-05-Real-Provider-Validation.md +235 -0
- package/docs/IA-05-VinGrade-Validation.md +339 -0
- package/docs/MVP-01.md +48 -0
- package/docs/MVP-02-Architecture.md +536 -0
- package/docs/MVP-02-CLI-HTTP-Runtime.md +50 -0
- package/docs/MVP-02-HTTP-Lifecycle.md +37 -0
- package/docs/MVP-02-Knowledge-Manifest-Generation.md +75 -0
- package/docs/MVP-02-Knowledge-Manifest.md +279 -0
- package/docs/MVP-02-Knowledge-Research-Orchestration.md +62 -0
- package/docs/MVP-02-Knowledge-Store.md +87 -0
- package/docs/MVP-02-PyPI-Registry-Adapter.md +97 -0
- package/docs/MVP-02-Research-Planning.md +196 -0
- package/docs/MVP-02-Source-Provenance.md +81 -0
- package/docs/MVP-02-npm-Registry-Adapter.md +99 -0
- package/docs/RR-01-End-to-End-and-Real-Provider-Validation.md +393 -0
- package/docs/RR-01-RERUN-Extractive-Contract-Validation.md +566 -0
- package/docs/RR-02-Full-Product-Workflow-and-Developer-CLI-UX-Review.md +35 -0
- package/docs/RR02-FIX-01-Persistent-Qualification-Resolution.md +32 -0
- package/docs/RR02-FIX-02-Stage-aware-CLI-Progress-and-Heartbeat.md +28 -0
- package/docs/RR02-FIX-03-npm-Capture-Evidence-Exclusion.md +33 -0
- package/docs/RR02-FIX-03A-Complete-Package-Exclusion-and-Evidence-Commit.md +223 -0
- package/docs/RR02-FIX-04-Event-loop-safe-Heartbeat.md +393 -0
- package/docs/RR02-FIX-05-Materialize-Persisted-Qualification.md +207 -0
- package/docs/RR02-RERUN-CLI-Qualification-Progress-UX-and-Package-Validation.md +342 -0
- package/docs/VinGrade-MVP-02-Validation.md +480 -0
- package/docs/VinGrade-RC02-Live-Validation.md +273 -0
- package/docs/ai-capability-discovery.md +573 -0
- package/docs/ai-engineering-production-readiness.md +390 -0
- package/docs/ai-engineering-review.md +251 -0
- package/docs/ai-runtime-governance-discovery.md +754 -0
- package/docs/architecture-overview.md +78 -0
- package/docs/cli-progress.md +111 -0
- package/docs/decisions/diff-01-brand-distribution-identity.md +450 -0
- package/docs/decisions/diff-02-identity-compatibility-contract.md +274 -0
- package/docs/decisions/diff-03-repository-docs-community-migration.md +165 -0
- package/docs/decisions/diff-04-release-evidence-gap-acceptance.md +110 -0
- package/docs/discovery/mvp-05-ai-migration-planning-discovery.md +350 -0
- package/docs/gateway-runtime-discovery.md +614 -0
- package/docs/live-ai-validation.md +299 -0
- package/docs/migration-planning-qualification-resolution.md +99 -0
- package/docs/migrations/upgradelens-to-depverdict.md +97 -0
- package/docs/mp-r03-deterministic-upgrade-decision-architecture.md +135 -0
- package/docs/mp-r04-evidence-bounded-migration-handoff-architecture.md +116 -0
- package/docs/mp-r05-product-completion-and-decision-first-cli-architecture.md +162 -0
- package/docs/mvp-05-deterministic-context-runtime.md +127 -0
- package/docs/mvp-05-migration-checklist-contract.md +166 -0
- package/docs/mvp-05-migration-checklist-orchestration.md +105 -0
- package/docs/mvp-05-migration-evaluation-and-qualification.md +135 -0
- package/docs/mvp-05-provider-neutral-generator.md +79 -0
- package/docs/ollama-local-smoke-validation.md +156 -0
- package/docs/openai-compatible-runtime-discovery.md +789 -0
- package/docs/openrouter-one-dependency-validation.md +205 -0
- package/docs/oss-02-package-guard-hardening-architecture.md +131 -0
- package/docs/oss-04-public-ci-package-metadata-architecture.md +142 -0
- package/docs/package-content-policy.md +98 -0
- package/docs/releases/v0.5.0-technical-preview.md +114 -0
- package/docs/releases/v0.6.0-alpha.1-depverdict-preview.md +101 -0
- package/docs/reviews/diff-02-identity-contract-compatibility.md +413 -0
- package/docs/reviews/diff-03-repository-docs-community-migration.md +364 -0
- package/docs/reviews/diff-04-depverdict-distribution-identity-readiness-rereview.md +453 -0
- package/docs/reviews/diff-04-fix-post-rename-identity-release-remediation.md +310 -0
- package/docs/reviews/diff-05-final-preview-distribution-qualification.md +561 -0
- package/docs/reviews/mvp-05-final-product-value-workflow-rereview.md +471 -0
- package/docs/reviews/mvp-05-product-workflow-review.md +605 -0
- package/docs/reviews/oss-01-duplicate-artifact-investigation-cleanup.md +411 -0
- package/docs/reviews/oss-02-package-guard-hardening.md +303 -0
- package/docs/reviews/oss-03-community-scaffolding.md +378 -0
- package/docs/reviews/oss-04-public-ci-package-metadata.md +439 -0
- package/docs/reviews/oss-05-technical-preview-qualification.md +482 -0
- package/docs/reviews/upgradelens-vs-upgradedepdetective-source-comparison.md +355 -0
- package/docs/reviews/v0.5.0-pre-release-smoke.md +263 -0
- package/docs/reviews/v0.5.0-version-bump-release-verification.md +306 -0
- package/docs/runtime-contract-discovery.md +521 -0
- package/docs/structured-output-compatibility-report.md +100 -0
- package/docs/ts-fix-01-exact-duplicate-occurrence-target-selection-architecture.md +111 -0
- package/docs/version-analysis-architecture.md +827 -0
- package/eval/README.md +86 -0
- package/eval/datasets/generic/declared-constraint.json +59 -0
- package/eval/datasets/generic/evidence-conflict.json +73 -0
- package/eval/datasets/generic/major-breaking-release.json +66 -0
- package/eval/datasets/generic/missing-evidence.json +46 -0
- package/eval/datasets/generic/patch-release-low.json +59 -0
- package/eval/datasets/node/axios-patch-low.json +59 -0
- package/eval/datasets/node/react-major-breaking.json +66 -0
- package/eval/datasets/node/react-minor-compatibility.json +66 -0
- package/eval/datasets/python/fastapi-deprecation.json +66 -0
- package/eval/datasets/python/pydantic-major-breaking.json +66 -0
- package/eval/migration-planning/golden-dataset-v2.json +306 -0
- package/eval/migration-planning/golden-dataset.json +214 -0
- package/eval/schemas/expected-result.schema.json +88 -0
- package/eval/schemas/golden-case.schema.json +181 -0
- package/package.json +57 -0
- package/schemas/ai-scorecard.schema.json +132 -0
- package/schemas/benchmark-report.schema.json +189 -0
- package/schemas/benchmark.schema.json +55 -0
- package/schemas/capability-profile.schema.json +44 -0
- package/schemas/conformance-report.schema.json +150 -0
- package/schemas/deployment-profile.schema.json +64 -0
- package/schemas/evaluation-report.schema.json +158 -0
- package/schemas/knowledge-evidence-bundle.schema.json +150 -0
- package/schemas/knowledge-manifest.schema.json +548 -0
- package/schemas/metrics.schema.json +178 -0
- package/schemas/migration-checklist-extractive-candidate.schema.json +42 -0
- package/schemas/migration-checklist.schema.json +706 -0
- package/schemas/migration-evaluation-dataset-v2.schema.json +208 -0
- package/schemas/migration-evaluation-dataset.schema.json +204 -0
- package/schemas/migration-planning-qualification-record.schema.json +234 -0
- package/schemas/project-manifest.schema.json +308 -0
- package/schemas/qualification-record.schema.json +56 -0
- package/schemas/repository-impact-evidence.schema.json +232 -0
- package/schemas/repository-impact.schema.json +202 -0
- package/schemas/upgrade-decision.schema.json +273 -0
- package/schemas/usage-index.schema.json +179 -0
- package/schemas/version-analysis.schema.json +449 -0
- package/src/ai-runtime-debug.js +325 -0
- package/src/ai-runtime-error.js +42 -0
- package/src/ai-runtime.js +174 -0
- package/src/ai-scorecard.js +204 -0
- package/src/ai-version-analysis.js +484 -0
- package/src/artifact-root-compatibility.js +91 -0
- package/src/benchmark-report.js +111 -0
- package/src/benchmark-runner.js +191 -0
- package/src/canonical-json.js +69 -0
- package/src/cli.js +1299 -0
- package/src/conformance-report.js +158 -0
- package/src/conformance-runner.js +253 -0
- package/src/constants.js +73 -0
- package/src/cooperative-scheduler.js +79 -0
- package/src/dependencies.js +44 -0
- package/src/dependency-ai-context.js +625 -0
- package/src/detectors.js +253 -0
- package/src/discovery.js +234 -0
- package/src/ecosystem-version-adapter.js +294 -0
- package/src/environment-compatibility.js +77 -0
- package/src/evaluation-comparator.js +158 -0
- package/src/evaluation-report.js +76 -0
- package/src/evaluation-runner.js +248 -0
- package/src/evidence-source-adapter.js +472 -0
- package/src/files.js +89 -0
- package/src/governance-diagnostics.js +64 -0
- package/src/governance-loader.js +63 -0
- package/src/governance-metadata.js +346 -0
- package/src/governance-validator.js +360 -0
- package/src/http/bounded-fetch.js +278 -0
- package/src/http/cli-http-runtime.js +44 -0
- package/src/impact/input-loader.js +157 -0
- package/src/impact/matcher.js +40 -0
- package/src/impact/repository-impact.js +199 -0
- package/src/impact/runtime.js +24 -0
- package/src/impact/status.js +62 -0
- package/src/impact/writer.js +30 -0
- package/src/impact-evidence/input-loader.js +202 -0
- package/src/impact-evidence/repository-impact-evidence.js +234 -0
- package/src/impact-evidence/runtime.js +16 -0
- package/src/impact-evidence/writer.js +30 -0
- package/src/index.js +563 -0
- package/src/installed-version-baseline.js +196 -0
- package/src/knowledge-cache.js +324 -0
- package/src/knowledge-evidence-bundle.js +101 -0
- package/src/knowledge-evidence-producer.js +233 -0
- package/src/knowledge-manifest-builder.js +188 -0
- package/src/knowledge-manifest-writer.js +32 -0
- package/src/knowledge-manifest.js +255 -0
- package/src/knowledge-research.js +615 -0
- package/src/metrics-engine.js +205 -0
- package/src/migration-checklist/ai-candidate.js +320 -0
- package/src/migration-checklist/assembler.js +37 -0
- package/src/migration-checklist/context-runtime.js +828 -0
- package/src/migration-checklist/evaluation/action-criteria.js +244 -0
- package/src/migration-checklist/evaluation/comparator-v2.js +332 -0
- package/src/migration-checklist/evaluation/comparator.js +279 -0
- package/src/migration-checklist/evaluation/dataset-v2.js +227 -0
- package/src/migration-checklist/evaluation/dataset.js +336 -0
- package/src/migration-checklist/evaluation/extractive-fixtures-v2.js +148 -0
- package/src/migration-checklist/evaluation/metrics-v2.js +226 -0
- package/src/migration-checklist/evaluation/metrics.js +158 -0
- package/src/migration-checklist/evaluation/qualification-v2.js +321 -0
- package/src/migration-checklist/evaluation/qualification.js +239 -0
- package/src/migration-checklist/evaluation/runner-v2.js +294 -0
- package/src/migration-checklist/evaluation/runner.js +194 -0
- package/src/migration-checklist/evaluation/scorecard-v2.js +106 -0
- package/src/migration-checklist/evaluation/scorecard.js +86 -0
- package/src/migration-checklist/extractive-candidate.js +166 -0
- package/src/migration-checklist/extractive-prompt.js +62 -0
- package/src/migration-checklist/generator.js +702 -0
- package/src/migration-checklist/grounding-policy.js +117 -0
- package/src/migration-checklist/input-loader.js +613 -0
- package/src/migration-checklist/migration-checklist.js +635 -0
- package/src/migration-checklist/presentation.js +292 -0
- package/src/migration-checklist/progress.js +96 -0
- package/src/migration-checklist/prompt.js +83 -0
- package/src/migration-checklist/qualification-guard.js +462 -0
- package/src/migration-checklist/qualification-resolution.js +122 -0
- package/src/migration-checklist/qualification-store.js +225 -0
- package/src/migration-checklist/runtime.js +205 -0
- package/src/migration-checklist/verification.js +134 -0
- package/src/migration-checklist/writer.js +35 -0
- package/src/openai-compatible-provider.js +451 -0
- package/src/orchestration/failure-log.js +32 -0
- package/src/orchestration/pipeline.js +200 -0
- package/src/orchestration/progress-events.js +337 -0
- package/src/orchestration/progress-reporter.js +131 -0
- package/src/orchestration/text-writer.js +22 -0
- package/src/portable.js +13 -0
- package/src/product-completion.js +249 -0
- package/src/project-manifest-input.js +90 -0
- package/src/project-manifest.js +141 -0
- package/src/python-requirements.js +137 -0
- package/src/registry/npm-packument.js +256 -0
- package/src/registry/npm-registry-adapter.js +262 -0
- package/src/registry/pypi-project.js +300 -0
- package/src/registry/pypi-registry-adapter.js +235 -0
- package/src/registry/sanitize-registry-body.js +51 -0
- package/src/renderers/console.js +160 -0
- package/src/renderers/impact-presentation.js +278 -0
- package/src/renderers/markdown.js +172 -0
- package/src/research-plan.js +455 -0
- package/src/runtime-conformance.js +275 -0
- package/src/source-provenance.js +393 -0
- package/src/source-url.js +62 -0
- package/src/structured-output-schema.js +66 -0
- package/src/target-selector.js +306 -0
- package/src/upgrade-decision/input-loader.js +107 -0
- package/src/upgrade-decision/presentation.js +43 -0
- package/src/upgrade-decision/runtime.js +21 -0
- package/src/upgrade-decision/upgrade-decision.js +626 -0
- package/src/upgrade-decision/writer.js +30 -0
- package/src/usage/analyzer-registry.js +63 -0
- package/src/usage/coverage.js +116 -0
- package/src/usage/input-loader.js +139 -0
- package/src/usage/js/analyzer.js +240 -0
- package/src/usage/js/parser.js +21 -0
- package/src/usage/runtime.js +187 -0
- package/src/usage/scope.js +44 -0
- package/src/usage/source-files.js +50 -0
- package/src/usage/usage-index.js +217 -0
- package/src/usage/writer.js +31 -0
- package/src/version-analysis-loader.js +203 -0
- package/src/version-analysis-manifest.js +314 -0
- package/src/version-analysis-writer.js +30 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
import { mkdir, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CLI_NAME,
|
|
6
|
+
CAPABILITY_PROFILE_FILENAME,
|
|
7
|
+
DEFAULT_ANALYSIS_LOG_PATH,
|
|
8
|
+
DEFAULT_AI_SCORECARD_PATH,
|
|
9
|
+
DEFAULT_BENCHMARK_CONFIG_PATH,
|
|
10
|
+
DEFAULT_BENCHMARK_REPORT_PATH,
|
|
11
|
+
DEFAULT_CONFORMANCE_REPORT_PATH,
|
|
12
|
+
DEPLOYMENT_PROFILE_FILENAME,
|
|
13
|
+
DEFAULT_GOVERNANCE_DIRECTORY,
|
|
14
|
+
DEFAULT_KNOWLEDGE_MANIFEST_PATH,
|
|
15
|
+
DEFAULT_MANIFEST_PATH,
|
|
16
|
+
DEFAULT_EVALUATION_REPORT_PATH,
|
|
17
|
+
DEFAULT_METRICS_PATH,
|
|
18
|
+
QUALIFICATION_RECORD_FILENAME,
|
|
19
|
+
DEFAULT_VERSION_ANALYSIS_PATH,
|
|
20
|
+
DEFAULT_REPOSITORY_IMPACT_EVIDENCE_PATH,
|
|
21
|
+
DEFAULT_UPGRADE_DECISION_PATH,
|
|
22
|
+
DEFAULT_REPOSITORY_IMPACT_PATH,
|
|
23
|
+
DEFAULT_REPOSITORY_IMPACT_REPORT_PATH,
|
|
24
|
+
DEFAULT_MIGRATION_CHECKLIST_PATH,
|
|
25
|
+
DEFAULT_MIGRATION_PLANNING_QUALIFICATION_PATH,
|
|
26
|
+
DEFAULT_USAGE_INDEX_PATH,
|
|
27
|
+
PRODUCT_NAME,
|
|
28
|
+
VERSION
|
|
29
|
+
} from './constants.js';
|
|
30
|
+
import { resolveArtifactRootChain } from './artifact-root-compatibility.js';
|
|
31
|
+
import { resolveIdentityEnvironment } from './environment-compatibility.js';
|
|
32
|
+
import {
|
|
33
|
+
buildAiScorecard,
|
|
34
|
+
serializeAiScorecard,
|
|
35
|
+
writeAiScorecard
|
|
36
|
+
} from './ai-scorecard.js';
|
|
37
|
+
import {
|
|
38
|
+
analyzeDependencyAiContext,
|
|
39
|
+
buildVersionAnalysisPrompt
|
|
40
|
+
} from './ai-version-analysis.js';
|
|
41
|
+
import {
|
|
42
|
+
createHttpJsonAiProvider,
|
|
43
|
+
createProviderAiRuntime
|
|
44
|
+
} from './ai-runtime.js';
|
|
45
|
+
import {
|
|
46
|
+
DEFAULT_AI_TIMEOUT_MS,
|
|
47
|
+
createOpenAiCompatibleProvider
|
|
48
|
+
} from './openai-compatible-provider.js';
|
|
49
|
+
import { isAiRuntimeDebugEnabled } from './ai-runtime-debug.js';
|
|
50
|
+
import {
|
|
51
|
+
buildDependencyAiContext,
|
|
52
|
+
resolveDependencyAnalysisInputs
|
|
53
|
+
} from './dependency-ai-context.js';
|
|
54
|
+
import { createEvidenceSourceAdapter } from './evidence-source-adapter.js';
|
|
55
|
+
import { discoverProject } from './discovery.js';
|
|
56
|
+
import {
|
|
57
|
+
DEFAULT_EVALUATION_DATASET_PATH,
|
|
58
|
+
runEvaluation,
|
|
59
|
+
writeEvaluationReport
|
|
60
|
+
} from './evaluation-runner.js';
|
|
61
|
+
import { serializeEvaluationReport } from './evaluation-report.js';
|
|
62
|
+
import {
|
|
63
|
+
loadBenchmarkConfig,
|
|
64
|
+
runBenchmark,
|
|
65
|
+
serializeBenchmarkReport,
|
|
66
|
+
writeBenchmarkReport
|
|
67
|
+
} from './benchmark-runner.js';
|
|
68
|
+
import { runConformance } from './conformance-runner.js';
|
|
69
|
+
import {
|
|
70
|
+
serializeConformanceReport,
|
|
71
|
+
writeConformanceReport
|
|
72
|
+
} from './conformance-report.js';
|
|
73
|
+
import {
|
|
74
|
+
createDefaultGovernanceArtifacts,
|
|
75
|
+
serializeGovernanceArtifacts,
|
|
76
|
+
writeGovernanceArtifacts
|
|
77
|
+
} from './governance-metadata.js';
|
|
78
|
+
import {
|
|
79
|
+
buildMetrics,
|
|
80
|
+
loadEvaluationReportForMetrics,
|
|
81
|
+
writeMetrics
|
|
82
|
+
} from './metrics-engine.js';
|
|
83
|
+
import { createCliHttpRuntime } from './http/cli-http-runtime.js';
|
|
84
|
+
import {
|
|
85
|
+
DEFAULT_KNOWLEDGE_CACHE_DIRECTORY,
|
|
86
|
+
createKnowledgeCache
|
|
87
|
+
} from './knowledge-cache.js';
|
|
88
|
+
import {
|
|
89
|
+
buildKnowledgeEvidenceBundle,
|
|
90
|
+
writeKnowledgeEvidenceBundle
|
|
91
|
+
} from './knowledge-evidence-producer.js';
|
|
92
|
+
import { buildKnowledgeManifest } from './knowledge-manifest-builder.js';
|
|
93
|
+
import { serializeKnowledgeManifest, writeKnowledgeManifest } from './knowledge-manifest-writer.js';
|
|
94
|
+
import { createKnowledgeResearchOrchestrator } from './knowledge-research.js';
|
|
95
|
+
import { runImpactEvidenceGeneration } from './impact-evidence/runtime.js';
|
|
96
|
+
import { writeRepositoryImpactEvidence } from './impact-evidence/writer.js';
|
|
97
|
+
import { runImpactAnalysis } from './impact/runtime.js';
|
|
98
|
+
import { writeRepositoryImpact } from './impact/writer.js';
|
|
99
|
+
import {
|
|
100
|
+
PipelineCancellationError,
|
|
101
|
+
PipelineStageError,
|
|
102
|
+
createAnalysisStages,
|
|
103
|
+
runAnalysisPipeline
|
|
104
|
+
} from './orchestration/pipeline.js';
|
|
105
|
+
import { writeAnalysisFailureLog } from './orchestration/failure-log.js';
|
|
106
|
+
import { createProgressReporter } from './orchestration/progress-reporter.js';
|
|
107
|
+
import { writeTextArtifact } from './orchestration/text-writer.js';
|
|
108
|
+
import { loadProjectManifestInput } from './project-manifest-input.js';
|
|
109
|
+
import { createResearchPlan } from './research-plan.js';
|
|
110
|
+
import { createNpmRegistryAdapter } from './registry/npm-registry-adapter.js';
|
|
111
|
+
import { createPypiRegistryAdapter } from './registry/pypi-registry-adapter.js';
|
|
112
|
+
import { renderConsoleSummary } from './renderers/console.js';
|
|
113
|
+
import { buildImpactPresentationViewModel } from './renderers/impact-presentation.js';
|
|
114
|
+
import { renderMarkdownReport } from './renderers/markdown.js';
|
|
115
|
+
import { isPortableRelativePath } from './portable.js';
|
|
116
|
+
import {
|
|
117
|
+
buildProductCompletion,
|
|
118
|
+
productCompletionExitCode
|
|
119
|
+
} from './product-completion.js';
|
|
120
|
+
import {
|
|
121
|
+
parseTargetSelector,
|
|
122
|
+
resolveTargetSelectors,
|
|
123
|
+
targetOccurrenceKey
|
|
124
|
+
} from './target-selector.js';
|
|
125
|
+
import {
|
|
126
|
+
DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH,
|
|
127
|
+
loadVersionAnalysisArtifacts
|
|
128
|
+
} from './version-analysis-loader.js';
|
|
129
|
+
import { buildVersionAnalysisManifest } from './version-analysis-manifest.js';
|
|
130
|
+
import { serializeVersionAnalysisManifest, writeVersionAnalysisManifest } from './version-analysis-writer.js';
|
|
131
|
+
import { runUsageDiscovery } from './usage/runtime.js';
|
|
132
|
+
import { writeUsageIndex } from './usage/writer.js';
|
|
133
|
+
import { runMigrationChecklistStage } from './migration-checklist/runtime.js';
|
|
134
|
+
import { runUpgradeDecisionStage } from './upgrade-decision/runtime.js';
|
|
135
|
+
import { resolveMigrationQualification } from './migration-checklist/qualification-resolution.js';
|
|
136
|
+
|
|
137
|
+
const legacyInvocationWarnings = new Set();
|
|
138
|
+
|
|
139
|
+
const HELP = `${PRODUCT_NAME} ${VERSION}
|
|
140
|
+
|
|
141
|
+
Decide whether evaluated dependency targets should be kept, planned, or investigated.
|
|
142
|
+
The default registry candidate is a fact, not an upgrade recommendation.
|
|
143
|
+
|
|
144
|
+
Usage:
|
|
145
|
+
${CLI_NAME} analyze <repository> [options]
|
|
146
|
+
${CLI_NAME} discover [path] [options]
|
|
147
|
+
${CLI_NAME} research [path] [options]
|
|
148
|
+
${CLI_NAME} analyze-version [path] [options]
|
|
149
|
+
${CLI_NAME} eval [options]
|
|
150
|
+
${CLI_NAME} scorecard [options]
|
|
151
|
+
${CLI_NAME} benchmark [options]
|
|
152
|
+
${CLI_NAME} conformance [options]
|
|
153
|
+
${CLI_NAME} governance [options]
|
|
154
|
+
${CLI_NAME} [path] [options]
|
|
155
|
+
|
|
156
|
+
Analyze options:
|
|
157
|
+
--offline Use fresh research cache entries only; never request registries
|
|
158
|
+
--max-depth <n> Maximum directory depth for discovery and usage scanning
|
|
159
|
+
--target <selector>
|
|
160
|
+
Select a caller-owned target. Repeatable.
|
|
161
|
+
Required fields: package=<canonical-id>,target=<version>
|
|
162
|
+
Optional occurrence fields: project,manifest,type,occurrence
|
|
163
|
+
occurrence is a stable sha256 ID shown in ambiguity guidance
|
|
164
|
+
Example: package=npm:react,target=20.0.0
|
|
165
|
+
Scoped example: package=npm:@scope/pkg,target=3.0.0
|
|
166
|
+
--fail-on-incomplete
|
|
167
|
+
Exit 2 for review-required or insufficient-data outcomes.
|
|
168
|
+
Partial provider/output/runtime results always exit 2.
|
|
169
|
+
--stdout Print only the machine-readable product completion summary
|
|
170
|
+
--experimental-migration-checklist
|
|
171
|
+
Generate an evidence-grounded migration checklist.
|
|
172
|
+
Experimental. Every migration action requires human review.
|
|
173
|
+
--migration-qualification <path>
|
|
174
|
+
Migration Planning v2 qualification record relative to the
|
|
175
|
+
repository (default: ${DEFAULT_MIGRATION_PLANNING_QUALIFICATION_PATH})
|
|
176
|
+
--progress <mode> Control analysis progress: auto, interactive, or plain
|
|
177
|
+
auto uses interactive output on a TTY and stable plain
|
|
178
|
+
lines otherwise. Heartbeats show real elapsed time;
|
|
179
|
+
no percentage or ETA is inferred. (default: auto)
|
|
180
|
+
|
|
181
|
+
Discover options:
|
|
182
|
+
-o, --output <path> Project Manifest path relative to the project root
|
|
183
|
+
(default: ${DEFAULT_MANIFEST_PATH})
|
|
184
|
+
--stdout Print the manifest instead of writing a file
|
|
185
|
+
--no-pretty Emit compact JSON
|
|
186
|
+
--max-depth <n> Maximum directory depth to scan
|
|
187
|
+
--fail-on-warning Return exit code 2 when discovery has warnings
|
|
188
|
+
|
|
189
|
+
Research options:
|
|
190
|
+
-o, --output <path> Knowledge Manifest path relative to the project root
|
|
191
|
+
(default: ${DEFAULT_KNOWLEDGE_MANIFEST_PATH})
|
|
192
|
+
--stdout Print only the Knowledge Manifest JSON
|
|
193
|
+
--offline Use fresh cache entries only; never request registries
|
|
194
|
+
|
|
195
|
+
Analyze-version options:
|
|
196
|
+
-o, --output <path> Version Analysis artifact path relative to the project root
|
|
197
|
+
(default: ${DEFAULT_VERSION_ANALYSIS_PATH})
|
|
198
|
+
--package <id> Analyze one exact canonical package ID (for example, pypi:langsmith)
|
|
199
|
+
--target <selector>
|
|
200
|
+
Select explicit targets using the same analyze selector syntax
|
|
201
|
+
--stdout Print only the Version Analysis JSON
|
|
202
|
+
|
|
203
|
+
Eval options:
|
|
204
|
+
--dataset <path> Golden Dataset file or directory
|
|
205
|
+
(default: ${DEFAULT_EVALUATION_DATASET_PATH})
|
|
206
|
+
-o, --output <path> Evaluation report path
|
|
207
|
+
(default: ${DEFAULT_EVALUATION_REPORT_PATH})
|
|
208
|
+
--stdout Print only the Evaluation Report JSON
|
|
209
|
+
|
|
210
|
+
Scorecard options:
|
|
211
|
+
--report <path> Evaluation Report path
|
|
212
|
+
(default: ${DEFAULT_EVALUATION_REPORT_PATH})
|
|
213
|
+
--metrics-output <path>
|
|
214
|
+
Metrics artifact path
|
|
215
|
+
(default: ${DEFAULT_METRICS_PATH})
|
|
216
|
+
-o, --output <path> AI Scorecard path
|
|
217
|
+
(default: ${DEFAULT_AI_SCORECARD_PATH})
|
|
218
|
+
--stdout Print only the AI Scorecard JSON
|
|
219
|
+
|
|
220
|
+
Benchmark options:
|
|
221
|
+
--config <path> Benchmark config path
|
|
222
|
+
(default: ${DEFAULT_BENCHMARK_CONFIG_PATH})
|
|
223
|
+
-o, --output <path> Benchmark Report path
|
|
224
|
+
(default: ${DEFAULT_BENCHMARK_REPORT_PATH})
|
|
225
|
+
--stdout Print only the Benchmark Report JSON
|
|
226
|
+
|
|
227
|
+
Conformance options:
|
|
228
|
+
-o, --output <path> Offline Conformance Report path
|
|
229
|
+
(default: ${DEFAULT_CONFORMANCE_REPORT_PATH})
|
|
230
|
+
--stdout Print only the Offline Conformance Report JSON
|
|
231
|
+
|
|
232
|
+
Governance options:
|
|
233
|
+
-o, --output <path> Directory for portable governance artifacts
|
|
234
|
+
(default: ${DEFAULT_GOVERNANCE_DIRECTORY})
|
|
235
|
+
--stdout Print a bundle containing the three governance artifacts
|
|
236
|
+
-h, --help Show help
|
|
237
|
+
-v, --version Show version
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
function takeValue(args, index, option) {
|
|
241
|
+
const value = args[index + 1];
|
|
242
|
+
if (value === undefined || value.startsWith('-')) throw new Error(`${option} requires a value`);
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function outputPath(root, output) {
|
|
247
|
+
if (!isPortableRelativePath(output)) throw new Error('--output must be a portable path relative to the repository root');
|
|
248
|
+
return path.resolve(root, output);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function compatibilityDiagnostic(io, message) {
|
|
252
|
+
(io.stderr ?? process.stderr).write(`${message}\n`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function withResolvedEnvironment(io) {
|
|
256
|
+
if (io.environmentResolved) return io;
|
|
257
|
+
const env = Object.hasOwn(io, 'env') ? io.env : process.env;
|
|
258
|
+
return {
|
|
259
|
+
...io,
|
|
260
|
+
env: resolveIdentityEnvironment(env, {
|
|
261
|
+
overrides: io.environmentOverrides,
|
|
262
|
+
diagnosticState: io.environmentDiagnosticState,
|
|
263
|
+
onDiagnostic: (message) => compatibilityDiagnostic(io, message)
|
|
264
|
+
}),
|
|
265
|
+
environmentResolved: true
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function parseArguments(argv) {
|
|
270
|
+
const args = [...argv];
|
|
271
|
+
const command = ['analyze', 'discover', 'research', 'analyze-version', 'eval', 'scorecard', 'benchmark', 'conformance', 'governance'].includes(args[0])
|
|
272
|
+
? args.shift()
|
|
273
|
+
: 'discover';
|
|
274
|
+
const options = {
|
|
275
|
+
command,
|
|
276
|
+
root: '.',
|
|
277
|
+
output: command === 'analyze'
|
|
278
|
+
? DEFAULT_REPOSITORY_IMPACT_REPORT_PATH
|
|
279
|
+
: command === 'research'
|
|
280
|
+
? DEFAULT_KNOWLEDGE_MANIFEST_PATH
|
|
281
|
+
: command === 'analyze-version'
|
|
282
|
+
? DEFAULT_VERSION_ANALYSIS_PATH
|
|
283
|
+
: command === 'eval'
|
|
284
|
+
? DEFAULT_EVALUATION_REPORT_PATH
|
|
285
|
+
: command === 'scorecard'
|
|
286
|
+
? DEFAULT_AI_SCORECARD_PATH
|
|
287
|
+
: command === 'benchmark'
|
|
288
|
+
? DEFAULT_BENCHMARK_REPORT_PATH
|
|
289
|
+
: command === 'conformance'
|
|
290
|
+
? DEFAULT_CONFORMANCE_REPORT_PATH
|
|
291
|
+
: command === 'governance'
|
|
292
|
+
? DEFAULT_GOVERNANCE_DIRECTORY
|
|
293
|
+
: DEFAULT_MANIFEST_PATH,
|
|
294
|
+
dataset: DEFAULT_EVALUATION_DATASET_PATH,
|
|
295
|
+
report: DEFAULT_EVALUATION_REPORT_PATH,
|
|
296
|
+
metricsOutput: DEFAULT_METRICS_PATH,
|
|
297
|
+
config: DEFAULT_BENCHMARK_CONFIG_PATH,
|
|
298
|
+
pretty: true,
|
|
299
|
+
stdout: false,
|
|
300
|
+
failOnWarning: false,
|
|
301
|
+
failOnIncomplete: false,
|
|
302
|
+
offline: false,
|
|
303
|
+
experimentalMigrationChecklist: false,
|
|
304
|
+
targets: [],
|
|
305
|
+
progress: 'auto'
|
|
306
|
+
};
|
|
307
|
+
let rootSet = false;
|
|
308
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
309
|
+
const argument = args[index];
|
|
310
|
+
if (argument === '--help' || argument === '-h') options.help = true;
|
|
311
|
+
else if (argument === '--version' || argument === '-v') options.version = true;
|
|
312
|
+
else if (argument === '--stdout') options.stdout = true;
|
|
313
|
+
else if (argument === '--no-pretty') options.pretty = false;
|
|
314
|
+
else if (argument === '--fail-on-warning') options.failOnWarning = true;
|
|
315
|
+
else if (argument === '--fail-on-incomplete') options.failOnIncomplete = true;
|
|
316
|
+
else if (argument === '--offline') options.offline = true;
|
|
317
|
+
else if (argument === '--target') {
|
|
318
|
+
options.targets.push(parseTargetSelector(takeValue(args, index, argument)));
|
|
319
|
+
index += 1;
|
|
320
|
+
}
|
|
321
|
+
else if (argument === '--experimental-migration-checklist') {
|
|
322
|
+
options.experimentalMigrationChecklist = true;
|
|
323
|
+
} else if (argument === '--progress') {
|
|
324
|
+
options.progress = takeValue(args, index, argument);
|
|
325
|
+
index += 1;
|
|
326
|
+
} else if (argument === '--migration-qualification') {
|
|
327
|
+
options.migrationQualificationPath = takeValue(args, index, argument);
|
|
328
|
+
index += 1;
|
|
329
|
+
}
|
|
330
|
+
else if (argument === '--output' || argument === '-o') {
|
|
331
|
+
options.output = takeValue(args, index, argument);
|
|
332
|
+
index += 1;
|
|
333
|
+
} else if (argument === '--dataset') {
|
|
334
|
+
options.dataset = takeValue(args, index, argument);
|
|
335
|
+
index += 1;
|
|
336
|
+
} else if (argument === '--report') {
|
|
337
|
+
options.report = takeValue(args, index, argument);
|
|
338
|
+
index += 1;
|
|
339
|
+
} else if (argument === '--metrics-output') {
|
|
340
|
+
options.metricsOutput = takeValue(args, index, argument);
|
|
341
|
+
index += 1;
|
|
342
|
+
} else if (argument === '--config') {
|
|
343
|
+
options.config = takeValue(args, index, argument);
|
|
344
|
+
index += 1;
|
|
345
|
+
} else if (argument === '--package') {
|
|
346
|
+
if (options.packageId !== undefined) {
|
|
347
|
+
throw new Error('--package accepts exactly one canonical package ID.');
|
|
348
|
+
}
|
|
349
|
+
options.packageId = takeValue(args, index, argument);
|
|
350
|
+
index += 1;
|
|
351
|
+
} else if (argument === '--max-depth') {
|
|
352
|
+
const raw = takeValue(args, index, argument);
|
|
353
|
+
const depth = Number(raw);
|
|
354
|
+
if (!Number.isInteger(depth) || depth < 0) throw new Error('--max-depth must be a non-negative integer');
|
|
355
|
+
options.maxDepth = depth;
|
|
356
|
+
index += 1;
|
|
357
|
+
} else if (argument.startsWith('-')) throw new Error(`Unknown option: ${argument}`);
|
|
358
|
+
else if (!rootSet && command !== 'eval' && command !== 'scorecard' && command !== 'benchmark'
|
|
359
|
+
&& command !== 'conformance' && command !== 'governance') {
|
|
360
|
+
options.root = argument;
|
|
361
|
+
rootSet = true;
|
|
362
|
+
} else if (!rootSet && command === 'eval') {
|
|
363
|
+
options.dataset = argument;
|
|
364
|
+
rootSet = true;
|
|
365
|
+
} else if (!rootSet && command === 'scorecard') {
|
|
366
|
+
options.report = argument;
|
|
367
|
+
rootSet = true;
|
|
368
|
+
} else if (!rootSet && command === 'benchmark') {
|
|
369
|
+
options.config = argument;
|
|
370
|
+
rootSet = true;
|
|
371
|
+
} else if (!rootSet && command === 'conformance') {
|
|
372
|
+
throw new Error(`Unexpected argument: ${argument}`);
|
|
373
|
+
} else if (!rootSet && command === 'governance') {
|
|
374
|
+
throw new Error(`Unexpected argument: ${argument}`);
|
|
375
|
+
} else throw new Error(`Unexpected argument: ${argument}`);
|
|
376
|
+
}
|
|
377
|
+
if (!['research', 'analyze'].includes(command) && options.offline) {
|
|
378
|
+
throw new Error('--offline is only supported by research and analyze');
|
|
379
|
+
}
|
|
380
|
+
if (command !== 'analyze' && options.experimentalMigrationChecklist) {
|
|
381
|
+
throw new Error('--experimental-migration-checklist is only supported by analyze');
|
|
382
|
+
}
|
|
383
|
+
if (options.migrationQualificationPath !== undefined) {
|
|
384
|
+
if (command !== 'analyze' || !options.experimentalMigrationChecklist) {
|
|
385
|
+
throw new Error('--migration-qualification requires analyze with --experimental-migration-checklist');
|
|
386
|
+
}
|
|
387
|
+
if (!isPortableRelativePath(options.migrationQualificationPath)) {
|
|
388
|
+
throw new Error('--migration-qualification must be a portable path relative to the repository root');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (command !== 'analyze' && options.progress !== 'auto') {
|
|
392
|
+
throw new Error('--progress is only supported by analyze');
|
|
393
|
+
}
|
|
394
|
+
if (!['auto', 'interactive', 'plain'].includes(options.progress)) {
|
|
395
|
+
throw new Error('--progress must be auto, interactive, or plain');
|
|
396
|
+
}
|
|
397
|
+
if (!['discover', 'analyze'].includes(command) && options.maxDepth !== undefined) {
|
|
398
|
+
throw new Error('--max-depth is only supported by discover and analyze');
|
|
399
|
+
}
|
|
400
|
+
if (command !== 'discover' && options.failOnWarning) throw new Error('--fail-on-warning is only supported by discover');
|
|
401
|
+
if (command !== 'analyze' && options.failOnIncomplete) {
|
|
402
|
+
throw new Error('--fail-on-incomplete is only supported by analyze');
|
|
403
|
+
}
|
|
404
|
+
if (!['analyze', 'analyze-version'].includes(command) && options.targets.length > 0) {
|
|
405
|
+
throw new Error('--target is only supported by analyze and analyze-version');
|
|
406
|
+
}
|
|
407
|
+
if (command !== 'discover' && !options.pretty) throw new Error('--no-pretty is only supported by discover');
|
|
408
|
+
if (command !== 'eval' && options.dataset !== DEFAULT_EVALUATION_DATASET_PATH) throw new Error('--dataset is only supported by eval');
|
|
409
|
+
if (command !== 'scorecard' && options.report !== DEFAULT_EVALUATION_REPORT_PATH) throw new Error('--report is only supported by scorecard');
|
|
410
|
+
if (command !== 'scorecard' && options.metricsOutput !== DEFAULT_METRICS_PATH) {
|
|
411
|
+
throw new Error('--metrics-output is only supported by scorecard');
|
|
412
|
+
}
|
|
413
|
+
if (command !== 'benchmark' && options.config !== DEFAULT_BENCHMARK_CONFIG_PATH) throw new Error('--config is only supported by benchmark');
|
|
414
|
+
if (command !== 'analyze-version' && options.packageId !== undefined) {
|
|
415
|
+
throw new Error('--package is only supported by analyze-version');
|
|
416
|
+
}
|
|
417
|
+
if (options.packageId !== undefined) {
|
|
418
|
+
const separator = options.packageId.indexOf(':');
|
|
419
|
+
const ecosystem = options.packageId.slice(0, separator);
|
|
420
|
+
const name = options.packageId.slice(separator + 1);
|
|
421
|
+
if (separator <= 0 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(ecosystem) || name.length === 0 || /\s/.test(name)) {
|
|
422
|
+
throw new Error('--package must be an exact canonical package ID such as npm:react or pypi:langsmith.');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return options;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function writeProjectManifest(root, output, contents) {
|
|
429
|
+
const target = outputPath(root, output);
|
|
430
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
431
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
432
|
+
await writeFile(temporary, contents, 'utf8');
|
|
433
|
+
await rename(temporary, target);
|
|
434
|
+
return target;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function researchComponents(root, options, io) {
|
|
438
|
+
if (io.adapters) {
|
|
439
|
+
return {
|
|
440
|
+
adapters: io.adapters,
|
|
441
|
+
evidenceSourceAdapter: io.evidenceSourceAdapter ?? null
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
const clock = io.clock ?? (() => new Date());
|
|
445
|
+
const cache = createKnowledgeCache({
|
|
446
|
+
rootDirectory: path.join(root, DEFAULT_KNOWLEDGE_CACHE_DIRECTORY),
|
|
447
|
+
clock
|
|
448
|
+
});
|
|
449
|
+
const adapterOptions = { cache, clock, fetch: io.fetch, offline: options.offline };
|
|
450
|
+
return {
|
|
451
|
+
adapters: {
|
|
452
|
+
npm: createNpmRegistryAdapter(adapterOptions),
|
|
453
|
+
pypi: createPypiRegistryAdapter(adapterOptions)
|
|
454
|
+
},
|
|
455
|
+
evidenceSourceAdapter: io.evidenceSourceAdapter !== undefined
|
|
456
|
+
? io.evidenceSourceAdapter
|
|
457
|
+
: io.defaultEvidenceEnrichment === false
|
|
458
|
+
? null
|
|
459
|
+
: createEvidenceSourceAdapter({
|
|
460
|
+
cache,
|
|
461
|
+
clock,
|
|
462
|
+
fetch: io.fetch,
|
|
463
|
+
offline: options.offline
|
|
464
|
+
})
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function runResearch(options, io) {
|
|
469
|
+
const defaultEvidenceEnrichment = io.evidenceSourceAdapter !== undefined
|
|
470
|
+
|| (!io.adapters && !io.fetch);
|
|
471
|
+
const runtime = options.offline || io.fetch
|
|
472
|
+
? null
|
|
473
|
+
: (io.createHttpRuntime ?? createCliHttpRuntime)();
|
|
474
|
+
let result;
|
|
475
|
+
let primaryError;
|
|
476
|
+
try {
|
|
477
|
+
result = await executeResearch(options, {
|
|
478
|
+
...io,
|
|
479
|
+
fetch: runtime?.fetch ?? io.fetch,
|
|
480
|
+
defaultEvidenceEnrichment
|
|
481
|
+
});
|
|
482
|
+
} catch (error) {
|
|
483
|
+
primaryError = error;
|
|
484
|
+
}
|
|
485
|
+
if (runtime) {
|
|
486
|
+
try {
|
|
487
|
+
await runtime.close();
|
|
488
|
+
} catch {
|
|
489
|
+
if (!primaryError) throw new Error('Unable to close the CLI HTTP runtime.');
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (primaryError) throw primaryError;
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function executeResearch(options, io) {
|
|
497
|
+
const root = path.resolve(options.root);
|
|
498
|
+
const selection = await resolveArtifactRootChain(root, [DEFAULT_MANIFEST_PATH], {
|
|
499
|
+
onDiagnostic: (message) => compatibilityDiagnostic(io, message)
|
|
500
|
+
});
|
|
501
|
+
const [projectManifestArtifact] = selection.artifacts;
|
|
502
|
+
const loaded = await loadProjectManifestInput(path.join(root, projectManifestArtifact), {
|
|
503
|
+
artifact: projectManifestArtifact
|
|
504
|
+
});
|
|
505
|
+
const plan = createResearchPlan(loaded);
|
|
506
|
+
const clock = io.clock ?? (() => new Date());
|
|
507
|
+
const components = researchComponents(root, options, io);
|
|
508
|
+
const orchestrator = createKnowledgeResearchOrchestrator({
|
|
509
|
+
adapters: components.adapters,
|
|
510
|
+
evidenceSourceAdapter: components.evidenceSourceAdapter,
|
|
511
|
+
clock,
|
|
512
|
+
concurrency: io.concurrency ?? 4
|
|
513
|
+
});
|
|
514
|
+
const result = await orchestrator.run(plan);
|
|
515
|
+
if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
|
|
516
|
+
const manifest = (io.buildKnowledgeManifest ?? buildKnowledgeManifest)(result, {
|
|
517
|
+
policy: {
|
|
518
|
+
mode: options.offline ? 'offline' : 'online',
|
|
519
|
+
registryBases: { npm: 'https://registry.npmjs.org', pypi: 'https://pypi.org' }
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const contents = serializeKnowledgeManifest(manifest);
|
|
523
|
+
if (options.stdout) {
|
|
524
|
+
io.stdout.write(contents);
|
|
525
|
+
return 0;
|
|
526
|
+
}
|
|
527
|
+
const manifestBytes = Buffer.from(contents, 'utf8');
|
|
528
|
+
const evidenceBundle = (io.buildKnowledgeEvidenceBundle ?? buildKnowledgeEvidenceBundle)(manifest, {
|
|
529
|
+
knowledgeManifestArtifact: options.output,
|
|
530
|
+
knowledgeManifestBytes: manifestBytes,
|
|
531
|
+
generatedAt: manifest.generatedAt,
|
|
532
|
+
enrichedEvidence: result.evidence
|
|
533
|
+
});
|
|
534
|
+
if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
|
|
535
|
+
const target = await (io.writeKnowledgeManifest ?? writeKnowledgeManifest)(outputPath(root, options.output), manifest);
|
|
536
|
+
const evidenceTarget = await (io.writeKnowledgeEvidenceBundle ?? writeKnowledgeEvidenceBundle)(
|
|
537
|
+
outputPath(root, DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH),
|
|
538
|
+
evidenceBundle
|
|
539
|
+
);
|
|
540
|
+
io.stderr.write('✓ Loaded Project Manifest\n');
|
|
541
|
+
io.stderr.write(`✓ Planned research (${plan.packages.length} packages)\n`);
|
|
542
|
+
io.stderr.write('✓ Research complete\n');
|
|
543
|
+
io.stderr.write('✓ Knowledge Manifest validated\n');
|
|
544
|
+
io.stderr.write(`✓ Wrote:\n${target}\n`);
|
|
545
|
+
io.stderr.write(`✓ Knowledge Evidence Bundle validated\n`);
|
|
546
|
+
io.stderr.write(`✓ Wrote:\n${evidenceTarget}\n`);
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function createDefaultAiRuntime(io) {
|
|
551
|
+
const normalizedIo = withResolvedEnvironment(io);
|
|
552
|
+
const env = normalizedIo.env;
|
|
553
|
+
const providerName = env.DEPVERDICT_AI_PROVIDER ?? 'http-json';
|
|
554
|
+
if (!env.DEPVERDICT_AI_ENDPOINT) {
|
|
555
|
+
throw new Error('AI runtime is not configured. Set DEPVERDICT_AI_ENDPOINT or provide an AiRuntime.');
|
|
556
|
+
}
|
|
557
|
+
if (providerName === 'openai-compatible') {
|
|
558
|
+
const provider = createOpenAiCompatibleProvider({
|
|
559
|
+
endpoint: env.DEPVERDICT_AI_ENDPOINT,
|
|
560
|
+
model: env.DEPVERDICT_AI_MODEL,
|
|
561
|
+
authorization: env.DEPVERDICT_AI_AUTHORIZATION,
|
|
562
|
+
fetchImplementation: normalizedIo.fetch,
|
|
563
|
+
timeoutMs: configuredAiTimeoutMs(env),
|
|
564
|
+
debug: isAiRuntimeDebugEnabled(env),
|
|
565
|
+
debugWriter: normalizedIo.aiDebugWriter ?? normalizedIo.stderr ?? process.stderr
|
|
566
|
+
});
|
|
567
|
+
return createProviderAiRuntime({ provider });
|
|
568
|
+
}
|
|
569
|
+
const headers = {};
|
|
570
|
+
if (env.DEPVERDICT_AI_AUTHORIZATION) headers.authorization = env.DEPVERDICT_AI_AUTHORIZATION;
|
|
571
|
+
const provider = createHttpJsonAiProvider({
|
|
572
|
+
endpoint: env.DEPVERDICT_AI_ENDPOINT,
|
|
573
|
+
fetchImplementation: normalizedIo.fetch,
|
|
574
|
+
headers,
|
|
575
|
+
provider: providerName,
|
|
576
|
+
model: env.DEPVERDICT_AI_MODEL ?? 'unknown'
|
|
577
|
+
});
|
|
578
|
+
return createProviderAiRuntime({
|
|
579
|
+
provider,
|
|
580
|
+
promptBuilder: buildVersionAnalysisPrompt
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function configuredAiTimeoutMs(env) {
|
|
585
|
+
const raw = env.DEPVERDICT_AI_TIMEOUT_MS;
|
|
586
|
+
if (raw === undefined) return DEFAULT_AI_TIMEOUT_MS;
|
|
587
|
+
const timeoutMs = Number(raw);
|
|
588
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
|
|
589
|
+
throw new Error('DEPVERDICT_AI_TIMEOUT_MS must be a positive integer.');
|
|
590
|
+
}
|
|
591
|
+
return timeoutMs;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function failureRecoveryGuidance(stage, error) {
|
|
595
|
+
const message = error?.message ?? '';
|
|
596
|
+
const code = error?.code ?? '';
|
|
597
|
+
if (/(?:DEPVERDICT|UPGRADELENS)_AI_(?:ENDPOINT|MODEL)|AI runtime/i.test(message)) {
|
|
598
|
+
return [
|
|
599
|
+
`Stage: ${stage?.label ?? 'Version Analysis'}`,
|
|
600
|
+
'Required configuration: DEPVERDICT_AI_ENDPOINT and DEPVERDICT_AI_MODEL; authorization is optional.',
|
|
601
|
+
'Next action: configure one supported provider runtime and rerun. No credential value was printed.'
|
|
602
|
+
];
|
|
603
|
+
}
|
|
604
|
+
if (/LINEAGE|lineage|digest mismatch|stale/i.test(`${code} ${message}`)) {
|
|
605
|
+
return [
|
|
606
|
+
`Stage: ${stage?.label ?? 'Artifact validation'}`,
|
|
607
|
+
'The input artifact chain is stale or mismatched.',
|
|
608
|
+
'Next action: rerun the upstream DepVerdict workflow to regenerate the affected artifacts.'
|
|
609
|
+
];
|
|
610
|
+
}
|
|
611
|
+
if (code === 'ENOENT' || /ENOENT|not found|missing artifact/i.test(message)) {
|
|
612
|
+
return [
|
|
613
|
+
`Stage: ${stage?.label ?? 'Artifact loading'}`,
|
|
614
|
+
'A required upstream artifact is missing.',
|
|
615
|
+
'Next action: run `depverdict analyze <repository>` to regenerate the supported artifact chain.'
|
|
616
|
+
];
|
|
617
|
+
}
|
|
618
|
+
return [];
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function executeAnalyzeVersion(options, io) {
|
|
622
|
+
io = withResolvedEnvironment(io);
|
|
623
|
+
const root = path.resolve(options.root);
|
|
624
|
+
const selection = await resolveArtifactRootChain(root, [
|
|
625
|
+
DEFAULT_MANIFEST_PATH,
|
|
626
|
+
DEFAULT_KNOWLEDGE_MANIFEST_PATH,
|
|
627
|
+
DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH
|
|
628
|
+
], {
|
|
629
|
+
onDiagnostic: (message) => compatibilityDiagnostic(io, message)
|
|
630
|
+
});
|
|
631
|
+
const [
|
|
632
|
+
projectManifestArtifact,
|
|
633
|
+
knowledgeManifestArtifact,
|
|
634
|
+
evidenceBundleArtifact
|
|
635
|
+
] = selection.artifacts;
|
|
636
|
+
const artifacts = await loadVersionAnalysisArtifacts({
|
|
637
|
+
projectManifest: path.join(root, projectManifestArtifact),
|
|
638
|
+
knowledgeManifest: path.join(root, knowledgeManifestArtifact),
|
|
639
|
+
evidenceBundle: path.join(root, evidenceBundleArtifact)
|
|
640
|
+
}, {
|
|
641
|
+
projectManifestArtifact,
|
|
642
|
+
knowledgeManifestArtifact,
|
|
643
|
+
evidenceBundleArtifact
|
|
644
|
+
});
|
|
645
|
+
const allInputs = resolveDependencyAnalysisInputs(artifacts);
|
|
646
|
+
let inputs = allInputs;
|
|
647
|
+
if (options.packageId !== undefined) {
|
|
648
|
+
const packageExists = artifacts.knowledgeManifest.packages.some((item) => item.id === options.packageId);
|
|
649
|
+
if (!packageExists) {
|
|
650
|
+
throw new Error(`Selected package ${options.packageId} was not found in the Knowledge Manifest; no runtime call was made.`);
|
|
651
|
+
}
|
|
652
|
+
inputs = allInputs.filter((input) => input.packageRecord.id === options.packageId);
|
|
653
|
+
if (inputs.length === 0) {
|
|
654
|
+
throw new Error(`Selected package ${options.packageId} is not eligible for Version Analysis because it has no parsed dependency occurrence; no runtime call was made.`);
|
|
655
|
+
}
|
|
656
|
+
if (inputs.length > 1) {
|
|
657
|
+
throw new Error(`Selected package ${options.packageId} matches ${inputs.length} dependency occurrences; one-dependency selection is ambiguous and no runtime call was made.`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const selectedTargets = resolveTargetSelectors(inputs, options.targets ?? []);
|
|
661
|
+
const contexts = inputs.map((input) => {
|
|
662
|
+
const selected = selectedTargets.get(targetOccurrenceKey(input));
|
|
663
|
+
return buildDependencyAiContext(artifacts, {
|
|
664
|
+
input,
|
|
665
|
+
target: selected?.target ?? { policy: 'registryLatest' }
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
const needsRuntime = contexts.some((context) => context.knowledge.evidence.length > 0
|
|
669
|
+
&& context.versions.analysisMode !== 'unsupportedBaseline'
|
|
670
|
+
&& context.versions.targetVersion !== null);
|
|
671
|
+
const runtime = io.aiRuntime ?? (needsRuntime ? createDefaultAiRuntime(io) : null);
|
|
672
|
+
const results = [];
|
|
673
|
+
for (let index = 0; index < contexts.length; index += 1) {
|
|
674
|
+
const context = contexts[index];
|
|
675
|
+
if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
|
|
676
|
+
try {
|
|
677
|
+
io.onProgress?.({
|
|
678
|
+
activityKind: 'WAIT_FOR_ANALYSIS_RESPONSE',
|
|
679
|
+
subject: `Waiting for analysis response: ${context.dependency.declaredName}`,
|
|
680
|
+
completed: index,
|
|
681
|
+
total: contexts.length
|
|
682
|
+
});
|
|
683
|
+
} catch {
|
|
684
|
+
// Progress callbacks are presentation-only.
|
|
685
|
+
}
|
|
686
|
+
results.push(await analyzeDependencyAiContext(context, {
|
|
687
|
+
runtime,
|
|
688
|
+
signal: io.signal
|
|
689
|
+
}));
|
|
690
|
+
if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
|
|
691
|
+
}
|
|
692
|
+
const manifest = (io.buildVersionAnalysisManifest ?? buildVersionAnalysisManifest)({
|
|
693
|
+
input: artifacts.input,
|
|
694
|
+
contexts,
|
|
695
|
+
results,
|
|
696
|
+
generatedAt: io.clock ? io.clock() : new Date()
|
|
697
|
+
});
|
|
698
|
+
const contents = serializeVersionAnalysisManifest(manifest);
|
|
699
|
+
if (options.stdout) {
|
|
700
|
+
io.stdout.write(contents);
|
|
701
|
+
return manifest;
|
|
702
|
+
}
|
|
703
|
+
const target = await (io.writeVersionAnalysisManifest ?? writeVersionAnalysisManifest)(outputPath(root, options.output), manifest);
|
|
704
|
+
io.stderr.write('✓ Loaded Project, Knowledge, and Evidence artifacts\n');
|
|
705
|
+
io.stderr.write(`✓ Built AI contexts (${contexts.length} dependencies)\n`);
|
|
706
|
+
io.stderr.write('✓ AI Version Analysis complete\n');
|
|
707
|
+
io.stderr.write('✓ Version Analysis artifact validated\n');
|
|
708
|
+
io.stderr.write(`✓ Wrote:\n${target}\n`);
|
|
709
|
+
return manifest;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function evaluationRuntime(options, io) {
|
|
713
|
+
if (io.aiRuntime) return {
|
|
714
|
+
runtime: io.aiRuntime,
|
|
715
|
+
model: io.model ?? { provider: 'injected', name: 'injected' }
|
|
716
|
+
};
|
|
717
|
+
const env = withResolvedEnvironment(io).env;
|
|
718
|
+
if (env.DEPVERDICT_AI_ENDPOINT) {
|
|
719
|
+
return {
|
|
720
|
+
runtime: createDefaultAiRuntime(io),
|
|
721
|
+
model: {
|
|
722
|
+
provider: env.DEPVERDICT_AI_PROVIDER ?? 'http-json',
|
|
723
|
+
name: env.DEPVERDICT_AI_MODEL ?? 'unknown'
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
runtime: null,
|
|
729
|
+
model: { provider: 'golden-fake', name: 'golden-fake' }
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
async function executeEval(options, io) {
|
|
734
|
+
const selected = evaluationRuntime(options, io);
|
|
735
|
+
const report = await (io.runEvaluation ?? runEvaluation)({
|
|
736
|
+
datasetPath: options.dataset,
|
|
737
|
+
runtime: selected.runtime,
|
|
738
|
+
model: selected.model,
|
|
739
|
+
generatedAt: io.clock ? io.clock() : new Date()
|
|
740
|
+
});
|
|
741
|
+
const contents = serializeEvaluationReport(report);
|
|
742
|
+
if (options.stdout) {
|
|
743
|
+
io.stdout.write(contents);
|
|
744
|
+
return report.summary.failed > 0 ? 2 : 0;
|
|
745
|
+
}
|
|
746
|
+
const target = await (io.writeEvaluationReport ?? writeEvaluationReport)(outputPath('.', options.output), report);
|
|
747
|
+
io.stderr.write(`✓ Loaded Golden Dataset (${report.summary.totalCases} cases)\n`);
|
|
748
|
+
io.stderr.write('✓ Evaluation complete\n');
|
|
749
|
+
io.stderr.write(`✓ Passed: ${report.summary.passed}; Failed: ${report.summary.failed}\n`);
|
|
750
|
+
io.stderr.write(`✓ Wrote:\n${target}\n`);
|
|
751
|
+
return report.summary.failed > 0 ? 2 : 0;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async function executeScorecard(options, io) {
|
|
755
|
+
const report = await (io.loadEvaluationReportForMetrics ?? loadEvaluationReportForMetrics)(path.resolve(options.report));
|
|
756
|
+
const metrics = (io.buildMetrics ?? buildMetrics)(report, {
|
|
757
|
+
generatedAt: io.clock ? io.clock() : new Date()
|
|
758
|
+
});
|
|
759
|
+
const scorecard = (io.buildAiScorecard ?? buildAiScorecard)(metrics, {
|
|
760
|
+
generatedAt: io.clock ? io.clock() : new Date()
|
|
761
|
+
});
|
|
762
|
+
if (options.stdout) {
|
|
763
|
+
io.stdout.write(serializeAiScorecard(scorecard));
|
|
764
|
+
return 0;
|
|
765
|
+
}
|
|
766
|
+
const metricsTarget = await (io.writeMetrics ?? writeMetrics)(path.resolve(options.metricsOutput), metrics);
|
|
767
|
+
const scorecardTarget = await (io.writeAiScorecard ?? writeAiScorecard)(outputPath('.', options.output), scorecard);
|
|
768
|
+
io.stderr.write(`✓ Loaded Evaluation Report (${report.summary.totalCases} cases)\n`);
|
|
769
|
+
io.stderr.write('✓ Metrics complete\n');
|
|
770
|
+
io.stderr.write(`✓ Scorecard: ${scorecard.overallScore}/100\n`);
|
|
771
|
+
io.stderr.write(`✓ Wrote metrics:\n${metricsTarget}\n`);
|
|
772
|
+
io.stderr.write(`✓ Wrote scorecard:\n${scorecardTarget}\n`);
|
|
773
|
+
return 0;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function benchmarkRuntimeFactory(io) {
|
|
777
|
+
return async (run, config) => {
|
|
778
|
+
if (io.benchmarkRuntimeFactory) return io.benchmarkRuntimeFactory(run, config);
|
|
779
|
+
if (run.runtime.type === 'goldenFake') return null;
|
|
780
|
+
if (run.runtime.type === 'environment') return createDefaultAiRuntime(io);
|
|
781
|
+
throw new Error(`Benchmark runtime ${run.runtime.type} is not supported by the CLI.`);
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async function executeBenchmark(options, io) {
|
|
786
|
+
const configPath = path.resolve(options.config);
|
|
787
|
+
const config = await (io.loadBenchmarkConfig ?? loadBenchmarkConfig)(configPath);
|
|
788
|
+
const report = await (io.runBenchmark ?? runBenchmark)(config, {
|
|
789
|
+
configPath,
|
|
790
|
+
runtimeFactory: benchmarkRuntimeFactory(io),
|
|
791
|
+
generatedAt: io.clock ? io.clock() : new Date()
|
|
792
|
+
});
|
|
793
|
+
const contents = serializeBenchmarkReport(report);
|
|
794
|
+
if (options.stdout) {
|
|
795
|
+
io.stdout.write(contents);
|
|
796
|
+
return 0;
|
|
797
|
+
}
|
|
798
|
+
const target = await (io.writeBenchmarkReport ?? writeBenchmarkReport)(outputPath('.', options.output), report);
|
|
799
|
+
io.stderr.write(`✓ Loaded Benchmark Config (${report.benchmark.runCount} runs)\n`);
|
|
800
|
+
io.stderr.write('✓ Benchmark complete\n');
|
|
801
|
+
io.stderr.write(`✓ Top run: ${report.ranking[0]?.runId ?? 'none'}\n`);
|
|
802
|
+
io.stderr.write(`✓ Wrote:\n${target}\n`);
|
|
803
|
+
return 0;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async function executeConformance(options, io) {
|
|
807
|
+
const env = withResolvedEnvironment(io).env;
|
|
808
|
+
const report = await (io.runConformance ?? runConformance)({
|
|
809
|
+
runtime: {
|
|
810
|
+
provider: env.DEPVERDICT_AI_PROVIDER ?? 'openai-compatible',
|
|
811
|
+
model: env.DEPVERDICT_AI_MODEL ?? 'offline-fixture'
|
|
812
|
+
},
|
|
813
|
+
generatedAt: io.clock ? io.clock() : new Date()
|
|
814
|
+
});
|
|
815
|
+
const contents = serializeConformanceReport(report);
|
|
816
|
+
if (options.stdout) {
|
|
817
|
+
io.stdout.write(contents);
|
|
818
|
+
return report.summary.failed > 0 ? 2 : 0;
|
|
819
|
+
}
|
|
820
|
+
const target = await (io.writeConformanceReport ?? writeConformanceReport)(
|
|
821
|
+
outputPath('.', options.output),
|
|
822
|
+
report
|
|
823
|
+
);
|
|
824
|
+
io.stderr.write(`✓ Offline conformance complete (${report.summary.total} cases)\n`);
|
|
825
|
+
io.stderr.write(`✓ Passed: ${report.summary.passed}; Failed: ${report.summary.failed}\n`);
|
|
826
|
+
io.stderr.write(`✓ Recommendation: ${report.recommendation}\n`);
|
|
827
|
+
io.stderr.write(`✓ Wrote:\n${target}\n`);
|
|
828
|
+
return report.summary.failed > 0 ? 2 : 0;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function governancePositiveInteger(value, fallback, name) {
|
|
832
|
+
if (value === undefined) return fallback;
|
|
833
|
+
const number = Number(value);
|
|
834
|
+
if (!Number.isSafeInteger(number) || number <= 0) {
|
|
835
|
+
throw new Error(`${name} must be a positive integer.`);
|
|
836
|
+
}
|
|
837
|
+
return number;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function executeGovernance(options, io) {
|
|
841
|
+
const env = withResolvedEnvironment(io).env;
|
|
842
|
+
const artifacts = (io.createDefaultGovernanceArtifacts ?? createDefaultGovernanceArtifacts)({
|
|
843
|
+
provider: env.DEPVERDICT_AI_PROVIDER ?? 'openai-compatible',
|
|
844
|
+
endpoint: env.DEPVERDICT_AI_ENDPOINT,
|
|
845
|
+
model: env.DEPVERDICT_AI_MODEL ?? 'offline-fixture',
|
|
846
|
+
timeoutSeconds: governancePositiveInteger(
|
|
847
|
+
env.DEPVERDICT_AI_TIMEOUT_SECONDS,
|
|
848
|
+
180,
|
|
849
|
+
'DEPVERDICT_AI_TIMEOUT_SECONDS'
|
|
850
|
+
),
|
|
851
|
+
maxResponseBytes: governancePositiveInteger(
|
|
852
|
+
env.DEPVERDICT_AI_MAX_RESPONSE_BYTES,
|
|
853
|
+
1_048_576,
|
|
854
|
+
'DEPVERDICT_AI_MAX_RESPONSE_BYTES'
|
|
855
|
+
)
|
|
856
|
+
});
|
|
857
|
+
if (options.stdout) {
|
|
858
|
+
io.stdout.write(serializeGovernanceArtifacts(artifacts));
|
|
859
|
+
return 0;
|
|
860
|
+
}
|
|
861
|
+
const targets = await (io.writeGovernanceArtifacts ?? writeGovernanceArtifacts)(
|
|
862
|
+
outputPath('.', options.output),
|
|
863
|
+
artifacts
|
|
864
|
+
);
|
|
865
|
+
io.stderr.write('✓ Governance metadata validated\n');
|
|
866
|
+
io.stderr.write(`✓ Wrote ${CAPABILITY_PROFILE_FILENAME}:\n${targets.capabilityProfile}\n`);
|
|
867
|
+
io.stderr.write(`✓ Wrote ${DEPLOYMENT_PROFILE_FILENAME}:\n${targets.deploymentProfile}\n`);
|
|
868
|
+
io.stderr.write(`✓ Wrote ${QUALIFICATION_RECORD_FILENAME}:\n${targets.qualificationRecord}\n`);
|
|
869
|
+
return 0;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function silentStream() {
|
|
873
|
+
return Object.freeze({ write: () => true });
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function migrationRuntimeMetadata(io) {
|
|
877
|
+
if (io.migrationRuntimeMetadata) return io.migrationRuntimeMetadata;
|
|
878
|
+
const env = withResolvedEnvironment(io).env;
|
|
879
|
+
const provider = env.DEPVERDICT_AI_PROVIDER ?? 'unknown';
|
|
880
|
+
return {
|
|
881
|
+
provider,
|
|
882
|
+
model: env.DEPVERDICT_AI_MODEL ?? 'unknown',
|
|
883
|
+
adapter: provider === 'openai-compatible' ? 'openai-compatible' : provider
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function migrationEventListener(options, io, progress) {
|
|
888
|
+
if (!options.experimentalMigrationChecklist) return undefined;
|
|
889
|
+
return (event) => {
|
|
890
|
+
try {
|
|
891
|
+
if (event.type === 'stage:start') {
|
|
892
|
+
progress?.({
|
|
893
|
+
activityKind: 'PREPARE_MIGRATION_CONTEXTS',
|
|
894
|
+
subject: 'Prepared eligible Migration Checklist contexts',
|
|
895
|
+
completed: 0,
|
|
896
|
+
total: event.total
|
|
897
|
+
});
|
|
898
|
+
} else if (event.type === 'migration:context-start') {
|
|
899
|
+
progress?.({
|
|
900
|
+
activityKind: 'WAIT_FOR_MIGRATION_RESPONSE',
|
|
901
|
+
subject: `Waiting for migration response: ${event.packageName}`,
|
|
902
|
+
completed: event.processed,
|
|
903
|
+
total: event.total
|
|
904
|
+
});
|
|
905
|
+
} else if ([
|
|
906
|
+
'migration:context-complete',
|
|
907
|
+
'migration:abstained',
|
|
908
|
+
'migration:trust-rejected',
|
|
909
|
+
'migration:fallback'
|
|
910
|
+
].includes(event.type)) {
|
|
911
|
+
progress?.({
|
|
912
|
+
activityKind: 'SELECT_OFFICIAL_GUIDANCE',
|
|
913
|
+
subject: `Processed migration guidance: ${event.packageName}`,
|
|
914
|
+
completed: event.processed,
|
|
915
|
+
total: event.total
|
|
916
|
+
});
|
|
917
|
+
} else if (event.type === 'migration:artifact-written') {
|
|
918
|
+
progress?.({
|
|
919
|
+
activityKind: 'WRITE_MIGRATION_ARTIFACT',
|
|
920
|
+
subject: 'Validated and wrote Migration Checklist artifact'
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
} catch {
|
|
924
|
+
// Pipeline progress remains isolated from Migration Checklist semantics.
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
if (typeof io.migrationProgressListener === 'function') io.migrationProgressListener(event);
|
|
928
|
+
} catch {
|
|
929
|
+
// External progress observation is presentation-only.
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
export function createCliAnalysisStageRunners(options, io) {
|
|
935
|
+
const root = path.resolve(options.root);
|
|
936
|
+
const quiet = silentStream();
|
|
937
|
+
const clockOptions = io.clock ? { clock: io.clock } : {};
|
|
938
|
+
return {
|
|
939
|
+
async projectDiscovery({ progress, signal } = {}) {
|
|
940
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
941
|
+
const manifest = await discoverProject(root, {
|
|
942
|
+
maxDepth: options.maxDepth,
|
|
943
|
+
signal
|
|
944
|
+
});
|
|
945
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
946
|
+
progress?.({
|
|
947
|
+
activityKind: 'WRITE_PROJECT_MANIFEST',
|
|
948
|
+
subject: 'Writing Project Manifest'
|
|
949
|
+
});
|
|
950
|
+
await writeProjectManifest(root, DEFAULT_MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
951
|
+
return manifest;
|
|
952
|
+
},
|
|
953
|
+
async knowledgeResearch({ progress, signal } = {}) {
|
|
954
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
955
|
+
await runResearch({
|
|
956
|
+
...options,
|
|
957
|
+
command: 'research',
|
|
958
|
+
root,
|
|
959
|
+
output: DEFAULT_KNOWLEDGE_MANIFEST_PATH,
|
|
960
|
+
stdout: false
|
|
961
|
+
}, { ...io, stdout: quiet, stderr: quiet, signal });
|
|
962
|
+
progress?.({
|
|
963
|
+
activityKind: 'WRITE_KNOWLEDGE_ARTIFACTS',
|
|
964
|
+
subject: 'Validated Knowledge Research artifacts'
|
|
965
|
+
});
|
|
966
|
+
return DEFAULT_KNOWLEDGE_MANIFEST_PATH;
|
|
967
|
+
},
|
|
968
|
+
async versionAnalysis({ progress, signal } = {}) {
|
|
969
|
+
return executeAnalyzeVersion({
|
|
970
|
+
...options,
|
|
971
|
+
command: 'analyze-version',
|
|
972
|
+
root,
|
|
973
|
+
output: DEFAULT_VERSION_ANALYSIS_PATH,
|
|
974
|
+
stdout: false,
|
|
975
|
+
packageId: undefined
|
|
976
|
+
}, { ...io, stdout: quiet, stderr: quiet, signal, onProgress: progress });
|
|
977
|
+
},
|
|
978
|
+
async usageDiscovery({ progress, signal } = {}) {
|
|
979
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
980
|
+
const usageIndex = await runUsageDiscovery({
|
|
981
|
+
repositoryRoot: root,
|
|
982
|
+
maxDepth: options.maxDepth,
|
|
983
|
+
signal,
|
|
984
|
+
...clockOptions
|
|
985
|
+
});
|
|
986
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
987
|
+
progress?.({
|
|
988
|
+
activityKind: 'WRITE_USAGE_INDEX',
|
|
989
|
+
subject: 'Writing Repository Usage Index'
|
|
990
|
+
});
|
|
991
|
+
await writeUsageIndex(path.join(root, DEFAULT_USAGE_INDEX_PATH), usageIndex);
|
|
992
|
+
return usageIndex;
|
|
993
|
+
},
|
|
994
|
+
async impactAnalysis({ progress, signal } = {}) {
|
|
995
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
996
|
+
const repositoryImpact = await runImpactAnalysis({
|
|
997
|
+
sources: {
|
|
998
|
+
projectManifest: path.join(root, DEFAULT_MANIFEST_PATH),
|
|
999
|
+
versionAnalysis: path.join(root, DEFAULT_VERSION_ANALYSIS_PATH),
|
|
1000
|
+
usageIndex: path.join(root, DEFAULT_USAGE_INDEX_PATH)
|
|
1001
|
+
},
|
|
1002
|
+
...clockOptions
|
|
1003
|
+
});
|
|
1004
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1005
|
+
progress?.({
|
|
1006
|
+
activityKind: 'WRITE_REPOSITORY_IMPACT',
|
|
1007
|
+
subject: 'Writing Repository Impact artifact'
|
|
1008
|
+
});
|
|
1009
|
+
await writeRepositoryImpact(path.join(root, DEFAULT_REPOSITORY_IMPACT_PATH), repositoryImpact);
|
|
1010
|
+
return repositoryImpact;
|
|
1011
|
+
},
|
|
1012
|
+
async impactEvidence({ progress, signal } = {}) {
|
|
1013
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1014
|
+
const impactEvidence = await runImpactEvidenceGeneration({
|
|
1015
|
+
sources: {
|
|
1016
|
+
projectManifest: path.join(root, DEFAULT_MANIFEST_PATH),
|
|
1017
|
+
versionAnalysis: path.join(root, DEFAULT_VERSION_ANALYSIS_PATH),
|
|
1018
|
+
usageIndex: path.join(root, DEFAULT_USAGE_INDEX_PATH),
|
|
1019
|
+
repositoryImpact: path.join(root, DEFAULT_REPOSITORY_IMPACT_PATH)
|
|
1020
|
+
},
|
|
1021
|
+
...clockOptions
|
|
1022
|
+
});
|
|
1023
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1024
|
+
progress?.({
|
|
1025
|
+
activityKind: 'WRITE_IMPACT_EVIDENCE',
|
|
1026
|
+
subject: 'Writing Repository Impact Evidence artifact'
|
|
1027
|
+
});
|
|
1028
|
+
await writeRepositoryImpactEvidence(
|
|
1029
|
+
path.join(root, DEFAULT_REPOSITORY_IMPACT_EVIDENCE_PATH),
|
|
1030
|
+
impactEvidence
|
|
1031
|
+
);
|
|
1032
|
+
return impactEvidence;
|
|
1033
|
+
},
|
|
1034
|
+
async upgradeDecision({ progress, signal } = {}) {
|
|
1035
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1036
|
+
const decision = await (io.runUpgradeDecisionStage ?? runUpgradeDecisionStage)({
|
|
1037
|
+
repositoryRoot: root,
|
|
1038
|
+
artifactPath: DEFAULT_UPGRADE_DECISION_PATH,
|
|
1039
|
+
onCompatibilityDiagnostic: (message) => compatibilityDiagnostic(io, message)
|
|
1040
|
+
});
|
|
1041
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1042
|
+
progress?.({
|
|
1043
|
+
activityKind: 'WRITE_UPGRADE_DECISION',
|
|
1044
|
+
subject: 'Writing deterministic Upgrade Decision artifact'
|
|
1045
|
+
});
|
|
1046
|
+
return decision;
|
|
1047
|
+
},
|
|
1048
|
+
async migrationChecklist({ progress, signal } = {}) {
|
|
1049
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1050
|
+
const onMigrationEvent = migrationEventListener(options, io, progress);
|
|
1051
|
+
const runtimeMetadata = migrationRuntimeMetadata(io);
|
|
1052
|
+
const resolver = io.resolveMigrationQualification ?? resolveMigrationQualification;
|
|
1053
|
+
const qualificationOptions = {
|
|
1054
|
+
repositoryRoot: root,
|
|
1055
|
+
runtimeMetadata,
|
|
1056
|
+
allowExperimental: true,
|
|
1057
|
+
qualificationPath: options.migrationQualificationPath,
|
|
1058
|
+
onCompatibilityDiagnostic: (message) => compatibilityDiagnostic(io, message)
|
|
1059
|
+
};
|
|
1060
|
+
if (Object.hasOwn(io, 'migrationQualification')) {
|
|
1061
|
+
qualificationOptions.qualification = io.migrationQualification;
|
|
1062
|
+
}
|
|
1063
|
+
const qualificationDecision = await resolver(qualificationOptions);
|
|
1064
|
+
return (io.runMigrationChecklistStage ?? runMigrationChecklistStage)({
|
|
1065
|
+
repositoryRoot: root,
|
|
1066
|
+
aiRuntime: io.migrationAiRuntime ?? io.aiRuntime ?? null,
|
|
1067
|
+
createAiRuntime: () => createDefaultAiRuntime(io),
|
|
1068
|
+
runtimeMetadata,
|
|
1069
|
+
qualificationDecision,
|
|
1070
|
+
allowExperimental: true,
|
|
1071
|
+
generatedAt: io.clock ? io.clock() : new Date(),
|
|
1072
|
+
artifactPath: DEFAULT_MIGRATION_CHECKLIST_PATH,
|
|
1073
|
+
onCompatibilityDiagnostic: (message) => compatibilityDiagnostic(io, message),
|
|
1074
|
+
onEvent: onMigrationEvent,
|
|
1075
|
+
signal
|
|
1076
|
+
});
|
|
1077
|
+
},
|
|
1078
|
+
async markdownReport({ artifacts, progress, signal }) {
|
|
1079
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1080
|
+
const viewModel = buildImpactPresentationViewModel({
|
|
1081
|
+
projectManifest: artifacts.projectDiscovery,
|
|
1082
|
+
versionAnalysis: artifacts.versionAnalysis,
|
|
1083
|
+
repositoryImpact: artifacts.impactAnalysis,
|
|
1084
|
+
impactEvidence: artifacts.impactEvidence
|
|
1085
|
+
});
|
|
1086
|
+
const completion = buildProductCompletion({
|
|
1087
|
+
upgradeDecision: artifacts.upgradeDecision,
|
|
1088
|
+
versionAnalysis: artifacts.versionAnalysis,
|
|
1089
|
+
migrationChecklistViewModel: artifacts.migrationChecklist?.viewModel,
|
|
1090
|
+
artifactPaths: {
|
|
1091
|
+
report: options.output,
|
|
1092
|
+
upgradeDecision: DEFAULT_UPGRADE_DECISION_PATH,
|
|
1093
|
+
migrationChecklist: artifacts.migrationChecklist?.artifactPath ?? null
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
const contents = renderMarkdownReport({
|
|
1097
|
+
viewModel,
|
|
1098
|
+
upgradeDecision: artifacts.upgradeDecision,
|
|
1099
|
+
migrationChecklistViewModel: artifacts.migrationChecklist?.viewModel,
|
|
1100
|
+
completion
|
|
1101
|
+
});
|
|
1102
|
+
if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
|
|
1103
|
+
progress?.({
|
|
1104
|
+
activityKind: 'WRITE_MARKDOWN_REPORT',
|
|
1105
|
+
subject: 'Writing Markdown report'
|
|
1106
|
+
});
|
|
1107
|
+
const target = await (io.writeMarkdownReport ?? writeTextArtifact)(
|
|
1108
|
+
outputPath(root, options.output),
|
|
1109
|
+
contents
|
|
1110
|
+
);
|
|
1111
|
+
return Object.freeze({
|
|
1112
|
+
target,
|
|
1113
|
+
viewModel,
|
|
1114
|
+
completion,
|
|
1115
|
+
migrationChecklistViewModel: artifacts.migrationChecklist?.viewModel ?? null
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
export async function executeAnalyze(options, io) {
|
|
1122
|
+
io = withResolvedEnvironment(io);
|
|
1123
|
+
const root = path.resolve(options.root);
|
|
1124
|
+
const runners = {
|
|
1125
|
+
...createCliAnalysisStageRunners(options, io),
|
|
1126
|
+
...io.analysisStageRunners
|
|
1127
|
+
};
|
|
1128
|
+
const env = io.env ?? process.env;
|
|
1129
|
+
const progressReporter = io.progressReporter ?? createProgressReporter(io.stderr, {
|
|
1130
|
+
mode: options.progress,
|
|
1131
|
+
noColor: Object.hasOwn(env, 'NO_COLOR')
|
|
1132
|
+
});
|
|
1133
|
+
const abortController = io.signal ? null : (io.abortController ?? new AbortController());
|
|
1134
|
+
const signal = io.signal ?? abortController.signal;
|
|
1135
|
+
const signalHost = io.signalHost ?? process;
|
|
1136
|
+
const installSignalHandler = !io.signal && io.handleSignals !== false
|
|
1137
|
+
&& typeof signalHost?.once === 'function';
|
|
1138
|
+
const onSigint = () => {
|
|
1139
|
+
if (!abortController.signal.aborted) {
|
|
1140
|
+
abortController.abort(new Error('User requested cancellation.'));
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
if (installSignalHandler) signalHost.once('SIGINT', onSigint);
|
|
1144
|
+
const progressOptions = {};
|
|
1145
|
+
if (io.progressMonotonicClock) progressOptions.monotonicClock = io.progressMonotonicClock;
|
|
1146
|
+
if (io.progressWallClock) progressOptions.wallClock = io.progressWallClock;
|
|
1147
|
+
if (io.progressScheduler) progressOptions.scheduler = io.progressScheduler;
|
|
1148
|
+
if (io.heartbeatIntervalMs !== undefined) {
|
|
1149
|
+
progressOptions.heartbeatIntervalMs = io.heartbeatIntervalMs;
|
|
1150
|
+
}
|
|
1151
|
+
let result;
|
|
1152
|
+
try {
|
|
1153
|
+
result = await (io.runAnalysisPipeline ?? runAnalysisPipeline)({
|
|
1154
|
+
repositoryRoot: root,
|
|
1155
|
+
runners,
|
|
1156
|
+
progressReporter,
|
|
1157
|
+
progressListener: io.progressListener,
|
|
1158
|
+
progressOptions,
|
|
1159
|
+
signal,
|
|
1160
|
+
stages: createAnalysisStages({
|
|
1161
|
+
migrationChecklist: options.experimentalMigrationChecklist
|
|
1162
|
+
})
|
|
1163
|
+
});
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
if (error instanceof PipelineCancellationError || signal.aborted) {
|
|
1166
|
+
io.stderr.write(
|
|
1167
|
+
`\n${PRODUCT_NAME} product outcome: CANCELLED\n`
|
|
1168
|
+
+ 'Next action: rerun when you are ready; no successful completion was published.\n'
|
|
1169
|
+
);
|
|
1170
|
+
return 130;
|
|
1171
|
+
}
|
|
1172
|
+
if (!(error instanceof PipelineStageError)) throw error;
|
|
1173
|
+
let logTarget;
|
|
1174
|
+
try {
|
|
1175
|
+
logTarget = await (io.writeAnalysisFailureLog ?? writeAnalysisFailureLog)(root, error);
|
|
1176
|
+
} catch {
|
|
1177
|
+
io.stderr.write(
|
|
1178
|
+
`\n${PRODUCT_NAME} product outcome: FAILED\n${error.stage.label} failed.\n\n`
|
|
1179
|
+
+ 'Unable to write analysis log.\n'
|
|
1180
|
+
);
|
|
1181
|
+
return 1;
|
|
1182
|
+
}
|
|
1183
|
+
const displayLog = path.relative(root, logTarget).split(path.sep).join('/');
|
|
1184
|
+
const decision = error.cause?.decision;
|
|
1185
|
+
if (decision) {
|
|
1186
|
+
const lines = [
|
|
1187
|
+
'',
|
|
1188
|
+
`${PRODUCT_NAME} product outcome: FAILED`,
|
|
1189
|
+
`${error.stage.label} failed.`,
|
|
1190
|
+
'',
|
|
1191
|
+
`Qualification status: ${decision.status}`,
|
|
1192
|
+
`Reason: ${decision.reasonCode}`,
|
|
1193
|
+
`Qualification source: ${decision.sourceKind}`,
|
|
1194
|
+
`Expected runtime: ${decision.runtimeIdentity.provider} / ${decision.runtimeIdentity.model} / ${decision.runtimeIdentity.adapter}`
|
|
1195
|
+
];
|
|
1196
|
+
if (decision.sourcePath) lines.push(`Qualification path: ${decision.sourcePath}`);
|
|
1197
|
+
if (decision.recordRuntimeIdentity) {
|
|
1198
|
+
lines.push(
|
|
1199
|
+
`Record runtime: ${decision.recordRuntimeIdentity.provider} / ${decision.recordRuntimeIdentity.model} / ${decision.recordRuntimeIdentity.adapter}`
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
lines.push(`Next action: ${decision.nextAction}`, '', 'See:', displayLog || DEFAULT_ANALYSIS_LOG_PATH);
|
|
1203
|
+
io.stderr.write(`${lines.join('\n')}\n`);
|
|
1204
|
+
} else {
|
|
1205
|
+
const guidance = failureRecoveryGuidance(error.stage, error.cause);
|
|
1206
|
+
const guidanceText = guidance.length > 0 ? `\n${guidance.join('\n')}\n` : '';
|
|
1207
|
+
io.stderr.write(
|
|
1208
|
+
`\n${PRODUCT_NAME} product outcome: FAILED\n${error.stage.label} failed.\n`
|
|
1209
|
+
+ `${guidanceText}\nSee:\n`
|
|
1210
|
+
+ `${displayLog || DEFAULT_ANALYSIS_LOG_PATH}\n`
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
return 1;
|
|
1214
|
+
} finally {
|
|
1215
|
+
if (installSignalHandler) signalHost.removeListener?.('SIGINT', onSigint);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const completion = result.artifacts.markdownReport.completion;
|
|
1219
|
+
if (options.stdout) {
|
|
1220
|
+
io.stdout.write(`${JSON.stringify(completion, null, 2)}\n`);
|
|
1221
|
+
} else {
|
|
1222
|
+
io.stdout.write(renderConsoleSummary({
|
|
1223
|
+
viewModel: result.artifacts.markdownReport.viewModel,
|
|
1224
|
+
completion,
|
|
1225
|
+
reportPath: options.output,
|
|
1226
|
+
upgradeDecision: result.artifacts.upgradeDecision,
|
|
1227
|
+
upgradeDecisionPath: DEFAULT_UPGRADE_DECISION_PATH,
|
|
1228
|
+
migrationChecklistViewModel: result.artifacts.migrationChecklist?.viewModel,
|
|
1229
|
+
migrationChecklistPath: result.artifacts.migrationChecklist?.artifactPath
|
|
1230
|
+
}));
|
|
1231
|
+
}
|
|
1232
|
+
return productCompletionExitCode(completion, { strict: options.failOnIncomplete });
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
export async function runCli(argv, io = {}) {
|
|
1236
|
+
const stdout = io.stdout ?? process.stdout;
|
|
1237
|
+
const stderr = io.stderr ?? process.stderr;
|
|
1238
|
+
const invocationName = io.invocationName ?? CLI_NAME;
|
|
1239
|
+
const deprecationState = io.deprecationState ?? legacyInvocationWarnings;
|
|
1240
|
+
if (invocationName === 'upgradelens' && !deprecationState.has('legacy-cli')) {
|
|
1241
|
+
deprecationState.add('legacy-cli');
|
|
1242
|
+
stderr.write(
|
|
1243
|
+
'The `upgradelens` command is deprecated and will be removed after the '
|
|
1244
|
+
+ '0.6.x preview compatibility window. Use `depverdict` instead.\n'
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
try {
|
|
1248
|
+
const options = parseArguments(argv);
|
|
1249
|
+
if (options.help) {
|
|
1250
|
+
stdout.write(HELP);
|
|
1251
|
+
return 0;
|
|
1252
|
+
}
|
|
1253
|
+
if (options.version) {
|
|
1254
|
+
stdout.write(`${VERSION}\n`);
|
|
1255
|
+
return 0;
|
|
1256
|
+
}
|
|
1257
|
+
const commandIo = [
|
|
1258
|
+
'analyze',
|
|
1259
|
+
'analyze-version',
|
|
1260
|
+
'eval',
|
|
1261
|
+
'benchmark',
|
|
1262
|
+
'conformance',
|
|
1263
|
+
'governance'
|
|
1264
|
+
].includes(options.command)
|
|
1265
|
+
? withResolvedEnvironment({ ...io, stdout, stderr })
|
|
1266
|
+
: { ...io, stdout, stderr };
|
|
1267
|
+
if (options.command === 'analyze') return await executeAnalyze(options, commandIo);
|
|
1268
|
+
if (options.command === 'research') return await runResearch(options, { ...io, stdout, stderr });
|
|
1269
|
+
if (options.command === 'analyze-version') {
|
|
1270
|
+
await executeAnalyzeVersion(options, commandIo);
|
|
1271
|
+
return 0;
|
|
1272
|
+
}
|
|
1273
|
+
if (options.command === 'eval') return await executeEval(options, commandIo);
|
|
1274
|
+
if (options.command === 'scorecard') return await executeScorecard(options, { ...io, stdout, stderr });
|
|
1275
|
+
if (options.command === 'benchmark') return await executeBenchmark(options, commandIo);
|
|
1276
|
+
if (options.command === 'conformance') return await executeConformance(options, commandIo);
|
|
1277
|
+
if (options.command === 'governance') return await executeGovernance(options, commandIo);
|
|
1278
|
+
|
|
1279
|
+
const manifest = await discoverProject(options.root, { maxDepth: options.maxDepth });
|
|
1280
|
+
const contents = `${JSON.stringify(manifest, null, options.pretty ? 2 : 0)}\n`;
|
|
1281
|
+
if (options.stdout) stdout.write(contents);
|
|
1282
|
+
else {
|
|
1283
|
+
const target = await writeProjectManifest(options.root, options.output, contents);
|
|
1284
|
+
stderr.write(`Discovered ${manifest.summary.projectCount} project(s).\n`);
|
|
1285
|
+
stderr.write(`Manifest: ${target}\n`);
|
|
1286
|
+
if (manifest.warnings.length) stderr.write(`Warnings: ${manifest.warnings.length}\n`);
|
|
1287
|
+
}
|
|
1288
|
+
return options.failOnWarning && manifest.warnings.length > 0 ? 2 : 0;
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
const guidance = failureRecoveryGuidance(null, error);
|
|
1291
|
+
stderr.write(
|
|
1292
|
+
`${CLI_NAME}: ${error.message}\n`
|
|
1293
|
+
+ (guidance.length > 0 ? `${guidance.join('\n')}\n` : '')
|
|
1294
|
+
);
|
|
1295
|
+
return 1;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
export { HELP };
|