Skip to content

Commit 150f812

Browse files
authored
Add move-pages script (#842)
* Add `move-pages` script * Allow renaming a page
1 parent 06315ec commit 150f812

File tree

3 files changed

+192
-5
lines changed

3 files changed

+192
-5
lines changed

website/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"build": "rm -rf .next && rm -rf out && next build",
99
"fetch-remote-filepaths": "tsx scripts/fetch-remote-filepaths.ts",
1010
"fix-pages-structure": "tsx scripts/fix-pages-structure.ts",
11+
"move-pages": "tsx scripts/move-pages.ts",
1112
"predev": "pnpm fetch-remote-filepaths",
1213
"prebuild": "pnpm fetch-remote-filepaths && pnpm fix-pages-structure",
1314
"postbuild": "next-sitemap --config next-sitemap.config.mjs && node scripts/sitemap-ci.js",

website/scripts/fix-pages-structure.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
* referencing a non-existent directory in the meta file
2121
*/
2222

23-
import fs from 'fs/promises'
24-
import path from 'path'
23+
import fs from 'node:fs/promises'
24+
import path from 'node:path'
2525

2626
const FORCE_META = process.argv.includes('--force-meta')
2727

@@ -31,7 +31,7 @@ const META_FILENAME = '_meta.js'
3131
const CATCH_ALL_PREFIX = '[[...'
3232
const HIDDEN_FILE_PREFIX = '.'
3333

34-
async function fileExists(filepath: string) {
34+
async function fileExists(filepath: string): Promise<boolean> {
3535
try {
3636
await fs.access(filepath)
3737
return true
@@ -153,12 +153,20 @@ async function main() {
153153
for (const locale of [SOURCE_LOCALE, ...translatedLocales]) {
154154
const { directories } = await getPagesStructure(locale)
155155
for (const directory of directories) {
156-
if (!sourceStructure.contentDirectories.has(directory)) {
156+
// Delete directory if it has no content files in source language
157+
// AND none of its subdirectories have content files
158+
const existsInSource =
159+
sourceStructure.contentDirectories.has(directory) ||
160+
Array.from(sourceStructure.contentDirectories).some((sourceDir) => sourceDir.startsWith(directory + '/'))
161+
if (!existsInSource) {
157162
console.log(`Removing directory ${path.join(locale, directory)}`)
158163
await fs.rm(path.join(PAGES_DIRECTORY, locale, directory), { recursive: true, force: true })
159164
}
160165
}
161166
}
162167
}
163168

164-
main().catch(console.error)
169+
main().catch((error) => {
170+
console.error(error.message)
171+
process.exit(1)
172+
})

website/scripts/move-pages.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* This script moves and/or renames pages or directories of pages.
3+
* It performs these operations in order:
4+
*
5+
* 1. Prepares the move using English locale:
6+
* - Gets list of files to move (scanning directory if needed)
7+
* - Errors if files would be overwritten (except _meta.js)
8+
*
9+
* 2. Moves files in all locales:
10+
* - Creates destination directories as needed
11+
* - Skips files that don't exist or would be overwritten
12+
*
13+
* 3. Runs `fix-pages-structure` to:
14+
* - Clean up any orphaned directories
15+
* - Create missing meta files
16+
* - Ensure directory structure is consistent
17+
*/
18+
19+
import { execFile } from 'node:child_process'
20+
import fs from 'node:fs/promises'
21+
import path from 'node:path'
22+
import { promisify } from 'node:util'
23+
24+
const execFileAsync = promisify(execFile)
25+
26+
const PAGES_DIRECTORY = path.join(process.cwd(), 'pages')
27+
const SOURCE_LOCALE = 'en'
28+
const META_FILENAME = '_meta.js'
29+
30+
type FileToMove = {
31+
sourcePath: string // Relative to locale directory
32+
destinationPath: string // Relative to locale directory
33+
}
34+
35+
async function fileExists(filepath: string): Promise<boolean> {
36+
try {
37+
await fs.access(filepath)
38+
return true
39+
} catch {
40+
return false
41+
}
42+
}
43+
44+
async function getSourceFiles(sourcePath: string): Promise<string[]> {
45+
const sourceDirectory = path.join(PAGES_DIRECTORY, SOURCE_LOCALE)
46+
const fullPath = path.join(sourceDirectory, sourcePath)
47+
48+
if (!(await fileExists(fullPath))) {
49+
throw new Error(`Source path '${sourcePath}' does not exist in source locale (${SOURCE_LOCALE})`)
50+
}
51+
52+
const fileStat = await fs.stat(fullPath)
53+
if (fileStat.isDirectory()) {
54+
const files: string[] = []
55+
56+
async function scan(directory: string) {
57+
const items = await fs.readdir(directory, { withFileTypes: true })
58+
for (const item of items) {
59+
const itemPath = path.join(directory, item.name)
60+
if (item.isDirectory()) {
61+
await scan(itemPath)
62+
} else {
63+
files.push(path.relative(sourceDirectory, itemPath))
64+
}
65+
}
66+
}
67+
68+
await scan(fullPath)
69+
return files
70+
}
71+
72+
return [sourcePath]
73+
}
74+
75+
async function main() {
76+
const [sourcePath, destinationPath] = process.argv.slice(2)
77+
if (!sourcePath || !destinationPath) {
78+
throw new Error(
79+
'Usage: pnpm run move-pages <source-path> <destination>\n' +
80+
'Examples:\n' +
81+
' pnpm run move-pages page.mdx new-directory Move page (keeping name)\n' +
82+
' pnpm run move-pages page.mdx new-name.mdx Rename page\n' +
83+
' pnpm run move-pages page.mdx new-dir/new-name.mdx Move and rename page\n' +
84+
' pnpm run move-pages developing subgraphs/developing Move directory\n' +
85+
' pnpm run move-pages developing subgraphs Rename directory\n',
86+
)
87+
}
88+
89+
// Normalize paths
90+
const normalizedSource = path.normalize(sourcePath).replace(/^[/\\]|[/\\]$/g, '')
91+
const normalizedDestination = path.normalize(destinationPath).replace(/^[/\\]|[/\\]$/g, '')
92+
93+
// Get list of files to move from source locale
94+
const sourceFiles = await getSourceFiles(normalizedSource)
95+
96+
// Build destination paths
97+
const isFile = (await fs.stat(path.join(PAGES_DIRECTORY, SOURCE_LOCALE, normalizedSource))).isFile()
98+
const filesToMove = sourceFiles.map((file) => {
99+
if (isFile) {
100+
// When moving a single file:
101+
// - If destination has extension, use it as the new filename
102+
// - Otherwise treat destination as directory and keep original filename
103+
const destinationHasExtension = path.extname(normalizedDestination) !== ''
104+
const newPath = destinationHasExtension
105+
? normalizedDestination
106+
: path.join(normalizedDestination, path.basename(file))
107+
108+
return {
109+
sourcePath: file,
110+
destinationPath: newPath,
111+
}
112+
} else {
113+
// When moving a directory, preserve the relative paths
114+
const relativePath = path.relative(normalizedSource, file)
115+
return {
116+
sourcePath: file,
117+
destinationPath: path.join(normalizedDestination, relativePath),
118+
}
119+
}
120+
})
121+
122+
// Validate moves in English locale
123+
const sourceDirectory = path.join(PAGES_DIRECTORY, SOURCE_LOCALE)
124+
for (const { sourcePath, destinationPath } of filesToMove) {
125+
// Allow _meta.js files to exist in destination since we skip them during move if they exist
126+
if (path.basename(destinationPath) === META_FILENAME) {
127+
continue
128+
}
129+
130+
// Don't allow overwriting existing files
131+
const destinationFile = path.join(sourceDirectory, destinationPath)
132+
if (await fileExists(destinationFile)) {
133+
throw new Error(`Destination path '${destinationPath}' already exists in source locale (${SOURCE_LOCALE})`)
134+
}
135+
}
136+
137+
// Get all locales
138+
const locales = (await fs.readdir(PAGES_DIRECTORY)).filter((directory) => /^[a-z]{2}$/.test(directory))
139+
140+
// Move files in each locale
141+
for (const locale of locales) {
142+
const localeDirectory = path.join(PAGES_DIRECTORY, locale)
143+
144+
// First create all necessary destination directories
145+
const destinationDirs = new Set(filesToMove.map(({ destinationPath }) => path.dirname(destinationPath)))
146+
for (const dir of destinationDirs) {
147+
await fs.mkdir(path.join(localeDirectory, dir), { recursive: true })
148+
}
149+
150+
// Then move the files
151+
for (const { sourcePath, destinationPath } of filesToMove) {
152+
const sourceFile = path.join(localeDirectory, sourcePath)
153+
const destinationFile = path.join(localeDirectory, destinationPath)
154+
155+
// Skip if source doesn't exist or would overwrite an existing _meta.js
156+
if (
157+
!(await fileExists(sourceFile)) ||
158+
(path.basename(destinationPath) === META_FILENAME && (await fileExists(destinationFile)))
159+
) {
160+
continue
161+
}
162+
163+
console.log(`Moving ${path.join(locale, sourcePath)}`)
164+
console.log(` to ${path.join(locale, destinationPath)}`)
165+
await fs.rename(sourceFile, destinationFile)
166+
}
167+
console.log() // Add blank line between locales
168+
}
169+
170+
// Run fix-pages-structure to clean up and update meta files
171+
console.log('\nRunning fix-pages-structure...')
172+
await execFileAsync('tsx', ['scripts/fix-pages-structure.ts'])
173+
}
174+
175+
main().catch((error) => {
176+
console.error(error.message)
177+
process.exit(1)
178+
})

0 commit comments

Comments
 (0)