Skip to content

Commit

Permalink
Add move-pages script (#842)
Browse files Browse the repository at this point in the history
* Add `move-pages` script

* Allow renaming a page
  • Loading branch information
benface authored Dec 16, 2024
1 parent 06315ec commit 150f812
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 5 deletions.
1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "rm -rf .next && rm -rf out && next build",
"fetch-remote-filepaths": "tsx scripts/fetch-remote-filepaths.ts",
"fix-pages-structure": "tsx scripts/fix-pages-structure.ts",
"move-pages": "tsx scripts/move-pages.ts",
"predev": "pnpm fetch-remote-filepaths",
"prebuild": "pnpm fetch-remote-filepaths && pnpm fix-pages-structure",
"postbuild": "next-sitemap --config next-sitemap.config.mjs && node scripts/sitemap-ci.js",
Expand Down
18 changes: 13 additions & 5 deletions website/scripts/fix-pages-structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
* referencing a non-existent directory in the meta file
*/

import fs from 'fs/promises'
import path from 'path'
import fs from 'node:fs/promises'
import path from 'node:path'

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

Expand All @@ -31,7 +31,7 @@ const META_FILENAME = '_meta.js'
const CATCH_ALL_PREFIX = '[[...'
const HIDDEN_FILE_PREFIX = '.'

async function fileExists(filepath: string) {
async function fileExists(filepath: string): Promise<boolean> {
try {
await fs.access(filepath)
return true
Expand Down Expand Up @@ -153,12 +153,20 @@ async function main() {
for (const locale of [SOURCE_LOCALE, ...translatedLocales]) {
const { directories } = await getPagesStructure(locale)
for (const directory of directories) {
if (!sourceStructure.contentDirectories.has(directory)) {
// Delete directory if it has no content files in source language
// AND none of its subdirectories have content files
const existsInSource =
sourceStructure.contentDirectories.has(directory) ||
Array.from(sourceStructure.contentDirectories).some((sourceDir) => sourceDir.startsWith(directory + '/'))
if (!existsInSource) {
console.log(`Removing directory ${path.join(locale, directory)}`)
await fs.rm(path.join(PAGES_DIRECTORY, locale, directory), { recursive: true, force: true })
}
}
}
}

main().catch(console.error)
main().catch((error) => {
console.error(error.message)
process.exit(1)
})
178 changes: 178 additions & 0 deletions website/scripts/move-pages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* This script moves and/or renames pages or directories of pages.
* It performs these operations in order:
*
* 1. Prepares the move using English locale:
* - Gets list of files to move (scanning directory if needed)
* - Errors if files would be overwritten (except _meta.js)
*
* 2. Moves files in all locales:
* - Creates destination directories as needed
* - Skips files that don't exist or would be overwritten
*
* 3. Runs `fix-pages-structure` to:
* - Clean up any orphaned directories
* - Create missing meta files
* - Ensure directory structure is consistent
*/

import { execFile } from 'node:child_process'
import fs from 'node:fs/promises'
import path from 'node:path'
import { promisify } from 'node:util'

const execFileAsync = promisify(execFile)

const PAGES_DIRECTORY = path.join(process.cwd(), 'pages')
const SOURCE_LOCALE = 'en'
const META_FILENAME = '_meta.js'

type FileToMove = {
sourcePath: string // Relative to locale directory
destinationPath: string // Relative to locale directory
}

async function fileExists(filepath: string): Promise<boolean> {
try {
await fs.access(filepath)
return true
} catch {
return false
}
}

async function getSourceFiles(sourcePath: string): Promise<string[]> {
const sourceDirectory = path.join(PAGES_DIRECTORY, SOURCE_LOCALE)
const fullPath = path.join(sourceDirectory, sourcePath)

if (!(await fileExists(fullPath))) {
throw new Error(`Source path '${sourcePath}' does not exist in source locale (${SOURCE_LOCALE})`)
}

const fileStat = await fs.stat(fullPath)
if (fileStat.isDirectory()) {
const files: string[] = []

async function scan(directory: string) {
const items = await fs.readdir(directory, { withFileTypes: true })
for (const item of items) {
const itemPath = path.join(directory, item.name)
if (item.isDirectory()) {
await scan(itemPath)
} else {
files.push(path.relative(sourceDirectory, itemPath))
}
}
}

await scan(fullPath)
return files
}

return [sourcePath]
}

async function main() {
const [sourcePath, destinationPath] = process.argv.slice(2)
if (!sourcePath || !destinationPath) {
throw new Error(
'Usage: pnpm run move-pages <source-path> <destination>\n' +
'Examples:\n' +
' pnpm run move-pages page.mdx new-directory Move page (keeping name)\n' +
' pnpm run move-pages page.mdx new-name.mdx Rename page\n' +
' pnpm run move-pages page.mdx new-dir/new-name.mdx Move and rename page\n' +
' pnpm run move-pages developing subgraphs/developing Move directory\n' +
' pnpm run move-pages developing subgraphs Rename directory\n',
)
}

// Normalize paths
const normalizedSource = path.normalize(sourcePath).replace(/^[/\\]|[/\\]$/g, '')
const normalizedDestination = path.normalize(destinationPath).replace(/^[/\\]|[/\\]$/g, '')

// Get list of files to move from source locale
const sourceFiles = await getSourceFiles(normalizedSource)

// Build destination paths
const isFile = (await fs.stat(path.join(PAGES_DIRECTORY, SOURCE_LOCALE, normalizedSource))).isFile()
const filesToMove = sourceFiles.map((file) => {
if (isFile) {
// When moving a single file:
// - If destination has extension, use it as the new filename
// - Otherwise treat destination as directory and keep original filename
const destinationHasExtension = path.extname(normalizedDestination) !== ''
const newPath = destinationHasExtension
? normalizedDestination
: path.join(normalizedDestination, path.basename(file))

return {
sourcePath: file,
destinationPath: newPath,
}
} else {
// When moving a directory, preserve the relative paths
const relativePath = path.relative(normalizedSource, file)
return {
sourcePath: file,
destinationPath: path.join(normalizedDestination, relativePath),
}
}
})

// Validate moves in English locale
const sourceDirectory = path.join(PAGES_DIRECTORY, SOURCE_LOCALE)
for (const { sourcePath, destinationPath } of filesToMove) {
// Allow _meta.js files to exist in destination since we skip them during move if they exist
if (path.basename(destinationPath) === META_FILENAME) {
continue
}

// Don't allow overwriting existing files
const destinationFile = path.join(sourceDirectory, destinationPath)
if (await fileExists(destinationFile)) {
throw new Error(`Destination path '${destinationPath}' already exists in source locale (${SOURCE_LOCALE})`)
}
}

// Get all locales
const locales = (await fs.readdir(PAGES_DIRECTORY)).filter((directory) => /^[a-z]{2}$/.test(directory))

// Move files in each locale
for (const locale of locales) {
const localeDirectory = path.join(PAGES_DIRECTORY, locale)

// First create all necessary destination directories
const destinationDirs = new Set(filesToMove.map(({ destinationPath }) => path.dirname(destinationPath)))
for (const dir of destinationDirs) {
await fs.mkdir(path.join(localeDirectory, dir), { recursive: true })
}

// Then move the files
for (const { sourcePath, destinationPath } of filesToMove) {
const sourceFile = path.join(localeDirectory, sourcePath)
const destinationFile = path.join(localeDirectory, destinationPath)

// Skip if source doesn't exist or would overwrite an existing _meta.js
if (
!(await fileExists(sourceFile)) ||
(path.basename(destinationPath) === META_FILENAME && (await fileExists(destinationFile)))
) {
continue
}

console.log(`Moving ${path.join(locale, sourcePath)}`)
console.log(` to ${path.join(locale, destinationPath)}`)
await fs.rename(sourceFile, destinationFile)
}
console.log() // Add blank line between locales
}

// Run fix-pages-structure to clean up and update meta files
console.log('\nRunning fix-pages-structure...')
await execFileAsync('tsx', ['scripts/fix-pages-structure.ts'])
}

main().catch((error) => {
console.error(error.message)
process.exit(1)
})

0 comments on commit 150f812

Please sign in to comment.