Skip to content

Commit 334e125

Browse files
authored
feat(docs/migrate): add ability to unzip archives (#1226)
| 🚥 Resolves RM-12508 | | :------------------- | ## 🧰 Changes - [x] add auto-detection(ish) of zip files and unzips them before sending that data to plugin hook - [x] enhances our `pre_markdown_file_scan` hook to now send data about the unzip results - [x] adds a bunch of JSDocs for our exported functions/types ## 🧬 QA & Testing this isn't a ton of logic but it could definitely use a test or two. will do a more comprehensive pass of tests in follow-up PRs later this week.
1 parent fc5ca4d commit 334e125

File tree

10 files changed

+213
-13
lines changed

10 files changed

+213
-13
lines changed

package-lock.json

Lines changed: 81 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"tmp-promise": "^3.0.3",
7676
"toposort": "^2.0.2",
7777
"undici": "^5.28.4",
78+
"unzipper": "^0.12.3",
7879
"validator": "^13.7.0"
7980
},
8081
"devDependencies": {
@@ -95,6 +96,7 @@
9596
"@types/prompts": "^2.4.2",
9697
"@types/semver": "^7.3.12",
9798
"@types/toposort": "^2.0.7",
99+
"@types/unzipper": "^0.10.11",
98100
"@types/validator": "^13.7.6",
99101
"@vitest/coverage-v8": "^3.0.0",
100102
"@vitest/expect": "^3.0.0",

src/commands/docs/migrate.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { oraOptions } from '../../lib/logger.js';
1212
import promptTerminal from '../../lib/promptWrapper.js';
1313
import { fetchMappings, fetchSchema } from '../../lib/readmeAPIFetch.js';
1414
import { findPages } from '../../lib/readPage.js';
15+
import { attemptUnzip } from '../../lib/unzip.js';
1516

1617
const alphaNotice = 'This command is in an experimental alpha and is likely to change. Use at your own risk!';
1718

@@ -47,12 +48,13 @@ export default class DocsMigrateCommand extends BaseCommand<typeof DocsMigrateCo
4748

4849
const outputDir = rawOutputDir || (await dir({ prefix: 'rdme-migration-output' })).path;
4950

50-
let pathInput = rawPathInput;
51+
const zipResults = await attemptUnzip(rawPathInput);
52+
let { pathInput } = zipResults;
5153

5254
// todo: fix this type once https://github.com/oclif/core/pull/1359 is merged
5355
const fileScanHookResults: Hook.Result<PluginHooks['pre_markdown_file_scan']['return']> = await this.config.runHook(
5456
'pre_markdown_file_scan',
55-
{ pathInput },
57+
zipResults,
5658
);
5759

5860
fileScanHookResults.successes.forEach(success => {

src/lib/baseCommand.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import { handleAPIv2Res, readmeAPIv2Fetch, type ResponseBody } from './readmeAPI
1414
type Flags<T extends typeof OclifCommand> = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>;
1515
type Args<T extends typeof OclifCommand> = Interfaces.InferredArgs<T['args']>;
1616

17+
/**
18+
* This is a light wrapper around the oclif command class that adds some
19+
* additional functionality and standardizes the way we handle logging, error handling,
20+
* and API requests.
21+
*
22+
* @note This class is not meant to be used directly, but rather as a base class for other commands.
23+
* It is also experimental and may change in the future.
24+
*/
1725
export default abstract class BaseCommand<T extends typeof OclifCommand> extends OclifCommand {
1826
constructor(argv: string[], config: Config) {
1927
super(argv, config);

src/lib/hooks/exported.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,80 @@
11
import type { PageMetadata } from '../readPage.js';
22
import type { Hooks } from '@oclif/core/interfaces';
33

4+
interface MarkdownFileScanResultOptsBase {
5+
// required for the oclif hook types
6+
[key: string]: unknown;
7+
/**
8+
* The path to the input file or directory as passed to the command line.
9+
*/
10+
pathInput: string;
11+
/**
12+
* Whether or not the path input is a zip file.
13+
*/
14+
zipped: boolean;
15+
}
16+
17+
interface MarkdownFileScanResultOptsZipped extends MarkdownFileScanResultOptsBase {
18+
/**
19+
* The path to the temporary directory where the zip file was unzipped.
20+
* This is only present if the input file was a zip file.
21+
*/
22+
unzippedDir: string;
23+
zipped: true;
24+
}
25+
26+
interface MarkdownFileScanResultOptsUnzipped extends MarkdownFileScanResultOptsBase {
27+
zipped: false;
28+
}
29+
430
/**
31+
* The options passed to the `pre_markdown_file_scan` hook.
32+
*/
33+
export type MarkdownFileScanResultOpts = MarkdownFileScanResultOptsUnzipped | MarkdownFileScanResultOptsZipped;
34+
35+
/**
36+
* Hooks for usage in `rdme` plugins that invoke the `docs migrate` command.
37+
*
538
* @note These hooks are under active development for internal usage and are subject to change.
639
*/
740
export interface PluginHooks extends Hooks {
41+
/**
42+
* This hook is called before the Markdown files are searched for with the path input argument.
43+
* If the path input is a zip file, it will be unzipped to a temporary directory.
44+
*
45+
* It can be used to modify the path input to point to a different directory or file.
46+
*
47+
* For example, say the user wants to upload a zip file. This hook can be used to
48+
* set the working directory to a subdirectory within the unzipped contents.
49+
*/
850
pre_markdown_file_scan: {
9-
options: {
10-
pathInput: string;
11-
};
51+
options: MarkdownFileScanResultOpts;
52+
53+
/**
54+
* The new directory to scan for Markdown files. If `null` is returned,
55+
* this means the original path input will be used.
56+
*/
1257
return: string | null;
1358
};
59+
60+
/**
61+
* This hook is called after the Markdown files are scanned and before they are validated.
62+
* It can be used to modify the list of pages that will be validated.
63+
*
64+
* For example, this hook can be used to add or remove pages
65+
* (or to modify the content/frontmatter of an existing page)
66+
* from the list of pages that were found in the input directory.
67+
*/
1468
pre_markdown_validation: {
1569
options: {
70+
/**
71+
* The list of pages that were found in the input directory.
72+
*/
1673
pages: PageMetadata[];
1774
};
75+
/**
76+
* The list of pages that will be validated and written to the output directory.
77+
*/
1878
return: PageMetadata[];
1979
};
2080
}

src/lib/readPage.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import ora from 'ora';
1313
import { oraOptions } from './logger.js';
1414
import readdirRecursive from './readdirRecursive.js';
1515

16+
/**
17+
* The metadata for a page once it has been read.
18+
* This includes the Markdown contents of the file, the parsed frontmatter data,
19+
* the file path, and the derived slug.
20+
*/
1621
export interface PageMetadata<T = Record<string, unknown>> {
1722
/**
1823
* The contents of the Markdown file below the YAML frontmatter

src/lib/readmeAPIFetch.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ export interface Mappings {
5757
parentPages: Record<string, string>;
5858
}
5959

60+
/**
61+
* A generic response body type for responses from the ReadMe API.
62+
*/
6063
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6164
export interface ResponseBody extends Record<string, any> {}
6265

src/lib/types/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ export type GuidesRequestRepresentation = FromSchema<
3131
{ keepDefaultedPropertiesOptional: true }
3232
>;
3333

34+
/**
35+
* Derived from our API documentation, this is the schema for the `project` object
36+
* as we receive it to the ReadMe API.
37+
*/
3438
export type ProjectRepresentation = FromSchema<projectSchema, { keepDefaultedPropertiesOptional: true }>;
3539

40+
/**
41+
* Derived from our API documentation, this is the schema for the API key object
42+
* as we receive it to the ReadMe API.
43+
*/
3644
export type APIKeyRepresentation = FromSchema<apiKeySchema, { keepDefaultedPropertiesOptional: true }>;

0 commit comments

Comments
 (0)