@theholocron/holocron-plugin-github 5.0.0-alpha.380 → 5.0.0-alpha.382

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/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AddedItem, Auth, AuthError, BoardItem, BoardItemInput, BoardState, BoardSummary, BoardTemplate, Ci, CiRun, CiRunFilter, EnsureReport, Environment, Environments, ImportOptions, ImportResult, Issue, IssueRecord, IssueSearchFilter, IssueSelector, Issues, LabelDef, LifecycleResult, LifecycleSlot, MoveReport, PagesConfig, PluginToken, Projects, PullRequest, RepoRef, RepoSettings, ResolveTokenInput, Ruleset, SecretScope, Secrets, Source, SourceBranch, SourceFile, SourceRepoInfo, SourceRulesetDetail, SourceSecurity, SourceTeamMember, SyncResult, TeamEntry, TeamEntry as TeamEntry$1, TeamPermission, TeamPermission as TeamPermission$1, TrackerDoctorReport, TrackerUser, VerifyTokenFailure, VerifyTokenResult, VerifyTokenResult as VerifyTokenResult$1, VerifyTokenSuccess, createFeatureResolver } from "@theholocron/cli";
1
+ import { AddedItem, Auth, AuthError, BoardItem, BoardItemInput, BoardState, BoardSummary, BoardTemplate, Ci, CiRun, CiRunFilter, EnsureReport, Environment, Environments, ExportInfo, ImportOptions, ImportResult, Issue, IssueRecord, IssueSearchFilter, IssueSelector, Issues, LabelDef, LifecycleResult, LifecycleSlot, MoveReport, PagesConfig, PluginToken, Projects, PullRequest, RepoRef, RepoSettings, ResolveTokenInput, Ruleset, SecretScope, Secrets, Source, SourceBranch, SourceFile, SourceRepoInfo, SourceRulesetDetail, SourceSecurity, SourceTeamMember, SyncResult, TeamEntry, TeamEntry as TeamEntry$1, TeamPermission, TeamPermission as TeamPermission$1, TrackerDoctorReport, TrackerUser, VerifyTokenFailure, VerifyTokenResult, VerifyTokenResult as VerifyTokenResult$1, VerifyTokenSuccess, createFeatureResolver } from "@theholocron/cli";
2
2
  import { GitHubClient, GitHubClientOptions, GraphQLClient, createGitHubClient } from "@theholocron/github-client";
3
3
  /** Resolves the token for read-only GitHub access (`HOLOCRON_READ_TOKEN`, keyring key `github.read`). */
4
4
  export declare const resolveReadToken: import("@theholocron/cli").FeatureResolver;
