arol-ai 0.1.0 → 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 +113 -51
- package/dist/cli.js +10 -1
- package/dist/data.js +17 -3
- package/dist/report.js +39 -23
- package/dist/scanner.js +170 -13
- package/package.json +1 -1
- package/src/data/deprecations.json +173 -20
package/README.md
CHANGED
|
@@ -35,25 +35,28 @@ arol-ai scan
|
|
|
35
35
|
|
|
36
36
|
```
|
|
37
37
|
arol · local deprecation scan
|
|
38
|
-
Scanned 128 files ·
|
|
38
|
+
Scanned 128 files · 1 API detected
|
|
39
39
|
|
|
40
|
-
⚠
|
|
40
|
+
⚠ 1 deprecation found (1 high, 0 medium, 0 low)
|
|
41
41
|
|
|
42
|
-
●
|
|
43
|
-
sunsets
|
|
44
|
-
The
|
|
45
|
-
|
|
42
|
+
● OpenAI · Assistants API (beta) HIGH
|
|
43
|
+
sunsets 2026-08-26 (in 84 days)
|
|
44
|
+
The Assistants API beta is being removed on Aug 26, 2026; requests to
|
|
45
|
+
/v1/assistants and /v1/threads will fail. Migrate to the Responses API +
|
|
46
|
+
Conversations API.
|
|
46
47
|
found in:
|
|
47
|
-
|
|
48
|
-
src/
|
|
49
|
-
→ migrate: https://
|
|
48
|
+
src/agents/run.ts:42 → beta.assistants
|
|
49
|
+
src/agents/run.ts:88 → beta.threads
|
|
50
|
+
→ migrate: https://platform.openai.com/docs/assistants/migration
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
These are today's deprecations. New ones land constantly — get
|
|
54
|
-
alerted before the next one breaks you → arol.ai
|
|
52
|
+
────────────────────────────────────────────────────────────
|
|
53
|
+
⚠ These break on fixed dates. Get alerted before the next one hits you → arol.ai
|
|
55
54
|
```
|
|
56
55
|
|
|
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
|
+
|
|
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
|
+
|
|
57
60
|
When nothing is found:
|
|
58
61
|
|
|
59
62
|
```
|
|
@@ -62,20 +65,39 @@ When nothing is found:
|
|
|
62
65
|
|
|
63
66
|
## How detection works
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
Detection keys on **actual usage, not mere SDK presence.** Each dataset entry declares a `match` mode that decides what triggers it.
|
|
69
|
+
|
|
70
|
+
### `match: "pattern"` — the default
|
|
71
|
+
|
|
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**
|
|
84
|
+
|
|
85
|
+
- Extensions scanned: `.js .mjs .cjs .jsx .ts .mts .cts .tsx .py .go`
|
|
86
|
+
- Skipped directories: `node_modules`, `.git`, `dist`, `build`, `.next`, `out`, `coverage`, `.venv`, `venv`, `vendor`
|
|
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.
|
|
89
|
+
|
|
90
|
+
### `match: "sdk"`
|
|
91
|
+
|
|
92
|
+
Flags when a `detect.sdk` package appears in a manifest, **regardless of code** — for the rare "this whole SDK / version line is end-of-life" case.
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
- `package.json` (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`, plus simple npm `workspaces`)
|
|
69
|
-
- `requirements.txt` (Python)
|
|
70
|
-
- `go.mod` (Go)
|
|
94
|
+
### `match: "version"`
|
|
71
95
|
|
|
72
|
-
|
|
96
|
+
Flags when a `detect.sdk` package appears in a manifest **and** its declared version satisfies the entry's `version_range` (e.g. `"<3.0.0"`). Semver-style compare for npm; best-effort numeric compare for pip/go. If `version_range` is omitted, it behaves like `"sdk"`. *(No version entries ship today.)*
|
|
73
97
|
|
|
74
|
-
|
|
75
|
-
- Extensions scanned: `.js .mjs .cjs .jsx .ts .mts .cts .tsx .py .go`
|
|
76
|
-
- Skipped directories: `node_modules`, `.git`, `dist`, `build`, `.next`, `out`, `coverage`, `.venv`, `venv`, `vendor`
|
|
98
|
+
**Manifests parsed** (used by `sdk`/`version` modes): `package.json` (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`, plus simple npm `workspaces`), `requirements.txt` (Python), and `go.mod` (Go). A dependency matches a `detect.sdk` name case-insensitively, with PyPI-style `_ . -` normalization.
|
|
77
99
|
|
|
78
|
-
|
|
100
|
+
The report cites **source code locations** for `pattern` findings, and the **manifest line** (`package.json → name@version`) for `sdk`/`version` findings.
|
|
79
101
|
|
|
80
102
|
## CLI
|
|
81
103
|
|
|
@@ -89,6 +111,7 @@ arol-ai scan [path] [options]
|
|
|
89
111
|
| `--json` | Output machine-readable JSON instead of the report |
|
|
90
112
|
| `--no-color` | Disable colored output (also respects `NO_COLOR`) |
|
|
91
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'` |
|
|
92
115
|
| `--fail-on <severity>` | Exit non-zero if findings meet a level: `high` \| `medium` \| `low` \| `any` \| `none` (default `none`) |
|
|
93
116
|
| `-v, --version` | Print the version |
|
|
94
117
|
| `-h, --help` | Show help |
|
|
@@ -110,32 +133,66 @@ All detections are **data-driven** — the bundled dataset lives at
|
|
|
110
133
|
|
|
111
134
|
### Schema
|
|
112
135
|
|
|
136
|
+
The dataset is either a bare array of entries, or a `{ "deprecations": [ ... ] }` object. A typical `pattern` entry:
|
|
137
|
+
|
|
138
|
+
```jsonc
|
|
139
|
+
{
|
|
140
|
+
"id": "openai-assistants-api", // unique, stable identifier (required)
|
|
141
|
+
"vendor": "OpenAI", // who owns the API (required)
|
|
142
|
+
"title": "Assistants API (beta)", // short headline (required)
|
|
143
|
+
"severity": "high", // "high" | "medium" | "low" (required)
|
|
144
|
+
"match": "pattern", // "pattern" (default) | "sdk" | "version"
|
|
145
|
+
"sunset_date": "2026-08-26", // ISO YYYY-MM-DD, or "" if no fixed date
|
|
146
|
+
"detect": {
|
|
147
|
+
"sdk": ["openai"], // scope hint for "pattern"; the trigger for "sdk"/"version"
|
|
148
|
+
"patterns": [ // raw regexes: identifiers, endpoints, params
|
|
149
|
+
"beta\\.assistants",
|
|
150
|
+
"beta\\.threads",
|
|
151
|
+
"/v1/assistants"
|
|
152
|
+
],
|
|
153
|
+
"models": [] // model ids matched only inside string literals
|
|
154
|
+
},
|
|
155
|
+
"migration_url": "https://platform.openai.com/docs/assistants/migration",
|
|
156
|
+
"summary": "One or two sentences explaining the change and what to do."
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
A model-retirement entry uses `detect.models` so it only fires on a quoted model id, never on prose:
|
|
161
|
+
|
|
113
162
|
```jsonc
|
|
114
163
|
{
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"from\\s+['\"]aws-sdk['\"]"
|
|
129
|
-
]
|
|
130
|
-
},
|
|
131
|
-
"migration_url": "https://docs.aws.amazon.com/.../migrating-to-v3.html",
|
|
132
|
-
"summary": "One or two sentences explaining the change and what to do."
|
|
133
|
-
}
|
|
134
|
-
]
|
|
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."
|
|
135
177
|
}
|
|
136
178
|
```
|
|
137
179
|
|
|
138
|
-
A
|
|
180
|
+
A `version` entry instead flags on the installed SDK version (no patterns needed):
|
|
181
|
+
|
|
182
|
+
```jsonc
|
|
183
|
+
{
|
|
184
|
+
"id": "example-sdk-v2-eol",
|
|
185
|
+
"vendor": "Example",
|
|
186
|
+
"title": "example-sdk v2 line end-of-life",
|
|
187
|
+
"severity": "medium",
|
|
188
|
+
"match": "version",
|
|
189
|
+
"version_range": "<3.0.0", // flags only when the declared version is in range
|
|
190
|
+
"sunset_date": "",
|
|
191
|
+
"detect": { "sdk": ["example-sdk"], "patterns": [] },
|
|
192
|
+
"migration_url": "https://example.com/migrate",
|
|
193
|
+
"summary": "Upgrade example-sdk to v3+."
|
|
194
|
+
}
|
|
195
|
+
```
|
|
139
196
|
|
|
140
197
|
### Field reference
|
|
141
198
|
|
|
@@ -145,19 +202,24 @@ A bare top-level array (`[ { ...entry }, ... ]`) is also accepted.
|
|
|
145
202
|
| `vendor` | string | ✓ | Displayed before the title. |
|
|
146
203
|
| `title` | string | ✓ | Short headline for the finding. |
|
|
147
204
|
| `severity` | `"high"` \| `"medium"` \| `"low"` | ✓ | Drives color, sort order, and `--fail-on`. |
|
|
205
|
+
| `match` | `"pattern"` \| `"sdk"` \| `"version"` | – | How the entry is triggered. **Defaults to `"pattern"`** when omitted. See [How detection works](#how-detection-works). |
|
|
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"`. |
|
|
148
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"*). |
|
|
149
|
-
| `detect.sdk` | string[] | – | Manifest dependency/module names.
|
|
150
|
-
| `detect.patterns` | string[] | – | **JSON-escaped**
|
|
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. |
|
|
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. |
|
|
151
211
|
| `migration_url` | string | – | Link shown in the report. |
|
|
152
212
|
| `summary` | string | – | One or two sentences of guidance. |
|
|
153
213
|
|
|
154
|
-
>
|
|
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.
|
|
155
215
|
|
|
156
|
-
### Writing good patterns
|
|
216
|
+
### Writing good patterns & models
|
|
157
217
|
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
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).
|
|
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.
|
|
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.)
|
|
161
223
|
- Avoid `^`/`$` line anchors — matching runs against the whole file, not line-by-line; use `\b` word boundaries instead.
|
|
162
224
|
|
|
163
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)
|
|
@@ -103,6 +110,7 @@ function runScan(targetPath, opts) {
|
|
|
103
110
|
vendor: f.deprecation.vendor,
|
|
104
111
|
title: f.deprecation.title,
|
|
105
112
|
severity: f.deprecation.severity,
|
|
113
|
+
match: f.deprecation.match,
|
|
106
114
|
sunset_date: f.deprecation.sunset_date,
|
|
107
115
|
migration_url: f.deprecation.migration_url,
|
|
108
116
|
summary: f.deprecation.summary,
|
|
@@ -141,6 +149,7 @@ function main(argv) {
|
|
|
141
149
|
.option("--json", "output machine-readable JSON instead of the report")
|
|
142
150
|
.option("--no-color", "disable colored output")
|
|
143
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, [])
|
|
144
153
|
.option("--fail-on <severity>", "exit non-zero if findings meet this level: high | medium | low | any | none", "none")
|
|
145
154
|
.action((pathArg, options) => {
|
|
146
155
|
runScan(pathArg, options);
|
package/dist/data.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.loadDeprecations = loadDeprecations;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const SEVERITIES = ["high", "medium", "low"];
|
|
40
|
+
const MATCH_MODES = ["pattern", "sdk", "version"];
|
|
40
41
|
/**
|
|
41
42
|
* Locate the bundled deprecations.json. Tries several candidate locations so the
|
|
42
43
|
* tool works both when running the compiled output (dist/) from a published
|
|
@@ -80,16 +81,29 @@ function coerceDeprecation(raw) {
|
|
|
80
81
|
const detect = r.detect;
|
|
81
82
|
const sdk = detect && isStringArray(detect.sdk) ? detect.sdk : [];
|
|
82
83
|
const patterns = detect && isStringArray(detect.patterns) ? detect.patterns : [];
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
const models = detect && isStringArray(detect.models) ? detect.models : [];
|
|
85
|
+
// Default to "pattern" — detection keys on real usage, not SDK presence.
|
|
86
|
+
const match = MATCH_MODES.includes(r.match)
|
|
87
|
+
? r.match
|
|
88
|
+
: "pattern";
|
|
89
|
+
const version_range = isNonEmptyString(r.version_range)
|
|
90
|
+
? r.version_range
|
|
91
|
+
: undefined;
|
|
92
|
+
// Drop entries that can never fire under their match mode.
|
|
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)
|
|
95
|
+
return null;
|
|
96
|
+
if ((match === "sdk" || match === "version") && sdk.length === 0)
|
|
85
97
|
return null;
|
|
86
98
|
return {
|
|
87
99
|
id: r.id,
|
|
88
100
|
vendor: r.vendor,
|
|
89
101
|
title: r.title,
|
|
90
102
|
severity: r.severity,
|
|
103
|
+
match,
|
|
91
104
|
sunset_date: typeof r.sunset_date === "string" ? r.sunset_date : "",
|
|
92
|
-
detect: { sdk, patterns },
|
|
105
|
+
detect: { sdk, patterns, models },
|
|
106
|
+
version_range,
|
|
93
107
|
migration_url: typeof r.migration_url === "string" ? r.migration_url : "",
|
|
94
108
|
summary: typeof r.summary === "string" ? r.summary : "",
|
|
95
109
|
};
|
package/dist/report.js
CHANGED
|
@@ -67,22 +67,24 @@ function sunsetPhrase(s, sunsetDate, now) {
|
|
|
67
67
|
// Past or imminent sunsets are the urgent ones.
|
|
68
68
|
return days <= 30 ? s.red(base + hint) : s.yellow(base + hint);
|
|
69
69
|
}
|
|
70
|
-
/**
|
|
70
|
+
/** One line per source location: "path:line → matched text". */
|
|
71
71
|
function formatPatternMatches(s, finding) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
// De-duplicate identical file:line:text triples and order by file then line.
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const items = finding.patternMatches.filter((pm) => {
|
|
75
|
+
const key = `${pm.file}:${pm.line}:${pm.text}`;
|
|
76
|
+
if (seen.has(key))
|
|
77
|
+
return false;
|
|
78
|
+
seen.add(key);
|
|
79
|
+
return true;
|
|
80
|
+
});
|
|
81
|
+
items.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
82
|
+
const MAX = 12;
|
|
83
|
+
const shown = items.slice(0, MAX);
|
|
84
|
+
const lines = shown.map((pm) => `${s.cyan(pm.file)}${s.gray(":")}${pm.line} ${s.gray("→")} ${pm.text}`);
|
|
85
|
+
const extra = items.length - shown.length;
|
|
86
|
+
if (extra > 0) {
|
|
87
|
+
lines.push(s.gray(`+${extra} more location${extra === 1 ? "" : "s"}`));
|
|
86
88
|
}
|
|
87
89
|
return lines;
|
|
88
90
|
}
|
|
@@ -112,7 +114,7 @@ function renderReport(result, opts) {
|
|
|
112
114
|
if (findings.length === 0) {
|
|
113
115
|
out.push(s.green(s.bold("✓ No upcoming deprecations detected in your stack.")));
|
|
114
116
|
out.push("");
|
|
115
|
-
out.push(footer(s));
|
|
117
|
+
out.push(footer(s, findings));
|
|
116
118
|
return out.join("\n");
|
|
117
119
|
}
|
|
118
120
|
// Severity summary.
|
|
@@ -154,15 +156,29 @@ function renderReport(result, opts) {
|
|
|
154
156
|
}
|
|
155
157
|
out.push("");
|
|
156
158
|
}
|
|
157
|
-
out.push(footer(s));
|
|
159
|
+
out.push(footer(s, findings));
|
|
158
160
|
return out.join("\n");
|
|
159
161
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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");
|
|
166
182
|
}
|
|
167
183
|
/** Soft-wrap text to a width, indenting continuation lines. */
|
|
168
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,9 +171,9 @@ 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
|
-
|
|
137
|
-
|
|
138
|
-
const text =
|
|
174
|
+
// Cite the matched substring itself, normalized and length-capped, so
|
|
175
|
+
// the report points at exactly what triggered the finding.
|
|
176
|
+
const text = (m[0] ?? "").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
139
177
|
if (!recorded) {
|
|
140
178
|
recorded = [];
|
|
141
179
|
sink.set(deprecation.id, recorded);
|
|
@@ -171,18 +209,121 @@ function matchManifests(deprecations, refs) {
|
|
|
171
209
|
}
|
|
172
210
|
return byId;
|
|
173
211
|
}
|
|
212
|
+
/** Best-effort parse of a dotted numeric version from a declared string. */
|
|
213
|
+
function parseVersionNumbers(raw) {
|
|
214
|
+
if (!raw)
|
|
215
|
+
return null;
|
|
216
|
+
// Pull the first dotted-number run, ignoring ^ ~ >= <= operators and a "v" prefix.
|
|
217
|
+
const m = /(\d+(?:\.\d+)*)/.exec(raw);
|
|
218
|
+
if (!m)
|
|
219
|
+
return null;
|
|
220
|
+
return m[1].split(".").map((n) => parseInt(n, 10));
|
|
221
|
+
}
|
|
222
|
+
function compareVersions(a, b) {
|
|
223
|
+
const len = Math.max(a.length, b.length);
|
|
224
|
+
for (let i = 0; i < len; i++) {
|
|
225
|
+
const x = a[i] ?? 0;
|
|
226
|
+
const y = b[i] ?? 0;
|
|
227
|
+
if (x !== y)
|
|
228
|
+
return x < y ? -1 : 1;
|
|
229
|
+
}
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Best-effort check that a declared version satisfies a simple range such as
|
|
234
|
+
* "<3.0.0", ">=1.2.0", or "=2.1.0" / "2.1.0". Semver-ish: compares dotted numbers.
|
|
235
|
+
* Returns true when no range is given; false when a range is required but the
|
|
236
|
+
* declared version can't be parsed (stay conservative — don't over-flag).
|
|
237
|
+
*/
|
|
238
|
+
function versionInRange(declared, range) {
|
|
239
|
+
if (!range)
|
|
240
|
+
return true; // no constraint → presence already established by caller
|
|
241
|
+
const rm = /^\s*(<=|>=|<|>|={1,3})?\s*v?(\d+(?:\.\d+)*)\s*$/.exec(range);
|
|
242
|
+
if (!rm)
|
|
243
|
+
return true; // unparseable range → don't invent a false constraint
|
|
244
|
+
const op = rm[1] || "=";
|
|
245
|
+
const target = rm[2].split(".").map((n) => parseInt(n, 10));
|
|
246
|
+
const have = parseVersionNumbers(declared);
|
|
247
|
+
if (!have)
|
|
248
|
+
return false;
|
|
249
|
+
const cmp = compareVersions(have, target);
|
|
250
|
+
switch (op) {
|
|
251
|
+
case "<":
|
|
252
|
+
return cmp < 0;
|
|
253
|
+
case "<=":
|
|
254
|
+
return cmp <= 0;
|
|
255
|
+
case ">":
|
|
256
|
+
return cmp > 0;
|
|
257
|
+
case ">=":
|
|
258
|
+
return cmp >= 0;
|
|
259
|
+
default:
|
|
260
|
+
return cmp === 0;
|
|
261
|
+
}
|
|
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
|
+
}
|
|
174
296
|
/**
|
|
175
297
|
* Scan a repository for deprecation usage.
|
|
176
298
|
* @param root repo root to scan.
|
|
177
299
|
* @param deprecations validated dataset entries.
|
|
300
|
+
* @param options optional ignore globs (--ignore) and custom dataset path.
|
|
178
301
|
*/
|
|
179
|
-
function scanRepo(root, deprecations) {
|
|
302
|
+
function scanRepo(root, deprecations, options = {}) {
|
|
180
303
|
const absRoot = path.resolve(root);
|
|
181
|
-
//
|
|
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
|
+
}
|
|
318
|
+
// Partition by match mode: "pattern" entries key on real source usage, while
|
|
319
|
+
// "sdk"/"version" entries key on the manifest.
|
|
320
|
+
const patternDeps = deprecations.filter((d) => d.match === "pattern");
|
|
321
|
+
const manifestDeps = deprecations.filter((d) => d.match === "sdk" || d.match === "version");
|
|
322
|
+
// 1. Manifest scan (drives sdk/version entries; also lists the manifests read).
|
|
182
323
|
const { refs, manifests } = (0, manifests_1.collectManifestDeps)(absRoot);
|
|
183
|
-
const manifestMatches = matchManifests(
|
|
184
|
-
// 2. Inline scan.
|
|
185
|
-
const compiled = compileDeprecations(
|
|
324
|
+
const manifestMatches = matchManifests(manifestDeps, refs);
|
|
325
|
+
// 2. Inline source scan (drives pattern entries — usage, not mere presence).
|
|
326
|
+
const compiled = compileDeprecations(patternDeps);
|
|
186
327
|
const patternSink = new Map();
|
|
187
328
|
const files = fast_glob_1.default.sync([`**/*.{${SOURCE_EXTENSIONS.join(",")}}`], {
|
|
188
329
|
cwd: absRoot,
|
|
@@ -191,7 +332,7 @@ function scanRepo(root, deprecations) {
|
|
|
191
332
|
dot: false,
|
|
192
333
|
followSymbolicLinks: false,
|
|
193
334
|
suppressErrors: true,
|
|
194
|
-
ignore:
|
|
335
|
+
ignore: ignoreGlobs,
|
|
195
336
|
});
|
|
196
337
|
let scannedFiles = 0;
|
|
197
338
|
for (const rel of files) {
|
|
@@ -215,14 +356,30 @@ function scanRepo(root, deprecations) {
|
|
|
215
356
|
scannedFiles++;
|
|
216
357
|
scanContent(content, rel, compiled, patternSink);
|
|
217
358
|
}
|
|
218
|
-
// 3.
|
|
359
|
+
// 3. Build findings — one per deprecation, evaluated per its match mode.
|
|
219
360
|
const findings = [];
|
|
220
361
|
for (const deprecation of deprecations) {
|
|
362
|
+
if (deprecation.match === "pattern") {
|
|
363
|
+
// Flag ONLY on a real source hit; manifest/SDK presence is irrelevant here.
|
|
364
|
+
const pm = patternSink.get(deprecation.id) ?? [];
|
|
365
|
+
if (pm.length === 0)
|
|
366
|
+
continue;
|
|
367
|
+
findings.push({ deprecation, manifestMatches: [], patternMatches: pm });
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
// "sdk" / "version": evaluate against the manifest.
|
|
221
371
|
const mm = manifestMatches.get(deprecation.id) ?? [];
|
|
222
|
-
|
|
223
|
-
|
|
372
|
+
if (mm.length === 0)
|
|
373
|
+
continue;
|
|
374
|
+
if (deprecation.match === "version") {
|
|
375
|
+
const inRange = mm.filter((m) => versionInRange(m.version, deprecation.version_range));
|
|
376
|
+
if (inRange.length === 0)
|
|
377
|
+
continue;
|
|
378
|
+
findings.push({ deprecation, manifestMatches: inRange, patternMatches: [] });
|
|
224
379
|
continue;
|
|
225
|
-
|
|
380
|
+
}
|
|
381
|
+
// "sdk": mere presence in a manifest is enough.
|
|
382
|
+
findings.push({ deprecation, manifestMatches: mm, patternMatches: [] });
|
|
226
383
|
}
|
|
227
384
|
return { scannedFiles, manifestsScanned: manifests, findings };
|
|
228
385
|
}
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"vendor": "OpenAI",
|
|
5
5
|
"title": "Assistants API (beta)",
|
|
6
6
|
"severity": "high",
|
|
7
|
+
"match": "pattern",
|
|
7
8
|
"sunset_date": "2026-08-26",
|
|
8
9
|
"detect": {
|
|
9
10
|
"sdk": ["openai"],
|
|
@@ -23,10 +24,12 @@
|
|
|
23
24
|
"vendor": "Anthropic",
|
|
24
25
|
"title": "Claude Sonnet 4 & Opus 4",
|
|
25
26
|
"severity": "high",
|
|
27
|
+
"match": "pattern",
|
|
26
28
|
"sunset_date": "2026-06-15",
|
|
27
29
|
"detect": {
|
|
28
30
|
"sdk": ["@anthropic-ai/sdk", "anthropic"],
|
|
29
|
-
"patterns": [
|
|
31
|
+
"patterns": [],
|
|
32
|
+
"models": [
|
|
30
33
|
"claude-sonnet-4-20250514",
|
|
31
34
|
"claude-opus-4-20250514",
|
|
32
35
|
"claude-sonnet-4-0",
|
|
@@ -42,15 +45,17 @@
|
|
|
42
45
|
"vendor": "Anthropic",
|
|
43
46
|
"title": "Retired Claude models (Haiku 3, 3.5/3.7 Sonnet, Claude 2.x)",
|
|
44
47
|
"severity": "high",
|
|
48
|
+
"match": "pattern",
|
|
45
49
|
"sunset_date": "2026-04-20",
|
|
46
50
|
"detect": {
|
|
47
51
|
"sdk": ["@anthropic-ai/sdk", "anthropic"],
|
|
48
|
-
"patterns": [
|
|
52
|
+
"patterns": [],
|
|
53
|
+
"models": [
|
|
49
54
|
"claude-3-haiku-20240307",
|
|
50
55
|
"claude-3-5-sonnet",
|
|
51
56
|
"claude-3-7-sonnet",
|
|
52
|
-
"claude-2
|
|
53
|
-
"claude-2
|
|
57
|
+
"claude-2.1",
|
|
58
|
+
"claude-2.0",
|
|
54
59
|
"claude-instant"
|
|
55
60
|
]
|
|
56
61
|
},
|
|
@@ -63,14 +68,12 @@
|
|
|
63
68
|
"vendor": "Google (Gemini)",
|
|
64
69
|
"title": "Gemini 2.5 Pro / Flash / Flash-Lite",
|
|
65
70
|
"severity": "high",
|
|
71
|
+
"match": "pattern",
|
|
66
72
|
"sunset_date": "2026-10-16",
|
|
67
73
|
"detect": {
|
|
68
74
|
"sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
|
|
69
|
-
"patterns": [
|
|
70
|
-
|
|
71
|
-
"gemini-2\\.5-flash",
|
|
72
|
-
"gemini-2\\.5-flash-lite"
|
|
73
|
-
]
|
|
75
|
+
"patterns": [],
|
|
76
|
+
"models": ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"]
|
|
74
77
|
},
|
|
75
78
|
"migration_url": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
76
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.",
|
|
@@ -81,14 +84,16 @@
|
|
|
81
84
|
"vendor": "Google (Gemini)",
|
|
82
85
|
"title": "Gemini 2.0 Flash family",
|
|
83
86
|
"severity": "high",
|
|
87
|
+
"match": "pattern",
|
|
84
88
|
"sunset_date": "2026-06-01",
|
|
85
89
|
"detect": {
|
|
86
90
|
"sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
|
|
87
|
-
"patterns": [
|
|
88
|
-
|
|
89
|
-
"gemini-2
|
|
90
|
-
"gemini-2
|
|
91
|
-
"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"
|
|
92
97
|
]
|
|
93
98
|
},
|
|
94
99
|
"migration_url": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
@@ -100,18 +105,166 @@
|
|
|
100
105
|
"vendor": "Google (Gemini)",
|
|
101
106
|
"title": "Gemini 1.0 / 1.5 models",
|
|
102
107
|
"severity": "high",
|
|
108
|
+
"match": "pattern",
|
|
103
109
|
"sunset_date": "2026-04-29",
|
|
104
110
|
"detect": {
|
|
105
111
|
"sdk": ["@google/generative-ai", "@google/genai", "google-generativeai"],
|
|
106
|
-
"patterns": [
|
|
107
|
-
|
|
108
|
-
"gemini-1\\.5-flash",
|
|
109
|
-
"gemini-1\\.0-pro",
|
|
110
|
-
"gemini-pro"
|
|
111
|
-
]
|
|
112
|
+
"patterns": [],
|
|
113
|
+
"models": ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.0-pro", "gemini-pro"]
|
|
112
114
|
},
|
|
113
115
|
"migration_url": "https://ai.google.dev/gemini-api/docs/deprecations",
|
|
114
116
|
"summary": "All Gemini 1.0 and 1.5 models are shut down and return 404. Migrate to Gemini 2.5+ or 3.x.",
|
|
115
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"
|
|
116
269
|
}
|
|
117
270
|
]
|