-
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): adds CLI support for transforming a project from CJS to ESM
- Loading branch information
Showing
29 changed files
with
663 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
|
||
process.title = 'cjstoesm'; | ||
require("../dist/cli/index.js"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
export type CommandOptionType = "string" | "number" | "boolean"; | ||
export type CommandArgType = "string" | "string[]"; | ||
|
||
export interface CommandOption { | ||
shortHand?: string; | ||
type: CommandOptionType; | ||
defaultValue?: unknown; | ||
description: string; | ||
} | ||
|
||
export interface CommandOptions { | ||
[key: string]: CommandOption; | ||
} | ||
|
||
export interface CommandArg { | ||
type: CommandArgType; | ||
required: boolean; | ||
} | ||
|
||
export interface CommandArgs { | ||
[key: string]: CommandArg; | ||
} | ||
|
||
export interface CreateCommandOptions { | ||
name: string; | ||
description: string; | ||
args: CommandArgs; | ||
options: CommandOptions; | ||
isDefault: boolean; | ||
} | ||
|
||
export type CommandActionOptions<T extends CreateCommandOptions, U extends T["options"] = T["options"], J extends T["args"] = T["args"]> = { | ||
[Key in keyof U]: U[Key]["type"] extends "number" ? number : U[Key]["type"] extends "boolean" ? boolean : string; | ||
} & | ||
{[Key in keyof J]: J[Key]["type"] extends "string[]" ? string[] : string}; | ||
|
||
export type CommandAction<T extends CreateCommandOptions> = (options: CommandActionOptions<T>) => void; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import commander from "commander"; | ||
import {CommandAction, CommandActionOptions, CommandOptionType, CreateCommandOptions} from "./create-command-options"; | ||
|
||
// tslint:disable:no-any | ||
|
||
/** | ||
* Coerces the given option value into an acceptable data type | ||
* @param {CommandOptionType} type | ||
* @param {*} value | ||
*/ | ||
function coerceOptionValue( | ||
type: CommandOptionType, | ||
value: unknown | ||
): typeof type extends "boolean" ? boolean : typeof type extends "number" ? number : string { | ||
switch (type) { | ||
case "string": | ||
if (value === null) return "null"; | ||
else if (value === undefined) return "undefined"; | ||
return String(value); | ||
|
||
case "number": | ||
if (typeof value === "number") return (value as unknown) as string; | ||
else if (value === true) return (1 as unknown) as string; | ||
else if (value === false) return (0 as unknown) as string; | ||
return (parseFloat(value as string) as unknown) as string; | ||
case "boolean": | ||
if (value === "true" || value === "" || value === "1" || value === 1) return (true as unknown) as string; | ||
else if (value === "false" || value === "0" || value === 0) return (false as unknown) as string; | ||
return (Boolean(value) as unknown) as string; | ||
} | ||
} | ||
|
||
/** | ||
* Formats the given option flags | ||
* @param {string} shortHand | ||
* @param {string} longHand | ||
* @returns {string} | ||
*/ | ||
function formatOptionFlags(shortHand: string | undefined, longHand: string): string { | ||
const formattedLongHand = `${longHand} [arg]`; | ||
return shortHand != null ? `-${shortHand}, --${formattedLongHand}` : `--${formattedLongHand}`; | ||
} | ||
|
||
/** | ||
* Formats the given command name, along with its arguments | ||
* @param {T} options | ||
* @returns {string} | ||
*/ | ||
function formatCommandNameWithArgs<T extends CreateCommandOptions>(options: T): string { | ||
const formattedArgs = Object.entries(options.args) | ||
.map(([argName, {type, required}]) => { | ||
const left = required ? `<` : `[`; | ||
const right = required ? ">" : `]`; | ||
if (type === "string[]") { | ||
return `${left}${argName}...${right}`; | ||
} else { | ||
return `${left}${argName}${right}`; | ||
} | ||
}) | ||
.join(" "); | ||
return `${options.name} ${formattedArgs}`; | ||
} | ||
|
||
/** | ||
* Creates a new command | ||
* @param {T} options | ||
* @param {CommandAction<T>} action | ||
*/ | ||
export function createCommand<T extends CreateCommandOptions>(options: T, action: CommandAction<T>): void { | ||
// Add the command to the program | ||
const result = commander | ||
.command(formatCommandNameWithArgs(options), { | ||
isDefault: options.isDefault | ||
}) | ||
.description(options.description); | ||
|
||
// Add options to the command | ||
Object.entries(options.options).forEach(([longhand, {shortHand, description, type, defaultValue}]) => { | ||
result.option(formatOptionFlags(shortHand, longhand), description, coerceOptionValue.bind(null, type), defaultValue); | ||
}); | ||
// Add the action to the command | ||
result.action((...args: unknown[]) => { | ||
const argKeys = Object.keys(options.args); | ||
const optionKeys = Object.keys(options.options); | ||
const actionOptions = {} as CommandActionOptions<T>; | ||
for (let i = 0; i < args.length; i++) { | ||
if (argKeys[i] == null) continue; | ||
actionOptions[argKeys[i] as keyof typeof actionOptions] = args[i] as any; | ||
} | ||
|
||
// Take the last argument | ||
const lastArg = args[args.length - 1]; | ||
// Apply all option values | ||
for (const key of optionKeys) { | ||
actionOptions[key as keyof typeof actionOptions] = (lastArg as any)[key]; | ||
} | ||
|
||
// Invoke the action | ||
action(actionOptions); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import commander from "commander"; | ||
|
||
commander.parse(process.argv); | ||
|
||
// Show help if no arguments are given | ||
if (commander.args.length === 0) { | ||
commander.help(text => { | ||
return `Welcome to the CJS to ESM CLI!\n\n` + text; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const SHARED_OPTIONS = { | ||
debug: { | ||
shortHand: "d", | ||
type: "boolean", | ||
description: "Whether to print debug information" | ||
}, | ||
verbose: { | ||
shortHand: "v", | ||
type: "boolean", | ||
description: "Whether to print verbose information" | ||
}, | ||
silent: { | ||
shortHand: "s", | ||
type: "boolean", | ||
description: "Whether to not print anything" | ||
} | ||
} as const; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const TRANSFORM_COMMAND_OPTIONS = {} as const; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import {createCommand} from "../create-command/create-command"; | ||
import {TRANSFORM_COMMAND_OPTIONS} from "./transform-command-options"; | ||
import {generateTaskOptions} from "../../task/generate-task-options/generate-task-options"; | ||
import {SHARED_OPTIONS} from "../shared/shared-options"; | ||
|
||
createCommand( | ||
{ | ||
name: "transform", | ||
description: `Transforms CJS to ESM modules based on the input glob`, | ||
isDefault: true, | ||
args: { | ||
input: { | ||
type: "string", | ||
required: true, | ||
description: "A glob for all the files that should be transformed" | ||
}, | ||
outDir: { | ||
type: "string", | ||
required: true, | ||
description: `The directory to write the transformed files to.` | ||
} | ||
}, | ||
options: { | ||
...SHARED_OPTIONS, | ||
...TRANSFORM_COMMAND_OPTIONS | ||
} | ||
}, | ||
async args => { | ||
// Load the task | ||
const {transformTask} = await import("../../task/transform/transform-task"); | ||
const taskOptions = await generateTaskOptions(args); | ||
|
||
// Execute it | ||
await transformTask({ | ||
...taskOptions, | ||
input: args.input, | ||
outDir: args.outDir | ||
}); | ||
} | ||
); |
Oops, something went wrong.