@larkiny/astro-github-loader 0.11.3 → 0.13.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 +35 -55
  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 +5 -131
  10. package/dist/github.content.js +152 -794
  11. package/dist/github.dryrun.d.ts +9 -5
  12. package/dist/github.dryrun.js +49 -25
  13. package/dist/github.link-transform.d.ts +2 -2
  14. package/dist/github.link-transform.js +68 -57
  15. package/dist/github.loader.js +30 -46
  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 +16 -0
  21. package/dist/github.storage.js +115 -0
  22. package/dist/github.types.d.ts +40 -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 +259 -957
  37. package/src/github.dryrun.spec.ts +598 -0
  38. package/src/github.dryrun.ts +108 -54
  39. package/src/github.link-transform.spec.ts +1345 -0
  40. package/src/github.link-transform.ts +177 -95
  41. package/src/github.loader.spec.ts +75 -50
  42. package/src/github.loader.ts +101 -76
  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 +377 -0
  48. package/src/github.storage.ts +135 -0
  49. package/src/github.types.ts +54 -9
  50. package/src/index.ts +43 -6
  51. package/src/test-helpers.ts +215 -0
@@ -1,18 +1,18 @@
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 { generateId, generatePath, shouldIncludeFile } from "./github.content.js";
4
+ import { generateId, generatePath, shouldIncludeFile, } from "./github.content.js";
5
5
  const SLEEP_BETWEEN_DELETES = 10; // ms between file deletions
6
6
  /**
7
7
  * Sleep utility for pacing file operations
8
8
  */
9
9
  function sleep(ms) {
10
- return new Promise(resolve => setTimeout(resolve, ms));
10
+ return new Promise((resolve) => setTimeout(resolve, ms));
11
11
  }
12
12
  /**
13
13
  * Gets all files that should exist locally based on remote repository state
14
14
  */
