@larkiny/astro-github-loader 0.11.2 → 0.12.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.
Files changed (51) hide show
  1. package/README.md +69 -61
  2. package/dist/github.assets.d.ts +70 -0
  3. package/dist/github.assets.js +253 -0
  4. package/dist/github.auth.js +13 -9
  5. package/dist/github.cleanup.d.ts +3 -2
  6. package/dist/github.cleanup.js +30 -23
  7. package/dist/github.constants.d.ts +0 -16
  8. package/dist/github.constants.js +0 -16
  9. package/dist/github.content.d.ts +6 -132
  10. package/dist/github.content.js +154 -789
  11. package/dist/github.dryrun.d.ts +9 -5
  12. package/dist/github.dryrun.js +46 -25
  13. package/dist/github.link-transform.d.ts +2 -2
  14. package/dist/github.link-transform.js +65 -57
  15. package/dist/github.loader.js +45 -51
  16. package/dist/github.logger.d.ts +2 -2
  17. package/dist/github.logger.js +33 -24
  18. package/dist/github.paths.d.ts +76 -0
  19. package/dist/github.paths.js +190 -0
  20. package/dist/github.storage.d.ts +15 -0
  21. package/dist/github.storage.js +109 -0
  22. package/dist/github.types.d.ts +41 -4
  23. package/dist/index.d.ts +8 -6
  24. package/dist/index.js +3 -6
  25. package/dist/test-helpers.d.ts +130 -0
  26. package/dist/test-helpers.js +194 -0
  27. package/package.json +3 -1
  28. package/src/github.assets.spec.ts +717 -0
  29. package/src/github.assets.ts +365 -0
  30. package/src/github.auth.spec.ts +245 -0
  31. package/src/github.auth.ts +24 -10
  32. package/src/github.cleanup.spec.ts +380 -0
  33. package/src/github.cleanup.ts +91 -47
  34. package/src/github.constants.ts +0 -17
  35. package/src/github.content.spec.ts +305 -454
  36. package/src/github.content.ts +261 -950
  37. package/src/github.dryrun.spec.ts +586 -0
  38. package/src/github.dryrun.ts +105 -54
  39. package/src/github.link-transform.spec.ts +1345 -0
  40. package/src/github.link-transform.ts +174 -95
  41. package/src/github.loader.spec.ts +75 -50
  42. package/src/github.loader.ts +113 -78
  43. package/src/github.logger.spec.ts +795 -0
  44. package/src/github.logger.ts +77 -35
  45. package/src/github.paths.spec.ts +523 -0
  46. package/src/github.paths.ts +259 -0
  47. package/src/github.storage.spec.ts +367 -0
  48. package/src/github.storage.ts +127 -0
  49. package/src/github.types.ts +55 -9
  50. package/src/index.ts +43 -6
  51. package/src/test-helpers.ts +215 -0
@@ -1,9 +1,11 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { Octokit } from "octokit";
5
+ import type { Logger } from "./github.logger.js";
4
6
  import type { ImportOptions, LoaderContext } from "./github.types.js";
5
7
 
6
- const STATE_FILENAME = '.github-import-state.json';
8
+ const STATE_FILENAME = ".github-import-state.json";
7
9
 
8
10
  /**
9
11
  * Represents the state of a single import configuration
@@ -55,30 +57,48 @@ export interface RepositoryChangeInfo {
55
57
  * Creates a unique identifier for an import configuration
56
58
  */
57
59
  export function createConfigId(config: ImportOptions): string {
58
- return `${config.owner}/${config.repo}@${config.ref || 'main'}`;
60
+ return `${config.owner}/${config.repo}@${config.ref || "main"}`;
59
61
  }
60
62
 
61
63
  /**
62
64
  * Loads the import state from the state file
63
65
  */
