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)