@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,48 +1,26 @@
1
1
  import { toCollectionEntry } from "./github.content.js";
2
2
  import { performSelectiveCleanup } from "./github.cleanup.js";
3
- import { performDryRun, displayDryRunResults, updateImportState, loadImportState, createConfigId, getLatestCommitInfo } from "./github.dryrun.js";
4
- import { createLogger, type Logger, type ImportSummary } from "./github.logger.js";
3
+ import {
4
+ performDryRun,
5
+ displayDryRunResults,
6
+ updateImportState,
7
+ loadImportState,
8
+ createConfigId,
9
+ getLatestCommitInfo,
10
+ } from "./github.dryrun.js";
11
+ import {
12
+ createLogger,
13
+ type Logger,
14
+ type ImportSummary,
15
+ } from "./github.logger.js";
5
16
 
6
17
  import type {
7
18
  Loader,
8
19
  GithubLoaderOptions,
20
+ ExtendedLoaderContext,
9
21
  ImportOptions,
10
- SyncStats,
11
22
  } from "./github.types.js";
12
23
 
13
- /**
14
- * Performs selective cleanup for configurations with basePath
15
- * @param configs - Array of configuration objects
16
- * @param context - Loader context
17
- * @param octokit - GitHub API client
18
- * @internal
19
- */
20
- async function performSelectiveCleanups(
21
- configs: ImportOptions[],
22
- context: any,
23
- octokit: any
24
- ): Promise<SyncStats[]> {
25
- const results: SyncStats[] = [];
26
-
27
- // Process each config sequentially to avoid overwhelming Astro's file watcher
28
- for (const config of configs) {
29
- if (config.enabled === false) {
30
- context.logger.debug(`Skipping disabled config: ${config.name || `${config.owner}/${config.repo}`}`);
31
- continue;
32
- }
33
-
34
- try {
35
- const stats = await performSelectiveCleanup(config, context, octokit);
36
- results.push(stats);
37
- } catch (error: any) {
38
- context.logger.error(`Selective cleanup failed for ${config.name || `${config.owner}/${config.repo}`}: ${error}`);
39
- // Continue with other configs even if one fails
40
- }
41
- }
42
-
43
- return results;
44
- }
45
-
46
24
  /**
47
25
  * Loads data from GitHub repositories based on the provided configurations and options.
48
26
  *
@@ -67,13 +45,13 @@ export function githubLoader({
67
45
  return {
68
46
  name: "github-loader",
69
47
  load: async (context) => {
70
- const { store } = context;
71
-
72
48
  // Create global logger with specified level or default
73
- const globalLogger = createLogger(logLevel || 'default');
49
+ const globalLogger = createLogger(logLevel || "default");
74
50
 
75
51
  if (dryRun) {
76
- globalLogger.info("๐Ÿ” Dry run mode enabled - checking for changes only");
52
+ globalLogger.info(
53
+ "๐Ÿ” Dry run mode enabled - checking for changes only",
54
+ );
77
55
 
78
56
  try {
79
57
  const results = await performDryRun(configs, context, octokit);
@@ -83,40 +61,48 @@ export function githubLoader({
83
61
  globalLogger.info("๐Ÿ’ก Set dryRun: false to perform actual imports");
84
62
 
85
63
  return; // Exit without importing
86
- } catch (error: any) {
87
- globalLogger.error(`Dry run failed: ${error.message}`);
64
+ } catch (error: unknown) {
65
+ globalLogger.error(
66
+ `Dry run failed: ${error instanceof Error ? error.message : String(error)}`,
67
+ );
88
68
  throw error;
89
69
  }
90
70
  }
91
71
 
92
72
  globalLogger.debug(`Loading data from ${configs.length} sources`);
93
73
 
94
- // Always use standard processing - no file deletions to avoid Astro issues
95
- globalLogger.info(clear ? "Processing with content store clear" : "Processing without content store clear");
96
-
97
- if (clear) {
98
- store.clear();
99
- }
74
+ // Log clear mode status - actual clearing happens per-entry in toCollectionEntry
75
+ // to avoid breaking Astro's content collection by emptying the store all at once
76
+ globalLogger.info(
77
+ clear
78
+ ? "Processing with selective entry replacement"
79
+ : "Processing without entry replacement",
80
+ );
100
81
 
101
82
  // Process each config sequentially to avoid overwhelming GitHub API/CDN
102
83
  for (let i = 0; i < configs.length; i++) {
103
84
  const config = configs[i];
104
85
 
105
86
  if (config.enabled === false) {
106
- globalLogger.debug(`Skipping disabled config: ${config.name || `${config.owner}/${config.repo}`}`);
87
+ globalLogger.debug(
88
+ `Skipping disabled config: ${config.name || `${config.owner}/${config.repo}`}`,
89
+ );
107
90
  continue;
108
91
  }
109
92
 
110
93
  // Add small delay between configs to be gentler on GitHub's CDN
111
94
  if (i > 0) {
112
- await new Promise(resolve => setTimeout(resolve, 1000));
95
+ await new Promise((resolve) => setTimeout(resolve, 1000));
113
96
  }
114
97
 
115
98
  // Determine the effective log level for this config
116
- const effectiveLogLevel = logLevel || config.logLevel || 'default';
99
+ const effectiveLogLevel = logLevel || config.logLevel || "default";
117
100
  const configLogger = createLogger(effectiveLogLevel);
118
101
 
119
- const configName = config.name || `${config.owner}/${config.repo}`;
102
+ const langSuffix = config.language ? ` (${config.language})` : "";
103
+ const configName = config.name
104
+ ? `${config.name}${langSuffix}`
105
+ : `${config.owner}/${config.repo}${langSuffix}`;
120
106
  const repository = `${config.owner}/${config.repo}`;
121
107
 
122
108
  let summary: ImportSummary = {
@@ -127,7 +113,7 @@ export function githubLoader({
127
113
  filesUpdated: 0,
128
114
  filesUnchanged: 0,
129
115
  duration: 0,
130
- status: 'error',
116
+ status: "error",
131
117
  };
132
118
 
133
119
  const startTime = Date.now();
@@ -138,51 +124,92 @@ export function githubLoader({
138
124
 
139
125
  if (!force) {
140
126
  try {
141
- const state = await loadImportState(process.cwd());
127
+ const state = await loadImportState(process.cwd(), configLogger);
142
128
  const currentState = state.imports[configId];
143
129
 
144
130
  if (currentState && currentState.lastCommitSha) {
145
- configLogger.debug(`๐Ÿ” Checking repository changes for ${configName}...`);
131
+ configLogger.debug(
132
+ `๐Ÿ” Checking repository changes for ${configName}...`,
133
+ );
146
134
  const latestCommit = await getLatestCommitInfo(octokit, config);
147
135
 
148
- if (latestCommit && currentState.lastCommitSha === latestCommit.sha) {
149
- configLogger.info(`โœ… Repository ${configName} unchanged (${latestCommit.sha.slice(0, 7)}) - skipping import`);
136
+ if (
137
+ latestCommit &&
138
+ currentState.lastCommitSha === latestCommit.sha
139
+ ) {
140
+ configLogger.info(
141
+ `โœ… Repository ${configName} unchanged (${latestCommit.sha.slice(0, 7)}) - skipping import`,
142
+ );
150
143
 
151
144
  // Update summary for unchanged repository
152
145
  summary.duration = Date.now() - startTime;
153
146
  summary.filesProcessed = 0;
154
147
  summary.filesUpdated = 0;
155
148
  summary.filesUnchanged = 0;
156
- summary.status = 'success';
149
+ summary.status = "success";
157
150
 
158
151
  configLogger.logImportSummary(summary);
159
152
  continue; // Skip to next config
160
153
  } else if (latestCommit) {
161
- configLogger.info(`๐Ÿ”„ Repository ${configName} changed (${currentState.lastCommitSha?.slice(0, 7) || 'unknown'} -> ${latestCommit.sha.slice(0, 7)}) - proceeding with import`);
154
+ configLogger.info(
155
+ `๐Ÿ”„ Repository ${configName} changed (${currentState.lastCommitSha?.slice(0, 7) || "unknown"} -> ${latestCommit.sha.slice(0, 7)}) - proceeding with import`,
156
+ );
162
157
  }
163
158
  } else {
164
- configLogger.debug(`๐Ÿ“ฅ First time importing ${configName} - no previous state found`);
159
+ configLogger.debug(
160
+ `๐Ÿ“ฅ First time importing ${configName} - no previous state found`,
161
+ );
165
162
  }
166
163
  } catch (error) {
167
- configLogger.warn(`Failed to check repository state for ${configName}: ${error instanceof Error ? error.message : String(error)}`);
164
+ configLogger.warn(
165
+ `Failed to check repository state for ${configName}: ${error instanceof Error ? error.message : String(error)}`,
166
+ );
168
167
  // Continue with import if state check fails
169
168
  }
170
169
  } else {
171
- configLogger.info(`๐Ÿ”„ Force mode enabled for ${configName} - proceeding with full import`);
170
+ configLogger.info(
171
+ `๐Ÿ”„ Force mode enabled for ${configName} - proceeding with full import`,
172
+ );
173
+ }
174
+
175
+ // Determine effective clear setting: per-config takes precedence over global
176
+ const effectiveClear = config.clear ?? clear;
177
+
178
+ // Perform selective cleanup before importing if clear is enabled
179
+ if (effectiveClear) {
180
+ configLogger.info(
181
+ `๐Ÿงน Clearing obsolete files for ${configName}...`,
182
+ );
183
+ try {
184
+ await performSelectiveCleanup(
185
+ config,
186
+ { ...context, logger: configLogger } as ExtendedLoaderContext,
187
+ octokit,
188
+ );
189
+ } catch (error) {
190
+ configLogger.warn(
191
+ `Cleanup failed for ${configName}, continuing with import: ${error}`,
192
+ );
193
+ }
172
194
  }
173
195
 
174
196
  // Perform the import with spinner
175
197
  const stats = await globalLogger.withSpinner(
176
198
  `๐Ÿ”„ Importing ${configName}...`,
177
- () => toCollectionEntry({
178
- context: { ...context, logger: configLogger as any },
179
- octokit,
180
- options: config,
181
- fetchOptions,
182
- force,
183
- }),
199
+ () =>
200
+ toCollectionEntry({
201
+ context: {
202
+ ...context,
203
+ logger: configLogger,
204
+ } as ExtendedLoaderContext,
205
+ octokit,
206
+ options: config,
207
+ fetchOptions,
208
+ force,
209
+ clear: effectiveClear,
210
+ }),
184
211
  `โœ… ${configName} imported successfully`,
185
- `โŒ ${configName} import failed`
212
+ `โŒ ${configName} import failed`,
186
213
  );
187
214
 
188
215
  summary.duration = Date.now() - startTime;
@@ -191,7 +218,7 @@ export function githubLoader({
191
218
  summary.filesUnchanged = stats?.unchanged || 0;
192
219
  summary.assetsDownloaded = stats?.assetsDownloaded || 0;
193
220
  summary.assetsCached = stats?.assetsCached || 0;
194
- summary.status = 'success';
221
+ summary.status = "success";
195
222
 
196
223
  // Log structured summary
197
224
  configLogger.logImportSummary(summary);
@@ -202,21 +229,29 @@ export function githubLoader({
202
229
  const { data } = await octokit.rest.repos.listCommits({
203
230
  owner: config.owner,
204
231
  repo: config.repo,
205
- sha: config.ref || 'main',
206
- per_page: 1
232
+ sha: config.ref || "main",
233
+ per_page: 1,
207
234
  });
208
235
 
209
236
  if (data.length > 0) {
210
- await updateImportState(process.cwd(), config, data[0].sha);
237
+ await updateImportState(
238
+ process.cwd(),
239
+ config,
240
+ data[0].sha,
241
+ configLogger,
242
+ );
211
243
  }
212
244
  } catch (error) {
213
245
  // Don't fail the import if state tracking fails
214
- configLogger.debug(`Failed to update import state for ${configName}: ${error}`);
246
+ configLogger.debug(
247
+ `Failed to update import state for ${configName}: ${error}`,
248
+ );
215
249
  }
216
- } catch (error: any) {
250
+ } catch (error: unknown) {
217
251
  summary.duration = Date.now() - startTime;
218
- summary.status = 'error';
219
- summary.error = error.message;
252
+ summary.status = "error";
253
+ summary.error =
254
+ error instanceof Error ? error.message : String(error);
220
255
 
221
256
  configLogger.logImportSummary(summary);
222
257
  // Continue with other configs even if one fails