64
- export async function loadImportState(workingDir: string): Promise<StateFile> {
66
+ export async function loadImportState(
67
+ workingDir: string,
68
+ logger?: Logger,
69
+ ): Promise<StateFile> {
65
70
  const statePath = join(workingDir, STATE_FILENAME);
66
-
71
+
67
72
  if (!existsSync(statePath)) {
68
73
  return {
69
74
  imports: {},
70
- lastChecked: new Date().toISOString()
75
+ lastChecked: new Date().toISOString(),
71
76
  };
72
77
  }
73
78
 
74
79
  try {
75
- const content = await fs.readFile(statePath, 'utf-8');
76
- return JSON.parse(content);
80
+ const content = await fs.readFile(statePath, "utf-8");
81
+ const parsed = JSON.parse(content);
82
+ // Validate the parsed state has the expected shape
83
+ if (
84
+ !parsed ||
85
+ typeof parsed !== "object" ||
86
+ !("imports" in parsed) ||
87
+ typeof parsed.imports !== "object"
88
+ ) {
89
+ const msg = `Malformed state file at ${statePath}, starting fresh`;
90
+ // eslint-disable-next-line no-console -- fallback when no logger provided
91
+ logger ? logger.warn(msg) : console.warn(msg);
92
+ return { imports: {}, lastChecked: new Date().toISOString() };
93
+ }
94
+ return parsed;
77
95
  } catch (error) {
78
- console.warn(`Failed to load import state from ${statePath}, starting fresh:`, error);
96
+ const msg = `Failed to load import state from ${statePath}, starting fresh: ${error}`;
97
+ // eslint-disable-next-line no-console -- fallback when no logger provided
98
+ logger ? logger.warn(msg) : console.warn(msg);
79
99
  return {
80
100
  imports: {},
81
- lastChecked: new Date().toISOString()
101
+ lastChecked: new Date().toISOString(),
82
102
  };
83
103
  }
84
104
  }
@@ -86,13 +106,19 @@ export async function loadImportState(workingDir: string): Promise<StateFile> {
86
106
  /**
87
107
  * Saves the import state to the state file
88
108
  */
89
- async function saveImportState(workingDir: string, state: StateFile): Promise<void> {
109
+ async function saveImportState(
110
+ workingDir: string,
111
+ state: StateFile,
112
+ logger?: Logger,
113
+ ): Promise<void> {
90
114
  const statePath = join(workingDir, STATE_FILENAME);
91
-
115
+
92
116
  try {
93
- await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8');
117
+ await fs.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
94
118
  } catch (error) {
95
- console.warn(`Failed to save import state to ${statePath}:`, error);
119
+ const msg = `Failed to save import state to ${statePath}: ${error}`;
120
+ // eslint-disable-next-line no-console -- fallback when no logger provided
121
+ logger ? logger.warn(msg) : console.warn(msg);
96
122
  }
97
123
  }
98
124
 
@@ -100,9 +126,9 @@ async function saveImportState(workingDir: string, state: StateFile): Promise<vo
100
126
  * Gets the latest commit information for a repository path
101
127
  */
102
128
  export async function getLatestCommitInfo(
103
- octokit: any,
129
+ octokit: Octokit,
104
130
  config: ImportOptions,
105
- signal?: AbortSignal
131
+ signal?: AbortSignal,
106
132
  ): Promise<{ sha: string; message: string; date: string } | null> {
107
133
  const { owner, repo, ref = "main" } = config;
108
134
 
@@ -113,7 +139,7 @@ export async function getLatestCommitInfo(
113
139
  repo,
114
140
  sha: ref,
115
141
  per_page: 1,
116
- request: { signal }
142
+ request: { signal },
117
143
  });
118
144
 
119
145
  if (data.length === 0) {
@@ -123,11 +149,19 @@ export async function getLatestCommitInfo(
123
149
  const latestCommit = data[0];
124
150
  return {
125
151
  sha: latestCommit.sha,
126
- message: latestCommit.commit.message.split('\n')[0], // First line only
127
- date: latestCommit.commit.committer?.date || latestCommit.commit.author?.date || new Date().toISOString()
152
+ message: latestCommit.commit.message.split("\n")[0], // First line only
153
+ date:
154
+ latestCommit.commit.committer?.date ||
155
+ latestCommit.commit.author?.date ||
156
+ new Date().toISOString(),
128
157
  };
129
- } catch (error: any) {
130
- if (error.status === 404) {
158
+ } catch (error: unknown) {
159
+ if (
160
+ typeof error === "object" &&
161
+ error !== null &&
162
+ "status" in error &&
163
+ (error as { status: number }).status === 404
164
+ ) {
131
165
  throw new Error(`Repository not found: ${owner}/${repo}`);
132
166
  }
133
167
  throw error;
@@ -138,26 +172,28 @@ export async function getLatestCommitInfo(
138
172
  * Checks a single repository for changes
139
173
  */
140
174
  async function checkRepositoryForChanges(
141
- octokit: any,
175
+ octokit: Octokit,
142
176
  config: ImportOptions,
143
177
  currentState: ImportState,
144
- signal?: AbortSignal
178
+ signal?: AbortSignal,
145
179
  ): Promise<RepositoryChangeInfo> {
146
180
  const configName = config.name || `${config.owner}/${config.repo}`;
147
181
 
148
182
  try {
149
183
  const latestCommit = await getLatestCommitInfo(octokit, config, signal);
150
-
184
+
151
185
  if (!latestCommit) {
152
186
  return {
153
187
  config,
154
188
  state: currentState,
155
189
  needsReimport: false,
156
- error: "No commits found in repository"
190
+ error: "No commits found in repository",
157
191
  };
158
192
  }
159
193
 
160
- const needsReimport = !currentState.lastCommitSha || currentState.lastCommitSha !== latestCommit.sha;
194
+ const needsReimport =
195
+ !currentState.lastCommitSha ||
196
+ currentState.lastCommitSha !== latestCommit.sha;
161
197
 
162
198
  return {
163
199
  config,
@@ -165,15 +201,14 @@ async function checkRepositoryForChanges(
165
201
  needsReimport,
166
202
  latestCommitSha: latestCommit.sha,
167
203
  latestCommitMessage: latestCommit.message,
168
- latestCommitDate: latestCommit.date
204
+ latestCommitDate: latestCommit.date,
169
205
  };
170
-
171
- } catch (error: any) {
206
+ } catch (error: unknown) {
172
207
  return {
173
208
  config,
174
209
  state: currentState,
175
210
  needsReimport: false,
176
- error: error.message
211
+ error: error instanceof Error ? error.message : String(error),
177
212
  };
178
213
  }
179
214
  }
@@ -184,9 +219,10 @@ async function checkRepositoryForChanges(
184
219
  export async function updateImportState(
185
220
  workingDir: string,
186
221
  config: ImportOptions,
187
- commitSha?: string
222
+ commitSha?: string,
223
+ logger?: Logger,
188
224
  ): Promise<void> {
189
- const state = await loadImportState(workingDir);
225
+ const state = await loadImportState(workingDir, logger);
190
226
  const configId = createConfigId(config);
191
227
  const configName = config.name || `${config.owner}/${config.repo}`;
192
228
 
@@ -195,10 +231,10 @@ export async function updateImportState(
195
231
  repoId: configId,
196
232
  lastCommitSha: commitSha,
197
233
  lastImported: new Date().toISOString(),
198
- ref: config.ref || 'main'
234
+ ref: config.ref || "main",
199
235
  };
200
236
 
201
- await saveImportState(workingDir, state);
237
+ await saveImportState(workingDir, state, logger);
202
238
  }
203
239
 
204
240
  /**
@@ -207,14 +243,14 @@ export async function updateImportState(
207
243
  export async function performDryRun(
208
244
  configs: ImportOptions[],
209
245
  context: LoaderContext,
210
- octokit: any,
246
+ octokit: Octokit,
211
247
  workingDir: string = process.cwd(),
212
- signal?: AbortSignal
248
+ signal?: AbortSignal,
213
249
  ): Promise<RepositoryChangeInfo[]> {
214
250
  const { logger } = context;
215
-
251
+
216
252
  logger.info("šŸ” Performing dry run - checking for repository changes...");
217
-
253
+
218
254
  // Load current state
219
255
  const state = await loadImportState(workingDir);
220
256
  const results: RepositoryChangeInfo[] = [];
@@ -222,33 +258,40 @@ export async function performDryRun(
222
258
  // Check each configuration
223
259
  for (const config of configs) {
224
260
  if (config.enabled === false) {
225
- logger.debug(`Skipping disabled config: ${config.name || `${config.owner}/${config.repo}`}`);
261
+ logger.debug(
262
+ `Skipping disabled config: ${config.name || `${config.owner}/${config.repo}`}`,
263
+ );
226
264
  continue;
227
265
  }
228
266
 
229
267
  const configId = createConfigId(config);
230
268
  const configName = config.name || `${config.owner}/${config.repo}`;
231
-
269
+
232
270
  // Get current state for this config
233
271
  const currentState: ImportState = state.imports[configId] || {
234
272
  name: configName,
235
273
  repoId: configId,
236
- ref: config.ref || 'main'
274
+ ref: config.ref || "main",
237
275
  };
238
276
 
239
277
  logger.debug(`Checking ${configName}...`);
240
-
278
+
241
279
  try {
242
- const changeInfo = await checkRepositoryForChanges(octokit, config, currentState, signal);
280
+ const changeInfo = await checkRepositoryForChanges(
281
+ octokit,
282
+ config,
283
+ currentState,
284
+ signal,
285
+ );
243
286
  results.push(changeInfo);
244
- } catch (error: any) {
287
+ } catch (error: unknown) {
245
288
  if (signal?.aborted) throw error;
246
-
289
+
247
290
  results.push({
248
291
  config,
249
292
  state: currentState,
250
293
  needsReimport: false,
251
- error: `Failed to check repository: ${error.message}`
294
+ error: `Failed to check repository: ${error instanceof Error ? error.message : String(error)}`,
252
295
  });
253
296
  }
254
297
  }
@@ -263,16 +306,20 @@ export async function performDryRun(
263
306
  /**
264
307
  * Formats and displays the dry run results
265
308
  */
266
- export function displayDryRunResults(results: RepositoryChangeInfo[], logger: any): void {
309
+ export function displayDryRunResults(
310
+ results: RepositoryChangeInfo[],
311
+ logger: { info: (msg: string) => void },
312
+ ): void {
267
313
  logger.info("\nšŸ“Š Repository Import Status:");
268
- logger.info("=" .repeat(50));
314
+ logger.info("=".repeat(50));
269
315
 
270
316
  let needsReimportCount = 0;
271
317
  let errorCount = 0;
272
318
 
273
319
  for (const result of results) {
274
- const configName = result.config.name || `${result.config.owner}/${result.config.repo}`;
275
-
320
+ const configName =
321
+ result.config.name || `${result.config.owner}/${result.config.repo}`;
322
+
276
323
  if (result.error) {
277
324
  logger.info(`āŒ ${configName}: ${result.error}`);
278
325
  errorCount++;
@@ -304,12 +351,16 @@ export function displayDryRunResults(results: RepositoryChangeInfo[], logger: an
304
351
  }
305
352
  }
306
353
 
307
- logger.info("=" .repeat(50));
308
- logger.info(`šŸ“ˆ Summary: ${needsReimportCount} of ${results.length} repositories need re-import, ${errorCount} errors`);
309
-
354
+ logger.info("=".repeat(50));
355
+ logger.info(
356
+ `šŸ“ˆ Summary: ${needsReimportCount} of ${results.length} repositories need re-import, ${errorCount} errors`,
357
+ );
358
+
310
359
  if (needsReimportCount > 0) {
311
360
  logger.info("\nšŸ’” To import updated repositories:");
312
- logger.info("1. Delete the target import folders for repositories that need re-import");
361
+ logger.info(
362
+ "1. Delete the target import folders for repositories that need re-import",
363
+ );
313
364
  logger.info("2. Run the import process normally (dryRun: false)");
314
365
  logger.info("3. Fresh content will be imported automatically");
315
366
  } else {
@@ -336,4 +387,4 @@ function getTimeAgo(date: Date): string {
336
387
  } else {
337
388
  return date.toLocaleDateString();
338
389
  }
339
- }
390
+ }