@@ -105,7 +105,7 @@ export declare class GitHubIssues implements Issues {
105
105
  /** Checks the authenticated user, the repo and the label-backed lifecycle slots, and reports what resolves. */
106
106
  doctor(): Promise<TrackerDoctorReport>;
107
107
  /** Reads issues with their comments, priority label, parent and blockers, for `holocron migrate`. */
108
- export(selector: IssueSelector): Promise<IssueRecord[]>;
108
+ export(selector: IssueSelector, info?: ExportInfo): Promise<IssueRecord[]>;
109
109
  /** Makes the records as issues; one already made by an earlier run is skipped. */
110
110
  import(records: IssueRecord[], options?: ImportOptions): Promise<ImportResult[]>;
111
111
  /** Like `import`, and it also updates what an earlier import or sync made, for `holocron sync issues`. */
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
- import { AuthError, MissingTokenError, ProviderApiError, connectionProbe, createFeatureResolver } from "@theholocron/cli";
2
+ import { AuthError, MissingTokenError, ProviderApiError, connectionProbe, createFeatureResolver, pickByCreation, readKeysLimited, reportProgress } from "@theholocron/cli";
3
3
  import { createGitHubClient, createGitHubClient as createGitHubClient$1 } from "@theholocron/github-client";
4
4
  import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
5
5
  import { dirname, join } from "node:path";
@@ -209,26 +209,51 @@ function explainSecondaryLimit(err) {
209
209
  if (err instanceof ProviderApiError && (err.status === 403 || err.status === 429) && String(err.details).includes("secondary rate limit")) return /* @__PURE__ */ new Error("GitHub's secondary rate limit stopped the export. It limits bursts of requests, not the hourly quota, and nothing has been written. Wait a couple of minutes and run the command again.");
210
210
  return err;
211
211
  }
212
- /** Reads the issues `selector` names, with their comments, priority label, parent and blockers. */
213
- async function exportIssues(client, repo, selector) {
214
- return readIssues(client, repo, selector).catch((err) => {
212
+ /**
213
+ * Reads the issues `selector` names, with their comments, priority label, parent and blockers.
214
+ *
215
+ * With `limit`, only that many survivors of the other filters are read, from the `order` end of the creation order. A
216
+ * list of keys is sorted by issue number (which rises with creation in one repository) before anything is fetched, then
217
+ * fetched a few at a time until enough are kept; a listing already carries `created_at`, so it is cut before the
218
+ * per-issue comment and link requests. `info.matched` receives how many the selection matched before the cut.
219
+ */
220
+ async function exportIssues(client, repo, selector, info = {}) {
221
+ return readIssues(client, repo, selector, info).catch((err) => {
215
222
  throw explainSecondaryLimit(err);
216
223
  });
217
224
  }
218
- async function readIssues(client, repo, selector) {
225
+ async function readIssues(client, repo, selector, info) {
219
226
  const [owner = "", name = ""] = repo.split("/", 2);
220
- let raw;
221
- if (selector.keys) raw = await mapLimit(selector.keys, EXPORT_CONCURRENCY, (k) => client.issues.getIssue(repo, parseNumber(k)));
222
- else if (selector.milestone) {
223
- const milestone = await findMilestone(client, repo, selector.milestone);
224
- if (!milestone) throw new Error(`Milestone "${selector.milestone}" not found in ${repo}.`);
225
- raw = await listAll(client, repo, { milestone: String(milestone.number) });
226
- } else if (selector.all) raw = await listAll(client, repo, {});
227
- else throw new Error("export needs keys, a milestone or all");
228
227
  const since = selector.since ? Date.parse(selector.since) : void 0;
229
- const issues = raw.filter((i) => !i.pull_request && !(selector.openOnly && i.state === "closed") && (since === void 0 || Date.parse(i.updated_at) >= since));
228
+ const survives = (i) => !i.pull_request && !(selector.openOnly && i.state === "closed") && (since === void 0 || Date.parse(i.updated_at) >= since);
229
+ const { limit, order = "oldest" } = selector;
230
+ let issues;
231
+ if (selector.keys) {
232
+ const fetch = (k) => client.issues.getIssue(repo, parseNumber(k));
233
+ info.matched = selector.keys.length;
234
+ issues = limit === void 0 ? (await mapLimit(selector.keys, EXPORT_CONCURRENCY, fetch)).filter(survives) : await readKeysLimited({
235
+ keys: selector.keys,
236
+ order,
237
+ limit,
238
+ concurrency: EXPORT_CONCURRENCY,
239
+ fetch,
240
+ keep: survives
241
+ });
242
+ } else {
243
+ let raw;
244
+ if (selector.milestone) {
245
+ const milestone = await findMilestone(client, repo, selector.milestone);
246
+ if (!milestone) throw new Error(`Milestone "${selector.milestone}" not found in ${repo}.`);
247
+ raw = await listAll(client, repo, { milestone: String(milestone.number) });
248
+ } else if (selector.all) raw = await listAll(client, repo, {});
249
+ else throw new Error("export needs keys, a milestone or all");
250
+ issues = raw.filter(survives);
251
+ info.matched = issues.length;
252
+ if (limit !== void 0) issues = pickByCreation(issues, (i) => i.created_at ?? i.updated_at, order, limit);
253
+ }
230
254
  const records = [];
231
255
  for (const issue of issues) {
256
+ reportProgress(info.onProgress, records.length, issues.length);
232
257
  const comments = await client.issues.listComments(repo, issue.number);
233
258
  const links = (await client.graphql.query(LINK_QUERY, {
234
259
  owner,
@@ -265,6 +290,7 @@ async function readIssues(client, repo, selector) {
265
290
  ...migratedFrom ? { migratedFrom } : {}
266
291
  });
267
292
  }
293
+ reportProgress(info.onProgress, records.length, issues.length);
268
294
  return records;
269
295
  }
270
296
  /** Makes the records as issues, skipping any whose source address an earlier run already recorded. */
@@ -275,6 +301,7 @@ async function importIssues(client, repo, records, options = {}) {
275
301
  if (url) existing.set(url, issue.number);
276
302
  }
277
303
  const results = [];
304
+ const tick = () => reportProgress(options.onProgress, results.length, records.length);
278
305
  for (const record of records) {
279
306
  const found = existing.get(record.url);
280
307
  if (found !== void 0) {
@@ -284,6 +311,7 @@ async function importIssues(client, repo, records, options = {}) {
284
311
  created: false,
285
312
  dropped: []
286
313
  });
314
+ tick();
287
315
  continue;
288
316
  }
289
317
  const dropped = droppedFor(record);
@@ -293,6 +321,7 @@ async function importIssues(client, repo, records, options = {}) {
293
321
  created: true,
294
322
  dropped
295
323
  });
324
+ tick();
296
325
  continue;
297
326
  }
298
327
  const number = await createFromRecord(client, repo, record, options);
@@ -302,6 +331,7 @@ async function importIssues(client, repo, records, options = {}) {
302
331
  created: true,
303
332
  dropped
304
333
  });
334
+ tick();
305
335
  }
306
336
  return results;
307
337
  }
@@ -386,6 +416,7 @@ async function syncIssues(client, repo, records, options = {}) {
386
416
  for (const record of records) try {
387
417
  const found = existing.get(record.url);
388
418
  results.push(found ? await update(client, repo, found, record, options) : await create(client, repo, record, options));
419
+ reportProgress(options.onProgress, results.length, records.length);
389
420
  } catch (err) {
390
421
  if (isRateLimited(err)) return results;
391
422
  throw err;
@@ -628,8 +659,8 @@ var GitHubIssues = class {
628
659
  };
629
660
  }
630
661
  /** Reads issues with their comments, priority label, parent and blockers, for `holocron migrate`. */
631
- export(selector) {
632
- return exportIssues(this.client, this.repo, selector);
662
+ export(selector, info) {
663
+ return exportIssues(this.client, this.repo, selector, info);
633
664
  }
634
665
  /** Makes the records as issues; one already made by an earlier run is skipped. */
635
666
  import(records, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-github",
3
- "version": "5.0.0-alpha.380",
3
+ "version": "5.0.0-alpha.382",
4
4
  "description": "Holocron plugin for GitHub. Implements source, ci, secrets, environments, and issues capabilities against the GitHub REST API.",
5
5
  "keywords": [
6
6
  "ci",
@@ -44,21 +44,21 @@
44
44
  "tsx": "4.23.15",
45
45
  "typescript": "^5.9.3",
46
46
  "vitest": "^5.1.0",
47
- "@theholocron/cli": "5.0.0-alpha.380",
48
- "@theholocron/github-client": "^1.58.0-alpha.22",
49
- "@theholocron/fs-utils": "^1.17.0-alpha.2",
50
- "@theholocron/http-client": "^1.58.0-alpha.22",
51
- "@theholocron/eslint-config": "^8.9.0-alpha.12",
52
- "@theholocron/tsconfig": "^8.9.0-alpha.12",
47
+ "@theholocron/cli": "5.0.0-alpha.382",
48
+ "@theholocron/eslint-config": "^8.9.0-alpha.13",
49
+ "@theholocron/http-client": "^1.58.0-alpha.23",
50
+ "@theholocron/github-client": "^1.58.0-alpha.23",
53
51
  "@theholocron/misc-utils": "^1.17.0-alpha.2",
54
- "@theholocron/vitest-config": "^8.9.0-alpha.12",
55
- "@theholocron/tsdown-config": "^8.9.0-alpha.12"
52
+ "@theholocron/tsdown-config": "^8.9.0-alpha.13",
53
+ "@theholocron/fs-utils": "^1.17.0-alpha.2",
54
+ "@theholocron/tsconfig": "^8.9.0-alpha.13",
55
+ "@theholocron/vitest-config": "^8.9.0-alpha.13"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@theholocron/fs-utils": "^1.16.0",
59
59
  "@theholocron/github-client": "^1.58.0",
60
60
  "@theholocron/misc-utils": "^1.16.0",
61
- "@theholocron/cli": "5.0.0-alpha.380"
61
+ "@theholocron/cli": "5.0.0-alpha.382"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=22"