@aigne/doc-smith 0.1.3 → 0.2.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/utils/utils.mjs CHANGED
@@ -1,5 +1,34 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import { existsSync, mkdirSync } from "node:fs";
5
+ import { parse } from "yaml";
6
+ import {
7
+ DEFAULT_INCLUDE_PATTERNS,
8
+ DEFAULT_EXCLUDE_PATTERNS,
9
+ } from "./constants.mjs";
10
+
11
+ /**
12
+ * Normalize path to absolute path for consistent comparison
13
+ * @param {string} filePath - The path to normalize
14
+ * @returns {string} - Absolute path
15
+ */
16
+ export function normalizePath(filePath) {
17
+ return path.isAbsolute(filePath)
18
+ ? filePath
19
+ : path.resolve(process.cwd(), filePath);
20
+ }
21
+
22
+ /**
23
+ * Convert path to relative path from current working directory
24
+ * @param {string} filePath - The path to convert
25
+ * @returns {string} - Relative path
26
+ */
27
+ export function toRelativePath(filePath) {
28
+ return path.isAbsolute(filePath)
29
+ ? path.relative(process.cwd(), filePath)
30
+ : filePath;
31
+ }
3
32
 
4
33
  export function processContent({ content }) {
5
34
  // Match markdown regular links [text](link), exclude images ![text](link)
@@ -95,3 +124,273 @@ export async function saveDocWithTranslations({
95
124
  }
96
125
  return results;
97
126
  }
127
+
128
+ /**
129
+ * Get current git HEAD commit hash
130
+ * @returns {string} - The current git HEAD commit hash
131
+ */
132
+ export function getCurrentGitHead() {
133
+ try {
134
+ return execSync("git rev-parse HEAD", {
135
+ encoding: "utf8",
136
+ stdio: ["pipe", "pipe", "ignore"],
137
+ }).trim();
138
+ } catch (error) {
139
+ // Not in git repository or git command failed
140
+ console.warn("Failed to get git HEAD:", error.message);
141
+ return null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Save git HEAD to config.yaml file
147
+ * @param {string} gitHead - The current git HEAD commit hash
148
+ */
149
+ export async function saveGitHeadToConfig(gitHead) {
150
+ if (!gitHead) {
151
+ return; // Skip if no git HEAD available
152
+ }
153
+
154
+ try {
155
+ const docSmithDir = path.join(process.cwd(), "doc-smith");
156
+ if (!existsSync(docSmithDir)) {
157
+ mkdirSync(docSmithDir, { recursive: true });
158
+ }
159
+
160
+ const inputFilePath = path.join(docSmithDir, "config.yaml");
161
+ let fileContent = "";
162
+
163
+ // Read existing file content if it exists
164
+ if (existsSync(inputFilePath)) {
165
+ fileContent = await fs.readFile(inputFilePath, "utf8");
166
+ }
167
+
168
+ // Check if lastGitHead already exists in the file
169
+ const lastGitHeadRegex = /^lastGitHead:\s*.*$/m;
170
+ const newLastGitHeadLine = `lastGitHead: ${gitHead}`;
171
+
172
+ if (lastGitHeadRegex.test(fileContent)) {
173
+ // Replace existing lastGitHead line
174
+ fileContent = fileContent.replace(lastGitHeadRegex, newLastGitHeadLine);
175
+ } else {
176
+ // Add lastGitHead to the end of file
177
+ if (fileContent && !fileContent.endsWith("\n")) {
178
+ fileContent += "\n";
179
+ }
180
+ fileContent += newLastGitHeadLine + "\n";
181
+ }
182
+
183
+ await fs.writeFile(inputFilePath, fileContent);
184
+ } catch (error) {
185
+ console.warn("Failed to save git HEAD to config.yaml:", error.message);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Check if files have been modified between two git commits
191
+ * @param {string} fromCommit - Starting commit hash
192
+ * @param {string} toCommit - Ending commit hash (defaults to HEAD)
193
+ * @param {Array<string>} filePaths - Array of file paths to check
194
+ * @returns {Array<string>} - Array of modified file paths
195
+ */
196
+ export function getModifiedFilesBetweenCommits(
197
+ fromCommit,
198
+ toCommit = "HEAD",
199
+ filePaths = []
200
+ ) {
201
+ try {
202
+ // Get all modified files between commits
203
+ const modifiedFiles = execSync(
204
+ `git diff --name-only ${fromCommit}..${toCommit}`,
205
+ {
206
+ encoding: "utf8",
207
+ stdio: ["pipe", "pipe", "ignore"],
208
+ }
209
+ )
210
+ .trim()
211
+ .split("\n")
212
+ .filter(Boolean);
213
+
214
+ // Filter to only include files we care about
215
+ if (filePaths.length === 0) {
216
+ return modifiedFiles;
217
+ }
218
+
219
+ return modifiedFiles.filter((file) =>
220
+ filePaths.some((targetPath) => {
221
+ const absoluteFile = normalizePath(file);
222
+ const absoluteTarget = normalizePath(targetPath);
223
+ return absoluteFile === absoluteTarget;
224
+ })
225
+ );
226
+ } catch (error) {
227
+ console.warn(
228
+ `Failed to get modified files between ${fromCommit} and ${toCommit}:`,
229
+ error.message
230
+ );
231
+ return [];
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Check if any source files have changed based on modified files list
237
+ * @param {Array<string>} sourceIds - Source file paths
238
+ * @param {Array<string>} modifiedFiles - List of modified files between commits
239
+ * @returns {boolean} - True if any source files have changed
240
+ */
241
+ export function hasSourceFilesChanged(sourceIds, modifiedFiles) {
242
+ if (!sourceIds || sourceIds.length === 0 || !modifiedFiles) {
243
+ return false; // No source files or no modified files
244
+ }
245
+
246
+ return modifiedFiles.some((modifiedFile) =>
247
+ sourceIds.some((sourceId) => {
248
+ const absoluteModifiedFile = normalizePath(modifiedFile);
249
+ const absoluteSourceId = normalizePath(sourceId);
250
+ return absoluteModifiedFile === absoluteSourceId;
251
+ })
252
+ );
253
+ }
254
+
255
+ /**
256
+ * Check if there are any added or deleted files between two git commits that match the include/exclude patterns
257
+ * @param {string} fromCommit - Starting commit hash
258
+ * @param {string} toCommit - Ending commit hash (defaults to HEAD)
259
+ * @param {Array<string>} includePatterns - Include patterns to match files
260
+ * @param {Array<string>} excludePatterns - Exclude patterns to filter files
261
+ * @returns {boolean} - True if there are relevant added/deleted files
262
+ */
263
+ export function hasFileChangesBetweenCommits(
264
+ fromCommit,
265
+ toCommit = "HEAD",
266
+ includePatterns = DEFAULT_INCLUDE_PATTERNS,
267
+ excludePatterns = DEFAULT_EXCLUDE_PATTERNS
268
+ ) {
269
+ try {
270
+ // Get file changes with status (A=added, D=deleted, M=modified)
271
+ const changes = execSync(
272
+ `git diff --name-status ${fromCommit}..${toCommit}`,
273
+ {
274
+ encoding: "utf8",
275
+ stdio: ["pipe", "pipe", "ignore"],
276
+ }
277
+ )
278
+ .trim()
279
+ .split("\n")
280
+ .filter(Boolean);
281
+
282
+ // Only check for added (A) and deleted (D) files
283
+ const addedOrDeletedFiles = changes
284
+ .filter((line) => {
285
+ const [status, filePath] = line.split(/\s+/);
286
+ return (status === "A" || status === "D") && filePath;
287
+ })
288
+ .map((line) => line.split(/\s+/)[1]);
289
+
290
+ if (addedOrDeletedFiles.length === 0) {
291
+ return false;
292
+ }
293
+
294
+ // Check if any of the added/deleted files match the include patterns and don't match exclude patterns
295
+ return addedOrDeletedFiles.some((filePath) => {
296
+ // Check if file matches any include pattern
297
+ const matchesInclude = includePatterns.some((pattern) => {
298
+ // Convert glob pattern to regex for matching
299
+ const regexPattern = pattern
300
+ .replace(/\./g, "\\.")
301
+ .replace(/\*/g, ".*")
302
+ .replace(/\?/g, ".");
303
+ const regex = new RegExp(regexPattern);
304
+ return regex.test(filePath);
305
+ });
306
+
307
+ if (!matchesInclude) {
308
+ return false;
309
+ }
310
+
311
+ // Check if file matches any exclude pattern
312
+ const matchesExclude = excludePatterns.some((pattern) => {
313
+ // Convert glob pattern to regex for matching
314
+ const regexPattern = pattern
315
+ .replace(/\./g, "\\.")
316
+ .replace(/\*/g, ".*")
317
+ .replace(/\?/g, ".");
318
+ const regex = new RegExp(regexPattern);
319
+ return regex.test(filePath);
320
+ });
321
+
322
+ return !matchesExclude;
323
+ });
324
+ } catch (error) {
325
+ console.warn(
326
+ `Failed to check file changes between ${fromCommit} and ${toCommit}:`,
327
+ error.message
328
+ );
329
+ return false;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Load config from config.yaml file
335
+ * @returns {Promise<Object|null>} - The config object or null if file doesn't exist
336
+ */
337
+ export async function loadConfigFromFile() {
338
+ const configPath = path.join(process.cwd(), "doc-smith", "config.yaml");
339
+
340
+ try {
341
+ if (!existsSync(configPath)) {
342
+ return null;
343
+ }
344
+
345
+ const configContent = await fs.readFile(configPath, "utf8");
346
+ return parse(configContent);
347
+ } catch (error) {
348
+ console.warn("Failed to read config file:", error.message);
349
+ return null;
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Save value to config.yaml file
355
+ * @param {string} key - The config key to save
356
+ * @param {string} value - The value to save
357
+ */
358
+ export async function saveValueToConfig(key, value) {
359
+ if (!value) {
360
+ return; // Skip if no value provided
361
+ }
362
+
363
+ try {
364
+ const docSmithDir = path.join(process.cwd(), "doc-smith");
365
+ if (!existsSync(docSmithDir)) {
366
+ mkdirSync(docSmithDir, { recursive: true });
367
+ }
368
+
369
+ const configPath = path.join(docSmithDir, "config.yaml");
370
+ let fileContent = "";
371
+
372
+ // Read existing file content if it exists
373
+ if (existsSync(configPath)) {
374
+ fileContent = await fs.readFile(configPath, "utf8");
375
+ }
376
+
377
+ // Check if key already exists in the file
378
+ const keyRegex = new RegExp(`^${key}:\\s*.*$`, "m");
379
+ const newKeyLine = `${key}: ${value}`;
380
+
381
+ if (keyRegex.test(fileContent)) {
382
+ // Replace existing key line
383
+ fileContent = fileContent.replace(keyRegex, newKeyLine);
384
+ } else {
385
+ // Add key to the end of file
386
+ if (fileContent && !fileContent.endsWith("\n")) {
387
+ fileContent += "\n";
388
+ }
389
+ fileContent += newKeyLine + "\n";
390
+ }
391
+
392
+ await fs.writeFile(configPath, fileContent);
393
+ } catch (error) {
394
+ console.warn(`Failed to save ${key} to config.yaml:`, error.message);
395
+ }
396
+ }