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

[PFX-772] Add JSDoc Typechecking to create-gasket-app #943

Merged
merged 11 commits into from
Oct 24, 2024
9 changes: 2 additions & 7 deletions packages/create-gasket-app/lib/commands/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ import {

/**
* Parses comma separated option input to array
*
* @param {String} input - option argument
* @returns {String[]} results
* @type {import('../index').commasToArray}
*/
const commasToArray = input => input.split(',').map(name => name.trim());

Expand Down Expand Up @@ -97,10 +95,7 @@ const createCommand = {

/**
* createCommand action
* @param {string} appname Required cmd arg - name of the app to create
* @param {object} options cmd options
* @param {Command} command - the command instance
* @returns {Promise<void>} void
* @type {import('../index').createCommandAction}
*/
createCommand.action = async function run(appname, options, command) {
process.env.GASKET_ENV = 'create';
Expand Down
228 changes: 211 additions & 17 deletions packages/create-gasket-app/lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { GasketConfigDefinition, MaybeAsync, Plugin, GasketEngine } from '@gasket/core';
import type { PackageManager } from '@gasket/utils';
import type { PromptModule } from 'inquirer';
import type ora from 'ora';

export interface Dependencies {
dependencies?: Record<string, string>;
Expand All @@ -25,6 +26,43 @@ export interface PackageJson extends Dependencies {
homepage?: string;
}

export function commasToArray(value: string): string[];
jpina1-godaddy marked this conversation as resolved.
Show resolved Hide resolved

interface CommandArgument {
name: string;
description?: string;
required?: boolean;
default?: any;
}

interface CommandOption {
name: string;
description: string;
required?: boolean;
short?: string;
parse?: (value: string) => any;
type?: string;
conflicts?: string | string[];
hidden?: boolean;
default?: any;
}

export function createCommandAction(
appname: string,
options: CreateCommandOptions,
command: Command
): Promise<void>;

export interface CreateCommand {
id: string;
description: string;
args: CommandArgument[];
action?: createGasketAction;
options: CommandOption[];
hidden?: boolean;
default?: boolean;
}

export interface ModuleInfo {
name: string;
module: any;
Expand All @@ -38,36 +76,49 @@ export interface PresetInfo extends ModuleInfo { }
export interface PluginInfo extends ModuleInfo { }

export interface ConfigBuilder<Config> {
/**
* fields object
*/
/** fields object */
fields: { [key: string]: any };
original?: { [key: string]: any };
source?: Plugin;
orderBy?: string[];
orderedFields?: string[];
objectFields?: string[];
semverFields?: string[];
blame?: Map<string, string[]>;
force?: Set<any>;
warnings?: string[];

/**
* Adds all `[key, value]` pairs in the `fields` provided.
* @param fields - Object to merge. Can be a function that accepts the current fields and object to merge.
* @param source - Plugin to blame if conflicts arise from this operation.
*/
extend(
fields: Partial<Config> | ((current: Config) => Partial<Config>)
fields: Partial<Config> | ((current: Config) => Partial<Config>),
source: Plugin
): void;
extend(fields: Partial<Config> | ((current: Config) => Partial<Config>)): void;

/**
* Performs an intelligent, domain-aware merge of the `value` for
* the given `key` into the package.json fields associated with this instance.
*
* @param key - Field in package.json to add or extend.
* @param value - Target value to set for key provided.
* @param source - Plugin to blame if conflicts arise from this operation.
* @param [options] - Optional arguments for add behavior
* @param [options.force] - Should the semver version override other attempts
*
* Adapted from @vue/cli under MIT License:
* https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L117-L150
*/
add<Key extends keyof Config>(
add<Key extends keyof Config & string>(
key: Key,
value: Config[Key],
source: Plugin,
options?: { force?: boolean }
): void;
add(key: string, value: object, options?: object): void;

/**
* addPlugin - Add plugin import to the gasket file and use the value in the plugins array
Expand Down Expand Up @@ -146,43 +197,177 @@ export interface PackageJsonBuilder extends ConfigBuilder<PackageJson> {
has(key: keyof PackageJson, value: string): boolean;
}

export interface Files {
add(...args: string[]): void;
/**
* Interface for the Files class.
*/
interface Files {
/**
* Array of glob sets, each containing an array of globs and a source object.
*/
globSets: Array<{ globs: string[], source: Plugin }>;

/**
* Return array of globs.
* @deprecated
* @returns {string[]} `globby` compatible patterns.
*/
readonly globs: string[];

/**
* Adds the specified `globby` compatible patterns, `globs`,
* into the set of all sources for this set of files.
* @param {Object} params - Object containing `globs` and `source`.
* @param {string[]} params.globs - `globby` compatible patterns.
* @param {Object} params.source - Plugin to blame if conflicts arise from this operation.
*/
add(params: { globs: string[], source: object }): void;
add(...globs: string[]): void;

/** Add a warning message if this.warnings exists else log it as a warming */
warn(message: string): void;

/**
* Checks if a dependency has been already added
* @param {string} key Dependency bucket
* @param {string} value Dependency to search
* @returns {boolean} True if the dependency exists on the bucket
*/
has(key: string, value: string): boolean;

/**
* Returns the existing and target array merged without duplicates
* @param {Array} existing Partial lattice to merge.
* @param {Array} target Partial lattice to merge.
* @returns {Array} existing ∪ (i.e. union) target
*
* Adapted from @vue/cli under MIT License:
* https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L15
*/
mergeArrayDeduped(existing: any[], target: any[]): any[];

/**
* Attempts to merge all entries within the `value` provided by
* the plugin specified by `name` into the `existing` semver-aware
* Object `key` (e.g. dependencies, etc.) for this instance.
*
* Merge algorithm:
*
* - ∀ [dep, ver] := Object.entries(value)
* and [prev] := any existing version for dep
*
* - If ver is not valid semver ––> ■
* - If ¬∃ prev ––> set and blame [dep, ver]
* - If ver > prev ––> set and blame [dep, ver]
* - If ¬(ver ∩ prev) ––> Conflict. Print.
* @param {object} options
* @param {string} options.key {devD,peerD,optionalD,d}ependencies
* @param {object} options.value Updates for { name: version } pairs
* @param {object} options.existing Existing { name: version } pairs
* @param {string} options.name Plugin name providing merge `value``
* @param {boolean} [options.force] Should the semver version override other attempts
*
* Adapted from @vue/cli under MIT License:
* https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js
*/
semanticMerge({ key, value, existing, name, force }: {
key: string;
value: any;
existing: any;
name: string;
force?: boolean;
}): void;

/**
* Normalizes a potential semver range into a semver string
* and returns the newest version
* @param {string} r1 Semver string (potentially invalid).
* @param {string} r2 Semver string (potentially invalid).
* @returns {string | undefined} Newest semver version.
*
* Adapted from @vue/cli under MIT License:
* https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L58-L64
*/
tryGetNewerRange(r1: string, r2: string): string | undefined;

/**
* Performs a naive attempt to take a transform a semver range
* into a concrete version that may be used for "newness"
* comparison.
* @param {string} range Valid "basic" semver: ^X.Y.Z, ~A.B.C, >=2.3.x, 1.x.x
* @returns {string} Concrete as possible version: X.Y.Z, A.B.C, 2.3.0, 1.0.0
*/
rangeToVersion(range: string): string;

/**
* Orders top-level keys by `orderBy` options with any fields specified in
* the `orderFields` options having their keys sorted.
* @returns {object} Ready to be serialized JavaScript object.
*/
toJSON(): object;

/**
* Orders the given object, `obj`, applying any (optional)
* key order specified via `orderBy`. If no `orderBy` is provided
* keys are ordered lexographically.
* @param {object} obj Object to transform to ordered keys
* @param {string[]} [orderBy] Explicit key order to use.
* @returns {object} Shallow clone of `obj` with ordered keys
*
* Adapted from @vue/cli under MIT License:
* https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/sortObject.js
*/
toOrderedKeys(obj: object, orderBy?: string[]): object;
}

export interface Readme {
export class Readme {
/** Markdown content to be injected into the app readme */
markdown: string[];

/** Links to be added to the footer */
links: string[];

addNewLine(): this;

/** Add a markdown heading */
heading(content: string, level?: number): Readme;
heading(content: string, level?: number): this;

/** Add a markdown sub-heading */
subHeading(content: string): Readme;
subHeading(content: string): this;

/** Add markdown content */
content(markdown: string);

/** Add a markdown list */
list(items: string[]): Readme;
list(items: string[]): this;

/** Add a markdown link - printed in footer */
link(content: string, href: string): Readme;
link(content: string, href: string): this;

/** Add a markdown code block */
codeBlock(content: string, syntax?: string): Readme;
codeBlock(content: string, syntax?: string): this;

/** Add markdown file */
markdownFile(path: string): Promise<Readme>;
markdownFile(path: string): Promise<this>;
}

export type CreatePrompt = PromptModule;
type NoopPromptObject = {
[key: string]: any;
};
type NoopPromptFunction = () => NoopPromptObject;
export type CreatePrompt = PromptModule | NoopPromptFunction;

export interface CreateCommandOptions {
presets?: string[];
npmLink?: string[];
presetPath?: string[];
packageManager?: string;
prompts?: boolean;
config?: string;
configFile?: string;
}

declare module 'create-gasket-app' {
export interface CreateContext {
export class CreateContext {
/** Short name of the app */
appName: string;

Expand Down Expand Up @@ -223,9 +408,15 @@ declare module 'create-gasket-app' {
/** any generated files to show in report */
generatedFiles: Set<string>;

/** (INTERNAL) false to skip the prompts */
prompts: boolean;

/** Default empty array, populated by load-preset with actual imports */
presets: Array<Plugin>;

/** temporary directory */
tmpDir: string;

/** Default to object w/empty plugins array to be populated by `presetConfig` hook */
presetConfig: GasketConfigDefinition;

Expand Down Expand Up @@ -272,13 +463,16 @@ declare module 'create-gasket-app' {

/** Use to add content to the README.md */
readme: Readme;

constructor(initContext?: Partial<T>);
runWith(plugin: Plugin): Proxy<CreateContext>;
}
}

export interface ActionWrapperParams {
gasket: GasketEngine;
context: CreateContext;
spinner?: any;
spinner?: ora.Ora;
}

declare module '@gasket/core' {
Expand Down
Loading
Loading