Skip to content

Commit

Permalink
[PFX-772] Add JSDoc Typechecking to create-gasket-app (#943)
Browse files Browse the repository at this point in the history
* chore: updated types

* chore: add action types

* chore: action types

* chore: types for create command

* chore: fix types

* chore: added create-context partial

* chore: remove optinal

* chore: moved commasToArray to internal types
  • Loading branch information
jpina1-godaddy authored Oct 24, 2024
1 parent 84fdde4 commit e4b1eb4
Show file tree
Hide file tree
Showing 31 changed files with 479 additions and 293 deletions.
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('../internal').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
226 changes: 209 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,41 @@ export interface PackageJson extends Dependencies {
homepage?: string;
}

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 +74,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 +195,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 +406,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 +461,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

0 comments on commit e4b1eb4

Please sign in to comment.