arol-ai 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -13
- package/dist/cli.js +9 -1
- package/dist/data.js +4 -2
- package/dist/report.js +22 -8
- package/dist/scanner.js +90 -4
- package/package.json +1 -1
- package/src/data/deprecations.json +167 -20
package/README.md
CHANGED
|
@@ -49,12 +49,14 @@ Scanned 128 files · 1 API detected
|
|
|
49
49
|
src/agents/run.ts:88 → beta.threads
|
|
50
50
|
→ migrate: https://platform.openai.com/docs/assistants/migration
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
alerted before the next one
|
|
52
|
+
────────────────────────────────────────────────────────────
|
|
53
|
+
⚠ These break on fixed dates. Get alerted before the next one hits you → arol.ai
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Note the citations point at the **exact source lines that use the deprecated API**, not at the manifest. Having the `openai` package installed is not enough on its own — your code has to actually call the removed surface.
|
|
57
57
|
|
|
58
|
+
The closing line is **severity-aware**: a high-severity finding gets the prominent warning above; findings with no high-severity items get `Get continuous deprecation alerts for your stack → arol.ai`; and a clean scan gets `✓ Clean today — but new deprecations land constantly. Stay covered → arol.ai`.
|
|
59
|
+
|
|
58
60
|
When nothing is found:
|
|
59
61
|
|
|
60
62
|
```
|
|
@@ -67,13 +69,23 @@ Detection keys on **actual usage, not mere SDK presence.** Each dataset entry de
|
|
|
67
69
|
|
|
68
70
|
### `match: "pattern"` — the default
|
|
69
71
|
|
|
70
|
-
Flags **only** when
|
|
72
|
+
Flags **only** when your code actually references the deprecated API in a scanned **source file**. `detect.sdk` is just a scope hint here and is **never** a trigger on its own. A `pattern` entry carries two kinds of usage signal:
|
|
73
|
+
|
|
74
|
+
- **`detect.patterns`** — raw regexes for code identifiers, endpoints, and params (e.g. `beta\.assistants`, `/v1/threads`, `charges\.create`, `hapikey\s*=`). Matched anywhere in the file.
|
|
75
|
+
- **`detect.models`** — model family names matched **only inside a string literal**. Each becomes: an opening quote (`'` `"` or `` ` ``), the family name, an optional `[A-Za-z0-9._-]*` version/suffix, then the matching closing quote. So `"gpt-4o"`, `'gpt-4o'`, `` `gpt-4o` ``, and `"gpt-4o-2024-05-13"` match — but the same name sitting in prose, JSX, or a comment does **not**.
|
|
76
|
+
|
|
77
|
+
This split is what keeps a marketing page that mentions *"GPT-4o, GPT-4.1, and o4-mini"* from being reported as deprecated usage: those names aren't quoted string literals, so `detect.models` ignores them. Only something like `model: "o4-mini"` counts.
|
|
78
|
+
|
|
79
|
+
Each hit records the **file path, line number, and matched text**, and one deprecation aggregates **all** of its matched locations into a single finding.
|
|
80
|
+
|
|
81
|
+
> Having the `openai` package in `requirements.txt` does **not** flag the Assistants API deprecation. Your code has to actually use `beta.assistants` / a deprecated model id (etc.).
|
|
82
|
+
|
|
83
|
+
**Files scanned / skipped**
|
|
71
84
|
|
|
72
85
|
- Extensions scanned: `.js .mjs .cjs .jsx .ts .mts .cts .tsx .py .go`
|
|
73
86
|
- Skipped directories: `node_modules`, `.git`, `dist`, `build`, `.next`, `out`, `coverage`, `.venv`, `venv`, `vendor`
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
> Having the `openai` package in `requirements.txt` does **not** flag the Assistants API deprecation. Your code has to actually use `beta.assistants` / `beta.threads` (etc.).
|
|
87
|
+
- Skipped by default: `.md`, `.mdx`, `.txt` (docs/prose, where model names appear as text), plus the tool's own `deprecations.json` and `arol.config.*` / `.arolignore` files.
|
|
88
|
+
- Add a **`.arolignore`** file (gitignore-style globs) at the repo root, and/or pass **`--ignore <glob>`** (repeatable) to skip more paths.
|
|
77
89
|
|
|
78
90
|
### `match: "sdk"`
|
|
79
91
|
|
|
@@ -99,6 +111,7 @@ arol-ai scan [path] [options]
|
|
|
99
111
|
| `--json` | Output machine-readable JSON instead of the report |
|
|
100
112
|
| `--no-color` | Disable colored output (also respects `NO_COLOR`) |
|
|
101
113
|
| `--data <file>` | Use a custom `deprecations.json` instead of the bundled one |
|
|
114
|
+
| `--ignore <glob>` | Skip files matching this glob; repeatable. Combined with `.arolignore`. e.g. `--ignore 'docs/**' --ignore '**/*.gen.ts'` |
|
|
102
115
|
| `--fail-on <severity>` | Exit non-zero if findings meet a level: `high` \| `medium` \| `low` \| `any` \| `none` (default `none`) |
|
|
103
116
|
| `-v, --version` | Print the version |
|
|
104
117
|
| `-h, --help` | Show help |
|
|
@@ -132,17 +145,38 @@ The dataset is either a bare array of entries, or a `{ "deprecations": [ ... ] }
|
|
|
132
145
|
"sunset_date": "2026-08-26", // ISO YYYY-MM-DD, or "" if no fixed date
|
|
133
146
|
"detect": {
|
|
134
147
|
"sdk": ["openai"], // scope hint for "pattern"; the trigger for "sdk"/"version"
|
|
135
|
-
"patterns": [ //
|
|
148
|
+
"patterns": [ // raw regexes: identifiers, endpoints, params
|
|
136
149
|
"beta\\.assistants",
|
|
137
150
|
"beta\\.threads",
|
|
138
151
|
"/v1/assistants"
|
|
139
|
-
]
|
|
152
|
+
],
|
|
153
|
+
"models": [] // model ids matched only inside string literals
|
|
140
154
|
},
|
|
141
155
|
"migration_url": "https://platform.openai.com/docs/assistants/migration",
|
|
142
156
|
"summary": "One or two sentences explaining the change and what to do."
|
|
143
157
|
}
|
|
144
158
|
```
|
|
145
159
|
|
|
160
|
+
A model-retirement entry uses `detect.models` so it only fires on a quoted model id, never on prose:
|
|
161
|
+
|
|
162
|
+
```jsonc
|
|
163
|
+
{
|
|
164
|
+
"id": "openai-gpt4-family-shutdown",
|
|
165
|
+
"vendor": "OpenAI",
|
|
166
|
+
"title": "GPT-4 family models (API shutdown)",
|
|
167
|
+
"severity": "high",
|
|
168
|
+
"match": "pattern",
|
|
169
|
+
"sunset_date": "2026-10-23",
|
|
170
|
+
"detect": {
|
|
171
|
+
"sdk": ["openai"],
|
|
172
|
+
"patterns": [],
|
|
173
|
+
"models": ["gpt-4o", "gpt-4-turbo", "o4-mini", "gpt-4.5-preview"]
|
|
174
|
+
},
|
|
175
|
+
"migration_url": "https://platform.openai.com/docs/deprecations",
|
|
176
|
+
"summary": "Migrate to the GPT-5 family."
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
146
180
|
A `version` entry instead flags on the installed SDK version (no patterns needed):
|
|
147
181
|
|
|
148
182
|
```jsonc
|
|
@@ -172,17 +206,20 @@ A `version` entry instead flags on the installed SDK version (no patterns needed
|
|
|
172
206
|
| `version_range` | string | – | For `match: "version"` only — e.g. `"<3.0.0"`, `">=1.2.0"`, `"=2.1.0"`. If omitted, a `version` entry behaves like `"sdk"`. |
|
|
173
207
|
| `sunset_date` | string | – | ISO `YYYY-MM-DD`. Use `""` for unmaintained/no-fixed-date items; the report shows a relative hint (e.g. *"in 42 days"* / *"passed 12 days ago"*). |
|
|
174
208
|
| `detect.sdk` | string[] | – | Manifest dependency/module names. For `match: "pattern"` this is only a **scope hint and never triggers** a finding; for `sdk`/`version` it is the trigger. |
|
|
175
|
-
| `detect.patterns` | string[] | – | **JSON-escaped**
|
|
209
|
+
| `detect.patterns` | string[] | – | **JSON-escaped** regex strings (so `\d` becomes `\\d`). For code identifiers, endpoints, and params. Matched anywhere in a source file; invalid regexes are skipped safely. |
|
|
210
|
+
| `detect.models` | string[] | – | Model family names matched **only inside string literals** (quote-anchored, with an optional version suffix). Use this for model ids so prose/JSX mentions don't false-positive. Write the raw name (e.g. `gpt-4.5-preview`) — escaping is automatic. |
|
|
176
211
|
| `migration_url` | string | – | Link shown in the report. |
|
|
177
212
|
| `summary` | string | – | One or two sentences of guidance. |
|
|
178
213
|
|
|
179
|
-
> A `pattern` entry
|
|
214
|
+
> A `pattern` entry needs at least one `detect.patterns` **or** `detect.models` entry; an `sdk`/`version` entry needs at least one `detect.sdk`. Entries that can never fire are dropped at load time.
|
|
180
215
|
|
|
181
|
-
### Writing good patterns
|
|
216
|
+
### Writing good patterns & models
|
|
182
217
|
|
|
218
|
+
- **Put model ids in `detect.models`, not `detect.patterns`.** A bare model id as a raw pattern matches prose, JSX, comments, and changelogs. `detect.models` requires a quoted string literal, which is what real usage looks like (`model: "o4-mini"`).
|
|
219
|
+
- For `detect.models`, write the **raw family name** (e.g. `gpt-4.5-preview`, `claude-opus-4-20250514`) — escaping and quote-anchoring are automatic. The optional suffix means `gpt-4o` also catches `"gpt-4o-2024-05-13"`, so pick a family specific enough not to swallow a non-deprecated successor.
|
|
220
|
+
- For `detect.patterns`, match the **deprecated surface itself** — the method/property (`beta\.assistants`), endpoint path (`/v1/threads`), or param (`hapikey\s*=`) — not the import or package name. Importing an SDK isn't usage; calling the removed API is. Keep them specific (`client\.chat` is too broad — it hits unrelated SDKs).
|
|
183
221
|
- Patterns are matched **case-sensitively** with the global flag over each file's contents; the file path, line number, and matched text are reported.
|
|
184
|
-
-
|
|
185
|
-
- Escape backslashes (and literal dots) for JSON: a regex `beta\.assistants` is written `"beta\\.assistants"`.
|
|
222
|
+
- Escape backslashes (and literal dots) for JSON: a regex `beta\.assistants` is written `"beta\\.assistants"`. (Model entries don't need this — write `gpt-4.5-preview` as-is.)
|
|
186
223
|
- Avoid `^`/`$` line anchors — matching runs against the whole file, not line-by-line; use `\b` word boundaries instead.
|
|
187
224
|
|
|
188
225
|
## Development
|
package/dist/cli.js
CHANGED
|
@@ -62,6 +62,10 @@ function shouldUseColor(colorFlag) {
|
|
|
62
62
|
return Boolean(process.stdout.isTTY);
|
|
63
63
|
}
|
|
64
64
|
const SEVERITY_RANK = { high: 3, medium: 2, low: 1 };
|
|
65
|
+
/** Commander collector so --ignore can be passed multiple times. */
|
|
66
|
+
function collectIgnore(value, previous) {
|
|
67
|
+
return previous.concat([value]);
|
|
68
|
+
}
|
|
65
69
|
function runScan(targetPath, opts) {
|
|
66
70
|
const root = path.resolve(targetPath ?? ".");
|
|
67
71
|
// Validate the target directory up front for a friendly error.
|
|
@@ -88,7 +92,10 @@ function runScan(targetPath, opts) {
|
|
|
88
92
|
process.exitCode = 2;
|
|
89
93
|
return;
|
|
90
94
|
}
|
|
91
|
-
const result = (0, scanner_1.scanRepo)(root, deprecations
|
|
95
|
+
const result = (0, scanner_1.scanRepo)(root, deprecations, {
|
|
96
|
+
ignore: opts.ignore,
|
|
97
|
+
dataPath: opts.data,
|
|
98
|
+
});
|
|
92
99
|
if (opts.json) {
|
|
93
100
|
const counts = { high: 0, medium: 0, low: 0 };
|
|
94
101
|
for (const f of result.findings)
|
|
@@ -142,6 +149,7 @@ function main(argv) {
|
|
|
142
149
|
.option("--json", "output machine-readable JSON instead of the report")
|
|
143
150
|
.option("--no-color", "disable colored output")
|
|
144
151
|
.option("--data <file>", "use a custom deprecations.json dataset instead of the bundled one")
|
|
152
|
+
.option("--ignore <glob>", "skip files matching this glob (repeatable); also reads .arolignore", collectIgnore, [])
|
|
145
153
|
.option("--fail-on <severity>", "exit non-zero if findings meet this level: high | medium | low | any | none", "none")
|
|
146
154
|
.action((pathArg, options) => {
|
|
147
155
|
runScan(pathArg, options);
|
package/dist/data.js
CHANGED
|
@@ -81,6 +81,7 @@ function coerceDeprecation(raw) {
|
|
|
81
81
|
const detect = r.detect;
|
|
82
82
|
const sdk = detect && isStringArray(detect.sdk) ? detect.sdk : [];
|
|
83
83
|
const patterns = detect && isStringArray(detect.patterns) ? detect.patterns : [];
|
|
84
|
+
const models = detect && isStringArray(detect.models) ? detect.models : [];
|
|
84
85
|
// Default to "pattern" — detection keys on real usage, not SDK presence.
|
|
85
86
|
const match = MATCH_MODES.includes(r.match)
|
|
86
87
|
? r.match
|
|
@@ -89,7 +90,8 @@ function coerceDeprecation(raw) {
|
|
|
89
90
|
? r.version_range
|
|
90
91
|
: undefined;
|
|
91
92
|
// Drop entries that can never fire under their match mode.
|
|
92
|
-
|
|
93
|
+
// A "pattern" entry fires on either a raw pattern OR a model-string match.
|
|
94
|
+
if (match === "pattern" && patterns.length === 0 && models.length === 0)
|
|
93
95
|
return null;
|
|
94
96
|
if ((match === "sdk" || match === "version") && sdk.length === 0)
|
|
95
97
|
return null;
|
|
@@ -100,7 +102,7 @@ function coerceDeprecation(raw) {
|
|
|
100
102
|
severity: r.severity,
|
|
101
103
|
match,
|
|
102
104
|
sunset_date: typeof r.sunset_date === "string" ? r.sunset_date : "",
|
|
103
|
-
detect: { sdk, patterns },
|
|
105
|
+
detect: { sdk, patterns, models },
|
|
104
106
|
version_range,
|
|
105
107
|
migration_url: typeof r.migration_url === "string" ? r.migration_url : "",
|
|
106
108
|
summary: typeof r.summary === "string" ? r.summary : "",
|
package/dist/report.js
CHANGED
|
@@ -114,7 +114,7 @@ function renderReport(result, opts) {
|
|
|
114
114
|
if (findings.length === 0) {
|
|
115
115
|
out.push(s.green(s.bold("✓ No upcoming deprecations detected in your stack.")));
|
|
116
116
|
out.push("");
|
|
117
|
-
out.push(footer(s));
|
|
117
|
+
out.push(footer(s, findings));
|
|
118
118
|
return out.join("\n");
|
|
119
119
|
}
|
|
120
120
|
// Severity summary.
|
|
@@ -156,15 +156,29 @@ function renderReport(result, opts) {
|
|
|
156
156
|
}
|
|
157
157
|
out.push("");
|
|
158
158
|
}
|
|
159
|
-
out.push(footer(s));
|
|
159
|
+
out.push(footer(s, findings));
|
|
160
160
|
return out.join("\n");
|
|
161
161
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
/** Severity-aware closing CTA, visually separated from the findings above. */
|
|
163
|
+
function footer(s, findings) {
|
|
164
|
+
const sep = s.dim("─".repeat(60));
|
|
165
|
+
const brand = "arol.ai";
|
|
166
|
+
let message;
|
|
167
|
+
if (findings.some((f) => f.deprecation.severity === "high")) {
|
|
168
|
+
// Prominent: high-severity items break on fixed dates.
|
|
169
|
+
message = s.bold(s.red(`⚠ These break on fixed dates. Get alerted before the next one hits you → ${brand}`));
|
|
170
|
+
}
|
|
171
|
+
else if (findings.length > 0) {
|
|
172
|
+
message =
|
|
173
|
+
s.dim("Get continuous deprecation alerts for your stack → ") +
|
|
174
|
+
s.cyan(s.bold(brand));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
message =
|
|
178
|
+
s.green("✓ Clean today — but new deprecations land constantly. Stay covered → ") +
|
|
179
|
+
s.cyan(s.bold(brand));
|
|
180
|
+
}
|
|
181
|
+
return [sep, message].join("\n");
|
|
168
182
|
}
|
|
169
183
|
/** Soft-wrap text to a width, indenting continuation lines. */
|
|
170
184
|
function wrapText(text, width, indent) {
|
package/dist/scanner.js
CHANGED
|
@@ -67,13 +67,42 @@ const IGNORED_DIRS = [
|
|
|
67
67
|
"venv",
|
|
68
68
|
"vendor",
|
|
69
69
|
];
|
|
70
|
+
/**
|
|
71
|
+
* Files always skipped by default: documentation/prose (where model names show
|
|
72
|
+
* up as text, not code) and the tool's own dataset / config files.
|
|
73
|
+
*/
|
|
74
|
+
const DEFAULT_FILE_IGNORES = [
|
|
75
|
+
"**/*.md",
|
|
76
|
+
"**/*.mdx",
|
|
77
|
+
"**/*.txt",
|
|
78
|
+
"**/deprecations.json",
|
|
79
|
+
"**/.arolignore",
|
|
80
|
+
"**/arol.config.*",
|
|
81
|
+
];
|
|
70
82
|
/** Skip files larger than this (bytes) to keep the scan fast. */
|
|
71
83
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
72
84
|
/** Cap matches recorded per pattern per file to avoid pathological output. */
|
|
73
85
|
const MAX_MATCHES_PER_PATTERN_PER_FILE = 50;
|
|
86
|
+
/** Any of the three string-literal quote characters. */
|
|
87
|
+
const QUOTE_CLASS = "['\"`]";
|
|
88
|
+
/** Escape regex metacharacters in a literal model family name. */
|
|
89
|
+
function escapeRegex(s) {
|
|
90
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build a regex that matches a model family ONLY inside a string literal:
|
|
94
|
+
* an opening quote, the (escaped) family name, an optional version/suffix, then
|
|
95
|
+
* the SAME closing quote. So a quoted model id (single, double, or backtick)
|
|
96
|
+
* and its versioned snapshots match, but never a bare occurrence in
|
|
97
|
+
* prose/JSX/markdown.
|
|
98
|
+
*/
|
|
99
|
+
function modelRegexSource(family) {
|
|
100
|
+
return `(${QUOTE_CLASS})${escapeRegex(family)}[A-Za-z0-9._-]*\\1`;
|
|
101
|
+
}
|
|
74
102
|
function compileDeprecations(deprecations) {
|
|
75
103
|
return deprecations.map((deprecation) => {
|
|
76
104
|
const regexes = [];
|
|
105
|
+
// Raw patterns — code identifiers, endpoints, params.
|
|
77
106
|
for (const pattern of deprecation.detect.patterns) {
|
|
78
107
|
try {
|
|
79
108
|
// Global so we can iterate every match and derive line numbers.
|
|
@@ -83,6 +112,15 @@ function compileDeprecations(deprecations) {
|
|
|
83
112
|
// A malformed pattern in the dataset must not crash the scan.
|
|
84
113
|
}
|
|
85
114
|
}
|
|
115
|
+
// Model names — only matched inside string literals (quote-anchored).
|
|
116
|
+
for (const family of deprecation.detect.models) {
|
|
117
|
+
try {
|
|
118
|
+
regexes.push(new RegExp(modelRegexSource(family), "g"));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Defensive: a pathological family name must not crash the scan.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
86
124
|
return { deprecation, regexes };
|
|
87
125
|
});
|
|
88
126
|
}
|
|
@@ -133,8 +171,8 @@ function scanContent(content, relPath, compiled, sink) {
|
|
|
133
171
|
if (seenLines.has(line))
|
|
134
172
|
continue; // one record per line per pattern
|
|
135
173
|
seenLines.add(line);
|
|
136
|
-
// Cite the matched substring itself
|
|
137
|
-
//
|
|
174
|
+
// Cite the matched substring itself, normalized and length-capped, so
|
|
175
|
+
// the report points at exactly what triggered the finding.
|
|
138
176
|
const text = (m[0] ?? "").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
139
177
|
if (!recorded) {
|
|
140
178
|
recorded = [];
|
|
@@ -222,13 +260,61 @@ function versionInRange(declared, range) {
|
|
|
222
260
|
return cmp === 0;
|
|
223
261
|
}
|
|
224
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Convert one .arolignore line (gitignore-style) into fast-glob ignore globs.
|
|
265
|
+
* Supports comments (#), blank lines, leading "/" anchoring, and trailing "/"
|
|
266
|
+
* for directories. Negations ("!") are not supported and are skipped.
|
|
267
|
+
*/
|
|
268
|
+
function arolignoreLineToGlobs(rawLine) {
|
|
269
|
+
let line = rawLine.trim();
|
|
270
|
+
if (!line || line.startsWith("#") || line.startsWith("!"))
|
|
271
|
+
return [];
|
|
272
|
+
const anchored = line.startsWith("/");
|
|
273
|
+
if (anchored)
|
|
274
|
+
line = line.slice(1);
|
|
275
|
+
const isDir = line.endsWith("/");
|
|
276
|
+
if (isDir)
|
|
277
|
+
line = line.replace(/\/+$/, "");
|
|
278
|
+
if (!line)
|
|
279
|
+
return [];
|
|
280
|
+
const base = anchored ? line : `**/${line}`;
|
|
281
|
+
// A directory ignore covers its contents; a file/glob ignore covers both the
|
|
282
|
+
// entry itself and (harmlessly) anything beneath it if it is a directory.
|
|
283
|
+
return isDir ? [`${base}/**`] : [base, `${base}/**`];
|
|
284
|
+
}
|
|
285
|
+
/** Read and parse a repo's .arolignore file into ignore globs (empty if none). */
|
|
286
|
+
function loadArolignore(root) {
|
|
287
|
+
let content;
|
|
288
|
+
try {
|
|
289
|
+
content = fs.readFileSync(path.join(root, ".arolignore"), "utf8");
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
return content.split(/\r?\n/).flatMap(arolignoreLineToGlobs);
|
|
295
|
+
}
|
|
225
296
|
/**
|
|
226
297
|
* Scan a repository for deprecation usage.
|
|
227
298
|
* @param root repo root to scan.
|
|
228
299
|
* @param deprecations validated dataset entries.
|
|
300
|
+
* @param options optional ignore globs (--ignore) and custom dataset path.
|
|
229
301
|
*/
|
|
230
|
-
function scanRepo(root, deprecations) {
|
|
302
|
+
function scanRepo(root, deprecations, options = {}) {
|
|
231
303
|
const absRoot = path.resolve(root);
|
|
304
|
+
// Assemble the ignore list: dirs + default file skips + .arolignore + --ignore.
|
|
305
|
+
const ignoreGlobs = [
|
|
306
|
+
...IGNORED_DIRS.map((d) => `**/${d}/**`),
|
|
307
|
+
...DEFAULT_FILE_IGNORES,
|
|
308
|
+
...loadArolignore(absRoot),
|
|
309
|
+
...(options.ignore ?? []),
|
|
310
|
+
];
|
|
311
|
+
// Never scan the active custom dataset file, even if it lives in the tree.
|
|
312
|
+
if (options.dataPath) {
|
|
313
|
+
const relData = path.relative(absRoot, path.resolve(options.dataPath));
|
|
314
|
+
if (relData && !relData.startsWith("..") && !path.isAbsolute(relData)) {
|
|
315
|
+
ignoreGlobs.push(relData);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
232
318
|
// Partition by match mode: "pattern" entries key on real source usage, while
|
|
233
319
|
// "sdk"/"version" entries key on the manifest.
|
|
234
320
|
const patternDeps = deprecations.filter((d) => d.match === "pattern");
|
|
@@ -246,7 +332,7 @@ function scanRepo(root, deprecations) {
|
|
|
246
332
|
dot: false,
|
|
247
333
|
followSymbolicLinks: false,
|
|
248
334
|
suppressErrors: true,
|
|
249
|
-
ignore:
|
|
335
|
+
ignore: ignoreGlobs,
|
|
250
336
|
});
|
|
251
337
|
let scannedFiles = 0;
|
|
252
338
|
for (const rel of files) {
|
package/package.json
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"sunset_date": "2026-06-15",
|
|
29
29
|
"detect": {
|
|
30
30
|
"sdk": ["@anthropic-ai/sdk", "anthropic"],
|
|
31
|
-
"patterns": [
|
|
31
|
+
"patterns": [],
|
|
32
|
+
"models": [
|
|
32
33
|
"claude-sonnet-4-20250514",
|
|
33
34
|
"claude-opus-4-20250514",
|
|
34
35
|
"claude-sonnet-4-0",
|
|
@@ -48,12 +49,13 @@
|
|
|
48
49
|
"sunset_date": "2026-04-20",
|
|
49
50
|
"detect": {
|
|
50
51
|
"sdk": ["@anthropic-ai/sdk", "anthropic"],
|
|
51
|
-
"patterns": [
|
|
52
|
+
"patterns": [],
|
|
53
|
+
"models": [
|
|
52
54
|
"claude-3-haiku-20240307",
|
|
53
55
|
"claude-3-5-sonnet",
|
|
54
56
|
"claude-3-7-sonnet",
|
|
55
|
-
"claude-2
|
|
56
|
-
"claude-2
|
|
57
|
+
"claude-2.1",
|
|
58
|
+
"claude-2.0",
|
|
57
59
|
"claude-instant"
|
|
58
60
|
]
|
|
59
61
|
},
|
|
@@ -70,11 +72,8 @@
|
|
|
70
72
|
"sunset_date": "2026-10-16",
|
|
71
73
|
"detect": {
|
|
72
74
|
"sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
|
|
73
|
-
"patterns": [
|
|
74
|
-
|
|
75
|
-
"gemini-2\\.5-flash",
|
|
76
|
-
"gemini-2\\.5-flash-lite"
|
|
77
|
-
]
|
|
75
|
+
"patterns": [],
|
|
76
|
+
"models": ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"]
|
|
78
77
|
},
|
|
79
78
|
"migration_url": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
80
79
|
"summary": "Gemini 2.5 models scheduled for shutdown Oct 16, 2026 (Google states this is the earliest possible date). Migrate to Gemini 3.x.",
|
|
@@ -89,11 +88,12 @@
|
|
|
89
88
|
"sunset_date": "2026-06-01",
|
|
90
89
|
"detect": {
|
|
91
90
|
"sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
|
|
92
|
-
"patterns": [
|
|
93
|
-
|
|
94
|
-
"gemini-2
|
|
95
|
-
"gemini-2
|
|
96
|
-
"gemini-2
|
|
91
|
+
"patterns": [],
|
|
92
|
+
"models": [
|
|
93
|
+
"gemini-2.0-flash",
|
|
94
|
+
"gemini-2.0-flash-001",
|
|
95
|
+
"gemini-2.0-flash-lite",
|
|
96
|
+
"gemini-2.0-flash-lite-001"
|
|
97
97
|
]
|
|
98
98
|
},
|
|
99
99
|
"migration_url": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
@@ -109,15 +109,162 @@
|
|
|
109
109
|
"sunset_date": "2026-04-29",
|
|
110
110
|
"detect": {
|
|
111
111
|
"sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
|
|
112
|
-
"patterns": [
|
|
113
|
-
|
|
114
|
-
"gemini-1\\.5-flash",
|
|
115
|
-
"gemini-1\\.0-pro",
|
|
116
|
-
"gemini-pro"
|
|
117
|
-
]
|
|
112
|
+
"patterns": [],
|
|
113
|
+
"models": ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.0-pro", "gemini-pro"]
|
|
118
114
|
},
|
|
119
115
|
"migration_url": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
120
116
|
"summary": "All Gemini 1.0 and 1.5 models are shut down and return 404. Migrate to Gemini 2.5+ or 3.x.",
|
|
121
117
|
"source": "https://firebase.google.com/docs/ai-logic/models"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "openai-gpt4-family-shutdown",
|
|
121
|
+
"vendor": "OpenAI",
|
|
122
|
+
"title": "GPT-4 family models (API shutdown)",
|
|
123
|
+
"severity": "high",
|
|
124
|
+
"match": "pattern",
|
|
125
|
+
"sunset_date": "2026-10-23",
|
|
126
|
+
"date_confidence": "verify",
|
|
127
|
+
"detect": {
|
|
128
|
+
"sdk": ["openai"],
|
|
129
|
+
"patterns": [],
|
|
130
|
+
"models": [
|
|
131
|
+
"gpt-4o",
|
|
132
|
+
"gpt-4-turbo",
|
|
133
|
+
"gpt-4-0613",
|
|
134
|
+
"gpt-4-0125-preview",
|
|
135
|
+
"gpt-4-1106-preview",
|
|
136
|
+
"gpt-4-32k",
|
|
137
|
+
"gpt-4-vision-preview",
|
|
138
|
+
"o4-mini",
|
|
139
|
+
"gpt-4.5-preview"
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
"migration_url": "https://platform.openai.com/docs/deprecations",
|
|
143
|
+
"summary": "The GPT-4 family (gpt-4o snapshots, gpt-4-turbo, gpt-4-0613, o4-mini, etc.) is reported on a single API shutdown date of Oct 23, 2026; calls will then fail. Migrate to the GPT-5 family. VERIFY exact date and model membership on the official deprecations page before shipping.",
|
|
144
|
+
"source": "https://platform.openai.com/docs/deprecations"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "openai-legacy-retired-models",
|
|
148
|
+
"vendor": "OpenAI",
|
|
149
|
+
"title": "Retired legacy OpenAI models (GPT-3 era, early snapshots)",
|
|
150
|
+
"severity": "high",
|
|
151
|
+
"match": "pattern",
|
|
152
|
+
"sunset_date": "2024-01-04",
|
|
153
|
+
"detect": {
|
|
154
|
+
"sdk": ["openai"],
|
|
155
|
+
"patterns": [],
|
|
156
|
+
"models": [
|
|
157
|
+
"text-davinci-003",
|
|
158
|
+
"text-davinci-002",
|
|
159
|
+
"gpt-3.5-turbo-0301",
|
|
160
|
+
"gpt-3.5-turbo-0613",
|
|
161
|
+
"gpt-4-0314",
|
|
162
|
+
"code-davinci",
|
|
163
|
+
"text-curie",
|
|
164
|
+
"text-babbage"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"migration_url": "https://platform.openai.com/docs/deprecations",
|
|
168
|
+
"summary": "These legacy models (GPT-3-era completions and early dated GPT-4/3.5 snapshots) are already retired and return errors. Migrate to current models.",
|
|
169
|
+
"source": "https://platform.openai.com/docs/deprecations"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "stripe-removed-js-methods",
|
|
173
|
+
"vendor": "Stripe",
|
|
174
|
+
"title": "Removed legacy Stripe.js methods",
|
|
175
|
+
"severity": "high",
|
|
176
|
+
"match": "pattern",
|
|
177
|
+
"sunset_date": "2026-03-25",
|
|
178
|
+
"detect": {
|
|
179
|
+
"sdk": ["@stripe/stripe-js", "stripe"],
|
|
180
|
+
"patterns": [
|
|
181
|
+
"handleCardPayment",
|
|
182
|
+
"confirmPaymentIntent",
|
|
183
|
+
"handleFpxPayment",
|
|
184
|
+
"handleCardSetup",
|
|
185
|
+
"confirmSetupIntent",
|
|
186
|
+
"createSource",
|
|
187
|
+
"retrieveSource"
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"migration_url": "https://docs.stripe.com/changelog/dahlia/2026-03-25/remove-legacy-stripejs-methods",
|
|
191
|
+
"summary": "These legacy Stripe.js methods were removed and now throw errors. Replace handleCardPayment/confirmPaymentIntent with confirmCardPayment, handleCardSetup/confirmSetupIntent with confirmCardSetup, and migrate createSource/retrieveSource to the PaymentMethods API.",
|
|
192
|
+
"source": "https://docs.stripe.com/changelog/dahlia/2026-03-25/remove-legacy-stripejs-methods"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "stripe-sources-charges-legacy",
|
|
196
|
+
"vendor": "Stripe",
|
|
197
|
+
"title": "Sources API / legacy Charges API",
|
|
198
|
+
"severity": "medium",
|
|
199
|
+
"match": "pattern",
|
|
200
|
+
"sunset_date": "2024-05-15",
|
|
201
|
+
"detect": {
|
|
202
|
+
"sdk": ["stripe"],
|
|
203
|
+
"patterns": ["\\.sources\\.create", "charges\\.create", "Charge\\.create"]
|
|
204
|
+
},
|
|
205
|
+
"migration_url": "https://docs.stripe.com/payments/older-apis",
|
|
206
|
+
"summary": "The Sources API is deprecated (local payment methods stopped being accepted May 15, 2024) and the Charges API is legacy. Migrate to the PaymentMethods + PaymentIntents APIs.",
|
|
207
|
+
"source": "https://docs.stripe.com/payments/older-apis"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"id": "twilio-notify-eol",
|
|
211
|
+
"vendor": "Twilio",
|
|
212
|
+
"title": "Notify API",
|
|
213
|
+
"severity": "high",
|
|
214
|
+
"match": "pattern",
|
|
215
|
+
"sunset_date": "2027-01-31",
|
|
216
|
+
"date_confidence": "verify",
|
|
217
|
+
"detect": {
|
|
218
|
+
"sdk": ["twilio"],
|
|
219
|
+
"patterns": ["notify\\.v1", "\\.notify\\.services", "client\\.notify"]
|
|
220
|
+
},
|
|
221
|
+
"migration_url": "https://www.twilio.com/en-us/changelog",
|
|
222
|
+
"summary": "Twilio Notify reaches end of life Jan 31, 2027; after that all Notify API requests will fail. No 1:1 replacement — rebuild with Programmable Messaging / Conversations. Verify the date against Twilio's official EOL notice.",
|
|
223
|
+
"source": "https://www.courier.com/blog/twilio-notify-end-of-life"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"id": "twilio-programmable-chat-retired",
|
|
227
|
+
"vendor": "Twilio",
|
|
228
|
+
"title": "Programmable Chat API",
|
|
229
|
+
"severity": "high",
|
|
230
|
+
"match": "pattern",
|
|
231
|
+
"sunset_date": "2022-07-25",
|
|
232
|
+
"detect": {
|
|
233
|
+
"sdk": ["twilio", "twilio-chat"],
|
|
234
|
+
"patterns": ["chat\\.v2", "IpMessaging", "twilio-chat"]
|
|
235
|
+
},
|
|
236
|
+
"migration_url": "https://www.twilio.com/docs/conversations/migrating-chat-conversations",
|
|
237
|
+
"summary": "The standalone Programmable Chat API was sunset July 25, 2022 (Programmable Chat in Flex ended June 1, 2026). Migrate to the Conversations API.",
|
|
238
|
+
"source": "https://www.twilio.com/en-us/changelog/programmable-chat-end-of-life-notice"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"id": "aws-sdk-js-v2-eol",
|
|
242
|
+
"vendor": "AWS",
|
|
243
|
+
"title": "AWS SDK for JavaScript v2",
|
|
244
|
+
"severity": "medium",
|
|
245
|
+
"match": "sdk",
|
|
246
|
+
"sunset_date": "2025-09-08",
|
|
247
|
+
"detect": {
|
|
248
|
+
"sdk": ["aws-sdk"],
|
|
249
|
+
"patterns": []
|
|
250
|
+
},
|
|
251
|
+
"migration_url": "https://aws.amazon.com/blogs/developer/announcing-end-of-support-for-aws-sdk-for-javascript-v2/",
|
|
252
|
+
"summary": "AWS SDK for JavaScript v2 (the 'aws-sdk' package) reached end-of-support Sept 8, 2025 — no more updates or security fixes. Migrate to the modular AWS SDK v3 (@aws-sdk/* packages).",
|
|
253
|
+
"source": "https://aws.amazon.com/blogs/developer/announcing-end-of-support-for-aws-sdk-for-javascript-v2/"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"id": "hubspot-api-key-hapikey",
|
|
257
|
+
"vendor": "HubSpot",
|
|
258
|
+
"title": "Legacy API keys (hapikey)",
|
|
259
|
+
"severity": "high",
|
|
260
|
+
"match": "pattern",
|
|
261
|
+
"sunset_date": "2022-11-30",
|
|
262
|
+
"detect": {
|
|
263
|
+
"sdk": ["@hubspot/api-client"],
|
|
264
|
+
"patterns": ["hapikey\\s*=", "hapiKey\\s*="]
|
|
265
|
+
},
|
|
266
|
+
"migration_url": "https://developers.hubspot.com/changelog/upcoming-api-key-sunset",
|
|
267
|
+
"summary": "HubSpot deprecated legacy API keys (the hapikey query param) on Nov 30, 2022; requests using hapikey now fail. Migrate to Private App access tokens (Bearer auth). The eCommerce Bridge and Accounting Extension APIs were also sunset Dec 1, 2022.",
|
|
268
|
+
"source": "https://developers.hubspot.com/changelog/upcoming-api-key-sunset"
|
|
122
269
|
}
|
|
123
270
|
]
|