@vibgrate/cli 1.0.52 → 1.0.54
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/dist/{baseline-FRBISJ66.js → baseline-5ZOILNXB.js} +2 -2
- package/dist/{chunk-TCYJSLL2.js → chunk-CQMW6PVB.js} +1 -1
- package/dist/{chunk-TYAGUEXG.js → chunk-PQEUNAVK.js} +248 -214
- package/dist/cli.js +239 -9
- package/dist/index.d.ts +35 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -469,6 +469,17 @@ function formatExtended(ext) {
|
|
|
469
469
|
if (bc.peerConflictsDetected) {
|
|
470
470
|
lines.push(` ${chalk.red("\u26A0")} Peer dependency conflicts detected`);
|
|
471
471
|
}
|
|
472
|
+
lines.push(` Recommendation: ${chalk.bold(bc.overallRecommendation)}`);
|
|
473
|
+
const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
|
|
474
|
+
if (projectsWithPlans.length > 0) {
|
|
475
|
+
lines.push(" Major Upgrade Intelligence:");
|
|
476
|
+
for (const p of projectsWithPlans) {
|
|
477
|
+
lines.push(` - ${p.project} (${p.recommendation})`);
|
|
478
|
+
for (const pkg2 of p.packages.slice(0, 2)) {
|
|
479
|
+
lines.push(` \xB7 ${pkg2.package} ${pkg2.currentVersion ?? "?"} \u2192 ${pkg2.targetVersion ?? "?"} | touched ~${pkg2.usage.touchedPercent}% | ${pkg2.automatable}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
472
483
|
lines.push("");
|
|
473
484
|
}
|
|
474
485
|
}
|
|
@@ -1197,7 +1208,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
|
|
|
1197
1208
|
});
|
|
1198
1209
|
|
|
1199
1210
|
// src/commands/scan.ts
|
|
1200
|
-
import * as
|
|
1211
|
+
import * as path23 from "path";
|
|
1201
1212
|
import { Command as Command3 } from "commander";
|
|
1202
1213
|
import chalk6 from "chalk";
|
|
1203
1214
|
|
|
@@ -4945,307 +4956,253 @@ async function scanTsModernity(rootDir, cache) {
|
|
|
4945
4956
|
}
|
|
4946
4957
|
|
|
4947
4958
|
// src/scanners/breaking-change.ts
|
|
4959
|
+
import * as path16 from "path";
|
|
4960
|
+
import * as semver9 from "semver";
|
|
4948
4961
|
var DEPRECATED_PACKAGES2 = /* @__PURE__ */ new Set([
|
|
4949
|
-
// Fully deprecated / archived
|
|
4950
4962
|
"request",
|
|
4951
|
-
// deprecated 2020, use undici/node fetch
|
|
4952
4963
|
"request-promise",
|
|
4953
|
-
// deprecated with request
|
|
4954
4964
|
"request-promise-native",
|
|
4955
|
-
// deprecated with request
|
|
4956
4965
|
"moment",
|
|
4957
|
-
// deprecated, use date-fns / luxon / Temporal
|
|
4958
4966
|
"node-sass",
|
|
4959
|
-
// deprecated, use sass (Dart Sass)
|
|
4960
4967
|
"tslint",
|
|
4961
|
-
// archived, use eslint + typescript-eslint
|
|
4962
4968
|
"aws-sdk",
|
|
4963
|
-
// AWS SDK v2 EOL, use @aws-sdk/* v3
|
|
4964
4969
|
"babel-core",
|
|
4965
|
-
// replaced by @babel/core
|
|
4966
4970
|
"babel-preset-env",
|
|
4967
|
-
// replaced by @babel/preset-env
|
|
4968
4971
|
"babel-preset-react",
|
|
4969
|
-
// replaced by @babel/preset-react
|
|
4970
4972
|
"babel-loader",
|
|
4971
|
-
// 7.x deprecated, must pair with @babel/core
|
|
4972
4973
|
"core-js",
|
|
4973
|
-
// v2 deprecated (v3 ok but signals old config)
|
|
4974
4974
|
"istanbul",
|
|
4975
|
-
// replaced by nyc / c8
|
|
4976
4975
|
"istanbul-instrumenter-loader",
|
|
4977
|
-
// deprecated with istanbul
|
|
4978
4976
|
"left-pad",
|
|
4979
|
-
// meme package, use String.padStart
|
|
4980
4977
|
"popper.js",
|
|
4981
|
-
// deprecated, use @popperjs/core
|
|
4982
4978
|
"create-react-class",
|
|
4983
|
-
// deprecated React pattern
|
|
4984
4979
|
"react-addons-css-transition-group",
|
|
4985
|
-
// deprecated React addon
|
|
4986
4980
|
"react-addons-test-utils",
|
|
4987
|
-
// use react-dom/test-utils
|
|
4988
4981
|
"@types/express-serve-static-core",
|
|
4989
|
-
// now shipped with express
|
|
4990
4982
|
"enzyme",
|
|
4991
|
-
// unmaintained, use Testing Library or Vitest
|
|
4992
4983
|
"enzyme-adapter-react-16",
|
|
4993
|
-
// unmaintained
|
|
4994
4984
|
"enzyme-adapter-react-17",
|
|
4995
|
-
// unmaintained
|
|
4996
4985
|
"react-hot-loader",
|
|
4997
|
-
// deprecated, use React Fast Refresh
|
|
4998
4986
|
"react-loadable",
|
|
4999
|
-
// unmaintained, use React.lazy
|
|
5000
4987
|
"react-router-dom-v5-compat",
|
|
5001
|
-
// migration shim
|
|
5002
4988
|
"redux-thunk",
|
|
5003
|
-
// bundled into @reduxjs/toolkit since v1.5
|
|
5004
4989
|
"redux-saga",
|
|
5005
|
-
// declining, consider RTK Query
|
|
5006
4990
|
"recompose",
|
|
5007
|
-
// archived, use hooks
|
|
5008
4991
|
"classnames",
|
|
5009
|
-
// works but clsx is faster & smaller
|
|
5010
4992
|
"glamor",
|
|
5011
|
-
// deprecated CSS-in-JS
|
|
5012
4993
|
"radium",
|
|
5013
|
-
// deprecated CSS-in-JS
|
|
5014
4994
|
"material-ui",
|
|
5015
|
-
// replaced by @mui/material
|
|
5016
4995
|
"@material-ui/core",
|
|
5017
|
-
// replaced by @mui/material
|
|
5018
|
-
// Unmaintained / abandoned
|
|
5019
4996
|
"bower",
|
|
5020
|
-
// dead package manager
|
|
5021
4997
|
"grunt",
|
|
5022
|
-
// superseded by npm scripts / modern bundlers
|
|
5023
4998
|
"gulp",
|
|
5024
|
-
// declining, modern bundlers preferred
|
|
5025
4999
|
"browserify",
|
|
5026
|
-
// superseded by modern bundlers
|
|
5027
5000
|
"coffee-script",
|
|
5028
|
-
// CoffeeScript 1.x deprecated
|
|
5029
5001
|
"coffeescript",
|
|
5030
|
-
// declining
|
|
5031
5002
|
"jade",
|
|
5032
|
-
// renamed to pug
|
|
5033
5003
|
"nomnom",
|
|
5034
|
-
// deprecated CLI parser
|
|
5035
5004
|
"optimist",
|
|
5036
|
-
// deprecated CLI parser, use yargs/commander
|
|
5037
5005
|
"minimist",
|
|
5038
|
-
// unmaintained, use mri or parseArgs
|
|
5039
5006
|
"colors",
|
|
5040
|
-
// supply chain compromised, use chalk/picocolors
|
|
5041
5007
|
"faker",
|
|
5042
|
-
// supply chain compromised, use @faker-js/faker
|
|
5043
5008
|
"event-stream",
|
|
5044
|
-
// supply chain incident
|
|
5045
5009
|
"ua-parser-js",
|
|
5046
|
-
// had supply chain incident
|
|
5047
5010
|
"caniuse-db",
|
|
5048
|
-
// replaced by caniuse-lite
|
|
5049
5011
|
"circular-json",
|
|
5050
|
-
// deprecated, use flatted
|
|
5051
5012
|
"mkdirp",
|
|
5052
|
-
// Node has fs.mkdir recursive since v10
|
|
5053
5013
|
"rimraf",
|
|
5054
|
-
// Node has fs.rm recursive since v14
|
|
5055
5014
|
"glob",
|
|
5056
|
-
// consider fast-glob or Node fs.glob
|
|
5057
5015
|
"swig",
|
|
5058
|
-
// abandoned template engine
|
|
5059
5016
|
"dustjs-linkedin",
|
|
5060
|
-
// abandoned template engine
|
|
5061
5017
|
"hogan.js",
|
|
5062
|
-
// abandoned template engine
|
|
5063
5018
|
"passport-local-mongoose",
|
|
5064
|
-
// low maintenance
|
|
5065
|
-
// Known breaking-change magnets
|
|
5066
5019
|
"@angular/http",
|
|
5067
|
-
// removed in Angular 15+
|
|
5068
5020
|
"rxjs-compat",
|
|
5069
|
-
// migration shim for RxJS 5→6
|
|
5070
5021
|
"protractor",
|
|
5071
|
-
// deprecated by Angular team
|
|
5072
5022
|
"karma",
|
|
5073
|
-
// deprecated, Vitest/Jest preferred
|
|
5074
5023
|
"karma-jasmine",
|
|
5075
|
-
// deprecated with Karma
|
|
5076
5024
|
"jasmine"
|
|
5077
|
-
// declining in usage
|
|
5078
5025
|
]);
|
|
5079
5026
|
var LEGACY_POLYFILLS = /* @__PURE__ */ new Set([
|
|
5080
|
-
// Built-in fetch & web APIs (Node 18+)
|
|
5081
5027
|
"node-fetch",
|
|
5082
|
-
// native fetch since Node 18
|
|
5083
5028
|
"cross-fetch",
|
|
5084
|
-
// native fetch since Node 18
|
|
5085
5029
|
"isomorphic-fetch",
|
|
5086
|
-
// native fetch since Node 18
|
|
5087
5030
|
"whatwg-fetch",
|
|
5088
|
-
// native fetch since Node 18
|
|
5089
5031
|
"abort-controller",
|
|
5090
|
-
// native AbortController since Node 15
|
|
5091
5032
|
"form-data",
|
|
5092
|
-
// native FormData since Node 18
|
|
5093
5033
|
"formdata-polyfill",
|
|
5094
|
-
// native FormData since Node 18
|
|
5095
5034
|
"web-streams-polyfill",
|
|
5096
|
-
// native ReadableStream since Node 18
|
|
5097
5035
|
"whatwg-url",
|
|
5098
|
-
// native URL since Node 10
|
|
5099
5036
|
"url-parse",
|
|
5100
|
-
// native URL since Node 10
|
|
5101
5037
|
"domexception",
|
|
5102
|
-
// native DOMException since Node 17
|
|
5103
5038
|
"abortcontroller-polyfill",
|
|
5104
|
-
// native since Node 15
|
|
5105
|
-
// Built-in Node modules shimmed for browser
|
|
5106
5039
|
"querystring",
|
|
5107
|
-
// URLSearchParams preferred, native in Node
|
|
5108
5040
|
"string_decoder",
|
|
5109
|
-
// native TextDecoder preferred
|
|
5110
5041
|
"buffer",
|
|
5111
|
-
// native Buffer in Node, Uint8Array in browser
|
|
5112
5042
|
"events",
|
|
5113
|
-
// native EventTarget preferred
|
|
5114
5043
|
"path-browserify",
|
|
5115
|
-
// browser shim
|
|
5116
5044
|
"stream-browserify",
|
|
5117
|
-
// browser shim
|
|
5118
5045
|
"stream-http",
|
|
5119
|
-
// browser shim
|
|
5120
5046
|
"https-browserify",
|
|
5121
|
-
// browser shim
|
|
5122
5047
|
"os-browserify",
|
|
5123
|
-
// browser shim
|
|
5124
5048
|
"crypto-browserify",
|
|
5125
|
-
// native Web Crypto API
|
|
5126
5049
|
"assert",
|
|
5127
|
-
// native assert in Node, console.assert in browser
|
|
5128
5050
|
"util",
|
|
5129
|
-
// Node native, deprecations in browser bundles
|
|
5130
5051
|
"process",
|
|
5131
|
-
// shimmed, usually unnecessary
|
|
5132
5052
|
"timers-browserify",
|
|
5133
|
-
// native timers
|
|
5134
5053
|
"tty-browserify",
|
|
5135
|
-
// rarely needed
|
|
5136
5054
|
"vm-browserify",
|
|
5137
|
-
// rarely needed
|
|
5138
5055
|
"domain-browser",
|
|
5139
|
-
// domains deprecated in Node
|
|
5140
5056
|
"punycode",
|
|
5141
|
-
// native in URL since Node 10
|
|
5142
5057
|
"readable-stream",
|
|
5143
|
-
// Node streams polyfill, usually unnecessary
|
|
5144
|
-
// ES / language polyfills for ES2015+ (Node 18+ has all)
|
|
5145
5058
|
"es6-promise",
|
|
5146
|
-
// native Promise since Node 4
|
|
5147
5059
|
"promise-polyfill",
|
|
5148
|
-
// native Promise
|
|
5149
5060
|
"es6-symbol",
|
|
5150
|
-
// native Symbol since Node 4
|
|
5151
5061
|
"es6-map",
|
|
5152
|
-
// native Map since Node 4
|
|
5153
5062
|
"es6-set",
|
|
5154
|
-
// native Set since Node 4
|
|
5155
5063
|
"es6-weak-map",
|
|
5156
|
-
// native WeakMap since Node 4
|
|
5157
5064
|
"es6-iterator",
|
|
5158
|
-
// native iterators
|
|
5159
5065
|
"object-assign",
|
|
5160
|
-
// native Object.assign since Node 4
|
|
5161
5066
|
"object.assign",
|
|
5162
|
-
// same
|
|
5163
5067
|
"array.prototype.find",
|
|
5164
|
-
// native since ES2015
|
|
5165
5068
|
"array.prototype.findindex",
|
|
5166
|
-
// native since ES2015
|
|
5167
5069
|
"array.prototype.flat",
|
|
5168
|
-
// native since Node 11
|
|
5169
5070
|
"array.prototype.flatmap",
|
|
5170
|
-
// native since Node 11
|
|
5171
5071
|
"array-includes",
|
|
5172
|
-
// native Array.includes since Node 6
|
|
5173
5072
|
"string.prototype.startswith",
|
|
5174
|
-
// native since ES2015
|
|
5175
5073
|
"string.prototype.endswith",
|
|
5176
|
-
// native since ES2015
|
|
5177
5074
|
"string.prototype.includes",
|
|
5178
|
-
// native since ES2015
|
|
5179
5075
|
"string.prototype.padstart",
|
|
5180
|
-
// native since Node 8
|
|
5181
5076
|
"string.prototype.padend",
|
|
5182
|
-
// native since Node 8
|
|
5183
5077
|
"string.prototype.matchall",
|
|
5184
|
-
// native since Node 12
|
|
5185
5078
|
"string.prototype.replaceall",
|
|
5186
|
-
// native since Node 15
|
|
5187
5079
|
"string.prototype.trimstart",
|
|
5188
|
-
// native since Node 10
|
|
5189
5080
|
"string.prototype.trimend",
|
|
5190
|
-
// native since Node 10
|
|
5191
5081
|
"string.prototype.at",
|
|
5192
|
-
// native since Node 16.6
|
|
5193
5082
|
"object.entries",
|
|
5194
|
-
// native since Node 7
|
|
5195
5083
|
"object.values",
|
|
5196
|
-
// native since Node 7
|
|
5197
5084
|
"object.fromentries",
|
|
5198
|
-
// native since Node 12
|
|
5199
5085
|
"globalthis",
|
|
5200
|
-
// native globalThis since Node 12
|
|
5201
5086
|
"symbol-observable",
|
|
5202
|
-
// TC39 withdrawn
|
|
5203
5087
|
"setimmediate",
|
|
5204
|
-
// Node-only, setTimeout(fn, 0) preferred
|
|
5205
5088
|
"regenerator-runtime",
|
|
5206
|
-
// async/await native since Node 8
|
|
5207
5089
|
"@babel/polyfill",
|
|
5208
|
-
// deprecated, use core-js directly
|
|
5209
5090
|
"whatwg-encoding",
|
|
5210
|
-
// native TextEncoder/TextDecoder
|
|
5211
5091
|
"text-encoding",
|
|
5212
|
-
// native TextEncoder/TextDecoder since Node 11
|
|
5213
5092
|
"encoding",
|
|
5214
|
-
// native TextEncoder/TextDecoder
|
|
5215
5093
|
"unorm",
|
|
5216
|
-
// native String.normalize since Node 0.12
|
|
5217
5094
|
"number.isnan",
|
|
5218
|
-
// native Number.isNaN since Node 0.12
|
|
5219
5095
|
"is-nan",
|
|
5220
|
-
// native Number.isNaN
|
|
5221
5096
|
"has-symbols",
|
|
5222
|
-
// native Symbol detection
|
|
5223
5097
|
"has",
|
|
5224
|
-
// native Object.hasOwn since Node 16.9
|
|
5225
5098
|
"hasown",
|
|
5226
|
-
// shim for Object.hasOwn
|
|
5227
5099
|
"safe-buffer",
|
|
5228
|
-
// Buffer.from available since Node 5.10
|
|
5229
5100
|
"safer-buffer"
|
|
5230
|
-
// Buffer.from available since Node 5.10
|
|
5231
5101
|
]);
|
|
5232
|
-
|
|
5102
|
+
var BREAKING_SIGNAL_PHRASES = [
|
|
5103
|
+
"BREAKING",
|
|
5104
|
+
"Breaking Change",
|
|
5105
|
+
"Removed",
|
|
5106
|
+
"Deprecated",
|
|
5107
|
+
"Migration",
|
|
5108
|
+
"Rename",
|
|
5109
|
+
"Drop support"
|
|
5110
|
+
];
|
|
5111
|
+
var PACKAGE_PLAYBOOKS = {
|
|
5112
|
+
vue: {
|
|
5113
|
+
impactedFeatures: ["Options API-heavy components", "Deprecated hooks", "Template filters", "$listeners / $attrs merge behavior", "Mixin-heavy architecture"],
|
|
5114
|
+
usagePatterns: [/\bexport\s+default\s*\{/, /\bmixins\s*:/, /\bfilters\s*:/, /\$listeners\b/, /beforeDestroy\b/, /destroyed\b/],
|
|
5115
|
+
automation: "manual"
|
|
5116
|
+
},
|
|
5117
|
+
"react-router-dom": {
|
|
5118
|
+
impactedFeatures: ["Switch/Route v5 patterns", "history prop mutation flows", "withRouter HOC usage"],
|
|
5119
|
+
usagePatterns: [/\bSwitch\b/, /\bwithRouter\b/, /\bhistory\./, /<Route\s+component=/],
|
|
5120
|
+
automation: "deterministic-recipe"
|
|
5121
|
+
},
|
|
5122
|
+
eslint: {
|
|
5123
|
+
impactedFeatures: ["Flat config migration", ".eslintrc plugin resolution", "legacy parser/plugin options"],
|
|
5124
|
+
usagePatterns: [/\.eslintrc/, /eslintConfig/, /module\.exports\s*=\s*\[/, /extends\s*:/],
|
|
5125
|
+
automation: "deterministic-recipe"
|
|
5126
|
+
},
|
|
5127
|
+
typescript: {
|
|
5128
|
+
impactedFeatures: ["Stricter type checks", "tsconfig option removals", "moduleResolution defaults changes"],
|
|
5129
|
+
usagePatterns: [/tsconfig\.json/, /strictNullChecks/, /noImplicitAny/, /moduleResolution/],
|
|
5130
|
+
automation: "manual"
|
|
5131
|
+
},
|
|
5132
|
+
"@angular/core": {
|
|
5133
|
+
impactedFeatures: ["NgModule bootstrap assumptions", "Standalone component migration", "Deprecated lifecycle signatures"],
|
|
5134
|
+
usagePatterns: [/@NgModule\b/, /ngOnInit\(/, /providers\s*:/],
|
|
5135
|
+
automation: "codemod-available",
|
|
5136
|
+
codemod: "ng update"
|
|
5137
|
+
}
|
|
5138
|
+
};
|
|
5139
|
+
function detectDecision(majorItems, manualHotspots, codemodItems) {
|
|
5140
|
+
if (majorItems === 0) return "do-nothing";
|
|
5141
|
+
if (codemodItems > 0 && manualHotspots === 0) return "codemod-available";
|
|
5142
|
+
if (manualHotspots > 0) return "manual-hotspots";
|
|
5143
|
+
if (majorItems <= 2) return "upgrade-safely-now";
|
|
5144
|
+
return "plan-major-upgrade";
|
|
5145
|
+
}
|
|
5146
|
+
function normalizeMajor(version) {
|
|
5147
|
+
if (!version) return null;
|
|
5148
|
+
const parsed = semver9.coerce(version);
|
|
5149
|
+
return parsed?.major ?? null;
|
|
5150
|
+
}
|
|
5151
|
+
function resolveCurrentVersion(dep) {
|
|
5152
|
+
if (dep.resolvedVersion && semver9.valid(semver9.coerce(dep.resolvedVersion))) return semver9.coerce(dep.resolvedVersion)?.version ?? null;
|
|
5153
|
+
const min = semver9.minVersion(dep.currentSpec);
|
|
5154
|
+
return min?.version ?? null;
|
|
5155
|
+
}
|
|
5156
|
+
function listInterimMajorTargets(current, target) {
|
|
5157
|
+
if (target <= current + 1) return [];
|
|
5158
|
+
const out = [];
|
|
5159
|
+
for (let m = current + 1; m < target; m++) out.push(`${m}.x`);
|
|
5160
|
+
return out;
|
|
5161
|
+
}
|
|
5162
|
+
async function buildProjectUsageIndex(project, rootDir, fileCache, candidatePackages) {
|
|
5163
|
+
const index = /* @__PURE__ */ new Map();
|
|
5164
|
+
for (const pkg2 of candidatePackages) index.set(pkg2, { importSites: 0, filesTouched: 0, patternHits: 0 });
|
|
5165
|
+
const entries = await fileCache.walkDir(rootDir);
|
|
5166
|
+
const projectPrefix = project.path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5167
|
+
const projectEntries = entries.filter((e) => e.isFile && e.relPath.replace(/\\/g, "/").startsWith(projectPrefix));
|
|
5168
|
+
const codeEntries = projectEntries.filter((e) => /\.(ts|tsx|js|jsx|vue|mjs|cjs|json)$/.test(e.name));
|
|
5169
|
+
for (const file of codeEntries) {
|
|
5170
|
+
let content = "";
|
|
5171
|
+
try {
|
|
5172
|
+
content = await fileCache.readTextFile(path16.join(rootDir, file.relPath));
|
|
5173
|
+
} catch {
|
|
5174
|
+
continue;
|
|
5175
|
+
}
|
|
5176
|
+
for (const pkg2 of candidatePackages) {
|
|
5177
|
+
const current = index.get(pkg2);
|
|
5178
|
+
if (!current) continue;
|
|
5179
|
+
const escaped = pkg2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5180
|
+
const importRegex = new RegExp(`(?:from\\s+['"]${escaped}['"]|require\\(\\s*['"]${escaped}['"]\\s*\\)|import\\(\\s*['"]${escaped}['"]\\s*\\))`, "g");
|
|
5181
|
+
const importMatches = content.match(importRegex) ?? [];
|
|
5182
|
+
if (importMatches.length > 0) {
|
|
5183
|
+
current.importSites += importMatches.length;
|
|
5184
|
+
current.filesTouched += 1;
|
|
5185
|
+
}
|
|
5186
|
+
const playbook = PACKAGE_PLAYBOOKS[pkg2];
|
|
5187
|
+
if (playbook) {
|
|
5188
|
+
for (const pattern of playbook.usagePatterns) {
|
|
5189
|
+
const matches = content.match(new RegExp(pattern.source, "g"));
|
|
5190
|
+
if (matches) current.patternHits += matches.length;
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
return index;
|
|
5196
|
+
}
|
|
5197
|
+
async function scanBreakingChangeExposure(projects, rootDir, fileCache) {
|
|
5233
5198
|
const deprecated = /* @__PURE__ */ new Set();
|
|
5234
5199
|
const legacyPolyfills = /* @__PURE__ */ new Set();
|
|
5235
5200
|
let peerConflictsDetected = false;
|
|
5236
|
-
const allDeps = /* @__PURE__ */ new Set();
|
|
5237
5201
|
for (const project of projects) {
|
|
5238
5202
|
for (const dep of project.dependencies) {
|
|
5239
|
-
|
|
5240
|
-
if (
|
|
5241
|
-
|
|
5242
|
-
}
|
|
5243
|
-
if (LEGACY_POLYFILLS.has(dep.package)) {
|
|
5244
|
-
legacyPolyfills.add(dep.package);
|
|
5245
|
-
}
|
|
5246
|
-
if (dep.section === "peerDependencies" && dep.majorsBehind !== null && dep.majorsBehind >= 2) {
|
|
5247
|
-
peerConflictsDetected = true;
|
|
5248
|
-
}
|
|
5203
|
+
if (DEPRECATED_PACKAGES2.has(dep.package)) deprecated.add(dep.package);
|
|
5204
|
+
if (LEGACY_POLYFILLS.has(dep.package)) legacyPolyfills.add(dep.package);
|
|
5205
|
+
if (dep.section === "peerDependencies" && dep.majorsBehind !== null && dep.majorsBehind >= 2) peerConflictsDetected = true;
|
|
5249
5206
|
}
|
|
5250
5207
|
}
|
|
5251
5208
|
let score = 0;
|
|
@@ -5253,17 +5210,94 @@ function scanBreakingChangeExposure(projects) {
|
|
|
5253
5210
|
score += Math.min(legacyPolyfills.size * 5, 30);
|
|
5254
5211
|
score += peerConflictsDetected ? 20 : 0;
|
|
5255
5212
|
score = Math.min(score, 100);
|
|
5213
|
+
const projectIntelligence = [];
|
|
5214
|
+
const solutionRollup = /* @__PURE__ */ new Map();
|
|
5215
|
+
let majorPackageCount = 0;
|
|
5216
|
+
let manualHotspots = 0;
|
|
5217
|
+
let codemodCandidates = 0;
|
|
5218
|
+
for (const project of projects) {
|
|
5219
|
+
const majorDeps = project.dependencies.filter((d) => (d.majorsBehind ?? 0) >= 1 && d.latestStable);
|
|
5220
|
+
if (majorDeps.length === 0) {
|
|
5221
|
+
projectIntelligence.push({
|
|
5222
|
+
project: project.name,
|
|
5223
|
+
projectPath: project.path,
|
|
5224
|
+
packages: [],
|
|
5225
|
+
recommendation: "do-nothing"
|
|
5226
|
+
});
|
|
5227
|
+
continue;
|
|
5228
|
+
}
|
|
5229
|
+
const usageIndex = await buildProjectUsageIndex(project, rootDir, fileCache, majorDeps.map((d) => d.package));
|
|
5230
|
+
const packages = majorDeps.map((dep) => {
|
|
5231
|
+
const currentVersion = resolveCurrentVersion(dep);
|
|
5232
|
+
const targetVersion = dep.latestStable;
|
|
5233
|
+
const currentMajor = normalizeMajor(currentVersion);
|
|
5234
|
+
const targetMajor = normalizeMajor(targetVersion);
|
|
5235
|
+
const interimMajors = currentMajor !== null && targetMajor !== null ? listInterimMajorTargets(currentMajor, targetMajor) : [];
|
|
5236
|
+
const usage = usageIndex.get(dep.package) ?? { importSites: 0, filesTouched: 0, patternHits: 0 };
|
|
5237
|
+
const fileCount = Math.max(1, project.fileCount ?? 1);
|
|
5238
|
+
const touchedPercent = Math.min(100, Math.round((usage.filesTouched + usage.patternHits) / fileCount * 100));
|
|
5239
|
+
const playbook = PACKAGE_PLAYBOOKS[dep.package];
|
|
5240
|
+
const impactedFeatures = playbook?.impactedFeatures ?? ["Public API usage patterns", "Configuration surface", "Runtime compatibility expectations"];
|
|
5241
|
+
const automatable = playbook?.automation ?? (usage.patternHits > 0 ? "manual" : "deterministic-recipe");
|
|
5242
|
+
majorPackageCount++;
|
|
5243
|
+
if (automatable === "manual") manualHotspots++;
|
|
5244
|
+
if (automatable === "codemod-available") codemodCandidates++;
|
|
5245
|
+
return {
|
|
5246
|
+
package: dep.package,
|
|
5247
|
+
currentVersion,
|
|
5248
|
+
targetVersion,
|
|
5249
|
+
majorJumpCount: dep.majorsBehind ?? 0,
|
|
5250
|
+
interimMajors,
|
|
5251
|
+
releaseNoteSources: ["GitHub Releases", "Repository tags", "CHANGELOG.md"],
|
|
5252
|
+
parsedSignals: [...BREAKING_SIGNAL_PHRASES],
|
|
5253
|
+
impactedFeatures,
|
|
5254
|
+
usage: {
|
|
5255
|
+
importSites: usage.importSites,
|
|
5256
|
+
filesTouchedEstimate: usage.filesTouched,
|
|
5257
|
+
functionsTouchedEstimate: usage.patternHits,
|
|
5258
|
+
touchedPercent
|
|
5259
|
+
},
|
|
5260
|
+
automatable,
|
|
5261
|
+
codemod: playbook?.codemod
|
|
5262
|
+
};
|
|
5263
|
+
});
|
|
5264
|
+
const rec = detectDecision(packages.length, packages.filter((p) => p.automatable === "manual").length, packages.filter((p) => p.automatable === "codemod-available").length);
|
|
5265
|
+
projectIntelligence.push({
|
|
5266
|
+
project: project.name,
|
|
5267
|
+
projectPath: project.path,
|
|
5268
|
+
packages,
|
|
5269
|
+
recommendation: rec
|
|
5270
|
+
});
|
|
5271
|
+
if (project.solutionId) {
|
|
5272
|
+
const key = project.solutionId;
|
|
5273
|
+
const existing = solutionRollup.get(key) ?? {
|
|
5274
|
+
solutionId: key,
|
|
5275
|
+
solutionName: project.solutionName ?? key,
|
|
5276
|
+
projectCount: 0,
|
|
5277
|
+
majorPackages: 0,
|
|
5278
|
+
recommendation: "do-nothing"
|
|
5279
|
+
};
|
|
5280
|
+
existing.projectCount += 1;
|
|
5281
|
+
existing.majorPackages += packages.length;
|
|
5282
|
+
existing.recommendation = detectDecision(existing.majorPackages, manualHotspots, codemodCandidates);
|
|
5283
|
+
solutionRollup.set(key, existing);
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
const overallRecommendation = detectDecision(majorPackageCount, manualHotspots, codemodCandidates);
|
|
5256
5287
|
return {
|
|
5257
5288
|
deprecatedPackages: [...deprecated].sort(),
|
|
5258
5289
|
legacyPolyfills: [...legacyPolyfills].sort(),
|
|
5259
5290
|
peerConflictsDetected,
|
|
5260
|
-
exposureScore: score
|
|
5291
|
+
exposureScore: score,
|
|
5292
|
+
projectIntelligence,
|
|
5293
|
+
solutionIntelligence: [...solutionRollup.values()],
|
|
5294
|
+
overallRecommendation
|
|
5261
5295
|
};
|
|
5262
5296
|
}
|
|
5263
5297
|
|
|
5264
5298
|
// src/scanners/file-hotspots.ts
|
|
5265
5299
|
import * as fs4 from "fs/promises";
|
|
5266
|
-
import * as
|
|
5300
|
+
import * as path17 from "path";
|
|
5267
5301
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5268
5302
|
"node_modules",
|
|
5269
5303
|
".git",
|
|
@@ -5308,9 +5342,9 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
5308
5342
|
const entries = await cache.walkDir(rootDir);
|
|
5309
5343
|
for (const entry of entries) {
|
|
5310
5344
|
if (!entry.isFile) continue;
|
|
5311
|
-
const ext =
|
|
5345
|
+
const ext = path17.extname(entry.name).toLowerCase();
|
|
5312
5346
|
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
5313
|
-
const depth = entry.relPath.split(
|
|
5347
|
+
const depth = entry.relPath.split(path17.sep).length - 1;
|
|
5314
5348
|
if (depth > maxDepth) maxDepth = depth;
|
|
5315
5349
|
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
5316
5350
|
try {
|
|
@@ -5339,15 +5373,15 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
5339
5373
|
for (const e of entries) {
|
|
5340
5374
|
if (e.isDirectory) {
|
|
5341
5375
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
5342
|
-
await walk(
|
|
5376
|
+
await walk(path17.join(dir, e.name), depth + 1);
|
|
5343
5377
|
} else if (e.isFile) {
|
|
5344
|
-
const ext =
|
|
5378
|
+
const ext = path17.extname(e.name).toLowerCase();
|
|
5345
5379
|
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
5346
5380
|
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
5347
5381
|
try {
|
|
5348
|
-
const stat3 = await fs4.stat(
|
|
5382
|
+
const stat3 = await fs4.stat(path17.join(dir, e.name));
|
|
5349
5383
|
allFiles.push({
|
|
5350
|
-
path:
|
|
5384
|
+
path: path17.relative(rootDir, path17.join(dir, e.name)),
|
|
5351
5385
|
bytes: stat3.size
|
|
5352
5386
|
});
|
|
5353
5387
|
} catch {
|
|
@@ -5370,7 +5404,7 @@ async function scanFileHotspots(rootDir, cache) {
|
|
|
5370
5404
|
}
|
|
5371
5405
|
|
|
5372
5406
|
// src/scanners/security-posture.ts
|
|
5373
|
-
import * as
|
|
5407
|
+
import * as path18 from "path";
|
|
5374
5408
|
var LOCKFILES = {
|
|
5375
5409
|
"pnpm-lock.yaml": "pnpm",
|
|
5376
5410
|
"package-lock.json": "npm",
|
|
@@ -5391,14 +5425,14 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
5391
5425
|
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
5392
5426
|
const foundLockfiles = [];
|
|
5393
5427
|
for (const [file, type] of Object.entries(LOCKFILES)) {
|
|
5394
|
-
if (await _pathExists(
|
|
5428
|
+
if (await _pathExists(path18.join(rootDir, file))) {
|
|
5395
5429
|
foundLockfiles.push(type);
|
|
5396
5430
|
}
|
|
5397
5431
|
}
|
|
5398
5432
|
result.lockfilePresent = foundLockfiles.length > 0;
|
|
5399
5433
|
result.multipleLockfileTypes = foundLockfiles.length > 1;
|
|
5400
5434
|
result.lockfileTypes = foundLockfiles.sort();
|
|
5401
|
-
const gitignorePath =
|
|
5435
|
+
const gitignorePath = path18.join(rootDir, ".gitignore");
|
|
5402
5436
|
if (await _pathExists(gitignorePath)) {
|
|
5403
5437
|
try {
|
|
5404
5438
|
const content = await _readTextFile(gitignorePath);
|
|
@@ -5413,7 +5447,7 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
5413
5447
|
}
|
|
5414
5448
|
}
|
|
5415
5449
|
for (const envFile of [".env", ".env.local", ".env.development", ".env.production"]) {
|
|
5416
|
-
if (await _pathExists(
|
|
5450
|
+
if (await _pathExists(path18.join(rootDir, envFile))) {
|
|
5417
5451
|
if (!result.gitignoreCoversEnv) {
|
|
5418
5452
|
result.envFilesTracked = true;
|
|
5419
5453
|
break;
|
|
@@ -5425,7 +5459,7 @@ async function scanSecurityPosture(rootDir, cache) {
|
|
|
5425
5459
|
|
|
5426
5460
|
// src/scanners/security-scanners.ts
|
|
5427
5461
|
import { spawn as spawn3 } from "child_process";
|
|
5428
|
-
import * as
|
|
5462
|
+
import * as path19 from "path";
|
|
5429
5463
|
var TOOL_MATRIX = [
|
|
5430
5464
|
{ key: "semgrep", category: "sast", command: "semgrep", versionArgs: ["--version"], minRecommendedVersion: "1.75.0" },
|
|
5431
5465
|
{ key: "gitleaks", category: "secrets", command: "gitleaks", versionArgs: ["version"], minRecommendedVersion: "8.20.0" },
|
|
@@ -5520,7 +5554,7 @@ async function detectSecretHeuristics(rootDir, cache) {
|
|
|
5520
5554
|
const findings = [];
|
|
5521
5555
|
for (const entry of entries) {
|
|
5522
5556
|
if (!entry.isFile) continue;
|
|
5523
|
-
const ext =
|
|
5557
|
+
const ext = path19.extname(entry.name).toLowerCase();
|
|
5524
5558
|
if (ext && [".png", ".jpg", ".jpeg", ".gif", ".zip", ".pdf"].includes(ext)) continue;
|
|
5525
5559
|
const content = await cache.readTextFile(entry.absPath);
|
|
5526
5560
|
if (!content || content.length > 3e5) continue;
|
|
@@ -5543,9 +5577,9 @@ async function scanSecurityScanners(rootDir, cache, runner = defaultRunner) {
|
|
|
5543
5577
|
const [semgrep, gitleaks, trufflehog] = await Promise.all(TOOL_MATRIX.map((tool) => assessTool(tool, runner)));
|
|
5544
5578
|
const heuristicFindings = await detectSecretHeuristics(rootDir, cache);
|
|
5545
5579
|
const configFiles = {
|
|
5546
|
-
semgrep: await cache.pathExists(
|
|
5547
|
-
gitleaks: await cache.pathExists(
|
|
5548
|
-
trufflehog: await cache.pathExists(
|
|
5580
|
+
semgrep: await cache.pathExists(path19.join(rootDir, ".semgrep.yml")) || await cache.pathExists(path19.join(rootDir, ".semgrep.yaml")),
|
|
5581
|
+
gitleaks: await cache.pathExists(path19.join(rootDir, ".gitleaks.toml")),
|
|
5582
|
+
trufflehog: await cache.pathExists(path19.join(rootDir, ".trufflehog.yml")) || await cache.pathExists(path19.join(rootDir, ".trufflehog.yaml"))
|
|
5549
5583
|
};
|
|
5550
5584
|
return {
|
|
5551
5585
|
semgrep,
|
|
@@ -5970,7 +6004,7 @@ function scanServiceDependencies(projects) {
|
|
|
5970
6004
|
}
|
|
5971
6005
|
|
|
5972
6006
|
// src/scanners/architecture.ts
|
|
5973
|
-
import * as
|
|
6007
|
+
import * as path20 from "path";
|
|
5974
6008
|
import * as fs5 from "fs/promises";
|
|
5975
6009
|
var ARCHETYPE_SIGNALS = [
|
|
5976
6010
|
// Meta-frameworks (highest priority — they imply routing patterns)
|
|
@@ -6269,9 +6303,9 @@ async function walkSourceFiles(rootDir, cache) {
|
|
|
6269
6303
|
const entries = await cache.walkDir(rootDir);
|
|
6270
6304
|
return entries.filter((e) => {
|
|
6271
6305
|
if (!e.isFile) return false;
|
|
6272
|
-
const name =
|
|
6306
|
+
const name = path20.basename(e.absPath);
|
|
6273
6307
|
if (name.startsWith(".") && name !== ".") return false;
|
|
6274
|
-
const ext =
|
|
6308
|
+
const ext = path20.extname(name);
|
|
6275
6309
|
return SOURCE_EXTENSIONS.has(ext);
|
|
6276
6310
|
}).map((e) => e.relPath);
|
|
6277
6311
|
}
|
|
@@ -6285,15 +6319,15 @@ async function walkSourceFiles(rootDir, cache) {
|
|
|
6285
6319
|
}
|
|
6286
6320
|
for (const entry of entries) {
|
|
6287
6321
|
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
6288
|
-
const fullPath =
|
|
6322
|
+
const fullPath = path20.join(dir, entry.name);
|
|
6289
6323
|
if (entry.isDirectory()) {
|
|
6290
6324
|
if (!IGNORE_DIRS.has(entry.name)) {
|
|
6291
6325
|
await walk(fullPath);
|
|
6292
6326
|
}
|
|
6293
6327
|
} else if (entry.isFile()) {
|
|
6294
|
-
const ext =
|
|
6328
|
+
const ext = path20.extname(entry.name);
|
|
6295
6329
|
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
6296
|
-
files.push(
|
|
6330
|
+
files.push(path20.relative(rootDir, fullPath));
|
|
6297
6331
|
}
|
|
6298
6332
|
}
|
|
6299
6333
|
}
|
|
@@ -6317,7 +6351,7 @@ function classifyFile(filePath, archetype) {
|
|
|
6317
6351
|
}
|
|
6318
6352
|
}
|
|
6319
6353
|
if (!bestMatch || bestMatch.confidence < 0.7) {
|
|
6320
|
-
const baseName =
|
|
6354
|
+
const baseName = path20.basename(filePath, path20.extname(filePath));
|
|
6321
6355
|
const cleanBase = baseName.replace(/\.(test|spec)$/, "");
|
|
6322
6356
|
for (const rule of SUFFIX_RULES) {
|
|
6323
6357
|
if (cleanBase.endsWith(rule.suffix)) {
|
|
@@ -6426,7 +6460,7 @@ function generateLayerFlowMermaid(layers) {
|
|
|
6426
6460
|
return lines.join("\n");
|
|
6427
6461
|
}
|
|
6428
6462
|
async function buildProjectArchitectureMermaid(rootDir, project, archetype, cache) {
|
|
6429
|
-
const projectRoot =
|
|
6463
|
+
const projectRoot = path20.resolve(rootDir, project.path || ".");
|
|
6430
6464
|
const allFiles = await walkSourceFiles(projectRoot, cache);
|
|
6431
6465
|
const layerSet = /* @__PURE__ */ new Set();
|
|
6432
6466
|
for (const rel of allFiles) {
|
|
@@ -6552,7 +6586,7 @@ async function scanArchitecture(rootDir, projects, tooling, services, cache) {
|
|
|
6552
6586
|
}
|
|
6553
6587
|
|
|
6554
6588
|
// src/scanners/code-quality.ts
|
|
6555
|
-
import * as
|
|
6589
|
+
import * as path21 from "path";
|
|
6556
6590
|
import * as ts from "typescript";
|
|
6557
6591
|
var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
6558
6592
|
var DEFAULT_RESULT = {
|
|
@@ -6583,9 +6617,9 @@ async function scanCodeQuality(rootDir, cache) {
|
|
|
6583
6617
|
continue;
|
|
6584
6618
|
}
|
|
6585
6619
|
if (!raw.trim()) continue;
|
|
6586
|
-
const rel = normalizeModuleId(
|
|
6620
|
+
const rel = normalizeModuleId(path21.relative(rootDir, filePath));
|
|
6587
6621
|
const source = ts.createSourceFile(filePath, raw, ts.ScriptTarget.Latest, true);
|
|
6588
|
-
const imports = collectLocalImports(source,
|
|
6622
|
+
const imports = collectLocalImports(source, path21.dirname(filePath), rootDir);
|
|
6589
6623
|
depGraph.set(rel, imports);
|
|
6590
6624
|
const fileMetrics = computeFileMetrics(source, raw);
|
|
6591
6625
|
totalFunctions += fileMetrics.functionsAnalyzed;
|
|
@@ -6619,9 +6653,9 @@ async function scanCodeQuality(rootDir, cache) {
|
|
|
6619
6653
|
async function findSourceFiles(rootDir, cache) {
|
|
6620
6654
|
if (cache) {
|
|
6621
6655
|
const entries = await cache.walkDir(rootDir);
|
|
6622
|
-
return entries.filter((entry) => entry.isFile && SOURCE_EXTENSIONS2.has(
|
|
6656
|
+
return entries.filter((entry) => entry.isFile && SOURCE_EXTENSIONS2.has(path21.extname(entry.name).toLowerCase())).map((entry) => entry.absPath);
|
|
6623
6657
|
}
|
|
6624
|
-
const files = await findFiles(rootDir, (name) => SOURCE_EXTENSIONS2.has(
|
|
6658
|
+
const files = await findFiles(rootDir, (name) => SOURCE_EXTENSIONS2.has(path21.extname(name).toLowerCase()));
|
|
6625
6659
|
return files;
|
|
6626
6660
|
}
|
|
6627
6661
|
function collectLocalImports(source, fileDir, rootDir) {
|
|
@@ -6642,8 +6676,8 @@ function collectLocalImports(source, fileDir, rootDir) {
|
|
|
6642
6676
|
}
|
|
6643
6677
|
function resolveLocalImport(specifier, fileDir, rootDir) {
|
|
6644
6678
|
if (!specifier.startsWith(".")) return null;
|
|
6645
|
-
const rawTarget =
|
|
6646
|
-
const normalized =
|
|
6679
|
+
const rawTarget = path21.resolve(fileDir, specifier);
|
|
6680
|
+
const normalized = path21.relative(rootDir, rawTarget).replace(/\\/g, "/");
|
|
6647
6681
|
if (!normalized || normalized.startsWith("..")) return null;
|
|
6648
6682
|
return normalizeModuleId(normalized);
|
|
6649
6683
|
}
|
|
@@ -6785,7 +6819,7 @@ function visitEach(node, cb) {
|
|
|
6785
6819
|
|
|
6786
6820
|
// src/scanners/owasp-category-mapping.ts
|
|
6787
6821
|
import { spawn as spawn4 } from "child_process";
|
|
6788
|
-
import * as
|
|
6822
|
+
import * as path22 from "path";
|
|
6789
6823
|
var OWASP_CONFIG = "p/owasp-top-ten";
|
|
6790
6824
|
var DEFAULT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6791
6825
|
".js",
|
|
@@ -6864,7 +6898,7 @@ function parseFindings(results, rootDir) {
|
|
|
6864
6898
|
const metadata = r.extra?.metadata;
|
|
6865
6899
|
return {
|
|
6866
6900
|
ruleId: r.check_id ?? "unknown",
|
|
6867
|
-
path: r.path ?
|
|
6901
|
+
path: r.path ? path22.relative(rootDir, path22.resolve(rootDir, r.path)) : "",
|
|
6868
6902
|
line: r.start?.line ?? 1,
|
|
6869
6903
|
endLine: r.end?.line,
|
|
6870
6904
|
message: r.extra?.message ?? "Potential security issue",
|
|
@@ -6924,7 +6958,7 @@ async function scanOwaspCategoryMapping(rootDir, cache, options = {}, runner = r
|
|
|
6924
6958
|
}
|
|
6925
6959
|
}
|
|
6926
6960
|
const entries = cache ? await cache.walkDir(rootDir) : [];
|
|
6927
|
-
const files = entries.filter((e) => e.isFile && DEFAULT_EXTENSIONS.has(
|
|
6961
|
+
const files = entries.filter((e) => e.isFile && DEFAULT_EXTENSIONS.has(path22.extname(e.name).toLowerCase()));
|
|
6928
6962
|
const findings = [];
|
|
6929
6963
|
const errors = [];
|
|
6930
6964
|
let scannedFiles = 0;
|
|
@@ -7372,17 +7406,17 @@ async function discoverSolutions(rootDir, fileCache) {
|
|
|
7372
7406
|
for (const solutionFile of solutionFiles) {
|
|
7373
7407
|
try {
|
|
7374
7408
|
const content = await fileCache.readTextFile(solutionFile);
|
|
7375
|
-
const dir =
|
|
7376
|
-
const relSolutionPath =
|
|
7409
|
+
const dir = path23.dirname(solutionFile);
|
|
7410
|
+
const relSolutionPath = path23.relative(rootDir, solutionFile).replace(/\\/g, "/");
|
|
7377
7411
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
7378
7412
|
const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.csproj)"/g;
|
|
7379
7413
|
let match;
|
|
7380
7414
|
while ((match = projectRegex.exec(content)) !== null) {
|
|
7381
7415
|
const projectRelative = match[2];
|
|
7382
|
-
const absProjectPath =
|
|
7383
|
-
projectPaths.add(
|
|
7416
|
+
const absProjectPath = path23.resolve(dir, projectRelative.replace(/\\/g, "/"));
|
|
7417
|
+
projectPaths.add(path23.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
|
|
7384
7418
|
}
|
|
7385
|
-
const solutionName =
|
|
7419
|
+
const solutionName = path23.basename(solutionFile, path23.extname(solutionFile));
|
|
7386
7420
|
parsed.push({
|
|
7387
7421
|
path: relSolutionPath,
|
|
7388
7422
|
name: solutionName,
|
|
@@ -7575,7 +7609,7 @@ async function runScan(rootDir, opts) {
|
|
|
7575
7609
|
project.drift = computeDriftScore([project]);
|
|
7576
7610
|
project.projectId = computeProjectId(project.path, project.name, workspaceId);
|
|
7577
7611
|
}
|
|
7578
|
-
const solutionsManifestPath =
|
|
7612
|
+
const solutionsManifestPath = path23.join(rootDir, ".vibgrate", "solutions.json");
|
|
7579
7613
|
const persistedSolutionIds = /* @__PURE__ */ new Map();
|
|
7580
7614
|
if (await pathExists(solutionsManifestPath)) {
|
|
7581
7615
|
try {
|
|
@@ -7650,8 +7684,8 @@ async function runScan(rootDir, opts) {
|
|
|
7650
7684
|
if (scannerPolicy.breakingChangeExposure && scanners?.breakingChangeExposure?.enabled !== false) {
|
|
7651
7685
|
progress.startStep("breaking");
|
|
7652
7686
|
scannerTasks.push(
|
|
7653
|
-
Promise.resolve().then(() => {
|
|
7654
|
-
extended.breakingChangeExposure = scanBreakingChangeExposure(allProjects);
|
|
7687
|
+
Promise.resolve().then(async () => {
|
|
7688
|
+
extended.breakingChangeExposure = await scanBreakingChangeExposure(allProjects, rootDir, fileCache);
|
|
7655
7689
|
const bc = extended.breakingChangeExposure;
|
|
7656
7690
|
const bcTotal = bc.deprecatedPackages.length + bc.legacyPolyfills.length;
|
|
7657
7691
|
progress.completeStep(
|
|
@@ -7885,7 +7919,7 @@ async function runScan(rootDir, opts) {
|
|
|
7885
7919
|
schemaVersion: "1.0",
|
|
7886
7920
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7887
7921
|
vibgrateVersion: VERSION,
|
|
7888
|
-
rootPath:
|
|
7922
|
+
rootPath: path23.basename(rootDir),
|
|
7889
7923
|
...vcs.type !== "unknown" ? { vcs } : {},
|
|
7890
7924
|
repository,
|
|
7891
7925
|
projects: allProjects,
|
|
@@ -7899,7 +7933,7 @@ async function runScan(rootDir, opts) {
|
|
|
7899
7933
|
relationshipDiagram
|
|
7900
7934
|
};
|
|
7901
7935
|
if (opts.baseline) {
|
|
7902
|
-
const baselinePath =
|
|
7936
|
+
const baselinePath = path23.resolve(opts.baseline);
|
|
7903
7937
|
if (await pathExists(baselinePath)) {
|
|
7904
7938
|
try {
|
|
7905
7939
|
const baseline = await readJsonFile(baselinePath);
|
|
@@ -7911,10 +7945,10 @@ async function runScan(rootDir, opts) {
|
|
|
7911
7945
|
}
|
|
7912
7946
|
}
|
|
7913
7947
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
7914
|
-
const vibgrateDir =
|
|
7948
|
+
const vibgrateDir = path23.join(rootDir, ".vibgrate");
|
|
7915
7949
|
await ensureDir(vibgrateDir);
|
|
7916
|
-
await writeJsonFile(
|
|
7917
|
-
await writeJsonFile(
|
|
7950
|
+
await writeJsonFile(path23.join(vibgrateDir, "scan_result.json"), artifact);
|
|
7951
|
+
await writeJsonFile(path23.join(vibgrateDir, "solutions.json"), {
|
|
7918
7952
|
scannedAt: artifact.timestamp,
|
|
7919
7953
|
solutions: solutions.map((solution) => ({
|
|
7920
7954
|
solutionId: solution.solutionId,
|
|
@@ -7935,10 +7969,10 @@ async function runScan(rootDir, opts) {
|
|
|
7935
7969
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
7936
7970
|
for (const project of allProjects) {
|
|
7937
7971
|
if (project.drift && project.path) {
|
|
7938
|
-
const projectDir =
|
|
7939
|
-
const projectVibgrateDir =
|
|
7972
|
+
const projectDir = path23.resolve(rootDir, project.path);
|
|
7973
|
+
const projectVibgrateDir = path23.join(projectDir, ".vibgrate");
|
|
7940
7974
|
await ensureDir(projectVibgrateDir);
|
|
7941
|
-
await writeJsonFile(
|
|
7975
|
+
await writeJsonFile(path23.join(projectVibgrateDir, "project_score.json"), {
|
|
7942
7976
|
projectId: project.projectId,
|
|
7943
7977
|
name: project.name,
|
|
7944
7978
|
type: project.type,
|
|
@@ -7958,7 +7992,7 @@ async function runScan(rootDir, opts) {
|
|
|
7958
7992
|
if (opts.format === "json") {
|
|
7959
7993
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
7960
7994
|
if (opts.out) {
|
|
7961
|
-
await writeTextFile(
|
|
7995
|
+
await writeTextFile(path23.resolve(opts.out), jsonStr);
|
|
7962
7996
|
console.log(chalk6.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
7963
7997
|
} else {
|
|
7964
7998
|
console.log(jsonStr);
|
|
@@ -7967,7 +8001,7 @@ async function runScan(rootDir, opts) {
|
|
|
7967
8001
|
const sarif = formatSarif(artifact);
|
|
7968
8002
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
7969
8003
|
if (opts.out) {
|
|
7970
|
-
await writeTextFile(
|
|
8004
|
+
await writeTextFile(path23.resolve(opts.out), sarifStr);
|
|
7971
8005
|
console.log(chalk6.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
7972
8006
|
} else {
|
|
7973
8007
|
console.log(sarifStr);
|
|
@@ -7976,20 +8010,20 @@ async function runScan(rootDir, opts) {
|
|
|
7976
8010
|
const markdown = formatMarkdown(artifact);
|
|
7977
8011
|
console.log(markdown);
|
|
7978
8012
|
if (opts.out) {
|
|
7979
|
-
await writeTextFile(
|
|
8013
|
+
await writeTextFile(path23.resolve(opts.out), markdown);
|
|
7980
8014
|
}
|
|
7981
8015
|
} else {
|
|
7982
8016
|
const text = formatText(artifact);
|
|
7983
8017
|
console.log(text);
|
|
7984
8018
|
if (opts.out) {
|
|
7985
|
-
await writeTextFile(
|
|
8019
|
+
await writeTextFile(path23.resolve(opts.out), text);
|
|
7986
8020
|
}
|
|
7987
8021
|
}
|
|
7988
8022
|
return artifact;
|
|
7989
8023
|
}
|
|
7990
8024
|
async function buildRepositoryInfo(rootDir, remoteUrl, ciSystems) {
|
|
7991
|
-
const packageJsonPath =
|
|
7992
|
-
let name =
|
|
8025
|
+
const packageJsonPath = path23.join(rootDir, "package.json");
|
|
8026
|
+
let name = path23.basename(rootDir);
|
|
7993
8027
|
let version;
|
|
7994
8028
|
if (await pathExists(packageJsonPath)) {
|
|
7995
8029
|
try {
|
|
@@ -8081,7 +8115,7 @@ function parseNonNegativeNumber(value, label) {
|
|
|
8081
8115
|
return parsed;
|
|
8082
8116
|
}
|
|
8083
8117
|
var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif|md)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--install-tools", "Auto-install missing security scanners via Homebrew").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").action(async (targetPath, opts) => {
|
|
8084
|
-
const rootDir =
|
|
8118
|
+
const rootDir = path23.resolve(targetPath);
|
|
8085
8119
|
if (!await pathExists(rootDir)) {
|
|
8086
8120
|
console.error(chalk6.red(`Path does not exist: ${rootDir}`));
|
|
8087
8121
|
process.exit(1);
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
baselineCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CQMW6PVB.js";
|
|
5
5
|
import {
|
|
6
6
|
VERSION,
|
|
7
7
|
dsnCommand,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
pushCommand,
|
|
11
11
|
scanCommand,
|
|
12
12
|
writeDefaultConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PQEUNAVK.js";
|
|
14
14
|
import {
|
|
15
15
|
ensureDir,
|
|
16
16
|
pathExists,
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
} from "./chunk-RNVZIZNL.js";
|
|
20
20
|
|
|
21
21
|
// src/cli.ts
|
|
22
|
-
import { Command as
|
|
23
|
-
import
|
|
22
|
+
import { Command as Command6 } from "commander";
|
|
23
|
+
import chalk6 from "chalk";
|
|
24
24
|
|
|
25
25
|
// src/commands/init.ts
|
|
26
26
|
import * as path from "path";
|
|
@@ -39,7 +39,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
39
39
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
40
40
|
}
|
|
41
41
|
if (opts.baseline) {
|
|
42
|
-
const { runBaseline } = await import("./baseline-
|
|
42
|
+
const { runBaseline } = await import("./baseline-5ZOILNXB.js");
|
|
43
43
|
await runBaseline(rootDir);
|
|
44
44
|
}
|
|
45
45
|
console.log("");
|
|
@@ -409,9 +409,239 @@ var deltaCommand = new Command4("delta").description("Show SBOM delta between tw
|
|
|
409
409
|
});
|
|
410
410
|
var sbomCommand = new Command4("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
|
|
411
411
|
|
|
412
|
+
// src/commands/help.ts
|
|
413
|
+
import { Command as Command5 } from "commander";
|
|
414
|
+
import chalk5 from "chalk";
|
|
415
|
+
var HELP_URL = "https://vibgrate.com/help";
|
|
416
|
+
function printFooter() {
|
|
417
|
+
console.log("");
|
|
418
|
+
console.log(chalk5.dim(`See ${HELP_URL} for more guidance`));
|
|
419
|
+
}
|
|
420
|
+
var detailedHelp = {
|
|
421
|
+
scan: () => {
|
|
422
|
+
console.log("");
|
|
423
|
+
console.log(chalk5.bold.underline("vibgrate scan") + chalk5.dim(" \u2014 Scan a project for upgrade drift"));
|
|
424
|
+
console.log("");
|
|
425
|
+
console.log(chalk5.bold("Usage:"));
|
|
426
|
+
console.log(" vibgrate scan [path] [options]");
|
|
427
|
+
console.log("");
|
|
428
|
+
console.log(chalk5.bold("Arguments:"));
|
|
429
|
+
console.log(` ${chalk5.cyan("[path]")} Path to scan (default: current directory)`);
|
|
430
|
+
console.log("");
|
|
431
|
+
console.log(chalk5.bold("Output options:"));
|
|
432
|
+
console.log(` ${chalk5.cyan("--format <format>")} Output format: ${chalk5.white("text")} | json | sarif | md (default: text)`);
|
|
433
|
+
console.log(` ${chalk5.cyan("--out <file>")} Write output to a file instead of stdout`);
|
|
434
|
+
console.log("");
|
|
435
|
+
console.log(chalk5.bold("Baseline & gating:"));
|
|
436
|
+
console.log(` ${chalk5.cyan("--baseline <file>")} Compare results against a saved baseline`);
|
|
437
|
+
console.log(` ${chalk5.cyan("--drift-budget <score>")} Fail if drift score exceeds this value (0\u2013100)`);
|
|
438
|
+
console.log(` ${chalk5.cyan("--drift-worsening <percent>")} Fail if drift worsens by more than % since baseline`);
|
|
439
|
+
console.log(` ${chalk5.cyan("--fail-on <level>")} Fail exit code on warn or error findings`);
|
|
440
|
+
console.log("");
|
|
441
|
+
console.log(chalk5.bold("Performance:"));
|
|
442
|
+
console.log(` ${chalk5.cyan("--concurrency <n>")} Max concurrent registry calls (default: 8)`);
|
|
443
|
+
console.log(` ${chalk5.cyan("--changed-only")} Only scan files changed since last git commit`);
|
|
444
|
+
console.log("");
|
|
445
|
+
console.log(chalk5.bold("Privacy & offline:"));
|
|
446
|
+
console.log(` ${chalk5.cyan("--offline")} Run without any network calls; skip result upload`);
|
|
447
|
+
console.log(` ${chalk5.cyan("--package-manifest <file>")} Use a local package-version manifest (JSON or ZIP) for offline mode`);
|
|
448
|
+
console.log(` ${chalk5.cyan("--no-local-artifacts")} Do not write .vibgrate JSON artifacts to disk`);
|
|
449
|
+
console.log(` ${chalk5.cyan("--max-privacy")} Strongest privacy mode: minimal scanners + no local artifacts`);
|
|
450
|
+
console.log("");
|
|
451
|
+
console.log(chalk5.bold("Tooling:"));
|
|
452
|
+
console.log(` ${chalk5.cyan("--install-tools")} Auto-install missing security scanners via Homebrew`);
|
|
453
|
+
console.log(` ${chalk5.cyan("--ui-purpose")} Enable UI purpose evidence extraction (slower)`);
|
|
454
|
+
console.log("");
|
|
455
|
+
console.log(chalk5.bold("Uploading results:"));
|
|
456
|
+
console.log(` ${chalk5.cyan("--push")} Auto-push results to Vibgrate API after scan`);
|
|
457
|
+
console.log(` ${chalk5.cyan("--dsn <dsn>")} DSN token for push (or set VIBGRATE_DSN env var)`);
|
|
458
|
+
console.log(` ${chalk5.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
|
|
459
|
+
console.log(` ${chalk5.cyan("--strict")} Fail if the upload to Vibgrate API fails`);
|
|
460
|
+
console.log("");
|
|
461
|
+
console.log(chalk5.bold("Examples:"));
|
|
462
|
+
console.log(` ${chalk5.dim("# Scan the current directory and display a text report")}`);
|
|
463
|
+
console.log(" vibgrate scan .");
|
|
464
|
+
console.log("");
|
|
465
|
+
console.log(` ${chalk5.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
|
|
466
|
+
console.log(" vibgrate scan . --drift-budget 40 --format sarif --out drift.sarif");
|
|
467
|
+
console.log("");
|
|
468
|
+
console.log(` ${chalk5.dim("# Scan and automatically upload results via a DSN")}`);
|
|
469
|
+
console.log(" vibgrate scan . --push --dsn $VIBGRATE_DSN");
|
|
470
|
+
console.log("");
|
|
471
|
+
console.log(` ${chalk5.dim("# Offline scan using a pre-downloaded package manifest")}`);
|
|
472
|
+
console.log(" vibgrate scan . --offline --package-manifest ./manifest.zip");
|
|
473
|
+
},
|
|
474
|
+
init: () => {
|
|
475
|
+
console.log("");
|
|
476
|
+
console.log(chalk5.bold.underline("vibgrate init") + chalk5.dim(" \u2014 Initialise vibgrate in a project directory"));
|
|
477
|
+
console.log("");
|
|
478
|
+
console.log(chalk5.bold("Usage:"));
|
|
479
|
+
console.log(" vibgrate init [path] [options]");
|
|
480
|
+
console.log("");
|
|
481
|
+
console.log(chalk5.bold("Arguments:"));
|
|
482
|
+
console.log(` ${chalk5.cyan("[path]")} Directory to initialise (default: current directory)`);
|
|
483
|
+
console.log("");
|
|
484
|
+
console.log(chalk5.bold("Options:"));
|
|
485
|
+
console.log(` ${chalk5.cyan("--baseline")} Create an initial drift baseline after init`);
|
|
486
|
+
console.log(` ${chalk5.cyan("--yes")} Skip all confirmation prompts`);
|
|
487
|
+
console.log("");
|
|
488
|
+
console.log(chalk5.bold("What it does:"));
|
|
489
|
+
console.log(" \u2022 Creates a .vibgrate/ directory");
|
|
490
|
+
console.log(" \u2022 Writes a vibgrate.config.ts starter config");
|
|
491
|
+
console.log(" \u2022 Optionally runs an initial baseline scan (--baseline)");
|
|
492
|
+
console.log("");
|
|
493
|
+
console.log(chalk5.bold("Examples:"));
|
|
494
|
+
console.log(" vibgrate init");
|
|
495
|
+
console.log(" vibgrate init ./my-project --baseline");
|
|
496
|
+
},
|
|
497
|
+
baseline: () => {
|
|
498
|
+
console.log("");
|
|
499
|
+
console.log(chalk5.bold.underline("vibgrate baseline") + chalk5.dim(" \u2014 Save a drift baseline snapshot"));
|
|
500
|
+
console.log("");
|
|
501
|
+
console.log(chalk5.bold("Usage:"));
|
|
502
|
+
console.log(" vibgrate baseline [path]");
|
|
503
|
+
console.log("");
|
|
504
|
+
console.log(chalk5.bold("Arguments:"));
|
|
505
|
+
console.log(` ${chalk5.cyan("[path]")} Path to baseline (default: current directory)`);
|
|
506
|
+
console.log("");
|
|
507
|
+
console.log(chalk5.bold("What it does:"));
|
|
508
|
+
console.log(" Runs a full scan and saves the result as .vibgrate/baseline.json.");
|
|
509
|
+
console.log(" Future scans can compare against this file using --baseline.");
|
|
510
|
+
console.log("");
|
|
511
|
+
console.log(chalk5.bold("Examples:"));
|
|
512
|
+
console.log(" vibgrate baseline .");
|
|
513
|
+
console.log(" vibgrate scan . --baseline .vibgrate/baseline.json --drift-worsening 10");
|
|
514
|
+
},
|
|
515
|
+
report: () => {
|
|
516
|
+
console.log("");
|
|
517
|
+
console.log(chalk5.bold.underline("vibgrate report") + chalk5.dim(" \u2014 Generate a report from a saved scan artifact"));
|
|
518
|
+
console.log("");
|
|
519
|
+
console.log(chalk5.bold("Usage:"));
|
|
520
|
+
console.log(" vibgrate report [options]");
|
|
521
|
+
console.log("");
|
|
522
|
+
console.log(chalk5.bold("Options:"));
|
|
523
|
+
console.log(` ${chalk5.cyan("--in <file>")} Input artifact file (default: .vibgrate/scan_result.json)`);
|
|
524
|
+
console.log(` ${chalk5.cyan("--format <format>")} Output format: ${chalk5.white("text")} | md | json (default: text)`);
|
|
525
|
+
console.log("");
|
|
526
|
+
console.log(chalk5.bold("Examples:"));
|
|
527
|
+
console.log(" vibgrate report");
|
|
528
|
+
console.log(" vibgrate report --format md > DRIFT-REPORT.md");
|
|
529
|
+
console.log(" vibgrate report --in ./ci/scan_result.json --format json");
|
|
530
|
+
},
|
|
531
|
+
sbom: () => {
|
|
532
|
+
console.log("");
|
|
533
|
+
console.log(chalk5.bold.underline("vibgrate sbom") + chalk5.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
|
|
534
|
+
console.log("");
|
|
535
|
+
console.log(chalk5.bold("Usage:"));
|
|
536
|
+
console.log(" vibgrate sbom [options]");
|
|
537
|
+
console.log("");
|
|
538
|
+
console.log(chalk5.bold("Options:"));
|
|
539
|
+
console.log(` ${chalk5.cyan("--in <file>")} Input artifact (default: .vibgrate/scan_result.json)`);
|
|
540
|
+
console.log(` ${chalk5.cyan("--format <format>")} SBOM format: ${chalk5.white("cyclonedx")} | spdx (default: cyclonedx)`);
|
|
541
|
+
console.log(` ${chalk5.cyan("--out <file>")} Write SBOM to file instead of stdout`);
|
|
542
|
+
console.log("");
|
|
543
|
+
console.log(chalk5.bold("Examples:"));
|
|
544
|
+
console.log(" vibgrate sbom --format cyclonedx --out sbom.json");
|
|
545
|
+
console.log(" vibgrate sbom --format spdx --out sbom.spdx.json");
|
|
546
|
+
},
|
|
547
|
+
push: () => {
|
|
548
|
+
console.log("");
|
|
549
|
+
console.log(chalk5.bold.underline("vibgrate push") + chalk5.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
|
|
550
|
+
console.log("");
|
|
551
|
+
console.log(chalk5.bold("Usage:"));
|
|
552
|
+
console.log(" vibgrate push [options]");
|
|
553
|
+
console.log("");
|
|
554
|
+
console.log(chalk5.bold("Options:"));
|
|
555
|
+
console.log(` ${chalk5.cyan("--dsn <dsn>")} DSN token (or set VIBGRATE_DSN env var)`);
|
|
556
|
+
console.log(` ${chalk5.cyan("--file <file>")} Artifact to upload (default: .vibgrate/scan_result.json)`);
|
|
557
|
+
console.log(` ${chalk5.cyan("--region <region>")} Override data residency region: us | eu`);
|
|
558
|
+
console.log(` ${chalk5.cyan("--strict")} Fail with non-zero exit code on upload error`);
|
|
559
|
+
console.log("");
|
|
560
|
+
console.log(chalk5.bold("Examples:"));
|
|
561
|
+
console.log(" vibgrate push --dsn $VIBGRATE_DSN");
|
|
562
|
+
console.log(" vibgrate push --file ./ci/scan_result.json --strict");
|
|
563
|
+
},
|
|
564
|
+
dsn: () => {
|
|
565
|
+
console.log("");
|
|
566
|
+
console.log(chalk5.bold.underline("vibgrate dsn") + chalk5.dim(" \u2014 Manage DSN tokens for API authentication"));
|
|
567
|
+
console.log("");
|
|
568
|
+
console.log(chalk5.bold("Subcommands:"));
|
|
569
|
+
console.log(` ${chalk5.cyan("vibgrate dsn create")} Generate a new DSN token`);
|
|
570
|
+
console.log("");
|
|
571
|
+
console.log(chalk5.bold("dsn create options:"));
|
|
572
|
+
console.log(` ${chalk5.cyan("--workspace <id>")} Workspace ID ${chalk5.red("(required)")}`);
|
|
573
|
+
console.log(` ${chalk5.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
|
|
574
|
+
console.log(` ${chalk5.cyan("--ingest <url>")} Override ingest API URL`);
|
|
575
|
+
console.log(` ${chalk5.cyan("--write <path>")} Write the DSN to a file (add to .gitignore!)`);
|
|
576
|
+
console.log("");
|
|
577
|
+
console.log(chalk5.bold("Examples:"));
|
|
578
|
+
console.log(" vibgrate dsn create --workspace abc123");
|
|
579
|
+
console.log(" vibgrate dsn create --workspace abc123 --region eu --write .vibgrate/.dsn");
|
|
580
|
+
},
|
|
581
|
+
update: () => {
|
|
582
|
+
console.log("");
|
|
583
|
+
console.log(chalk5.bold.underline("vibgrate update") + chalk5.dim(" \u2014 Update the vibgrate CLI to the latest version"));
|
|
584
|
+
console.log("");
|
|
585
|
+
console.log(chalk5.bold("Usage:"));
|
|
586
|
+
console.log(" vibgrate update [options]");
|
|
587
|
+
console.log("");
|
|
588
|
+
console.log(chalk5.bold("Options:"));
|
|
589
|
+
console.log(` ${chalk5.cyan("--check")} Check for a newer version without installing`);
|
|
590
|
+
console.log(` ${chalk5.cyan("--pm <manager>")} Force a package manager: npm | pnpm | yarn | bun`);
|
|
591
|
+
console.log("");
|
|
592
|
+
console.log(chalk5.bold("Examples:"));
|
|
593
|
+
console.log(" vibgrate update");
|
|
594
|
+
console.log(" vibgrate update --check");
|
|
595
|
+
console.log(" vibgrate update --pm pnpm");
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
function printSummaryHelp() {
|
|
599
|
+
console.log("");
|
|
600
|
+
console.log(chalk5.bold("vibgrate") + chalk5.dim(" \u2014 Continuous Drift Intelligence"));
|
|
601
|
+
console.log("");
|
|
602
|
+
console.log(chalk5.bold("Usage:"));
|
|
603
|
+
console.log(" vibgrate <command> [options]");
|
|
604
|
+
console.log(" vibgrate help [command] Show detailed help for a command");
|
|
605
|
+
console.log("");
|
|
606
|
+
console.log(chalk5.bold("Getting started:"));
|
|
607
|
+
console.log(` ${chalk5.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
|
|
608
|
+
console.log("");
|
|
609
|
+
console.log(chalk5.bold("Core scanning:"));
|
|
610
|
+
console.log(` ${chalk5.cyan("scan")} Scan a project for upgrade drift and generate a report`);
|
|
611
|
+
console.log(` ${chalk5.cyan("baseline")} Save a baseline snapshot to compare future scans against`);
|
|
612
|
+
console.log("");
|
|
613
|
+
console.log(chalk5.bold("Reporting & export:"));
|
|
614
|
+
console.log(` ${chalk5.cyan("report")} Re-generate a report from a previously saved scan artifact`);
|
|
615
|
+
console.log(` ${chalk5.cyan("sbom")} Export a Software Bill of Materials (CycloneDX or SPDX)`);
|
|
616
|
+
console.log("");
|
|
617
|
+
console.log(chalk5.bold("CI/CD integration:"));
|
|
618
|
+
console.log(` ${chalk5.cyan("push")} Upload a scan artifact to the Vibgrate API`);
|
|
619
|
+
console.log(` ${chalk5.cyan("dsn")} Create and manage DSN tokens for API authentication`);
|
|
620
|
+
console.log("");
|
|
621
|
+
console.log(chalk5.bold("Maintenance:"));
|
|
622
|
+
console.log(` ${chalk5.cyan("update")} Update the vibgrate CLI to the latest version`);
|
|
623
|
+
console.log("");
|
|
624
|
+
console.log(chalk5.dim("Run") + ` ${chalk5.cyan("vibgrate help <command>")} ` + chalk5.dim("for detailed options, e.g.") + ` ${chalk5.cyan("vibgrate help scan")}`);
|
|
625
|
+
}
|
|
626
|
+
var helpCommand = new Command5("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
|
|
627
|
+
const name = cmd?.toLowerCase().trim();
|
|
628
|
+
if (name && detailedHelp[name]) {
|
|
629
|
+
detailedHelp[name]();
|
|
630
|
+
} else if (name) {
|
|
631
|
+
console.log("");
|
|
632
|
+
console.log(chalk5.red(`Unknown command: ${name}`));
|
|
633
|
+
console.log(chalk5.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
|
|
634
|
+
printSummaryHelp();
|
|
635
|
+
} else {
|
|
636
|
+
printSummaryHelp();
|
|
637
|
+
}
|
|
638
|
+
printFooter();
|
|
639
|
+
});
|
|
640
|
+
|
|
412
641
|
// src/cli.ts
|
|
413
|
-
var program = new
|
|
414
|
-
program.name("vibgrate").description("Continuous Drift Intelligence
|
|
642
|
+
var program = new Command6();
|
|
643
|
+
program.name("vibgrate").description("Continuous Drift Intelligence").version(VERSION).addHelpText("after", "\nSee https://vibgrate.com/help for more guidance");
|
|
644
|
+
program.addCommand(helpCommand);
|
|
415
645
|
program.addCommand(initCommand);
|
|
416
646
|
program.addCommand(scanCommand);
|
|
417
647
|
program.addCommand(baselineCommand);
|
|
@@ -424,8 +654,8 @@ function notifyIfUpdateAvailable() {
|
|
|
424
654
|
void checkForUpdate().then((update) => {
|
|
425
655
|
if (!update?.updateAvailable) return;
|
|
426
656
|
console.error("");
|
|
427
|
-
console.error(
|
|
428
|
-
console.error(
|
|
657
|
+
console.error(chalk6.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
|
|
658
|
+
console.error(chalk6.dim(' Run "vibgrate update" to install the latest version.'));
|
|
429
659
|
console.error("");
|
|
430
660
|
}).catch(() => {
|
|
431
661
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -287,11 +287,46 @@ interface TsModernityResult {
|
|
|
287
287
|
moduleType: 'esm' | 'cjs' | 'mixed' | null;
|
|
288
288
|
exportsField: boolean;
|
|
289
289
|
}
|
|
290
|
+
type UpgradeRecommendation = 'do-nothing' | 'upgrade-safely-now' | 'plan-major-upgrade' | 'codemod-available' | 'manual-hotspots';
|
|
291
|
+
interface BreakingChangePackageIntelligence {
|
|
292
|
+
package: string;
|
|
293
|
+
currentVersion: string | null;
|
|
294
|
+
targetVersion: string | null;
|
|
295
|
+
majorJumpCount: number;
|
|
296
|
+
interimMajors: string[];
|
|
297
|
+
releaseNoteSources: string[];
|
|
298
|
+
parsedSignals: string[];
|
|
299
|
+
impactedFeatures: string[];
|
|
300
|
+
usage: {
|
|
301
|
+
importSites: number;
|
|
302
|
+
filesTouchedEstimate: number;
|
|
303
|
+
functionsTouchedEstimate: number;
|
|
304
|
+
touchedPercent: number;
|
|
305
|
+
};
|
|
306
|
+
automatable: 'codemod-available' | 'deterministic-recipe' | 'manual';
|
|
307
|
+
codemod?: string;
|
|
308
|
+
}
|
|
309
|
+
interface BreakingChangeProjectIntelligence {
|
|
310
|
+
project: string;
|
|
311
|
+
projectPath: string;
|
|
312
|
+
packages: BreakingChangePackageIntelligence[];
|
|
313
|
+
recommendation: UpgradeRecommendation;
|
|
314
|
+
}
|
|
315
|
+
interface BreakingChangeSolutionIntelligence {
|
|
316
|
+
solutionId: string;
|
|
317
|
+
solutionName: string;
|
|
318
|
+
projectCount: number;
|
|
319
|
+
majorPackages: number;
|
|
320
|
+
recommendation: UpgradeRecommendation;
|
|
321
|
+
}
|
|
290
322
|
interface BreakingChangeExposureResult {
|
|
291
323
|
deprecatedPackages: string[];
|
|
292
324
|
legacyPolyfills: string[];
|
|
293
325
|
peerConflictsDetected: boolean;
|
|
294
326
|
exposureScore: number;
|
|
327
|
+
projectIntelligence: BreakingChangeProjectIntelligence[];
|
|
328
|
+
solutionIntelligence: BreakingChangeSolutionIntelligence[];
|
|
329
|
+
overallRecommendation: UpgradeRecommendation;
|
|
295
330
|
}
|
|
296
331
|
interface FileHotspot {
|
|
297
332
|
path: string;
|
package/dist/index.js
CHANGED