-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathmigrate.ts
More file actions
139 lines (109 loc) · 5.05 KB
/
migrate.ts
File metadata and controls
139 lines (109 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import type { MigrationStats, PluginHooks } from '../../lib/hooks/exported.js';
import { Args, Flags } from '@oclif/core';
import chalk from 'chalk';
import ora from 'ora';
import { dir } from 'tmp-promise';
import BaseCommand from '../../lib/baseCommand.js';
import { writeFixes } from '../../lib/frontmatter.js';
import { oraOptions } from '../../lib/logger.js';
import { baseFlags } from '../../lib/pageCommandProperties.js';
import { findPages } from '../../lib/readPage.js';
import { attemptUnzip } from '../../lib/unzip.js';
import { validateFrontmatter } from '../../lib/validateFrontmatter.js';
const alphaNotice = 'This command is in an experimental alpha and is likely to change. Use at your own risk!';
export default class DocsMigrateCommand extends BaseCommand<typeof DocsMigrateCommand> {
id = 'docs migrate' as const;
route = 'guides' as const;
static hidden = true;
static summary = `Migrates a directory of pages to the ReadMe Guides format.\n\n${alphaNotice}`;
static description =
"The path can either be a directory or a single Markdown file. The command will transform the Markdown using plugins and validate the frontmatter to conform to ReadMe's standards.";
static args = {
path: Args.string({ description: 'Path to a local Markdown file or folder of Markdown files.', required: true }),
};
static flags = {
out: Flags.string({
summary: 'The directory to write the migration output to. Defaults to a temporary directory.',
}),
// NOTE: the `baseFlags` function contains a handful of properties (e.g., `dry-run`, `max-errors`)
// that aren't actually used in this command, but are included for consistency/simplicity
// since this command is purely for internal use.
...baseFlags('Guides'),
'hide-experimental-warning': Flags.boolean({
description: 'Hides the warning message about this command being in an experimental alpha.',
hidden: true,
}),
};
async run(): Promise<{ outputDir: string; stats: MigrationStats }> {
if (!this.flags['hide-experimental-warning']) {
this.warn(alphaNotice);
}
const { path: rawPathInput }: { path: string } = this.args;
const { out: rawOutputDir, 'skip-validation': skipValidation } = this.flags;
const outputDir = rawOutputDir || (await dir({ prefix: 'rdme-migration-output' })).path;
const zipResults = await attemptUnzip(rawPathInput);
let { pathInput } = zipResults;
const fileScanHookResults = await this.config.runHook<'pre_markdown_file_scan', PluginHooks>(
'pre_markdown_file_scan',
zipResults,
);
fileScanHookResults.successes.forEach(success => {
if (success.result) {
pathInput = success.result;
}
});
fileScanHookResults.failures.forEach(fail => {
if (fail.error && fail.error instanceof Error) {
throw new Error(`Error executing the \`${fail.plugin.name}\` plugin: ${fail.error.message}`);
}
});
const stats: MigrationStats = {
migrationOutputDir: outputDir,
results: {},
timestamp: new Date().toISOString(),
};
if (zipResults.zipped) {
stats.unzippedAssetsDir = zipResults.unzippedDir;
}
let unsortedFiles = await findPages.call(this, pathInput);
let transformedByHooks: boolean = false;
const validationHookResults = await this.config.runHook<'pre_markdown_validation', PluginHooks>(
'pre_markdown_validation',
{ pages: unsortedFiles },
);
if (!validationHookResults.successes.length && !validationHookResults.failures.length) {
throw new Error('This command requires a valid migration plugin.');
}
validationHookResults.successes.forEach(success => {
if (success.result?.pages.length) {
transformedByHooks = true;
this.log(`🔌 ${success.result.pages.length} Markdown files updated via the \`${success.plugin.name}\` plugin`);
stats.results[success.plugin.name] = success.result.stats;
unsortedFiles = success.result.pages;
}
});
validationHookResults.failures.forEach(fail => {
if (fail.error && fail.error instanceof Error) {
throw new Error(`Error executing the \`${fail.plugin.name}\` plugin: ${fail.error.message}`);
}
});
if (skipValidation) {
this.debug('skipping validation');
} else {
unsortedFiles = (await validateFrontmatter.call(this, unsortedFiles, outputDir)).pages;
}
if (transformedByHooks) {
const fileUpdateSpinner = ora({ ...oraOptions() }).start(
`📝 Writing the updated files to the following directory: ${chalk.underline(outputDir)}...`,
);
const updatedFiles = unsortedFiles.map(file => {
// TODO: I think that this `writeFixes` call is redundant if `validateFrontmatter` above also writes to the file,
// but it's not even close to being a bottleneck so I'm not going to worry about it for now
return writeFixes.call(this, file, file.data, outputDir);
});
fileUpdateSpinner.succeed(`${fileUpdateSpinner.text} done!`);
unsortedFiles = updatedFiles;
}
return { outputDir, stats };
}
}