arol-ai 0.1.5 → 0.1.7

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # arol-ai
2
2
 
3
+ [![tests](https://github.com/benminor/arol/actions/workflows/ci.yml/badge.svg)](https://github.com/benminor/arol/actions/workflows/ci.yml)
4
+
3
5
  Scan a local code repo for usage of third-party APIs/SDKs that have **upcoming deprecations**, and print a clean report.
4
6
 
5
7
  **Everything runs locally.** No network calls, no telemetry, no uploads, no auth. Your code never leaves your machine.
package/dist/cli.js CHANGED
@@ -139,14 +139,7 @@ function runScan(targetPath, opts) {
139
139
  const within = Number.isFinite(parsedWithin) && parsedWithin >= 0
140
140
  ? parsedWithin
141
141
  : DEFAULT_WITHIN_DAYS;
142
- const tripped = result.findings.some((f) => {
143
- if (f.deprecation.severity === "high")
144
- return true;
145
- if ((0, status_1.effectiveStatus)(f.deprecation, now) !== "scheduled")
146
- return false;
147
- const days = (0, status_1.daysUntil)(f.deprecation.sunset_date, now);
148
- return days !== null && days >= 0 && days <= within;
149
- });
142
+ const tripped = result.findings.some((f) => (0, status_1.isActionable)(f.deprecation, now, within));
150
143
  if (tripped)
151
144
  process.exitCode = 1;
152
145
  }
package/dist/data.js CHANGED
@@ -107,6 +107,9 @@ function coerceDeprecation(raw) {
107
107
  return null;
108
108
  if ((match === "sdk" || match === "version") && sdk.length === 0)
109
109
  return null;
110
+ // INVARIANT: every array below is always defined here — detect.{sdk,patterns,
111
+ // models} default to [] and applies_to to ["*"] (above). scanner.ts depends on
112
+ // this and also guards with `?? []` as defense in depth. Don't drop the defaults.
110
113
  return {
111
114
  id: r.id,
112
115
  vendor: r.vendor,
package/dist/scanner.js CHANGED
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.modelRegexSource = modelRegexSource;
39
40
  exports.scanRepo = scanRepo;
40
41
  const fs = __importStar(require("fs"));
41
42
  const path = __importStar(require("path"));
@@ -104,7 +105,8 @@ function compileDeprecations(deprecations) {
104
105
  return deprecations.map((deprecation) => {
105
106
  const regexes = [];
106
107
  // Raw patterns — code identifiers, endpoints, params.
107
- for (const pattern of deprecation.detect.patterns) {
108
+ // `?? []` guards against entries not produced by the loader (e.g. tests).
109
+ for (const pattern of deprecation.detect.patterns ?? []) {
108
110
  try {
109
111
  // Global so we can iterate every match and derive line numbers.
110
112
  regexes.push(new RegExp(pattern, "g"));
@@ -114,7 +116,7 @@ function compileDeprecations(deprecations) {
114
116
  }
115
117
  }
116
118
  // Model names — only matched inside string literals (quote-anchored).
117
- for (const family of deprecation.detect.models) {
119
+ for (const family of deprecation.detect.models ?? []) {
118
120
  try {
119
121
  regexes.push(new RegExp(modelRegexSource(family), "g"));
120
122
  }
@@ -122,7 +124,9 @@ function compileDeprecations(deprecations) {
122
124
  // Defensive: a pathological family name must not crash the scan.
123
125
  }
124
126
  }
125
- const appliesTo = new Set((deprecation.applies_to.length > 0 ? deprecation.applies_to : ["*"]).map((e) => e.toLowerCase()));
127
+ // Missing/empty applies_to means "applies everywhere" (["*"]), preserved here.
128
+ const declaredExts = deprecation.applies_to ?? [];
129
+ const appliesTo = new Set((declaredExts.length > 0 ? declaredExts : ["*"]).map((e) => e.toLowerCase()));
126
130
  return { deprecation, regexes, appliesTo };
127
131
  });
128
132
  }
@@ -291,11 +295,12 @@ function scanContent(content, relPath, compiled, sink) {
291
295
  function matchManifests(deprecations, refs) {
292
296
  const byId = new Map();
293
297
  for (const deprecation of deprecations) {
294
- if (deprecation.detect.sdk.length === 0)
298
+ const sdks = deprecation.detect.sdk ?? [];
299
+ if (sdks.length === 0)
295
300
  continue;
296
301
  const matches = [];
297
302
  const seen = new Set();
298
- for (const sdk of deprecation.detect.sdk) {
303
+ for (const sdk of sdks) {
299
304
  for (const ref of refs) {
300
305
  if (!(0, manifests_1.nameMatches)(sdk, ref.name))
301
306
  continue;
package/dist/status.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseSunsetDate = parseSunsetDate;
4
4
  exports.daysUntil = daysUntil;
5
5
  exports.effectiveStatus = effectiveStatus;
6
+ exports.isActionable = isActionable;
6
7
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
7
8
  /** Midnight-UTC timestamp for a Date's calendar day. */
8
9
  function startOfDayUTC(d) {
@@ -43,3 +44,16 @@ function effectiveStatus(d, now) {
43
44
  return "deprecated";
44
45
  return t < startOfDayUTC(now) ? "retired" : "scheduled";
45
46
  }
47
+ /**
48
+ * Whether a finding should fail the CI gate (non-zero exit): any high-severity
49
+ * finding, or a scheduled finding landing within `within` days. Dateless
50
+ * "deprecated" and non-imminent medium/low findings are warn-only.
51
+ */
52
+ function isActionable(d, now, within) {
53
+ if (d.severity === "high")
54
+ return true;
55
+ if (effectiveStatus(d, now) !== "scheduled")
56
+ return false;
57
+ const days = daysUntil(d.sunset_date, now);
58
+ return days !== null && days >= 0 && days <= within;
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arol-ai",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Scan a local repo for upcoming third-party API/SDK deprecations. Fully local — no network, no telemetry, your code never leaves the machine.",
5
5
  "keywords": [
6
6
  "deprecation",
@@ -29,6 +29,8 @@
29
29
  "scripts": {
30
30
  "build": "tsc",
31
31
  "scan": "node dist/cli.js scan",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
32
34
  "prepublishOnly": "npm run build"
33
35
  },
34
36
  "dependencies": {
@@ -37,6 +39,7 @@
37
39
  },
38
40
  "devDependencies": {
39
41
  "@types/node": "^22.10.0",
40
- "typescript": "^5.7.2"
42
+ "typescript": "^5.7.2",
43
+ "vitest": "^4.1.8"
41
44
  }
42
45
  }
@@ -10,8 +10,8 @@
10
10
  "detect": {
11
11
  "sdk": ["openai"],
12
12
  "patterns": [
13
- "beta\\.assistants",
14
- "beta\\.threads",
13
+ "\\bbeta\\.assistants\\b",
14
+ "\\bbeta\\.threads\\b",
15
15
  "/v1/assistants",
16
16
  "/v1/threads"
17
17
  ]
@@ -180,13 +180,13 @@
180
180
  "detect": {
181
181
  "sdk": ["@stripe/stripe-js", "stripe"],
182
182
  "patterns": [
183
- "handleCardPayment",
184
- "confirmPaymentIntent",
185
- "handleFpxPayment",
186
- "handleCardSetup",
187
- "confirmSetupIntent",
188
- "createSource",
189
- "retrieveSource"
183
+ "\\bhandleCardPayment\\b",
184
+ "\\bconfirmPaymentIntent\\b",
185
+ "\\bhandleFpxPayment\\b",
186
+ "\\bhandleCardSetup\\b",
187
+ "\\bconfirmSetupIntent\\b",
188
+ "\\bstripe\\.createSource\\b",
189
+ "\\bstripe\\.retrieveSource\\b"
190
190
  ]
191
191
  },
192
192
  "migration_url": "https://docs.stripe.com/changelog/dahlia/2026-03-25/remove-legacy-stripejs-methods",
@@ -203,7 +203,7 @@
203
203
  "sunset_date": "2024-05-15",
204
204
  "detect": {
205
205
  "sdk": ["stripe"],
206
- "patterns": ["\\.sources\\.create", "charges\\.create", "Charge\\.create"]
206
+ "patterns": ["\\bstripe\\.sources\\.create", "\\bstripe\\.charges\\.create", "\\bstripe\\.Charge\\.create"]
207
207
  },
208
208
  "migration_url": "https://docs.stripe.com/payments/older-apis",
209
209
  "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.",
@@ -216,15 +216,14 @@
216
216
  "severity": "high",
217
217
  "match": "pattern",
218
218
  "applies_to": ["py", "js", "ts", "jsx", "tsx", "mjs"],
219
- "sunset_date": "2027-01-31",
220
- "date_confidence": "verify",
219
+ "sunset_date": "2025-12-31",
221
220
  "detect": {
222
221
  "sdk": ["twilio"],
223
- "patterns": ["notify\\.v1", "\\.notify\\.services", "client\\.notify"]
222
+ "patterns": ["\\bnotify\\.v1\\b", "\\.notify\\.services\\b"]
224
223
  },
225
- "migration_url": "https://www.twilio.com/en-us/changelog",
226
- "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.",
227
- "source": "https://www.courier.com/blog/twilio-notify-end-of-life"
224
+ "migration_url": "https://support.twilio.com/hc/en-us/articles/9198083260571-Transitioning-off-Notify",
225
+ "summary": "Twilio Notify reached end of life December 31, 2025; Notify API requests now fail. No 1:1 replacement — rebuild with Programmable Messaging / Conversations.",
226
+ "source": "https://www.twilio.com/en-us/changelog/notify-api-end-of-life-further-extension-notice"
228
227
  },
229
228
  {
230
229
  "id": "twilio-programmable-chat-retired",
@@ -236,12 +235,28 @@
236
235
  "sunset_date": "2022-07-25",
237
236
  "detect": {
238
237
  "sdk": ["twilio", "twilio-chat"],
239
- "patterns": ["chat\\.v2", "IpMessaging", "twilio-chat"]
238
+ "patterns": ["\\bchat\\.v2\\b", "\\bIpMessaging\\b"]
240
239
  },
241
240
  "migration_url": "https://www.twilio.com/docs/conversations/migrating-chat-conversations",
242
241
  "summary": "The standalone Programmable Chat API was sunset July 25, 2022 (Programmable Chat in Flex ended June 1, 2026). Migrate to the Conversations API.",
243
242
  "source": "https://www.twilio.com/en-us/changelog/programmable-chat-end-of-life-notice"
244
243
  },
244
+ {
245
+ "id": "twilio-chat-package-eol",
246
+ "vendor": "Twilio",
247
+ "title": "twilio-chat SDK package (Programmable Chat)",
248
+ "severity": "high",
249
+ "match": "sdk",
250
+ "applies_to": ["js", "ts", "jsx", "tsx", "mjs"],
251
+ "sunset_date": "2022-07-25",
252
+ "detect": {
253
+ "sdk": ["twilio-chat"],
254
+ "patterns": []
255
+ },
256
+ "migration_url": "https://www.twilio.com/docs/conversations/migrating-chat-conversations",
257
+ "summary": "The twilio-chat npm package is the client SDK for the retired Programmable Chat API (sunset July 25, 2022). Remove the dependency and migrate to @twilio/conversations.",
258
+ "source": "https://www.twilio.com/en-us/changelog/programmable-chat-end-of-life-notice"
259
+ },
245
260
  {
246
261
  "id": "aws-sdk-js-v2-eol",
247
262
  "vendor": "AWS",
@@ -285,10 +300,10 @@
285
300
  "detect": {
286
301
  "sdk": ["openai"],
287
302
  "patterns": [
288
- "openai\\.ChatCompletion",
289
- "openai\\.Completion\\.create",
290
- "openai\\.Embedding\\.create",
291
- "openai\\.Moderation\\.create"
303
+ "\\bopenai\\.ChatCompletion\\b",
304
+ "\\bopenai\\.Completion\\.create\\b",
305
+ "\\bopenai\\.Embedding\\.create\\b",
306
+ "\\bopenai\\.Moderation\\.create\\b"
292
307
  ]
293
308
  },
294
309
  "migration_url": "https://github.com/openai/openai-python/discussions/742",
@@ -308,9 +323,9 @@
308
323
  "patterns": [
309
324
  "from ['\"]ai/react['\"]",
310
325
  "from ['\"]ai/openai['\"]",
311
- "experimental_streamText",
312
- "experimental_generateText",
313
- "StreamingTextResponse"
326
+ "\\bexperimental_streamText\\b",
327
+ "\\bexperimental_generateText\\b",
328
+ "\\bStreamingTextResponse\\b"
314
329
  ]
315
330
  },
316
331
  "migration_url": "https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0",
@@ -331,8 +346,8 @@
331
346
  "from langchain\\.llms import",
332
347
  "from langchain\\.chat_models import",
333
348
  "from langchain\\.embeddings import",
334
- "initialize_agent",
335
- "LLMChain"
349
+ "\\binitialize_agent\\b",
350
+ "\\bLLMChain\\b"
336
351
  ]
337
352
  },
338
353
  "migration_url": "https://python.langchain.com/docs/versions/v0_2/",
@@ -350,10 +365,33 @@
350
365
  "sunset_date": null,
351
366
  "detect": {
352
367
  "sdk": ["resend"],
353
- "patterns": ["\\.audiences\\.", "\\.Audiences\\."]
368
+ "patterns": ["\\bresend\\.audiences\\.", "\\bresend\\.Audiences\\."]
354
369
  },
355
370
  "migration_url": "https://resend.com/docs/dashboard/segments/migrating-from-audiences-to-segments",
356
371
  "summary": "Resend's Audiences API is deprecated in favor of Segments. The endpoints still work but will be removed in the future (no date announced). Migrate the audiences.* calls to the Segments API.",
357
372
  "source": "https://resend.com/docs/api-reference/audiences/create-audience"
373
+ },
374
+ {
375
+ "id": "clerk-redirect-to-user-profile-component",
376
+ "vendor": "Clerk",
377
+ "title": "<RedirectToUserProfile /> control component deprecated",
378
+ "severity": "low",
379
+ "match": "pattern",
380
+ "applies_to": ["jsx", "tsx", "js", "ts"],
381
+ "sunset_date": null,
382
+ "status": "deprecated",
383
+ "detect": {
384
+ "sdk": [
385
+ "@clerk/nextjs",
386
+ "@clerk/clerk-react",
387
+ "@clerk/nuxt",
388
+ "@clerk/vue"
389
+ ],
390
+ "patterns": ["\\bRedirectToUserProfile\\b"],
391
+ "models": []
392
+ },
393
+ "migration_url": "https://clerk.com/docs/reference/objects/clerk#redirect-to-user-profile",
394
+ "summary": "Clerk's <RedirectToUserProfile /> control component is deprecated in favor of the redirectToUserProfile() method on the Clerk object. No removal date announced.",
395
+ "source": "https://clerk.com/docs/nextjs/reference/components/control/redirect-to-user-profile"
358
396
  }
359
397
  ]