Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/core-open/run-core-scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,11 @@ export async function runCoreScan(
const pythonProjectsP = timePromise(scanPythonProjects(rootDir, pypiCache, fileCache, projectScanTimeoutMs, runtimeCatalog));
const javaProjectsP = timePromise(scanJavaProjects(rootDir, mavenCache, fileCache, projectScanTimeoutMs, runtimeCatalog));
const rubyProjectsP = timePromise(scanRubyProjects(rootDir, rubygemsCache, fileCache, projectScanTimeoutMs, runtimeCatalog));
const swiftProjectsP = timePromise(scanSwiftProjects(rootDir, swiftCache, fileCache, projectScanTimeoutMs));
const swiftProjectsP = timePromise(scanSwiftProjects(rootDir, swiftCache, fileCache, projectScanTimeoutMs, runtimeCatalog));
const goProjectsP = timePromise(scanGoProjects(rootDir, goCache, fileCache, projectScanTimeoutMs, runtimeCatalog));
const rustProjectsP = timePromise(scanRustProjects(rootDir, cargoCache, fileCache, projectScanTimeoutMs));
const phpProjectsP = timePromise(scanPhpProjects(rootDir, composerCache, fileCache, projectScanTimeoutMs));
const dartProjectsP = timePromise(scanDartProjects(rootDir, pubCache, fileCache, projectScanTimeoutMs));
const phpProjectsP = timePromise(scanPhpProjects(rootDir, composerCache, fileCache, projectScanTimeoutMs, runtimeCatalog));
const dartProjectsP = timePromise(scanDartProjects(rootDir, pubCache, fileCache, projectScanTimeoutMs, runtimeCatalog));
const elixirProjectsP = timePromise(scanElixirProjects(rootDir, packageManifest, fileCache, projectScanTimeoutMs, offlineMode));
const dockerProjectsP = timePromise(scanDockerProjects(rootDir, packageManifest, fileCache, projectScanTimeoutMs, offlineMode));
const helmProjectsP = timePromise(scanHelmProjects(rootDir, packageManifest, fileCache, projectScanTimeoutMs, offlineMode));
Expand Down
8 changes: 6 additions & 2 deletions src/core-open/runtimes/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export function productForType(type: string): string | null {
case 'php': return 'php';
case 'ruby': return 'ruby';
case 'go': return 'go';
case 'swift': return 'swift';
case 'dart': return 'dart';
default: return null;
}
}
Expand All @@ -32,7 +34,9 @@ export function extractCycle(type: string, runtime: string | undefined | null):
case 'python':
case 'php':
case 'ruby':
case 'go': {
case 'go':
case 'swift':
case 'dart': {
// major.minor cycles, e.g. "3.11"
const m = runtime.match(/(\d+)\.(\d+)/);
return m ? `${m[1]}.${m[2]}` : null;
Expand Down Expand Up @@ -193,4 +197,4 @@ export function buildCatalog(
}

/** Products the catalog is expected to carry. */
export const RUNTIME_PRODUCTS = ['nodejs', 'python', 'dotnet', 'java', 'php', 'ruby', 'go'] as const;
export const RUNTIME_PRODUCTS = ['nodejs', 'python', 'dotnet', 'java', 'php', 'ruby', 'go', 'swift', 'dart'] as const;
27 changes: 27 additions & 0 deletions src/core-open/runtimes/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const BUNDLED_RUNTIME_CATALOG: RuntimeCatalog = {
php: {
product: 'php',
cycles: [
{ cycle: '8.5', releaseDate: '2025-11-20', eol: '2029-12-31' },
{ cycle: '8.4', releaseDate: '2024-11-21', eol: '2028-12-31' },
{ cycle: '8.3', releaseDate: '2023-11-23', eol: '2027-12-31' },
{ cycle: '8.2', releaseDate: '2022-12-08', eol: '2026-12-31' },
Expand All @@ -88,5 +89,31 @@ export const BUNDLED_RUNTIME_CATALOG: RuntimeCatalog = {
{ cycle: '1.22', releaseDate: '2024-02-06', eol: true },
],
},
// Swift and Dart are not published on endoflife.date. These cycles are the
// stable release lines (swift.org / dart.dev) so offline scans still have a
// latest version. `eol: false` means no end-of-life date is known.
swift: {
product: 'swift',
cycles: [
{ cycle: '6.4', releaseDate: '2026-09-15', eol: false },
{ cycle: '6.3', releaseDate: '2026-03-27', eol: false },
{ cycle: '6.2', releaseDate: '2025-09-17', eol: false },
{ cycle: '6.1', releaseDate: '2025-04-01', eol: false },
{ cycle: '6.0', releaseDate: '2024-09-17', eol: false },
],
},
dart: {
product: 'dart',
cycles: [
{ cycle: '3.13', releaseDate: '2026-08-05', eol: false },
{ cycle: '3.12', releaseDate: '2026-05-08', eol: false },
{ cycle: '3.11', releaseDate: '2026-02-09', eol: false },
{ cycle: '3.10', releaseDate: '2025-11-06', eol: false },
{ cycle: '3.9', releaseDate: '2025-08-11', eol: false },
{ cycle: '3.8', releaseDate: '2025-05-14', eol: false },
{ cycle: '3.7', releaseDate: '2025-02-05', eol: false },
{ cycle: '3.6', releaseDate: '2024-12-05', eol: false },
],
},
},
};
2 changes: 1 addition & 1 deletion src/core-open/runtimes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface RuntimeCatalog {
/** ISO timestamp the catalog was generated — the freshness/confidence signal. */
generatedAt: string;
source: 'endoflife.date';
/** Keyed by endoflife.date product slug: nodejs, python, dotnet, java, php, ruby, go. */
/** Keyed by product slug: nodejs, python, dotnet, java, php, ruby, go, swift, dart. */
products: Record<string, RuntimeProduct>;
}

Expand Down
25 changes: 15 additions & 10 deletions src/core-open/scanners/dart-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { assertLockfileText, LockfileParseError, rethrowLockfileParseError } fro
import { lockfileWarningPath, notePubspecLockText, withLockfileNotes } from '../utils/lockfile-unknown.js';
import { withTimeout } from '../utils/timeout.js';
import { PubCache, type PubDevMeta } from './pub-cache.js';
import { latestStable } from '../runtimes/catalog.js';
import { BUNDLED_RUNTIME_CATALOG } from '../runtimes/snapshot.js';
import type { RuntimeCatalog } from '../runtimes/types.js';
import type { ProjectScan, DependencyRow, DetectedFramework } from '../types.js';
import { compareCodeUnits } from '../utils/code-unit.js';

Expand Down Expand Up @@ -86,9 +89,6 @@ const KNOWN_DART_FRAMEWORKS: Record<string, string> = {
'lint': 'Lint',
};

/** Latest Dart major.minor (as of 2026) */
const LATEST_DART = { major: 3, minor: 6 };

interface DartDependency {
name: string;
version: string;
Expand Down Expand Up @@ -411,6 +411,7 @@ export async function scanDartProjects(
pubCache: PubCache,
cache?: FileCache,
projectScanTimeout?: number,
catalog: RuntimeCatalog = BUNDLED_RUNTIME_CATALOG,
): Promise<ProjectScan[]> {
// Find pubspec.yaml files
const manifestFiles = cache
Expand All @@ -423,7 +424,7 @@ export async function scanDartProjects(
for (const manifestFile of manifestFiles) {
const dir = path.dirname(manifestFile);
try {
const scanPromise = scanOneDartProject(dir, manifestFile, rootDir, pubCache, cache);
const scanPromise = scanOneDartProject(dir, manifestFile, rootDir, pubCache, cache, catalog);
const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
if (result.ok) {
results.push(result.value);
Expand Down Expand Up @@ -455,7 +456,8 @@ async function scanOneDartProject(
manifestFile: string,
rootDir: string,
pubCache: PubCache,
cache?: FileCache,
cache: FileCache | undefined,
catalog: RuntimeCatalog,
): Promise<ProjectScan> {
const relDir = path.relative(rootDir, dir) || '.';
const projectName = path.basename(dir === rootDir ? rootDir : dir);
Expand Down Expand Up @@ -488,12 +490,15 @@ async function scanOneDartProject(
if (verMatch) {
const reqMajor = parseInt(verMatch[1]!, 10);
const reqMinor = parseInt(verMatch[2]!, 10);
if (reqMajor === LATEST_DART.major) {
runtimeMajorsBehind = Math.max(0, LATEST_DART.minor - reqMinor);
} else if (reqMajor < LATEST_DART.major) {
runtimeMajorsBehind = LATEST_DART.minor + (LATEST_DART.major - reqMajor) * 10;
const latestDart = latestStable(catalog, 'dart');
if (latestDart) {
if (reqMajor === latestDart.major) {
runtimeMajorsBehind = Math.max(0, latestDart.minor - reqMinor);
} else if (reqMajor < latestDart.major) {
runtimeMajorsBehind = latestDart.minor + (latestDart.major - reqMajor) * 10;
}
runtimeLatest = `${latestDart.major}.${latestDart.minor}`;
}
runtimeLatest = `${LATEST_DART.major}.${LATEST_DART.minor}`;
}
}
}
Expand Down
25 changes: 15 additions & 10 deletions src/core-open/scanners/php-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { LockfileParseError, rethrowLockfileParseError } from '../utils/lockfile
import { lockfileWarningPath, noteComposerLock, withLockfileNotes } from '../utils/lockfile-unknown.js';
import { withTimeout } from '../utils/timeout.js';
import { ComposerCache } from './composer-cache.js';
import { latestStable } from '../runtimes/catalog.js';
import { BUNDLED_RUNTIME_CATALOG } from '../runtimes/snapshot.js';
import type { RuntimeCatalog } from '../runtimes/types.js';
import type { ProjectScan, DependencyRow, DetectedFramework } from '../types.js';
import { compareCodeUnits } from '../utils/code-unit.js';

Expand Down Expand Up @@ -76,9 +79,6 @@ const KNOWN_PHP_FRAMEWORKS: Record<string, string> = {
'squizlabs/php_codesniffer': 'PHP_CodeSniffer',
};

/** Latest PHP major.minor (as of 2026) */
const LATEST_PHP = { major: 8, minor: 4 };

interface PhpDependency {
name: string;
version: string;
Expand Down Expand Up @@ -207,6 +207,7 @@ export async function scanPhpProjects(
composerCache: ComposerCache,
cache?: FileCache,
projectScanTimeout?: number,
catalog: RuntimeCatalog = BUNDLED_RUNTIME_CATALOG,
): Promise<ProjectScan[]> {
// Find composer.json files
const manifestFiles = cache
Expand All @@ -219,7 +220,7 @@ export async function scanPhpProjects(
for (const manifestFile of manifestFiles) {
const dir = path.dirname(manifestFile);
try {
const scanPromise = scanOnePhpProject(dir, manifestFile, rootDir, composerCache, cache);
const scanPromise = scanOnePhpProject(dir, manifestFile, rootDir, composerCache, cache, catalog);
const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
if (result.ok) {
results.push(result.value);
Expand Down Expand Up @@ -251,7 +252,8 @@ async function scanOnePhpProject(
manifestFile: string,
rootDir: string,
composerCache: ComposerCache,
cache?: FileCache,
cache: FileCache | undefined,
catalog: RuntimeCatalog,
): Promise<ProjectScan> {
const relDir = path.relative(rootDir, dir) || '.';
const projectName = path.basename(dir === rootDir ? rootDir : dir);
Expand Down Expand Up @@ -283,12 +285,15 @@ async function scanOnePhpProject(
if (verMatch) {
const reqMajor = parseInt(verMatch[1]!, 10);
const reqMinor = parseInt(verMatch[2]!, 10);
if (reqMajor === LATEST_PHP.major) {
runtimeMajorsBehind = Math.max(0, LATEST_PHP.minor - reqMinor);
} else if (reqMajor < LATEST_PHP.major) {
runtimeMajorsBehind = LATEST_PHP.minor + (LATEST_PHP.major - reqMajor) * 10;
const latestPhp = latestStable(catalog, 'php');
if (latestPhp) {
if (reqMajor === latestPhp.major) {
runtimeMajorsBehind = Math.max(0, latestPhp.minor - reqMinor);
} else if (reqMajor < latestPhp.major) {
runtimeMajorsBehind = latestPhp.minor + (latestPhp.major - reqMajor) * 10;
}
runtimeLatest = `${latestPhp.major}.${latestPhp.minor}`;
}
runtimeLatest = `${LATEST_PHP.major}.${LATEST_PHP.minor}`;
}
}
}
Expand Down
25 changes: 15 additions & 10 deletions src/core-open/scanners/swift-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { parseLockfileJson, rethrowLockfileParseError } from '../utils/lockfile-
import { lockfileWarningPath, notePackageResolved, withLockfileNotes } from '../utils/lockfile-unknown.js';
import { withTimeout } from '../utils/timeout.js';
import { SwiftCache } from './swift-cache.js';
import { latestStable } from '../runtimes/catalog.js';
import { BUNDLED_RUNTIME_CATALOG } from '../runtimes/snapshot.js';
import type { RuntimeCatalog } from '../runtimes/types.js';
import type { ProjectScan, DependencyRow, DetectedFramework } from '../types.js';
import { compareCodeUnits } from '../utils/code-unit.js';

Expand Down Expand Up @@ -60,9 +63,6 @@ const KNOWN_SWIFT_FRAMEWORKS: Record<string, string> = {
'then': 'Then',
};

/** Latest Swift version (as of 2026) */
const LATEST_SWIFT = { major: 6, minor: 0 };

interface SwiftDependency {
name: string;
url: string;
Expand Down Expand Up @@ -291,6 +291,7 @@ export async function scanSwiftProjects(
swiftCache: SwiftCache,
cache?: FileCache,
projectScanTimeout?: number,
catalog: RuntimeCatalog = BUNDLED_RUNTIME_CATALOG,
): Promise<ProjectScan[]> {
// Find Package.swift files
const manifestFiles = cache
Expand All @@ -303,7 +304,7 @@ export async function scanSwiftProjects(
for (const manifestFile of manifestFiles) {
const dir = path.dirname(manifestFile);
try {
const scanPromise = scanOneSwiftProject(dir, manifestFile, rootDir, swiftCache, cache);
const scanPromise = scanOneSwiftProject(dir, manifestFile, rootDir, swiftCache, cache, catalog);
const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
if (result.ok) {
results.push(result.value);
Expand Down Expand Up @@ -335,7 +336,8 @@ async function scanOneSwiftProject(
manifestFile: string,
rootDir: string,
swiftCache: SwiftCache,
cache?: FileCache,
cache: FileCache | undefined,
catalog: RuntimeCatalog,
): Promise<ProjectScan> {
const relDir = path.relative(rootDir, dir) || '.';
const projectName = path.basename(dir === rootDir ? rootDir : dir);
Expand Down Expand Up @@ -364,12 +366,15 @@ async function scanOneSwiftProject(
if (verMatch) {
const reqMajor = parseInt(verMatch[1]!, 10);
const reqMinor = parseInt(verMatch[2]!, 10);
if (reqMajor === LATEST_SWIFT.major) {
runtimeMajorsBehind = Math.max(0, LATEST_SWIFT.minor - reqMinor);
} else if (reqMajor < LATEST_SWIFT.major) {
runtimeMajorsBehind = (LATEST_SWIFT.major - reqMajor) * 10 + LATEST_SWIFT.minor;
const latestSwift = latestStable(catalog, 'swift');
if (latestSwift) {
if (reqMajor === latestSwift.major) {
runtimeMajorsBehind = Math.max(0, latestSwift.minor - reqMinor);
} else if (reqMajor < latestSwift.major) {
runtimeMajorsBehind = (latestSwift.major - reqMajor) * 10 + latestSwift.minor;
}
runtimeLatest = `${latestSwift.major}.${latestSwift.minor}`;
}
runtimeLatest = `${LATEST_SWIFT.major}.${LATEST_SWIFT.minor}`;
}
}

Expand Down
Loading
Loading