15
- async function getExpectedFiles(octokit, options, signal) {
15
+ async function getExpectedFiles(octokit, options, logger, signal) {
16
16
  const { owner, repo, ref = "main" } = options;
17
17
  const expectedFiles = new Set();
18
18
  // Get all unique directory prefixes from include patterns to limit scanning
@@ -22,13 +22,15 @@ async function getExpectedFiles(octokit, options, signal) {
22
22
  // Extract directory part from pattern (before any glob wildcards)
23
23
  const pattern = includePattern.pattern;
24
24
  const beforeGlob = pattern.split(/[*?{]/)[0];
25
- const dirPart = beforeGlob.includes('/') ? beforeGlob.substring(0, beforeGlob.lastIndexOf('/')) : '';
25
+ const dirPart = beforeGlob.includes("/")
26
+ ? beforeGlob.substring(0, beforeGlob.lastIndexOf("/"))
27
+ : "";
26
28
  directoriesToScan.add(dirPart);
27
29
  }
28
30
  }
29
31
  else {
30
32
  // If no includes specified, scan from root
31
- directoriesToScan.add('');
33
+ directoriesToScan.add("");
32
34
  }
33
35
  async function processDirectory(dirPath) {
34
36
  try {
@@ -37,16 +39,19 @@ async function getExpectedFiles(octokit, options, signal) {
37
39
  repo,
38
40
  path: dirPath,
39
41
  ref,
40
- request: { signal }
42
+ request: { signal },
41
43
  });
42
44
  if (!Array.isArray(data)) {
43
45
  // Single file
44
- if (data.type === 'file' && shouldIncludeFile(data.path, options).included) {
46
+ if (data.type === "file" &&
47
+ shouldIncludeFile(data.path, options).included) {
45
48
  const id = generateId(data.path);
46
49
  const includeResult = shouldIncludeFile(data.path, options);
47
50
  const localPath = generatePath(data.path, includeResult.included ? includeResult.matchedPattern : null, options);
48
51
  // Convert to absolute path for consistent comparison
49
- const absolutePath = localPath.startsWith('/') ? localPath : join(process.cwd(), localPath);
52
+ const absolutePath = localPath.startsWith("/")
53
+ ? localPath
54
+ : join(process.cwd(), localPath);
50
55
  expectedFiles.add(absolutePath);
51
56
  }
52
57
  return;
@@ -69,7 +74,9 @@ async function getExpectedFiles(octokit, options, signal) {
69
74
  const includeResult = shouldIncludeFile(itemPath, options);
70
75
  const localPath = generatePath(itemPath, includeResult.included ? includeResult.matchedPattern : null, options);
71
76
  // Convert to absolute path for consistent comparison
72
- const absolutePath = localPath.startsWith('/') ? localPath : join(process.cwd(), localPath);
77
+ const absolutePath = localPath.startsWith("/")
78
+ ? localPath
79
+ : join(process.cwd(), localPath);
73
80
  expectedFiles.add(absolutePath);
74
81
  }
75
82
  });
@@ -78,7 +85,7 @@ async function getExpectedFiles(octokit, options, signal) {
78
85
  catch (error) {
79
86
  if (signal?.aborted)
80
87
  throw error;
81
- console.warn(`Failed to process directory ${dirPath}:`, error);
88
+ logger.warn(`Failed to process directory ${dirPath}: ${error}`);
82
89
  }
83
90
  }
84
91
  // Process only the directories that match our include patterns
@@ -90,7 +97,7 @@ async function getExpectedFiles(octokit, options, signal) {
90
97
  /**
91
98
  * Gets all existing local files in the basePath as absolute paths
92
99
  */
93
- async function getExistingFiles(basePath) {
100
+ async function getExistingFiles(basePath, logger) {
94
101
  const existingFiles = new Set();
95
102
  if (!existsSync(basePath)) {
96
103
  return existingFiles;
@@ -102,20 +109,20 @@ async function getExistingFiles(basePath) {
102
109
  const fullPath = join(dirPath, entry.name);
103
110
  if (entry.isDirectory()) {
104
111
  // Skip manifest files and other system directories
105
- if (!entry.name.startsWith('.')) {
112
+ if (!entry.name.startsWith(".")) {
106
113
  await walkDirectory(fullPath);
107
114
  }
108
115
  }
109
116
  else if (entry.isFile()) {
110
117
  // Skip manifest and system files
111
- if (!entry.name.startsWith('.')) {
118
+ if (!entry.name.startsWith(".")) {
112
119
  existingFiles.add(fullPath);
113
120
  }
114
121
  }
115
122
  }
116
123
  }
117
124
  catch (error) {
118
- console.warn(`Failed to read directory ${dirPath}:`, error);
125
+ logger.warn(`Failed to read directory ${dirPath}: ${error}`);
119
126
  }
120
127
  }
121
128
  await walkDirectory(basePath);
@@ -135,7 +142,7 @@ export async function performSelectiveCleanup(config, context, octokit, signal)
135
142
  updated: 0,
136
143
  deleted: 0,
137
144
  unchanged: 0,
138
- duration: Date.now() - startTime
145
+ duration: Date.now() - startTime,
139
146
  };
140
147
  }
141
148
  logger.debug(`Starting selective cleanup for ${configName}`);
@@ -143,8 +150,8 @@ export async function performSelectiveCleanup(config, context, octokit, signal)
143
150
  // Get existing local files from all include pattern base paths
144
151
  const allExistingFiles = new Set();
145
152
  for (const includePattern of config.includes) {
146
- const existingFiles = await getExistingFiles(includePattern.basePath);
147
- existingFiles.forEach(file => allExistingFiles.add(file));
153
+ const existingFiles = await getExistingFiles(includePattern.basePath, logger);
154
+ existingFiles.forEach((file) => allExistingFiles.add(file));
148
155
  }
149
156
  // If no existing files, skip cleanup (fresh import)
150
157
  if (allExistingFiles.size === 0) {
@@ -154,11 +161,11 @@ export async function performSelectiveCleanup(config, context, octokit, signal)
154
161
  updated: 0,
155
162
  deleted: 0,
156
163
  unchanged: 0,
157
- duration: Date.now() - startTime
164
+ duration: Date.now() - startTime,
158
165
  };
159
166
  }
160
167
  // Get expected files from remote repository
161
- const expectedFiles = await getExpectedFiles(octokit, config, signal);
168
+ const expectedFiles = await getExpectedFiles(octokit, config, logger, signal);
162
169
  // Find files to delete (exist locally but not in remote)
163
170
  const filesToDelete = [];
164
171
  for (const existingFile of allExistingFiles) {
@@ -184,10 +191,10 @@ export async function performSelectiveCleanup(config, context, octokit, signal)
184
191
  const duration = Date.now() - startTime;
185
192
  const stats = {
186
193
  added: 0, // Will be counted by main sync process
187
- updated: 0, // Will be counted by main sync process
194
+ updated: 0, // Will be counted by main sync process
188
195
  deleted: deletedCount,
189
196
  unchanged: 0, // Will be counted by main sync process
190
- duration
197
+ duration,
191
198
  };
192
199
  if (deletedCount > 0) {
193
200
  logger.info(`Cleanup completed for ${configName}: ${deletedCount} obsolete files deleted (${duration}ms)`);
@@ -203,14 +210,14 @@ export async function performSelectiveCleanup(config, context, octokit, signal)
203
210
  throw error;
204
211
  }
205
212
  const duration = Date.now() - startTime;
206
- logger.error(`Cleanup failed for ${configName} after ${duration}ms: ${error}`);
213
+ logger.error(`Cleanup failed for ${configName} after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`);
207
214
  // Don't throw - let the main sync process continue
208
215
  return {
209
216
  added: 0,
210
217
  updated: 0,
211
218
  deleted: 0,
212
219
  unchanged: 0,
213
- duration
220
+ duration,
214
221
  };
215
222
  }
216
223
  }
@@ -6,19 +6,3 @@
6
6
  * @internal
7
7
  */
8
8
  export declare const INVALID_STRING_ERROR = "Invalid string";
9
- /**
10
- * Represents an error message indicating that a provided URL is invalid.
11
- * This constant is typically used for validation or error handling when a URL
12
- * does not conform to the expected format or requirements.
13
- *
14
- * @internal
15
- */
16
- export declare const INVALID_URL_ERROR = "Invalid url";
17
- /**
18
- * A constant that holds a default error message indicating that a service response is invalid.
19
- * This value is typically used to signify that the response from a service or API call
20
- * does not meet the expected format, structure, or criteria.
21
- *
22
- * @internal
23
- */
24
- export declare const INVALID_SERVICE_RESPONSE = "Invalid service response";
@@ -6,19 +6,3 @@
6
6
  * @internal
7
7
  */
8
8
  export const INVALID_STRING_ERROR = "Invalid string";
9
- /**
10
- * Represents an error message indicating that a provided URL is invalid.
11
- * This constant is typically used for validation or error handling when a URL
12
- * does not conform to the expected format or requirements.
13
- *
14
- * @internal
15
- */
16
- export const INVALID_URL_ERROR = "Invalid url";
17
- /**
18
- * A constant that holds a default error message indicating that a service response is invalid.
19
- * This value is typically used to signify that the response from a service or API call
20
- * does not meet the expected format, structure, or criteria.
21
- *
22
- * @internal
23
- */
24
- export const INVALID_SERVICE_RESPONSE = "Invalid service response";
@@ -1,110 +1,8 @@
1
- import type { LoaderContext, CollectionEntryOptions, ImportOptions, MatchedPattern } from "./github.types.js";
2
- export interface ImportStats {
3
- processed: number;
4
- updated: number;
5
- unchanged: number;
6
- assetsDownloaded?: number;
7
- assetsCached?: number;
8
- }
9
- /**
10
- * Generates a unique identifier from a file path by removing the extension
11
- * @param filePath - The file path to generate ID from
12
- * @return {string} The generated identifier as a string with extension removed
13
- * @internal
14
- */
15
- export declare function generateId(filePath: string): string;
16
- /**
17
- * Applies path mapping logic to get the final filename for a file
18
- *
19
- * Supports two types of path mappings:
20
- * - **File mapping**: Exact file path match (e.g., 'docs/README.md' -> 'docs/overview.md')
21
- * - **Folder mapping**: Folder path with trailing slash (e.g., 'docs/capabilities/' -> 'docs/')
22
- *
23
- * @param filePath - Original source file path
24
- * @param matchedPattern - The pattern that matched this file
25
- * @param options - Import options containing path mappings
26
- * @returns Final filename after applying path mapping logic
27
- * @internal
28
- */
29
- export declare function applyRename(filePath: string, matchedPattern?: MatchedPattern | null, options?: ImportOptions): string;
30
- /**
31
- * Generates a local file path based on the matched pattern and file path
32
- * @param filePath - The original file path from the repository
33
- * @param matchedPattern - The pattern that matched this file (or null if no includes specified)
34
- * @param options - Import options containing includes patterns for path mapping lookups
35
- * @return {string} The local file path where this content should be stored
36
- * @internal
37
- */
38
- export declare function generatePath(filePath: string, matchedPattern?: MatchedPattern | null, options?: ImportOptions): string;
39
- /**
40
- * Synchronizes a file by ensuring the target directory exists and then writing the specified content to the file at the given path.
41
- *
42
- * @param {string} path - The path of the file to synchronize, including its directory and filename.
43
- * @param {string} content - The content to write into the file.
44
- * @return {Promise<void>} - A promise that resolves when the file has been successfully written.
45
- * @internal
46
- */
47
- export declare function syncFile(path: string, content: string): Promise<void>;
48
- /**
49
- * Checks if a file path should be included and returns the matching pattern
50
- * @param filePath - The file path to check (relative to the repository root)
51
- * @param options - Import options containing includes patterns
52
- * @returns Object with include status and matched pattern, or null if not included
53
- * @internal
54
- */
55
- export declare function shouldIncludeFile(filePath: string, options: ImportOptions): {
56
- included: true;
57
- matchedPattern: MatchedPattern | null;
58
- } | {
59
- included: false;
60
- matchedPattern: null;
61
- };
62
- /**
63
- * Detects asset references in markdown content using regex patterns
64
- * @param content - The markdown content to parse
65
- * @param assetPatterns - File extensions to treat as assets
66
- * @returns Array of detected asset paths
67
- * @internal
68
- */
69
- export declare function detectAssets(content: string, assetPatterns?: string[]): string[];
70
- /**
71
- * Downloads an asset from GitHub and saves it locally
72
- * @param octokit - GitHub API client
73
- * @param owner - Repository owner
74
- * @param repo - Repository name
75
- * @param ref - Git reference
76
- * @param assetPath - Path to the asset in the repository
77
- * @param localPath - Local path where the asset should be saved
78
- * @param signal - Abort signal for cancellation
79
- * @returns Promise that resolves when the asset is downloaded
80
- * @internal
81
- */
82
- export declare function downloadAsset(octokit: any, owner: string, repo: string, ref: string, assetPath: string, localPath: string, signal?: AbortSignal): Promise<void>;
83
- /**
84
- * Transforms asset references in markdown content to use local paths
85
- * @param content - The markdown content to transform
86
- * @param assetMap - Map of original asset paths to new local paths
87
- * @returns Transformed content with updated asset references
88
- * @internal
89
- */
90
- export declare function transformAssetReferences(content: string, assetMap: Map<string, string>): string;
91
- /**
92
- * Synchronizes an entry by fetching its contents, validating its metadata, and storing or rendering it as needed.
93
- *
94
- * @param {LoaderContext} context - The loader context containing the required utilities, metadata, and configuration.
95
- * @param {Object} urls - Object containing URL data.
96
- * @param {string | URL | null} urls.url - The URL of the entry to fetch. Throws an error if null or invalid.
97
- * @param {string} urls.editUrl - The URL for editing the entry.
98
- * @param {RootOptions} options - Configuration settings for processing the entry such as file paths and custom options.
99
- * @param {any} octokit - GitHub API client for downloading assets.
100
- * @param {RequestInit} [init] - Optional parameter for customizing the fetch request.
101
- * @return {Promise<void>} Resolves when the entry has been successfully processed and stored. Throws errors if invalid URL, missing configuration, or other issues occur.
102
- * @internal
103
- */
104
- export declare function syncEntry(context: LoaderContext, { url, editUrl }: {
105
- url: string | URL | null;
106
- editUrl: string;
107
- }, filePath: string, options: ImportOptions, octokit: any, init?: RequestInit): Promise<void>;
1
+ import type { CollectionEntryOptions } from "./github.types.js";
2
+ import { type ImportStats } from "./github.paths.js";
3
+ export { type ImportStats, generateId, generatePath, shouldIncludeFile, applyRename, getHeaders, syncHeaders, } from "./github.paths.js";
4
+ export { syncFile } from "./github.storage.js";
5
+ export { resolveAssetConfig, detectAssets, downloadAsset, transformAssetReferences, } from "./github.assets.js";
108
6
  /**
109
7
  * Converts a given GitHub repository path into a collection entry by fetching the content
110
8
  * from the GitHub repository using the provided Octokit instance and options.
@@ -112,27 +10,3 @@ export declare function syncEntry(context: LoaderContext, { url, editUrl }: {
112
10
  * @internal
113
11
  */
114
12
  export declare function toCollectionEntry({ context, octokit, options, signal, force, clear, }: CollectionEntryOptions): Promise<ImportStats>;
115
- /**
116
- * Get the headers needed to make a conditional request.
117
- * Uses the etag and last-modified values from the meta store.
118
- * @internal
119
- */
120
- export declare function getHeaders({ init, meta, id, }: {
121
- /** Initial headers to include */
122
- init?: RequestInit["headers"];
123
- /** Meta store to get etag and last-modified values from */
124
- meta: LoaderContext["meta"];
125
- id: string;
126
- }): Headers;
127
- /**
128
- * Store the etag or last-modified headers from a response in the meta store.
129
- * @internal
130
- */
131
- export declare function syncHeaders({ headers, meta, id, }: {
132
- /** Headers from the response */
133
- headers: Headers;
134
- /** Meta store to store etag and last-modified values in */
135
- meta: LoaderContext["meta"];
136
- /** id string */
137
- id: string;
138
- }): void;