|
1 | 1 | #!/usr/bin/env node |
2 | 2 |
|
3 | | -const assert = require('assert') |
4 | 3 | const fs = require('fs') |
5 | 4 | const path = require('path') |
6 | | -const walk = require('walk-sync') |
| 5 | +const mkdirp = require('mkdirp').sync |
| 6 | +const program = require('commander') |
7 | 7 | const { execSync } = require('child_process') |
8 | | -const matter = require('gray-matter') |
| 8 | +const frontmatter = require('../lib/read-frontmatter') |
9 | 9 | const addRedirectToFrontmatter = require('./helpers/add-redirect-to-frontmatter') |
10 | | -const contentDir = path.join(__dirname, '../content') |
| 10 | +const walkFiles = require('./helpers/walk-files') |
| 11 | +const contentFiles = walkFiles('content', '.md') |
| 12 | +const contentDir = path.posix.join(process.cwd(), 'content') |
11 | 13 |
|
12 | 14 | // [start-readme] |
13 | 15 | // |
14 | | -// Pass this script three arguments: |
15 | | -// 1. current category path (e.g., `github/automating-your-workflows-with-github-actions`) |
16 | | -// 2. new product ID (e.g., `actions`) |
17 | | -// 3. new product name in quotes (e.g., `"GitHub Actions"`) |
18 | | -// and it does everything that needs to be done to make the category into a new product. |
| 16 | +// Move the files from a category directory to a top-level product and add redirects. |
19 | 17 | // |
20 | 18 | // [end-readme] |
21 | 19 |
|
22 | | -// derive global values |
23 | | -const [relativePath, productId, productName] = process.argv.slice(2) |
24 | | -assert(relativePath, 'first arg must be a path to an existing category, e.g., github/working-with-github-pages') |
25 | | -assert(productId, 'second arg must be the ID of the new product, e.g., pages') |
26 | | -assert(productName, 'third arg must be the full name of the new product in quotes, e.g., "GitHub Pages"') |
27 | | -assert.strictEqual(relativePath.split('/').length, 2, 'first arg must only contain one slash, e.g., github/working-with-github-pages') |
| 20 | +program |
| 21 | + .description('Move a category-level docs set to the product level.') |
| 22 | + .requiredOption('-c, --category <PATH>', 'Provide the path of the existing category, e.g., github/github-pages') |
| 23 | + .requiredOption('-p, --product <PATH>', 'Provide the path of the new product, e.g., pages') |
| 24 | + .parse(process.argv) |
28 | 25 |
|
29 | | -const oldCategoryDir = path.join(contentDir, relativePath) |
30 | | -assert(fs.existsSync(oldCategoryDir), `directory does not exist: ${oldCategoryDir}`) |
| 26 | +const oldCategory = program.opts().category.replace('content/', '') |
| 27 | +const newProduct = program.opts().product.replace('content/', '') |
31 | 28 |
|
32 | | -const productDir = path.join(contentDir, productId) |
| 29 | +const [oldProductId, oldCategoryId] = oldCategory.split('/') |
| 30 | +const oldCategoryPath = path.posix.join(contentDir, oldCategory) |
| 31 | +const oldProductPath = path.posix.join(contentDir, oldProductId) |
33 | 32 |
|
34 | | -const [oldproductId, categoryName] = relativePath.split('/') |
35 | | - |
36 | | -// do all the moving/renaming/updating |
37 | | -makeNewProductDir() |
38 | | -moveFilesToNewDir() |
39 | | -createNewProductToc() |
40 | | -removeCategoryFromOldProductToc() |
41 | | -updateFrontmatter() |
42 | | - |
43 | | -console.log(`Moved files to content/${productId} and updated frontmatter!\n\nNext steps:\n`) |
44 | | - |
45 | | -// display data that needs to be manually added to lib files |
46 | | -printProductsModuleUpdate() |
47 | | -printFrontmatterSchemaUpdate() |
48 | | - |
49 | | -function makeNewProductDir () { |
50 | | - if (!fs.existsSync(productDir)) { |
51 | | - execSync(`mkdir ${productDir}`) |
52 | | - } |
53 | | -} |
54 | | - |
55 | | -function moveFilesToNewDir () { |
56 | | - execSync(`git mv ${oldCategoryDir} ${productDir}`) |
57 | | -} |
58 | | - |
59 | | -function createNewProductToc () { |
60 | | - const productTocPath = path.join(productDir, 'index.md') |
61 | | - const data = {} |
62 | | - data.title = `${productName} Documentation` |
63 | | - data.productVersions = {} |
64 | | - data.productVersions[productId] = '*' |
65 | | - const content = `\n{% link_with_intro /${categoryName} %}` |
66 | | - |
67 | | - fs.writeFileSync(productTocPath, matter.stringify(content, data, { lineWidth: 10000 })) |
| 33 | +if (!fs.existsSync(oldProductPath)) { |
| 34 | + console.error(`Error! Can't find ${oldProductPath}`) |
| 35 | + process.exit(1) |
68 | 36 | } |
69 | 37 |
|
70 | | -function removeCategoryFromOldProductToc () { |
71 | | - const oldProductTocPath = path.join(contentDir, oldproductId, 'index.md') |
72 | | - const tocContents = fs.readFileSync(oldProductTocPath, 'utf8') |
73 | | - const { content, data } = matter(tocContents) |
| 38 | +const oldCategoryFiles = contentFiles.filter(file => file.includes(`/${oldCategoryId}/`)) |
74 | 39 |
|
75 | | - const link = `(\n<!-- if page.version.*? -->)?\n{% link_in_list /${categoryName} %}\n(<!-- endif -->)?` |
76 | | - |
77 | | - const newContent = content.replace(new RegExp(link), '') |
78 | | - |
79 | | - fs.writeFileSync(oldProductTocPath, matter.stringify(newContent, data, { lineWidth: 10000 })) |
| 40 | +if (!oldCategoryFiles.length) { |
| 41 | + console.error(`Error! Can't find ${oldCategory} files`) |
| 42 | + process.exit(1) |
80 | 43 | } |
81 | 44 |
|
82 | | -function updateFrontmatter () { |
83 | | - const newCategoryDir = path.join(productDir, categoryName) |
| 45 | +const newProductPath = path.posix.join(process.cwd(), 'content', newProduct) |
84 | 46 |
|
85 | | - // for every article in the category, update productVersions and redirect frontmatter |
86 | | - walk(newCategoryDir, { includeBasePath: true }).forEach(file => { |
87 | | - const articleContents = fs.readFileSync(file, 'utf8') |
88 | | - const { content, data } = matter(articleContents) |
| 47 | +main() |
89 | 48 |
|
90 | | - const baseFilename = file.endsWith('index.md') ? '' : path.basename(file, '.md') |
| 49 | +function main () { |
| 50 | + // Create the new product dir. |
| 51 | + mkdirp(newProductPath) |
91 | 52 |
|
92 | | - const redirectString = path.join('/', oldproductId, categoryName, baseFilename) |
| 53 | + // Add redirects to the frontmatter of the to-be-moved files. |
| 54 | + oldCategoryFiles.forEach(file => { |
| 55 | + const { content, data } = frontmatter(fs.readFileSync(file, 'utf8')) |
| 56 | + const redirectString = file |
| 57 | + .replace(contentDir, '') |
| 58 | + .replace('index.md', '') |
| 59 | + .replace('.md', '') |
93 | 60 | data.redirect_from = addRedirectToFrontmatter(data.redirect_from, redirectString) |
94 | | - |
95 | | - data.productVersions = {} |
96 | | - data.productVersions[productId] = '*' |
97 | | - |
98 | | - const newContents = matter.stringify(content, data, { lineWidth: 10000 }) |
99 | | - fs.writeFileSync(file, newContents) |
| 61 | + fs.writeFileSync(file, frontmatter.stringify(content, data, { lineWidth: 10000 })) |
100 | 62 | }) |
101 | | -} |
102 | | - |
103 | | -function printProductsModuleUpdate () { |
104 | | - const newProduct = { |
105 | | - id: productId, |
106 | | - name: productName, |
107 | | - href: path.join('/', productId), |
108 | | - dir: path.join('content/', productId), |
109 | | - toc: path.join('content/', productId, 'index.md') |
110 | | - } |
111 | | - const obj = {} |
112 | | - obj[productId] = newProduct |
113 | | - |
114 | | - console.log('1. Add the following block to lib/products.js. Note: the order of this file determines the product order everywhere on the site.\n') |
115 | | - console.log(obj) |
116 | | -} |
117 | | - |
118 | | -function printFrontmatterSchemaUpdate () { |
119 | | - const newFrontmatter = { |
120 | | - type: 'string', |
121 | | - conform: '(add validSemverRange here)', |
122 | | - message: 'Must be a valid SemVer range' |
123 | | - } |
124 | | - const obj = {} |
125 | | - obj[productId] = newFrontmatter |
126 | 63 |
|
127 | | - console.log('\n2. Add the following block to the productVersions object in lib/frontmatter.js (ordered alphabetically). Make sure the \'conform\' property looks like the others. \n') |
128 | | - console.log(obj) |
| 64 | + // // Move the files. |
| 65 | + execSync(`git mv ${oldCategoryPath}/* ${newProductPath}`) |
| 66 | + |
| 67 | + // Remove the category from the old product TOC. |
| 68 | + const oldProductTocPath = path.posix.join(oldProductPath, 'index.md') |
| 69 | + const productToc = frontmatter(fs.readFileSync(oldProductTocPath, 'utf8')) |
| 70 | + productToc.data.children = productToc.data.children.filter(child => child !== `/${oldCategoryId}`) |
| 71 | + fs.writeFileSync(oldProductTocPath, frontmatter.stringify(productToc.content, productToc.data, { lineWidth: 10000 })) |
| 72 | + |
| 73 | + // Add the new product to the homepage TOC. |
| 74 | + const homepage = path.posix.join(contentDir, 'index.md') |
| 75 | + const homepageToc = frontmatter(fs.readFileSync(homepage, 'utf8')) |
| 76 | + homepageToc.data.children.push(newProduct) |
| 77 | + fs.writeFileSync(homepage, frontmatter.stringify(homepageToc.content, homepageToc.data, { lineWidth: 10000 })) |
| 78 | + |
| 79 | + console.log(`Moved ${oldCategory} files to ${newProduct}, added redirects, and updated TOCs!`) |
129 | 80 | } |
0 commit comments