Skip to content

Commit 7d2af1b

Browse files
committed
feat: add cripts/process-file-changes
1 parent ab0d601 commit 7d2af1b

File tree

3 files changed

+708
-112
lines changed

3 files changed

+708
-112
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync, spawn } = require('node:child_process');
4+
const fs = require('node:fs');
5+
const path = require('node:path');
6+
7+
/**
8+
* Execute a shell command and return the output
9+
*/
10+
function execCommand(command, options = {}) {
11+
try {
12+
return execSync(command, {
13+
encoding: 'utf8',
14+
stdio: ['pipe', 'pipe', 'pipe'],
15+
...options,
16+
}).trim();
17+
} catch (error) {
18+
if (options.allowEmpty && error.status === 1) {
19+
return '';
20+
}
21+
throw error;
22+
}
23+
}
24+
25+
/**
26+
* Get the current timestamp in ISO format
27+
*/
28+
function getCurrentTimestamp() {
29+
return new Date().toISOString();
30+
}
31+
32+
/**
33+
* Find all available translation locales
34+
*/
35+
function findAvailableLocales() {
36+
const contentDir = path.join(process.cwd(), 'apps/docs/content');
37+
38+
if (!fs.existsSync(contentDir)) {
39+
console.log('Content directory not found:', contentDir);
40+
return [];
41+
}
42+
43+
const locales = fs
44+
.readdirSync(contentDir, { withFileTypes: true })
45+
.filter((dirent) => dirent.isDirectory() && dirent.name !== 'en')
46+
.map((dirent) => dirent.name);
47+
48+
console.log('Available translation locales:', locales.join(' '));
49+
return locales;
50+
}
51+
52+
/**
53+
* Get renamed files from git status
54+
*/
55+
function getRenamedFiles() {
56+
try {
57+
const gitStatus = execCommand('git status --porcelain', {
58+
allowEmpty: true,
59+
});
60+
const renames = gitStatus
61+
.split('\n')
62+
.filter((line) => line.match(/^R\s+apps\/docs\/content\/en/))
63+
.map((line) => line.replace(/^R\s+/, ''));
64+
65+
console.log('Renamed files detected:', renames.length);
66+
return renames;
67+
} catch (error) {
68+
console.log('No renamed files detected or git error:', error.message);
69+
return [];
70+
}
71+
}
72+
73+
/**
74+
* Get deleted files from git status
75+
*/
76+
function getDeletedFiles() {
77+
try {
78+
const gitStatus = execCommand('git status --porcelain', {
79+
allowEmpty: true,
80+
});
81+
const deletes = gitStatus
82+
.split('\n')
83+
.filter((line) => line.match(/^D\s+apps\/docs\/content\/en/))
84+
.map((line) => line.replace(/^D\s+/, ''));
85+
86+
console.log('Deleted files detected:', deletes.length);
87+
return deletes;
88+
} catch (error) {
89+
console.log('No deleted files detected or git error:', error.message);
90+
return [];
91+
}
92+
}
93+
94+
/**
95+
* Check if a rename involves content changes
96+
*/
97+
function hasContentChanges(sourcePath, destPath) {
98+
try {
99+
const diffOutput = execCommand(
100+
`git diff --cached -M -- "${sourcePath}" "${destPath}"`,
101+
{ allowEmpty: true },
102+
);
103+
104+
// Count lines that start with +/- (changes) but ignore the rename header lines
105+
const changeLines = diffOutput
106+
.split('\n')
107+
.filter((line) => !line.startsWith('renamed:') && !line.startsWith('─'))
108+
.filter((line) => line.match(/^[\+\-]/));
109+
110+
const changeCount = changeLines.length;
111+
console.log(
112+
`Git diff for '${sourcePath}' → '${destPath}': ${changeCount} lines changed`,
113+
);
114+
115+
return changeCount > 0;
116+
} catch (error) {
117+
console.log('Error checking content changes:', error.message);
118+
return false;
119+
}
120+
}
121+
122+
/**
123+
* Update timestamps in a translation file
124+
*/
125+
function updateTimestamps(filePath, timestamp) {
126+
try {
127+
let content = fs.readFileSync(filePath, 'utf8');
128+
129+
// Update source-updated-at and translation-updated-at
130+
content = content.replace(
131+
/source-updated-at: .*/,
132+
`source-updated-at: ${timestamp}`,
133+
);
134+
content = content.replace(
135+
/translation-updated-at: .*/,
136+
`translation-updated-at: ${timestamp}`,
137+
);
138+
139+
fs.writeFileSync(filePath, content, 'utf8');
140+
console.log(`Updated timestamps in ${filePath}`);
141+
} catch (error) {
142+
console.log(`Error updating timestamps in ${filePath}:`, error.message);
143+
}
144+
}
145+
146+
/**
147+
* Process file renames for all locales
148+
*/
149+
function processRenames(renames, locales) {
150+
if (renames.length === 0) {
151+
console.log('No file renames detected in English docs.');
152+
return;
153+
}
154+
155+
console.log(
156+
'File renames detected in English docs. Processing for other languages...',
157+
);
158+
const currentTimestamp = getCurrentTimestamp();
159+
console.log('Current timestamp:', currentTimestamp);
160+
161+
for (const locale of locales) {
162+
console.log(`Processing renames for locale: ${locale}`);
163+
164+
for (const rename of renames) {
165+
// Parse the rename line: "oldname -> newname"
166+
const parts = rename.split(' -> ');
167+
if (parts.length !== 2) {
168+
console.log(`Invalid rename format: ${rename}`);
169+
continue;
170+
}
171+
172+
const [source, dest] = parts;
173+
174+
// Check for content changes
175+
const contentChanged = hasContentChanges(source, dest);
176+
const contentUnchanged = !contentChanged;
177+
178+
if (contentUnchanged) {
179+
console.log('Pure rename detected (no content changes)');
180+
} else {
181+
console.log('Content changes detected along with rename');
182+
}
183+
184+
// Replace 'en' with current locale in paths
185+
const sourceLocale = source.replace('content/en', `content/${locale}`);
186+
const destLocale = dest.replace('content/en', `content/${locale}`);
187+
188+
// Check if source file exists in this locale
189+
if (fs.existsSync(sourceLocale)) {
190+
console.log(`Renaming ${sourceLocale} to ${destLocale}`);
191+
192+
// Create directory if it doesn't exist
193+
const destDir = path.dirname(destLocale);
194+
if (!fs.existsSync(destDir)) {
195+
fs.mkdirSync(destDir, { recursive: true });
196+
}
197+
198+
// Move the file
199+
fs.renameSync(sourceLocale, destLocale);
200+
201+
// If content is unchanged, update timestamps
202+
if (contentUnchanged) {
203+
console.log(
204+
`Content unchanged, updating timestamps in ${destLocale}`,
205+
);
206+
updateTimestamps(destLocale, currentTimestamp);
207+
} else {
208+
console.log(
209+
'Content changed, keeping original timestamps for translation to detect changes',
210+
);
211+
}
212+
}
213+
}
214+
}
215+
}
216+
217+
/**
218+
* Process file deletions for all locales
219+
*/
220+
function processDeletions(deletions, locales) {
221+
if (deletions.length === 0) {
222+
console.log('No file deletions detected in English docs.');
223+
return;
224+
}
225+
226+
console.log(
227+
'File deletions detected in English docs. Processing for other languages...',
228+
);
229+
230+
for (const locale of locales) {
231+
console.log(`Processing deletions for locale: ${locale}`);
232+
233+
for (const deletedFile of deletions) {
234+
// Replace 'en' with current locale in path
235+
const fileLocale = deletedFile.replace('content/en', `content/${locale}`);
236+
237+
// Check if file exists in this locale
238+
if (fs.existsSync(fileLocale)) {
239+
console.log(`Deleting ${fileLocale}`);
240+
fs.unlinkSync(fileLocale);
241+
242+
// Check if parent directory is empty and remove it if it is
243+
const dir = path.dirname(fileLocale);
244+
try {
245+
if (fs.existsSync(dir)) {
246+
const dirContents = fs.readdirSync(dir);
247+
if (dirContents.length === 0) {
248+
console.log(`Removing empty directory: ${dir}`);
249+
fs.rmdirSync(dir);
250+
}
251+
}
252+
} catch (error) {
253+
// Directory might not be empty or other error, that's ok
254+
console.log(`Could not remove directory ${dir}:`, error.message);
255+
}
256+
}
257+
}
258+
}
259+
}
260+
261+
/**
262+
* Main function
263+
*/
264+
function main() {
265+
console.log('Processing file renames and deletions...');
266+
267+
try {
268+
// Find available locales
269+
const locales = findAvailableLocales();
270+
271+
if (locales.length === 0) {
272+
console.log('No translation locales found, skipping processing.');
273+
return;
274+
}
275+
276+
// Get renamed and deleted files
277+
const renames = getRenamedFiles();
278+
const deletions = getDeletedFiles();
279+
280+
// Process renames
281+
processRenames(renames, locales);
282+
283+
// Process deletions
284+
processDeletions(deletions, locales);
285+
286+
console.log('File processing completed successfully.');
287+
} catch (error) {
288+
console.error('Error processing file changes:', error.message);
289+
process.exit(1);
290+
}
291+
}
292+
293+
// Run the script if called directly
294+
if (require.main === module) {
295+
main();
296+
}
297+
298+
module.exports = {
299+
main,
300+
findAvailableLocales,
301+
getRenamedFiles,
302+
getDeletedFiles,
303+
processRenames,
304+
processDeletions,
305+
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy