check-npm-lockfile 0.0.1

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.
@@ -0,0 +1,398 @@
1
+ // src/parsers/yarn-lock.ts
2
+ import lockfile from "@yarnpkg/lockfile";
3
+ import { parse as parseYaml } from "yaml";
4
+ function detectYarnVersion(content) {
5
+ if (content.includes("__metadata:")) {
6
+ return "yarn-berry";
7
+ }
8
+ return "yarn-v1";
9
+ }
10
+ function parseYarnLockV1(content) {
11
+ const result = lockfile.parse(content);
12
+ if (result.type !== "success") {
13
+ throw new Error("Failed to parse yarn.lock file");
14
+ }
15
+ const packages = [];
16
+ const seen = /* @__PURE__ */ new Set();
17
+ for (const [key, value] of Object.entries(result.object)) {
18
+ const match = key.match(/^(@?[^@]+)@/);
19
+ if (!match) continue;
20
+ const name = match[1];
21
+ const version = value.version;
22
+ const uniqueKey = `${name}@${version}`;
23
+ if (seen.has(uniqueKey)) continue;
24
+ seen.add(uniqueKey);
25
+ packages.push({
26
+ name,
27
+ version,
28
+ resolved: value.resolved,
29
+ integrity: value.integrity
30
+ });
31
+ }
32
+ return packages;
33
+ }
34
+ function parseYarnLockBerry(content) {
35
+ const parsed = parseYaml(content);
36
+ const packages = [];
37
+ const seen = /* @__PURE__ */ new Set();
38
+ for (const [key, value] of Object.entries(parsed)) {
39
+ if (key === "__metadata") continue;
40
+ const descriptors = key.split(", ");
41
+ for (const descriptor of descriptors) {
42
+ const match = descriptor.match(/^(@?[^@]+)@/);
43
+ if (!match) continue;
44
+ const name = match[1];
45
+ const version = value.version;
46
+ const uniqueKey = `${name}@${version}`;
47
+ if (seen.has(uniqueKey)) continue;
48
+ seen.add(uniqueKey);
49
+ packages.push({
50
+ name,
51
+ version,
52
+ resolved: value.resolution,
53
+ integrity: value.checksum
54
+ });
55
+ break;
56
+ }
57
+ }
58
+ return packages;
59
+ }
60
+ function parseYarnLock(content) {
61
+ const version = detectYarnVersion(content);
62
+ const packages = version === "yarn-v1" ? parseYarnLockV1(content) : parseYarnLockBerry(content);
63
+ return { packages, version };
64
+ }
65
+
66
+ // src/parsers/package-lock.ts
67
+ function parsePackageLock(content) {
68
+ const parsed = JSON.parse(content);
69
+ const lockfileVersion = parsed.lockfileVersion || 1;
70
+ if (lockfileVersion >= 2) {
71
+ return parsePackageLockV2(parsed);
72
+ }
73
+ return parsePackageLockV1(parsed);
74
+ }
75
+ function parsePackageLockV2(data) {
76
+ const packages = [];
77
+ for (const [path, info] of Object.entries(data.packages || {})) {
78
+ if (path === "") continue;
79
+ const segments = path.split("node_modules/");
80
+ const packageName = segments[segments.length - 1];
81
+ if (!packageName || !info.version) continue;
82
+ packages.push({
83
+ name: packageName,
84
+ version: info.version,
85
+ resolved: info.resolved,
86
+ integrity: info.integrity
87
+ });
88
+ }
89
+ return deduplicatePackages(packages);
90
+ }
91
+ function parsePackageLockV1(data) {
92
+ const packages = [];
93
+ function traverse(deps) {
94
+ for (const [name, info] of Object.entries(deps)) {
95
+ packages.push({
96
+ name,
97
+ version: info.version,
98
+ resolved: info.resolved,
99
+ integrity: info.integrity
100
+ });
101
+ if (info.dependencies) {
102
+ traverse(info.dependencies);
103
+ }
104
+ }
105
+ }
106
+ traverse(data.dependencies || {});
107
+ return deduplicatePackages(packages);
108
+ }
109
+ function deduplicatePackages(packages) {
110
+ const seen = /* @__PURE__ */ new Map();
111
+ for (const pkg of packages) {
112
+ const key = `${pkg.name}@${pkg.version}`;
113
+ if (!seen.has(key)) {
114
+ seen.set(key, pkg);
115
+ }
116
+ }
117
+ return Array.from(seen.values());
118
+ }
119
+
120
+ // src/registry/npm-client.ts
121
+ import pLimit from "p-limit";
122
+ var NPM_REGISTRY = "https://registry.npmjs.org";
123
+ var NpmRegistryClient = class {
124
+ cache = /* @__PURE__ */ new Map();
125
+ limiter;
126
+ retryDelay = 1e3;
127
+ maxRetries = 3;
128
+ constructor(concurrency = 10) {
129
+ this.limiter = pLimit(concurrency);
130
+ }
131
+ async getPackageTime(name) {
132
+ const cached = this.cache.get(name);
133
+ if (cached) return cached;
134
+ return this.limiter(async () => {
135
+ const cachedAfterWait = this.cache.get(name);
136
+ if (cachedAfterWait) return cachedAfterWait;
137
+ const data = await this.fetchWithRetry(name);
138
+ this.cache.set(name, data);
139
+ return data;
140
+ });
141
+ }
142
+ async fetchWithRetry(name, attempt = 1) {
143
+ const url = `${NPM_REGISTRY}/${encodeURIComponent(name).replace("%40", "@")}`;
144
+ try {
145
+ const response = await fetch(url, {
146
+ headers: {
147
+ Accept: "application/json"
148
+ }
149
+ });
150
+ if (response.status === 429) {
151
+ if (attempt <= this.maxRetries) {
152
+ const delay = this.retryDelay * Math.pow(2, attempt - 1);
153
+ await this.sleep(delay);
154
+ return this.fetchWithRetry(name, attempt + 1);
155
+ }
156
+ throw new Error(`Rate limited after ${this.maxRetries} retries`);
157
+ }
158
+ if (response.status === 404) {
159
+ throw new Error(`Package not found: ${name}`);
160
+ }
161
+ if (!response.ok) {
162
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
163
+ }
164
+ const data = await response.json();
165
+ return data.time;
166
+ } catch (error) {
167
+ if (attempt <= this.maxRetries && this.isRetryableError(error)) {
168
+ const delay = this.retryDelay * Math.pow(2, attempt - 1);
169
+ await this.sleep(delay);
170
+ return this.fetchWithRetry(name, attempt + 1);
171
+ }
172
+ throw error;
173
+ }
174
+ }
175
+ isRetryableError(error) {
176
+ if (error instanceof Error) {
177
+ return error.message.includes("ECONNRESET") || error.message.includes("ETIMEDOUT") || error.message.includes("fetch failed");
178
+ }
179
+ return false;
180
+ }
181
+ sleep(ms) {
182
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
183
+ }
184
+ };
185
+
186
+ // src/analyzer/index.ts
187
+ import { readFileSync, existsSync } from "fs";
188
+ import { resolve, basename } from "path";
189
+ function parseMinimumReleaseAge(value) {
190
+ const match = value.match(/^(\d+)\s*(days?|hours?|minutes?)?$/i);
191
+ if (!match) {
192
+ throw new Error(
193
+ `Invalid minimumReleaseAge format: "${value}". Use format like "3 days", "7 days", or "24 hours"`
194
+ );
195
+ }
196
+ const num = parseInt(match[1], 10);
197
+ const unit = (match[2] || "days").toLowerCase();
198
+ if (unit.startsWith("day")) {
199
+ return num;
200
+ } else if (unit.startsWith("hour")) {
201
+ return num / 24;
202
+ } else if (unit.startsWith("minute")) {
203
+ return num / (24 * 60);
204
+ }
205
+ return num;
206
+ }
207
+ async function analyzeLockfile(lockfilePath, options, onProgress) {
208
+ const resolvedPath = resolveLockfilePath(lockfilePath);
209
+ const content = readFileSync(resolvedPath, "utf-8");
210
+ const { packages, lockfileType } = parseLockfile(resolvedPath, content);
211
+ const filteredPackages = packages.filter(
212
+ (pkg) => !options.exclude.includes(pkg.name)
213
+ );
214
+ const client = new NpmRegistryClient(options.concurrency);
215
+ const thresholdDays = parseMinimumReleaseAge(options.minimumReleaseAge);
216
+ const thresholdDate = /* @__PURE__ */ new Date();
217
+ thresholdDate.setDate(thresholdDate.getDate() - thresholdDays);
218
+ const recentPackages = [];
219
+ const errors = [];
220
+ const total = filteredPackages.length;
221
+ let processed = 0;
222
+ const checkPromises = filteredPackages.map(async (pkg) => {
223
+ try {
224
+ const timeInfo = await client.getPackageTime(pkg.name);
225
+ const publishedAt = new Date(timeInfo[pkg.version]);
226
+ if (isNaN(publishedAt.getTime())) {
227
+ throw new Error(`Invalid publish date for version ${pkg.version}`);
228
+ }
229
+ const daysAgo = Math.floor(
230
+ (Date.now() - publishedAt.getTime()) / (1e3 * 60 * 60 * 24)
231
+ );
232
+ const isWithinThreshold = publishedAt >= thresholdDate;
233
+ const isNew = isFirstVersion(timeInfo, pkg.version);
234
+ if (isWithinThreshold) {
235
+ recentPackages.push({
236
+ name: pkg.name,
237
+ version: pkg.version,
238
+ publishedAt,
239
+ isNew,
240
+ daysAgo,
241
+ isWithinThreshold
242
+ });
243
+ }
244
+ } catch (error) {
245
+ errors.push({
246
+ name: pkg.name,
247
+ version: pkg.version,
248
+ error: error instanceof Error ? error.message : String(error)
249
+ });
250
+ } finally {
251
+ processed++;
252
+ onProgress?.(processed, total);
253
+ }
254
+ });
255
+ await Promise.all(checkPromises);
256
+ recentPackages.sort(
257
+ (a, b) => b.publishedAt.getTime() - a.publishedAt.getTime()
258
+ );
259
+ return {
260
+ lockfileType,
261
+ lockfilePath: resolvedPath,
262
+ totalPackages: filteredPackages.length,
263
+ checkedPackages: filteredPackages.length - errors.length,
264
+ recentPackages,
265
+ errors,
266
+ analysisDate: /* @__PURE__ */ new Date(),
267
+ thresholdDays
268
+ };
269
+ }
270
+ function resolveLockfilePath(path) {
271
+ if (path) {
272
+ const resolved = resolve(path);
273
+ if (!existsSync(resolved)) {
274
+ throw new Error(`Lockfile not found: ${resolved}`);
275
+ }
276
+ return resolved;
277
+ }
278
+ const candidates = ["yarn.lock", "package-lock.json"];
279
+ for (const candidate of candidates) {
280
+ const resolved = resolve(candidate);
281
+ if (existsSync(resolved)) {
282
+ return resolved;
283
+ }
284
+ }
285
+ throw new Error(
286
+ "No lockfile found. Please specify a path or run in a directory with yarn.lock or package-lock.json"
287
+ );
288
+ }
289
+ function parseLockfile(path, content) {
290
+ const filename = basename(path);
291
+ if (filename === "yarn.lock") {
292
+ const { packages, version } = parseYarnLock(content);
293
+ return { packages, lockfileType: version };
294
+ }
295
+ if (filename === "package-lock.json") {
296
+ return {
297
+ packages: parsePackageLock(content),
298
+ lockfileType: "npm"
299
+ };
300
+ }
301
+ throw new Error(`Unsupported lockfile: ${filename}`);
302
+ }
303
+ function isFirstVersion(timeInfo, version) {
304
+ const versions = Object.keys(timeInfo).filter(
305
+ (k) => k !== "created" && k !== "modified"
306
+ );
307
+ if (versions.length === 0) return true;
308
+ let earliestVersion = versions[0];
309
+ let earliestDate = new Date(timeInfo[versions[0]]);
310
+ for (const v of versions) {
311
+ const date = new Date(timeInfo[v]);
312
+ if (date < earliestDate) {
313
+ earliestDate = date;
314
+ earliestVersion = v;
315
+ }
316
+ }
317
+ return version === earliestVersion;
318
+ }
319
+
320
+ // src/output/console.ts
321
+ import chalk from "chalk";
322
+ function formatConsole(result) {
323
+ console.log();
324
+ console.log(chalk.bold("Lockfile Analysis Report"));
325
+ console.log(chalk.gray("-".repeat(50)));
326
+ console.log(`File: ${chalk.cyan(result.lockfilePath)}`);
327
+ console.log(`Type: ${chalk.cyan(result.lockfileType)}`);
328
+ console.log(`Threshold: ${chalk.yellow(result.thresholdDays)} days`);
329
+ console.log(`Total packages: ${result.totalPackages}`);
330
+ console.log(`Checked: ${result.checkedPackages}`);
331
+ console.log();
332
+ if (result.recentPackages.length === 0) {
333
+ console.log(chalk.green("No recently published packages found."));
334
+ } else {
335
+ console.log(
336
+ chalk.red.bold(
337
+ `Found ${result.recentPackages.length} package(s) published within ${result.thresholdDays} days:`
338
+ )
339
+ );
340
+ console.log();
341
+ for (const pkg of result.recentPackages) {
342
+ const newBadge = pkg.isNew ? chalk.bgRed.white(" NEW ") + " " : " ";
343
+ const daysLabel = pkg.daysAgo === 0 ? "today" : pkg.daysAgo === 1 ? "1 day ago" : `${pkg.daysAgo} days ago`;
344
+ console.log(
345
+ `${newBadge}${chalk.bold(pkg.name)}@${chalk.yellow(pkg.version)}`
346
+ );
347
+ console.log(
348
+ ` Published: ${chalk.gray(pkg.publishedAt.toISOString())} (${chalk.yellow(daysLabel)})`
349
+ );
350
+ }
351
+ }
352
+ if (result.errors.length > 0) {
353
+ console.log();
354
+ console.log(
355
+ chalk.yellow(
356
+ `Warnings: ${result.errors.length} package(s) could not be checked`
357
+ )
358
+ );
359
+ for (const err of result.errors) {
360
+ console.log(chalk.gray(` - ${err.name}@${err.version}: ${err.error}`));
361
+ }
362
+ }
363
+ console.log();
364
+ }
365
+
366
+ // src/output/json.ts
367
+ function formatJson(result) {
368
+ const output = {
369
+ ...result,
370
+ recentPackages: result.recentPackages.map((pkg) => ({
371
+ ...pkg,
372
+ publishedAt: pkg.publishedAt.toISOString()
373
+ })),
374
+ analysisDate: result.analysisDate.toISOString()
375
+ };
376
+ console.log(JSON.stringify(output, null, 2));
377
+ }
378
+
379
+ // src/output/index.ts
380
+ function formatOutput(result, format) {
381
+ if (format === "json") {
382
+ formatJson(result);
383
+ } else {
384
+ formatConsole(result);
385
+ }
386
+ }
387
+
388
+ export {
389
+ parseYarnLock,
390
+ parsePackageLock,
391
+ NpmRegistryClient,
392
+ parseMinimumReleaseAge,
393
+ analyzeLockfile,
394
+ formatConsole,
395
+ formatJson,
396
+ formatOutput
397
+ };
398
+ //# sourceMappingURL=chunk-3YXJPGA4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parsers/yarn-lock.ts","../src/parsers/package-lock.ts","../src/registry/npm-client.ts","../src/analyzer/index.ts","../src/output/console.ts","../src/output/json.ts","../src/output/index.ts"],"sourcesContent":["import lockfile from \"@yarnpkg/lockfile\";\nimport { parse as parseYaml } from \"yaml\";\nimport type { ParsedPackage } from \"../types/index.js\";\n\nexport function detectYarnVersion(content: string): \"yarn-v1\" | \"yarn-berry\" {\n if (content.includes(\"__metadata:\")) {\n return \"yarn-berry\";\n }\n return \"yarn-v1\";\n}\n\nexport function parseYarnLockV1(content: string): ParsedPackage[] {\n const result = lockfile.parse(content);\n\n if (result.type !== \"success\") {\n throw new Error(\"Failed to parse yarn.lock file\");\n }\n\n const packages: ParsedPackage[] = [];\n const seen = new Set<string>();\n\n for (const [key, value] of Object.entries(result.object)) {\n const match = key.match(/^(@?[^@]+)@/);\n if (!match) continue;\n\n const name = match[1];\n const version = (value as { version: string }).version;\n const uniqueKey = `${name}@${version}`;\n\n if (seen.has(uniqueKey)) continue;\n seen.add(uniqueKey);\n\n packages.push({\n name,\n version,\n resolved: (value as { resolved?: string }).resolved,\n integrity: (value as { integrity?: string }).integrity,\n });\n }\n\n return packages;\n}\n\nexport function parseYarnLockBerry(content: string): ParsedPackage[] {\n const parsed = parseYaml(content) as Record<string, unknown>;\n const packages: ParsedPackage[] = [];\n const seen = new Set<string>();\n\n for (const [key, value] of Object.entries(parsed)) {\n if (key === \"__metadata\") continue;\n\n const descriptors = key.split(\", \");\n\n for (const descriptor of descriptors) {\n const match = descriptor.match(/^(@?[^@]+)@/);\n if (!match) continue;\n\n const name = match[1];\n const version = (value as { version: string }).version;\n const uniqueKey = `${name}@${version}`;\n\n if (seen.has(uniqueKey)) continue;\n seen.add(uniqueKey);\n\n packages.push({\n name,\n version,\n resolved: (value as { resolution?: string }).resolution,\n integrity: (value as { checksum?: string }).checksum,\n });\n\n break;\n }\n }\n\n return packages;\n}\n\nexport function parseYarnLock(content: string): {\n packages: ParsedPackage[];\n version: \"yarn-v1\" | \"yarn-berry\";\n} {\n const version = detectYarnVersion(content);\n const packages =\n version === \"yarn-v1\"\n ? parseYarnLockV1(content)\n : parseYarnLockBerry(content);\n return { packages, version };\n}\n","import type { ParsedPackage } from \"../types/index.js\";\n\ninterface PackageLockV2 {\n lockfileVersion: number;\n packages: Record<\n string,\n {\n version: string;\n resolved?: string;\n integrity?: string;\n }\n >;\n}\n\ninterface PackageLockV1 {\n lockfileVersion: 1;\n dependencies: Record<\n string,\n {\n version: string;\n resolved?: string;\n integrity?: string;\n dependencies?: Record<string, unknown>;\n }\n >;\n}\n\nexport function parsePackageLock(content: string): ParsedPackage[] {\n const parsed = JSON.parse(content) as { lockfileVersion?: number };\n const lockfileVersion = parsed.lockfileVersion || 1;\n\n if (lockfileVersion >= 2) {\n return parsePackageLockV2(parsed as PackageLockV2);\n }\n\n return parsePackageLockV1(parsed as PackageLockV1);\n}\n\nfunction parsePackageLockV2(data: PackageLockV2): ParsedPackage[] {\n const packages: ParsedPackage[] = [];\n\n for (const [path, info] of Object.entries(data.packages || {})) {\n if (path === \"\") continue;\n\n const segments = path.split(\"node_modules/\");\n const packageName = segments[segments.length - 1];\n\n if (!packageName || !info.version) continue;\n\n packages.push({\n name: packageName,\n version: info.version,\n resolved: info.resolved,\n integrity: info.integrity,\n });\n }\n\n return deduplicatePackages(packages);\n}\n\nfunction parsePackageLockV1(data: PackageLockV1): ParsedPackage[] {\n const packages: ParsedPackage[] = [];\n\n function traverse(deps: Record<string, { version: string; resolved?: string; integrity?: string; dependencies?: Record<string, unknown> }>) {\n for (const [name, info] of Object.entries(deps)) {\n packages.push({\n name,\n version: info.version,\n resolved: info.resolved,\n integrity: info.integrity,\n });\n\n if (info.dependencies) {\n traverse(info.dependencies as typeof deps);\n }\n }\n }\n\n traverse(data.dependencies || {});\n return deduplicatePackages(packages);\n}\n\nfunction deduplicatePackages(packages: ParsedPackage[]): ParsedPackage[] {\n const seen = new Map<string, ParsedPackage>();\n\n for (const pkg of packages) {\n const key = `${pkg.name}@${pkg.version}`;\n if (!seen.has(key)) {\n seen.set(key, pkg);\n }\n }\n\n return Array.from(seen.values());\n}\n","import pLimit from \"p-limit\";\nimport type { PackageTimeInfo } from \"../types/index.js\";\n\nconst NPM_REGISTRY = \"https://registry.npmjs.org\";\n\nexport class NpmRegistryClient {\n private cache: Map<string, PackageTimeInfo> = new Map();\n private limiter: ReturnType<typeof pLimit>;\n private retryDelay = 1000;\n private maxRetries = 3;\n\n constructor(concurrency = 10) {\n this.limiter = pLimit(concurrency);\n }\n\n async getPackageTime(name: string): Promise<PackageTimeInfo> {\n const cached = this.cache.get(name);\n if (cached) return cached;\n\n return this.limiter(async () => {\n const cachedAfterWait = this.cache.get(name);\n if (cachedAfterWait) return cachedAfterWait;\n\n const data = await this.fetchWithRetry(name);\n this.cache.set(name, data);\n return data;\n });\n }\n\n private async fetchWithRetry(\n name: string,\n attempt = 1\n ): Promise<PackageTimeInfo> {\n const url = `${NPM_REGISTRY}/${encodeURIComponent(name).replace(\"%40\", \"@\")}`;\n\n try {\n const response = await fetch(url, {\n headers: {\n Accept: \"application/json\",\n },\n });\n\n if (response.status === 429) {\n if (attempt <= this.maxRetries) {\n const delay = this.retryDelay * Math.pow(2, attempt - 1);\n await this.sleep(delay);\n return this.fetchWithRetry(name, attempt + 1);\n }\n throw new Error(`Rate limited after ${this.maxRetries} retries`);\n }\n\n if (response.status === 404) {\n throw new Error(`Package not found: ${name}`);\n }\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as { time: PackageTimeInfo };\n return data.time;\n } catch (error) {\n if (attempt <= this.maxRetries && this.isRetryableError(error)) {\n const delay = this.retryDelay * Math.pow(2, attempt - 1);\n await this.sleep(delay);\n return this.fetchWithRetry(name, attempt + 1);\n }\n throw error;\n }\n }\n\n private isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n return (\n error.message.includes(\"ECONNRESET\") ||\n error.message.includes(\"ETIMEDOUT\") ||\n error.message.includes(\"fetch failed\")\n );\n }\n return false;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import { readFileSync, existsSync } from \"fs\";\nimport { resolve, basename } from \"path\";\nimport { parseYarnLock } from \"../parsers/yarn-lock.js\";\nimport { parsePackageLock } from \"../parsers/package-lock.js\";\nimport { NpmRegistryClient } from \"../registry/npm-client.js\";\nimport type {\n ParsedPackage,\n AnalysisResult,\n PackageCheckResult,\n PackageError,\n CLIOptions,\n LockfileType,\n PackageTimeInfo,\n} from \"../types/index.js\";\n\nexport function parseMinimumReleaseAge(value: string): number {\n const match = value.match(/^(\\d+)\\s*(days?|hours?|minutes?)?$/i);\n if (!match) {\n throw new Error(\n `Invalid minimumReleaseAge format: \"${value}\". Use format like \"3 days\", \"7 days\", or \"24 hours\"`\n );\n }\n\n const num = parseInt(match[1], 10);\n const unit = (match[2] || \"days\").toLowerCase();\n\n if (unit.startsWith(\"day\")) {\n return num;\n } else if (unit.startsWith(\"hour\")) {\n return num / 24;\n } else if (unit.startsWith(\"minute\")) {\n return num / (24 * 60);\n }\n\n return num;\n}\n\nexport async function analyzeLockfile(\n lockfilePath: string | undefined,\n options: CLIOptions,\n onProgress?: (current: number, total: number) => void\n): Promise<AnalysisResult> {\n const resolvedPath = resolveLockfilePath(lockfilePath);\n const content = readFileSync(resolvedPath, \"utf-8\");\n\n const { packages, lockfileType } = parseLockfile(resolvedPath, content);\n\n const filteredPackages = packages.filter(\n (pkg) => !options.exclude.includes(pkg.name)\n );\n\n const client = new NpmRegistryClient(options.concurrency);\n const thresholdDays = parseMinimumReleaseAge(options.minimumReleaseAge);\n const thresholdDate = new Date();\n thresholdDate.setDate(thresholdDate.getDate() - thresholdDays);\n\n const recentPackages: PackageCheckResult[] = [];\n const errors: PackageError[] = [];\n\n const total = filteredPackages.length;\n let processed = 0;\n\n const checkPromises = filteredPackages.map(async (pkg) => {\n try {\n const timeInfo = await client.getPackageTime(pkg.name);\n const publishedAt = new Date(timeInfo[pkg.version]);\n\n if (isNaN(publishedAt.getTime())) {\n throw new Error(`Invalid publish date for version ${pkg.version}`);\n }\n\n const daysAgo = Math.floor(\n (Date.now() - publishedAt.getTime()) / (1000 * 60 * 60 * 24)\n );\n\n const isWithinThreshold = publishedAt >= thresholdDate;\n const isNew = isFirstVersion(timeInfo, pkg.version);\n\n if (isWithinThreshold) {\n recentPackages.push({\n name: pkg.name,\n version: pkg.version,\n publishedAt,\n isNew,\n daysAgo,\n isWithinThreshold,\n });\n }\n } catch (error) {\n errors.push({\n name: pkg.name,\n version: pkg.version,\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n processed++;\n onProgress?.(processed, total);\n }\n });\n\n await Promise.all(checkPromises);\n\n recentPackages.sort(\n (a, b) => b.publishedAt.getTime() - a.publishedAt.getTime()\n );\n\n return {\n lockfileType,\n lockfilePath: resolvedPath,\n totalPackages: filteredPackages.length,\n checkedPackages: filteredPackages.length - errors.length,\n recentPackages,\n errors,\n analysisDate: new Date(),\n thresholdDays,\n };\n}\n\nfunction resolveLockfilePath(path?: string): string {\n if (path) {\n const resolved = resolve(path);\n if (!existsSync(resolved)) {\n throw new Error(`Lockfile not found: ${resolved}`);\n }\n return resolved;\n }\n\n const candidates = [\"yarn.lock\", \"package-lock.json\"];\n\n for (const candidate of candidates) {\n const resolved = resolve(candidate);\n if (existsSync(resolved)) {\n return resolved;\n }\n }\n\n throw new Error(\n \"No lockfile found. Please specify a path or run in a directory with yarn.lock or package-lock.json\"\n );\n}\n\nfunction parseLockfile(\n path: string,\n content: string\n): { packages: ParsedPackage[]; lockfileType: LockfileType } {\n const filename = basename(path);\n\n if (filename === \"yarn.lock\") {\n const { packages, version } = parseYarnLock(content);\n return { packages, lockfileType: version };\n }\n\n if (filename === \"package-lock.json\") {\n return {\n packages: parsePackageLock(content),\n lockfileType: \"npm\",\n };\n }\n\n throw new Error(`Unsupported lockfile: ${filename}`);\n}\n\nfunction isFirstVersion(\n timeInfo: PackageTimeInfo,\n version: string\n): boolean {\n const versions = Object.keys(timeInfo).filter(\n (k) => k !== \"created\" && k !== \"modified\"\n );\n\n if (versions.length === 0) return true;\n\n let earliestVersion = versions[0];\n let earliestDate = new Date(timeInfo[versions[0]]);\n\n for (const v of versions) {\n const date = new Date(timeInfo[v]);\n if (date < earliestDate) {\n earliestDate = date;\n earliestVersion = v;\n }\n }\n\n return version === earliestVersion;\n}\n","import chalk from \"chalk\";\nimport type { AnalysisResult } from \"../types/index.js\";\n\nexport function formatConsole(result: AnalysisResult): void {\n console.log();\n console.log(chalk.bold(\"Lockfile Analysis Report\"));\n console.log(chalk.gray(\"-\".repeat(50)));\n console.log(`File: ${chalk.cyan(result.lockfilePath)}`);\n console.log(`Type: ${chalk.cyan(result.lockfileType)}`);\n console.log(`Threshold: ${chalk.yellow(result.thresholdDays)} days`);\n console.log(`Total packages: ${result.totalPackages}`);\n console.log(`Checked: ${result.checkedPackages}`);\n console.log();\n\n if (result.recentPackages.length === 0) {\n console.log(chalk.green(\"No recently published packages found.\"));\n } else {\n console.log(\n chalk.red.bold(\n `Found ${result.recentPackages.length} package(s) published within ${result.thresholdDays} days:`\n )\n );\n console.log();\n\n for (const pkg of result.recentPackages) {\n const newBadge = pkg.isNew ? chalk.bgRed.white(\" NEW \") + \" \" : \" \";\n const daysLabel =\n pkg.daysAgo === 0\n ? \"today\"\n : pkg.daysAgo === 1\n ? \"1 day ago\"\n : `${pkg.daysAgo} days ago`;\n\n console.log(\n `${newBadge}${chalk.bold(pkg.name)}@${chalk.yellow(pkg.version)}`\n );\n console.log(\n ` Published: ${chalk.gray(pkg.publishedAt.toISOString())} (${chalk.yellow(daysLabel)})`\n );\n }\n }\n\n if (result.errors.length > 0) {\n console.log();\n console.log(\n chalk.yellow(\n `Warnings: ${result.errors.length} package(s) could not be checked`\n )\n );\n\n for (const err of result.errors) {\n console.log(chalk.gray(` - ${err.name}@${err.version}: ${err.error}`));\n }\n }\n\n console.log();\n}\n","import type { AnalysisResult } from \"../types/index.js\";\n\nexport function formatJson(result: AnalysisResult): void {\n const output = {\n ...result,\n recentPackages: result.recentPackages.map((pkg) => ({\n ...pkg,\n publishedAt: pkg.publishedAt.toISOString(),\n })),\n analysisDate: result.analysisDate.toISOString(),\n };\n\n console.log(JSON.stringify(output, null, 2));\n}\n","import type { AnalysisResult } from \"../types/index.js\";\nimport { formatConsole } from \"./console.js\";\nimport { formatJson } from \"./json.js\";\n\nexport function formatOutput(\n result: AnalysisResult,\n format: \"console\" | \"json\"\n): void {\n if (format === \"json\") {\n formatJson(result);\n } else {\n formatConsole(result);\n }\n}\n\nexport { formatConsole } from \"./console.js\";\nexport { formatJson } from \"./json.js\";\n"],"mappings":"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","names":["resolve"]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ analyzeLockfile,
4
+ formatOutput
5
+ } from "./chunk-3YXJPGA4.js";
6
+
7
+ // src/cli.ts
8
+ import { Command } from "commander";
9
+ import ora from "ora";
10
+ var program = new Command();
11
+ program.name("check-npm-lockfile").description(
12
+ "Detect recently published npm packages in lockfiles for supply chain attack prevention"
13
+ ).version("0.0.1").argument("[lockfile]", "Path to lockfile (auto-detects if not specified)").option(
14
+ "--minimum-release-age <duration>",
15
+ "Minimum age threshold (e.g., '3 days', '7 days')",
16
+ "3 days"
17
+ ).option(
18
+ "-e, --exclude <packages...>",
19
+ "Packages to exclude from checking",
20
+ []
21
+ ).option("-f, --format <type>", "Output format: console or json", "console").option(
22
+ "-c, --concurrency <number>",
23
+ "Max concurrent API requests",
24
+ "10"
25
+ ).option("--no-exit-code", "Always exit with code 0").option("-v, --verbose", "Show verbose output").action(
26
+ async (lockfilePath, opts) => {
27
+ const options = {
28
+ minimumReleaseAge: opts.minimumReleaseAge,
29
+ exclude: opts.exclude,
30
+ format: opts.format,
31
+ concurrency: parseInt(opts.concurrency, 10),
32
+ exitCode: opts.exitCode,
33
+ verbose: opts.verbose
34
+ };
35
+ const spinner = options.format === "console" ? ora("Analyzing lockfile...").start() : null;
36
+ try {
37
+ const result = await analyzeLockfile(
38
+ lockfilePath,
39
+ options,
40
+ (current, total) => {
41
+ if (spinner && options.verbose) {
42
+ spinner.text = `Checking packages: ${current}/${total}`;
43
+ }
44
+ }
45
+ );
46
+ spinner?.stop();
47
+ formatOutput(result, options.format);
48
+ if (options.exitCode && result.recentPackages.length > 0) {
49
+ process.exit(1);
50
+ }
51
+ } catch (error) {
52
+ spinner?.fail(
53
+ error instanceof Error ? error.message : "Unknown error"
54
+ );
55
+ process.exit(1);
56
+ }
57
+ }
58
+ );
59
+ program.parse();
60
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport { analyzeLockfile } from \"./analyzer/index.js\";\nimport { formatOutput } from \"./output/index.js\";\nimport type { CLIOptions } from \"./types/index.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"check-npm-lockfile\")\n .description(\n \"Detect recently published npm packages in lockfiles for supply chain attack prevention\"\n )\n .version(\"0.0.1\")\n .argument(\"[lockfile]\", \"Path to lockfile (auto-detects if not specified)\")\n .option(\n \"--minimum-release-age <duration>\",\n \"Minimum age threshold (e.g., '3 days', '7 days')\",\n \"3 days\"\n )\n .option(\n \"-e, --exclude <packages...>\",\n \"Packages to exclude from checking\",\n []\n )\n .option(\"-f, --format <type>\", \"Output format: console or json\", \"console\")\n .option(\n \"-c, --concurrency <number>\",\n \"Max concurrent API requests\",\n \"10\"\n )\n .option(\"--no-exit-code\", \"Always exit with code 0\")\n .option(\"-v, --verbose\", \"Show verbose output\")\n .action(\n async (\n lockfilePath: string | undefined,\n opts: {\n minimumReleaseAge: string;\n exclude: string[];\n format: string;\n concurrency: string;\n exitCode: boolean;\n verbose: boolean;\n }\n ) => {\n const options: CLIOptions = {\n minimumReleaseAge: opts.minimumReleaseAge,\n exclude: opts.exclude,\n format: opts.format as \"console\" | \"json\",\n concurrency: parseInt(opts.concurrency, 10),\n exitCode: opts.exitCode,\n verbose: opts.verbose,\n };\n\n const spinner =\n options.format === \"console\" ? ora(\"Analyzing lockfile...\").start() : null;\n\n try {\n const result = await analyzeLockfile(\n lockfilePath,\n options,\n (current, total) => {\n if (spinner && options.verbose) {\n spinner.text = `Checking packages: ${current}/${total}`;\n }\n }\n );\n\n spinner?.stop();\n\n formatOutput(result, options.format);\n\n if (options.exitCode && result.recentPackages.length > 0) {\n process.exit(1);\n }\n } catch (error) {\n spinner?.fail(\n error instanceof Error ? error.message : \"Unknown error\"\n );\n process.exit(1);\n }\n }\n );\n\nprogram.parse();\n"],"mappings":";;;;;;;AACA,SAAS,eAAe;AACxB,OAAO,SAAS;AAKhB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,oBAAoB,EACzB;AAAA,EACC;AACF,EACC,QAAQ,OAAO,EACf,SAAS,cAAc,kDAAkD,EACzE;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA,CAAC;AACH,EACC,OAAO,uBAAuB,kCAAkC,SAAS,EACzE;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC,OAAO,kBAAkB,yBAAyB,EAClD,OAAO,iBAAiB,qBAAqB,EAC7C;AAAA,EACC,OACE,cACA,SAQG;AACH,UAAM,UAAsB;AAAA,MAC1B,mBAAmB,KAAK;AAAA,MACxB,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,MACb,aAAa,SAAS,KAAK,aAAa,EAAE;AAAA,MAC1C,UAAU,KAAK;AAAA,MACf,SAAS,KAAK;AAAA,IAChB;AAEA,UAAM,UACJ,QAAQ,WAAW,YAAY,IAAI,uBAAuB,EAAE,MAAM,IAAI;AAExE,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,QACA;AAAA,QACA,CAAC,SAAS,UAAU;AAClB,cAAI,WAAW,QAAQ,SAAS;AAC9B,oBAAQ,OAAO,sBAAsB,OAAO,IAAI,KAAK;AAAA,UACvD;AAAA,QACF;AAAA,MACF;AAEA,eAAS,KAAK;AAEd,mBAAa,QAAQ,QAAQ,MAAM;AAEnC,UAAI,QAAQ,YAAY,OAAO,eAAe,SAAS,GAAG;AACxD,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,SAAS,OAAO;AACd,eAAS;AAAA,QACP,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAC3C;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF;AAEF,QAAQ,MAAM;","names":[]}
@@ -0,0 +1,80 @@
1
+ /** Lockfile type discriminator */
2
+ type LockfileType = "yarn-v1" | "yarn-berry" | "npm";
3
+ /** Parsed package information from lockfile */
4
+ interface ParsedPackage {
5
+ name: string;
6
+ version: string;
7
+ resolved?: string;
8
+ integrity?: string;
9
+ }
10
+ /** npm registry package time information */
11
+ interface PackageTimeInfo {
12
+ created: string;
13
+ modified: string;
14
+ [version: string]: string;
15
+ }
16
+ /** Result of checking a single package */
17
+ interface PackageCheckResult {
18
+ name: string;
19
+ version: string;
20
+ publishedAt: Date;
21
+ isNew: boolean;
22
+ daysAgo: number;
23
+ isWithinThreshold: boolean;
24
+ }
25
+ /** Overall analysis result */
26
+ interface AnalysisResult {
27
+ lockfileType: LockfileType;
28
+ lockfilePath: string;
29
+ totalPackages: number;
30
+ checkedPackages: number;
31
+ recentPackages: PackageCheckResult[];
32
+ errors: PackageError[];
33
+ analysisDate: Date;
34
+ thresholdDays: number;
35
+ }
36
+ /** Error information for packages that couldn't be checked */
37
+ interface PackageError {
38
+ name: string;
39
+ version: string;
40
+ error: string;
41
+ }
42
+ /** CLI options */
43
+ interface CLIOptions {
44
+ minimumReleaseAge: string;
45
+ exclude: string[];
46
+ format: "console" | "json";
47
+ concurrency: number;
48
+ exitCode: boolean;
49
+ verbose: boolean;
50
+ }
51
+
52
+ declare function parseMinimumReleaseAge(value: string): number;
53
+ declare function analyzeLockfile(lockfilePath: string | undefined, options: CLIOptions, onProgress?: (current: number, total: number) => void): Promise<AnalysisResult>;
54
+
55
+ declare function parseYarnLock(content: string): {
56
+ packages: ParsedPackage[];
57
+ version: "yarn-v1" | "yarn-berry";
58
+ };
59
+
60
+ declare function parsePackageLock(content: string): ParsedPackage[];
61
+
62
+ declare class NpmRegistryClient {
63
+ private cache;
64
+ private limiter;
65
+ private retryDelay;
66
+ private maxRetries;
67
+ constructor(concurrency?: number);
68
+ getPackageTime(name: string): Promise<PackageTimeInfo>;
69
+ private fetchWithRetry;
70
+ private isRetryableError;
71
+ private sleep;
72
+ }
73
+
74
+ declare function formatConsole(result: AnalysisResult): void;
75
+
76
+ declare function formatJson(result: AnalysisResult): void;
77
+
78
+ declare function formatOutput(result: AnalysisResult, format: "console" | "json"): void;
79
+
80
+ export { type AnalysisResult, type CLIOptions, type LockfileType, NpmRegistryClient, type PackageCheckResult, type PackageError, type PackageTimeInfo, type ParsedPackage, analyzeLockfile, formatConsole, formatJson, formatOutput, parseMinimumReleaseAge, parsePackageLock, parseYarnLock };
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import {
2
+ NpmRegistryClient,
3
+ analyzeLockfile,
4
+ formatConsole,
5
+ formatJson,
6
+ formatOutput,
7
+ parseMinimumReleaseAge,
8
+ parsePackageLock,
9
+ parseYarnLock
10
+ } from "./chunk-3YXJPGA4.js";
11
+ export {
12
+ NpmRegistryClient,
13
+ analyzeLockfile,
14
+ formatConsole,
15
+ formatJson,
16
+ formatOutput,
17
+ parseMinimumReleaseAge,
18
+ parsePackageLock,
19
+ parseYarnLock
20
+ };
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "check-npm-lockfile",
3
+ "version": "0.0.1",
4
+ "description": "Detect recently published npm packages in lockfiles for supply chain attack prevention",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "check-npm-lockfile": "./dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "author": "Masahiro Saito",
20
+ "license": "MIT",
21
+ "engines": {
22
+ "node": ">=22.0.0"
23
+ },
24
+ "dependencies": {
25
+ "@yarnpkg/lockfile": "^1.1.0",
26
+ "chalk": "^5.4.1",
27
+ "commander": "^13.0.0",
28
+ "ora": "^8.1.1",
29
+ "p-limit": "^6.2.0",
30
+ "yaml": "^2.7.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.10.5",
34
+ "tsup": "^8.3.5",
35
+ "typescript": "^5.7.2"
36
+ }
37
+ }