Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add move-pages script #842

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
Loading