@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.
- package/README.md +69 -61
- package/dist/github.assets.d.ts +70 -0
- package/dist/github.assets.js +253 -0
- package/dist/github.auth.js +13 -9
- package/dist/github.cleanup.d.ts +3 -2
- package/dist/github.cleanup.js +30 -23
- package/dist/github.constants.d.ts +0 -16
- package/dist/github.constants.js +0 -16
- package/dist/github.content.d.ts +6 -132
- package/dist/github.content.js +154 -789
- package/dist/github.dryrun.d.ts +9 -5
- package/dist/github.dryrun.js +46 -25
- package/dist/github.link-transform.d.ts +2 -2
- package/dist/github.link-transform.js +65 -57
- package/dist/github.loader.js +45 -51
- package/dist/github.logger.d.ts +2 -2
- package/dist/github.logger.js +33 -24
- package/dist/github.paths.d.ts +76 -0
- package/dist/github.paths.js +190 -0
- package/dist/github.storage.d.ts +15 -0
- package/dist/github.storage.js +109 -0
- package/dist/github.types.d.ts +41 -4
- package/dist/index.d.ts +8 -6
- package/dist/index.js +3 -6
- package/dist/test-helpers.d.ts +130 -0
- package/dist/test-helpers.js +194 -0
- package/package.json +3 -1
- package/src/github.assets.spec.ts +717 -0
- package/src/github.assets.ts +365 -0
- package/src/github.auth.spec.ts +245 -0
- package/src/github.auth.ts +24 -10
- package/src/github.cleanup.spec.ts +380 -0
- package/src/github.cleanup.ts +91 -47
- package/src/github.constants.ts +0 -17
- package/src/github.content.spec.ts +305 -454
- package/src/github.content.ts +261 -950
- package/src/github.dryrun.spec.ts +586 -0
- package/src/github.dryrun.ts +105 -54
- package/src/github.link-transform.spec.ts +1345 -0
- package/src/github.link-transform.ts +174 -95
- package/src/github.loader.spec.ts +75 -50
- package/src/github.loader.ts +113 -78
- package/src/github.logger.spec.ts +795 -0
- package/src/github.logger.ts +77 -35
- package/src/github.paths.spec.ts +523 -0
- package/src/github.paths.ts +259 -0
- package/src/github.storage.spec.ts +367 -0
- package/src/github.storage.ts +127 -0
- package/src/github.types.ts +55 -9
- package/src/index.ts +43 -6
- package/src/test-helpers.ts +215 -0
package/src/github.dryrun.ts
CHANGED
|
@@ -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 =
|
|
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 ||
|
|
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(
|
|
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,
|
|
76
|
-
|
|
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
|
-
|
|
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(
|
|
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),
|
|
117
|
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
94
118
|
} catch (error) {
|
|
95
|
-
|
|
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:
|
|
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(
|
|
127
|
-
date:
|
|
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:
|
|
130
|
-
if (
|
|
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:
|
|
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 =
|
|
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 ||
|
|
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:
|
|
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(
|
|
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 ||
|
|
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(
|
|
280
|
+
const changeInfo = await checkRepositoryForChanges(
|
|
281
|
+
octokit,
|
|
282
|
+
config,
|
|
283
|
+
currentState,
|
|
284
|
+
signal,
|
|
285
|
+
);
|
|
243
286
|
results.push(changeInfo);
|
|
244
|
-
} catch (error:
|
|
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(
|
|
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("="
|
|
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 =
|
|
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("="
|
|
308
|
-
logger.info(
|
|
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(
|
|
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
|
+
}
|