@adonisjs/content 1.2.0 → 1.4.0

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.
@@ -1,229 +1,155 @@
1
+ import {
2
+ aggregateInstalls,
3
+ aggregateStars,
4
+ createCache,
5
+ fetchAllSponsors,
6
+ fetchContributorsForOrg,
7
+ fetchReleases,
8
+ mergeArrays
9
+ } from "../../chunk-WO5VTK7D.js";
1
10
  import {
2
11
  debug_default
3
12
  } from "../../chunk-LB6JFRVG.js";
4
13
 
5
- // src/loaders/gh_sponsors.ts
6
- import dayjs from "dayjs";
14
+ // src/loaders/json.ts
7
15
  import vine from "@vinejs/vine";
8
16
  import { dirname } from "path";
9
- import { mkdir, readFile, writeFile } from "fs/promises";
17
+ import { readFile } from "fs/promises";
18
+ var JsonLoader = class {
19
+ /** Path to the JSON file to load */
20
+ #source;
21
+ /**
22
+ * Creates a new JSON loader instance.
23
+ *
24
+ * @param source - Absolute or relative path to the JSON file
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const loader = new JsonLoader('./data/menu.json')
29
+ * ```
30
+ */
31
+ constructor(source) {
32
+ this.#source = source;
33
+ }
34
+ /**
35
+ * Loads and validates JSON data from the configured file.
36
+ * The directory of the source file is provided as metadata during validation.
37
+ *
38
+ * @param schema - VineJS schema to validate the loaded JSON data against
39
+ * @param metadata - Optional metadata to pass to the validator
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const data = await loader.load(menuSchema)
44
+ * ```
45
+ */
46
+ async load(schema, metadata) {
47
+ const menuFileRoot = dirname(this.#source);
48
+ const menu = JSON.parse(await readFile(this.#source, "utf-8"));
49
+ debug_default('loading file "%s"', this.#source);
50
+ return vine.validate({ schema, data: menu, meta: { menuFileRoot, ...metadata } });
51
+ }
52
+ };
10
53
 
11
- // src/utils.ts
12
- import { Octokit } from "@octokit/rest";
13
- import { graphql } from "@octokit/graphql";
14
- async function fetchAllSponsors({
15
- login,
16
- isOrg,
17
- ghToken
18
- }) {
19
- let hasNext = true;
20
- let cursor = null;
21
- const allSponsors = [];
22
- const query = `
23
- query($login: String!, $cursor: String) {
24
- ${isOrg ? `organization(login: $login)` : `user(login: $login)`} {
25
- sponsorshipsAsMaintainer(first: 100, after: $cursor) {
26
- nodes {
27
- id
28
- createdAt
29
- privacyLevel
30
- tier {
31
- name
32
- monthlyPriceInCents
33
- }
34
- sponsorEntity {
35
- __typename
36
- ... on User {
37
- login
38
- name
39
- avatarUrl
40
- url
41
- }
42
- ... on Organization {
43
- login
44
- name
45
- avatarUrl
46
- url
47
- }
48
- }
49
- }
50
- pageInfo {
51
- hasNextPage
52
- endCursor
53
- }
54
- }
55
- }
56
- }
57
- `;
58
- while (hasNext) {
59
- const data = await graphql(query, {
60
- headers: {
61
- authorization: `token ${ghToken}`
62
- },
63
- login,
64
- cursor
54
+ // src/loaders/oss_stats.ts
55
+ import vine2 from "@vinejs/vine";
56
+ var OssStatsLoader = class {
57
+ /** Configuration options for the OSS stats loader */
58
+ #options;
59
+ /** Cache instance for storing and retrieving OSS stats data */
60
+ #cache;
61
+ /**
62
+ * Creates a new OSS stats loader instance.
63
+ *
64
+ * @param options - Configuration options for loading OSS statistics
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const loader = new OssStatsLoader({
69
+ * outputPath: './cache/oss-stats.json',
70
+ * refresh: 'weekly',
71
+ * sources: [
72
+ * {
73
+ * type: 'github',
74
+ * org: 'adonisjs',
75
+ * ghToken: process.env.GITHUB_TOKEN
76
+ * }
77
+ * ]
78
+ * })
79
+ * ```
80
+ */
81
+ constructor(options) {
82
+ this.#options = options;
83
+ this.#cache = createCache({
84
+ key: "ossStats",
85
+ outputPath: options.outputPath,
86
+ refresh: options.refresh
65
87
  });
66
- const root = isOrg ? data.organization : data.user;
67
- if (!root) {
68
- break;
69
- }
70
- const conn = root.sponsorshipsAsMaintainer;
71
- if (!conn || !conn.nodes) {
72
- break;
73
- }
74
- for (const node of conn.nodes) {
75
- const sponsorEntity = node.sponsorEntity;
76
- allSponsors.push({
77
- id: node.id,
78
- createdAt: node.createdAt,
79
- privacyLevel: node.privacyLevel,
80
- tierName: node.tier?.name ?? null,
81
- tierMonthlyPriceInCents: node.tier?.monthlyPriceInCents ?? null,
82
- sponsorType: sponsorEntity.__typename,
83
- sponsorLogin: sponsorEntity.login,
84
- sponsorName: sponsorEntity.name ?? null,
85
- sponsorAvatarUrl: sponsorEntity.avatarUrl ?? null,
86
- sponsorUrl: sponsorEntity.url ?? null
87
- });
88
- }
89
- hasNext = conn.pageInfo.hasNextPage;
90
- cursor = conn.pageInfo.endCursor;
91
88
  }
92
- return allSponsors;
93
- }
94
- async function fetchReleases({
95
- org,
96
- ghToken,
97
- filters
98
- }) {
99
- let hasMoreRepos = true;
100
- let orgCursor = null;
101
- const allReleases = [];
102
- while (hasMoreRepos) {
103
- const query = `
104
- query($cursor: String) {
105
- organization(login: "${org}") {
106
- repositories(
107
- first: 10
108
- after: $cursor
109
- privacy: PUBLIC
110
- isArchived: false
111
- ) {
112
- nodes {
113
- name
114
- releases(first: 50, orderBy: {field: CREATED_AT, direction: DESC}) {
115
- nodes {
116
- name
117
- tagName
118
- publishedAt
119
- url
120
- description
121
- }
122
- pageInfo {
123
- endCursor
124
- hasNextPage
125
- }
126
- }
127
- }
128
- pageInfo {
129
- endCursor
130
- hasNextPage
131
- }
132
- }
133
- }
89
+ /**
90
+ * Fetches and aggregates statistics from all configured sources.
91
+ * Processes GitHub and npm sources separately, then combines the results.
92
+ *
93
+ * @returns Aggregated statistics with total stars and installs
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * const stats = await loader.fetchStats()
98
+ * console.log(`Total stars: ${stats.stars}`)
99
+ * console.log(`Total installs: ${stats.installs}`)
100
+ * ```
101
+ */
102
+ async #fetchStats() {
103
+ let totalStars = 0;
104
+ let totalInstalls = 0;
105
+ for (const source of this.#options.sources) {
106
+ if (source.type === "github") {
107
+ const stars = await aggregateStars({
108
+ org: source.org,
109
+ ghToken: source.ghToken
110
+ });
111
+ totalStars += stars;
112
+ } else if (source.type === "npm") {
113
+ const installs = await aggregateInstalls(source.packages);
114
+ totalInstalls += installs;
134
115
  }
135
- `;
136
- const data = await graphql(query, {
137
- headers: {
138
- authorization: `token ${ghToken}`
139
- },
140
- cursor: orgCursor
141
- });
142
- for (const repo of data.organization.repositories.nodes) {
143
- const filtered = repo.releases.nodes.filter((r) => {
144
- if (!filters) {
145
- return true;
146
- }
147
- let pickRelease = true;
148
- if (filters.nameDoesntInclude) {
149
- pickRelease = !filters.nameDoesntInclude.some((substr) => r.name.includes(substr));
150
- }
151
- if (pickRelease && filters.nameIncludes) {
152
- pickRelease = filters.nameIncludes.some((substr) => r.name.includes(substr));
153
- }
154
- return pickRelease;
155
- }).map((r) => ({
156
- repo: repo.name,
157
- ...r
158
- }));
159
- allReleases.push(...filtered);
160
- }
161
- hasMoreRepos = data.organization.repositories.pageInfo.hasNextPage;
162
- orgCursor = data.organization.repositories.pageInfo.endCursor;
163
- }
164
- return allReleases;
165
- }
166
- async function fetchContributorsForOrg({
167
- org,
168
- ghToken
169
- }) {
170
- const REPO_PAGE_SIZE = 100;
171
- const CONTRIB_PAGE_SIZE = 100;
172
- const octokit = new Octokit({ auth: ghToken });
173
- const repos = await octokit.paginate(octokit.repos.listForOrg, {
174
- org,
175
- type: "public",
176
- per_page: REPO_PAGE_SIZE
177
- });
178
- const activeRepos = repos.filter((r) => !r.archived);
179
- const result = [];
180
- for (const repo of activeRepos) {
181
- const repoName = repo.name;
182
- try {
183
- const contributors = await octokit.paginate(
184
- octokit.repos.listContributors,
185
- {
186
- owner: org,
187
- repo: repoName,
188
- per_page: CONTRIB_PAGE_SIZE
189
- },
190
- (response) => response.data
191
- );
192
- contributors.forEach((c) => {
193
- if (c.login) {
194
- result.push({
195
- login: c.login,
196
- id: c.id,
197
- avatar_url: c.avatar_url ?? null,
198
- html_url: c.html_url ?? null,
199
- contributions: c.contributions ?? 0
200
- });
201
- }
202
- });
203
- } catch (err) {
204
- console.warn(
205
- `Warning: failed to fetch contributors for ${org}/${repoName}: ${err?.message ?? err}`
206
- );
207
116
  }
117
+ return { stars: totalStars, installs: totalInstalls };
208
118
  }
209
- return result;
210
- }
211
- function mergeArrays(existing, fresh, key) {
212
- const seen = /* @__PURE__ */ new Set();
213
- const deduped = [...existing];
214
- for (const r of fresh) {
215
- if (!seen.has(r[key])) {
216
- seen.add(r[key]);
217
- deduped.push(r);
119
+ /**
120
+ * Loads and validates OSS statistics data.
121
+ * Uses cached data if available and not expired, otherwise fetches fresh data
122
+ * from configured sources and updates the cache.
123
+ *
124
+ * @param schema - VineJS schema to validate the statistics data against
125
+ * @param metadata - Optional metadata to pass to the validator
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const stats = await loader.load(ossStatsSchema)
130
+ * ```
131
+ */
132
+ async load(schema, metadata) {
133
+ let stats = await this.#cache.get();
134
+ if (!stats) {
135
+ stats = await this.#fetchStats();
136
+ await this.#cache.put(stats);
218
137
  }
138
+ return vine2.validate({
139
+ schema,
140
+ data: stats,
141
+ meta: metadata
142
+ });
219
143
  }
220
- return deduped;
221
- }
144
+ };
222
145
 
223
146
  // src/loaders/gh_sponsors.ts
147
+ import vine3 from "@vinejs/vine";
224
148
  var GithubSponsorsLoader = class {
225
149
  /** Configuration options for the GitHub sponsors loader */
226
150
  #options;
151
+ /** Cache instance for storing and retrieving sponsors data */
152
+ #cache;
227
153
  /**
228
154
  * Creates a new GitHub sponsors loader instance.
229
155
  *
@@ -242,54 +168,11 @@ var GithubSponsorsLoader = class {
242
168
  */
243
169
  constructor(options) {
244
170
  this.#options = options;
245
- }
246
- /**
247
- * Loads previously cached sponsors from the output file.
248
- * Returns an object containing lastFetched timestamp and sponsors array,
249
- * or null if the file doesn't exist.
250
- *
251
- * @internal
252
- */
253
- async #loadExistingSponsors() {
254
- try {
255
- debug_default('loading existing sponsors file "%s"', this.#options.outputPath);
256
- return JSON.parse(await readFile(this.#options.outputPath, "utf-8"));
257
- } catch (error) {
258
- if (error.code !== "ENOENT") {
259
- throw error;
260
- }
261
- }
262
- return null;
263
- }
264
- /**
265
- * Writes sponsors to the cache file with the current timestamp.
266
- * Creates the output directory if it doesn't exist.
267
- *
268
- * @param sponsors - Array of GitHub sponsors to cache
269
- * @internal
270
- */
271
- async #cacheSponsors(sponsors) {
272
- debug_default('caching sponsors "%s"', this.#options.outputPath);
273
- const fileContents = { lastFetched: (/* @__PURE__ */ new Date()).toISOString(), sponsors };
274
- await mkdir(dirname(this.#options.outputPath), { recursive: true });
275
- await writeFile(this.#options.outputPath, JSON.stringify(fileContents));
276
- return fileContents;
277
- }
278
- /**
279
- * Determines if the cached data has expired based on the refresh schedule.
280
- *
281
- * @param fetchDate - The date when data was last fetched
282
- * @internal
283
- */
284
- #isExpired(fetchDate) {
285
- switch (this.#options.refresh) {
286
- case "daily":
287
- return dayjs().isAfter(fetchDate, "day");
288
- case "weekly":
289
- return dayjs().isAfter(fetchDate, "week");
290
- case "monthly":
291
- return dayjs().isAfter(fetchDate, "month");
292
- }
171
+ this.#cache = createCache({
172
+ key: "sponsors",
173
+ outputPath: options.outputPath,
174
+ refresh: options.refresh
175
+ });
293
176
  }
294
177
  /**
295
178
  * Loads and validates GitHub sponsors data.
@@ -305,28 +188,26 @@ var GithubSponsorsLoader = class {
305
188
  * ```
306
189
  */
307
190
  async load(schema, metadata) {
308
- let existingSponsors = await this.#loadExistingSponsors();
309
- if (!existingSponsors || this.#isExpired(new Date(existingSponsors.lastFetched))) {
310
- debug_default('fetching sponsors from github "%s"', this.#options.login);
311
- const sponsors = await fetchAllSponsors(this.#options);
312
- existingSponsors = await this.#cacheSponsors(sponsors);
191
+ let sponsors = await this.#cache.get();
192
+ if (!sponsors) {
193
+ sponsors = await fetchAllSponsors(this.#options);
194
+ await this.#cache.put(sponsors);
313
195
  }
314
- return vine.validate({
196
+ return vine3.validate({
315
197
  schema,
316
- data: existingSponsors.sponsors,
198
+ data: sponsors,
317
199
  meta: metadata
318
200
  });
319
201
  }
320
202
  };
321
203
 
322
204
  // src/loaders/gh_releases.ts
323
- import dayjs2 from "dayjs";
324
- import vine2 from "@vinejs/vine";
325
- import { dirname as dirname2 } from "path";
326
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
205
+ import vine4 from "@vinejs/vine";
327
206
  var GithubReleasesLoader = class {
328
207
  /** Configuration options for the GitHub release loader */
329
208
  #options;
209
+ /** Cache instance for storing and retrieving releases data */
210
+ #cache;
330
211
  /**
331
212
  * Creates a new GitHub release loader instance.
332
213
  *
@@ -344,54 +225,11 @@ var GithubReleasesLoader = class {
344
225
  */
345
226
  constructor(options) {
346
227
  this.#options = options;
347
- }
348
- /**
349
- * Loads previously cached releases from the output file.
350
- * Returns an object containing lastFetched timestamp and releases array,
351
- * or null if the file doesn't exist.
352
- *
353
- * @internal
354
- */
355
- async #loadExistingReleases() {
356
- try {
357
- debug_default('loading existing releases file "%s"', this.#options.outputPath);
358
- return JSON.parse(await readFile2(this.#options.outputPath, "utf-8"));
359
- } catch (error) {
360
- if (error.code !== "ENOENT") {
361
- throw error;
362
- }
363
- }
364
- return null;
365
- }
366
- /**
367
- * Writes releases to the cache file with the current timestamp.
368
- * Creates the output directory if it doesn't exist.
369
- *
370
- * @param releases - Array of GitHub releases to cache
371
- * @internal
372
- */
373
- async #cacheReleases(releases) {
374
- debug_default('caching releasing "%s"', this.#options.outputPath);
375
- const fileContents = { lastFetched: (/* @__PURE__ */ new Date()).toISOString(), releases };
376
- await mkdir2(dirname2(this.#options.outputPath), { recursive: true });
377
- await writeFile2(this.#options.outputPath, JSON.stringify(fileContents));
378
- return fileContents;
379
- }
380
- /**
381
- * Determines if the cached data has expired based on the refresh schedule.
382
- *
383
- * @param fetchDate - The date when data was last fetched
384
- * @internal
385
- */
386
- #isExpired(fetchDate) {
387
- switch (this.#options.refresh) {
388
- case "daily":
389
- return dayjs2().isAfter(fetchDate, "day");
390
- case "weekly":
391
- return dayjs2().isAfter(fetchDate, "week");
392
- case "monthly":
393
- return dayjs2().isAfter(fetchDate, "month");
394
- }
228
+ this.#cache = createCache({
229
+ key: "releases",
230
+ outputPath: options.outputPath,
231
+ refresh: options.refresh
232
+ });
395
233
  }
396
234
  /**
397
235
  * Loads and validates GitHub releases data.
@@ -407,29 +245,27 @@ var GithubReleasesLoader = class {
407
245
  * ```
408
246
  */
409
247
  async load(schema, metadata) {
410
- let existingReleases = await this.#loadExistingReleases();
411
- if (!existingReleases || this.#isExpired(new Date(existingReleases.lastFetched))) {
412
- debug_default('fetching releases from github "%s"', this.#options.org);
413
- const releases = await fetchReleases(this.#options);
414
- const mergedReleases = existingReleases ? mergeArrays(existingReleases.releases, releases, "url") : releases;
415
- existingReleases = await this.#cacheReleases(mergedReleases);
248
+ let cachedReleases = await this.#cache.get();
249
+ if (!cachedReleases) {
250
+ const freshReleases = await fetchReleases(this.#options);
251
+ const mergedReleases = cachedReleases ? mergeArrays(cachedReleases, freshReleases, "url") : freshReleases;
252
+ cachedReleases = await this.#cache.put(mergedReleases);
416
253
  }
417
- return vine2.validate({
254
+ return vine4.validate({
418
255
  schema,
419
- data: existingReleases.releases,
256
+ data: cachedReleases,
420
257
  meta: metadata
421
258
  });
422
259
  }
423
260
  };
424
261
 
425
262
  // src/loaders/gh_contributors.ts
426
- import dayjs3 from "dayjs";
427
- import vine3 from "@vinejs/vine";
428
- import { dirname as dirname3 } from "path";
429
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
263
+ import vine5 from "@vinejs/vine";
430
264
  var GithubContributorsLoader = class {
431
265
  /** Configuration options for the GitHub contributors loader */
432
266
  #options;
267
+ /** Cache instance for storing and retrieving contributors data */
268
+ #cache;
433
269
  /**
434
270
  * Creates a new GitHub contributors loader instance.
435
271
  *
@@ -447,54 +283,11 @@ var GithubContributorsLoader = class {
447
283
  */
448
284
  constructor(options) {
449
285
  this.#options = options;
450
- }
451
- /**
452
- * Loads previously cached contributors from the output file.
453
- * Returns an object containing lastFetched timestamp and contributors array,
454
- * or null if the file doesn't exist.
455
- *
456
- * @internal
457
- */
458
- async #loadExistingContributors() {
459
- try {
460
- debug_default('loading existing contributors file "%s"', this.#options.outputPath);
461
- return JSON.parse(await readFile3(this.#options.outputPath, "utf-8"));
462
- } catch (error) {
463
- if (error.code !== "ENOENT") {
464
- throw error;
465
- }
466
- }
467
- return null;
468
- }
469
- /**
470
- * Writes contributors to the cache file with the current timestamp.
471
- * Creates the output directory if it doesn't exist.
472
- *
473
- * @param contributors - Array of GitHub contributors to cache
474
- * @internal
475
- */
476
- async #cacheContributors(contributors) {
477
- debug_default('caching contributors "%s"', this.#options.outputPath);
478
- const fileContents = { lastFetched: (/* @__PURE__ */ new Date()).toISOString(), contributors };
479
- await mkdir3(dirname3(this.#options.outputPath), { recursive: true });
480
- await writeFile3(this.#options.outputPath, JSON.stringify(fileContents));
481
- return fileContents;
482
- }
483
- /**
484
- * Determines if the cached data has expired based on the refresh schedule.
485
- *
486
- * @param fetchDate - The date when data was last fetched
487
- * @internal
488
- */
489
- #isExpired(fetchDate) {
490
- switch (this.#options.refresh) {
491
- case "daily":
492
- return dayjs3().isAfter(fetchDate, "day");
493
- case "weekly":
494
- return dayjs3().isAfter(fetchDate, "week");
495
- case "monthly":
496
- return dayjs3().isAfter(fetchDate, "month");
497
- }
286
+ this.#cache = createCache({
287
+ key: "contributors",
288
+ outputPath: options.outputPath,
289
+ refresh: options.refresh
290
+ });
498
291
  }
499
292
  /**
500
293
  * Loads and validates GitHub contributors data.
@@ -510,60 +303,19 @@ var GithubContributorsLoader = class {
510
303
  * ```
511
304
  */
512
305
  async load(schema, metadata) {
513
- let existingContributors = await this.#loadExistingContributors();
514
- if (!existingContributors || this.#isExpired(new Date(existingContributors.lastFetched))) {
515
- debug_default('fetching contributors from github "%s"', this.#options.org);
516
- const contributors = await fetchContributorsForOrg(this.#options);
517
- existingContributors = await this.#cacheContributors(contributors);
306
+ let contributors = await this.#cache.get();
307
+ if (!contributors) {
308
+ contributors = await fetchContributorsForOrg(this.#options);
309
+ await this.#cache.put(contributors);
518
310
  }
519
- return vine3.validate({
311
+ return vine5.validate({
520
312
  schema,
521
- data: existingContributors.contributors,
313
+ data: contributors,
522
314
  meta: metadata
523
315
  });
524
316
  }
525
317
  };
526
318
 
527
- // src/loaders/json.ts
528
- import vine4 from "@vinejs/vine";
529
- import { dirname as dirname4 } from "path";
530
- import { readFile as readFile4 } from "fs/promises";
531
- var JsonLoader = class {
532
- /** Path to the JSON file to load */
533
- #source;
534
- /**
535
- * Creates a new JSON loader instance.
536
- *
537
- * @param source - Absolute or relative path to the JSON file
538
- *
539
- * @example
540
- * ```ts
541
- * const loader = new JsonLoader('./data/menu.json')
542
- * ```
543
- */
544
- constructor(source) {
545
- this.#source = source;
546
- }
547
- /**
548
- * Loads and validates JSON data from the configured file.
549
- * The directory of the source file is provided as metadata during validation.
550
- *
551
- * @param schema - VineJS schema to validate the loaded JSON data against
552
- * @param metadata - Optional metadata to pass to the validator
553
- *
554
- * @example
555
- * ```ts
556
- * const data = await loader.load(menuSchema)
557
- * ```
558
- */
559
- async load(schema, metadata) {
560
- const menuFileRoot = dirname4(this.#source);
561
- const menu = JSON.parse(await readFile4(this.#source, "utf-8"));
562
- debug_default('loading file "%s"', this.#source);
563
- return vine4.validate({ schema, data: menu, meta: { menuFileRoot, ...metadata } });
564
- }
565
- };
566
-
567
319
  // src/loaders/main.ts
568
320
  var loaders = {
569
321
  /**
@@ -624,6 +376,35 @@ var loaders = {
624
376
  ghReleases(options) {
625
377
  return new GithubReleasesLoader(options);
626
378
  },
379
+ /**
380
+ * Creates an OSS statistics loader instance.
381
+ *
382
+ * @param options - Configuration options for the OSS stats loader
383
+ *
384
+ * @example
385
+ * ```ts
386
+ * const loader = loaders.ossStats({
387
+ * outputPath: './cache/oss-stats.json',
388
+ * refresh: 'daily',
389
+ * sources: [
390
+ * {
391
+ * type: 'github',
392
+ * org: 'adonisjs',
393
+ * ghToken: process.env.GITHUB_TOKEN
394
+ * },
395
+ * {
396
+ * type: 'npm',
397
+ * packages: [
398
+ * { name: '@adonisjs/core', startDate: '2020-01-01' }
399
+ * ]
400
+ * }
401
+ * ]
402
+ * })
403
+ * ```
404
+ */
405
+ ossStats(options) {
406
+ return new OssStatsLoader(options);
407
+ },
627
408
  /**
628
409
  * Creates a JSON file loader instance.
629
410
  *