From 7c0351771cf1a3d795803295a41dfea755176b19 Mon Sep 17 00:00:00 2001 From: Ivo Murrell Date: Wed, 15 Feb 2023 11:57:54 +0000 Subject: [PATCH] feat: handle default option values with zod --- core/create/src/prompts/options.ts | 16 ++++++++++++---- lib/types/src/index.ts | 7 +++---- lib/types/src/schema.ts | 2 +- lib/types/src/schema/eslint.ts | 2 +- lib/types/src/schema/mocha.ts | 2 +- lib/types/src/schema/node.ts | 6 +++--- lib/types/src/schema/nodemon.ts | 6 +++--- lib/types/src/schema/prettier.ts | 12 +++++++++--- lib/types/src/schema/serverless.ts | 4 ++-- lib/types/src/schema/upload-assets-to-s3.ts | 18 +++++++++--------- plugins/eslint/src/tasks/eslint.ts | 6 +----- plugins/mocha/src/tasks/mocha.ts | 6 +----- plugins/node/src/tasks/node.ts | 8 +------- plugins/nodemon/src/tasks/nodemon.ts | 8 +------- plugins/prettier/src/tasks/prettier.ts | 12 ------------ plugins/serverless/src/tasks/run.ts | 7 +------ .../src/tasks/upload-assets-to-s3.ts | 16 ++-------------- 17 files changed, 51 insertions(+), 87 deletions(-) diff --git a/core/create/src/prompts/options.ts b/core/create/src/prompts/options.ts index 94ee47225..177193d5d 100644 --- a/core/create/src/prompts/options.ts +++ b/core/create/src/prompts/options.ts @@ -144,6 +144,11 @@ export interface OptionsParams { configPath: string } +type ZodPartial = z.ZodOptional | z.ZodDefault + +const isPartialOptional = (partial: ZodPartial): partial is z.ZodOptional => + typeof (partial as z.ZodOptional).unwrap === 'function' + export default async ({ logger, config, toolKitConfig, configPath }: OptionsParams): Promise => { for (const plugin of Object.keys(config.plugins)) { let options: z.AnyZodObject @@ -163,9 +168,9 @@ export default async ({ logger, config, toolKitConfig, configPath }: OptionsPara continue } - const [optional, required] = partition<[string, z.ZodTypeAny], [string, z.ZodOptional]>( + const [partial, required] = partition<[string, z.ZodTypeAny], [string, ZodPartial]>( Object.entries(options.shape), - (schema): schema is [string, z.ZodOptionalType] => schema[1].isOptional() + (schema): schema is [string, ZodPartial] => schema[1].isOptional() ) const anyRequired = required.length > 0 @@ -196,7 +201,7 @@ export default async ({ logger, config, toolKitConfig, configPath }: OptionsPara } } - if (optional.length > 0) { + if (partial.length > 0) { const { confirm } = await prompt({ name: 'confirm', type: 'confirm', @@ -211,7 +216,10 @@ export default async ({ logger, config, toolKitConfig, configPath }: OptionsPara await optionsPromptForPlugin( toolKitConfig, plugin, - optional.map(([name, optionType]) => [name, optionType.unwrap()]) + partial.map(([name, partialType]) => [ + name, + isPartialOptional(partialType) ? partialType.unwrap() : partialType.removeDefault() + ]) ) } else if (!anyRequired) { delete toolKitConfig.options[plugin] diff --git a/lib/types/src/index.ts b/lib/types/src/index.ts index 9bfd96d78..d175d4360 100644 --- a/lib/types/src/index.ts +++ b/lib/types/src/index.ts @@ -148,15 +148,14 @@ export abstract class Task extends Base { return taskSymbol } - static defaultOptions: Record = {} - options: z.infer + options: z.output logger: Logger - constructor(logger: Logger, options: Partial> = {}) { + constructor(logger: Logger, options: z.output) { super() const staticThis = this.constructor as typeof Task - this.options = Object.assign({}, staticThis.defaultOptions as z.infer, options) + this.options = options this.logger = logger.child({ task: staticThis.id }) } diff --git a/lib/types/src/schema.ts b/lib/types/src/schema.ts index 49c7d046c..9908e07d3 100644 --- a/lib/types/src/schema.ts +++ b/lib/types/src/schema.ts @@ -11,7 +11,7 @@ export type PromptGenerators = T extends z.ZodObject ? { [option in keyof Shape as Shape[option] extends z.ZodType ? option - : never]?: Shape[option] extends z.ZodType ? SchemaPromptGenerator> : never + : never]?: Shape[option] extends z.ZodType ? SchemaPromptGenerator> : never } : never diff --git a/lib/types/src/schema/eslint.ts b/lib/types/src/schema/eslint.ts index c46f1d9fe..67022ec1e 100644 --- a/lib/types/src/schema/eslint.ts +++ b/lib/types/src/schema/eslint.ts @@ -1,7 +1,7 @@ import { z } from 'zod' export const ESLintSchema = z.object({ - files: z.string().array(), + files: z.string().array().default(['**/*.js']), config: z.record(z.unknown()).optional(), // @deprecated: use options instead options: z.record(z.unknown()).optional() }) diff --git a/lib/types/src/schema/mocha.ts b/lib/types/src/schema/mocha.ts index a6a0c76e8..f52438d3b 100644 --- a/lib/types/src/schema/mocha.ts +++ b/lib/types/src/schema/mocha.ts @@ -1,7 +1,7 @@ import { z } from 'zod' export const MochaSchema = z.object({ - files: z.string(), + files: z.string().default('test/**/*.js'), configPath: z.string().optional() }) export type MochaOptions = z.infer diff --git a/lib/types/src/schema/node.ts b/lib/types/src/schema/node.ts index ed3f251c6..e839c6d06 100644 --- a/lib/types/src/schema/node.ts +++ b/lib/types/src/schema/node.ts @@ -1,10 +1,10 @@ import { z } from 'zod' export const NodeSchema = z.object({ - entry: z.string().optional(), + entry: z.string().default('./server/app.js'), args: z.string().array().optional(), - useVault: z.boolean().optional(), - ports: z.number().array().optional() + useVault: z.boolean().default(true), + ports: z.number().array().default([3001, 3002, 3003]) }) export type NodeOptions = z.infer diff --git a/lib/types/src/schema/nodemon.ts b/lib/types/src/schema/nodemon.ts index 219c09eff..11a12c945 100644 --- a/lib/types/src/schema/nodemon.ts +++ b/lib/types/src/schema/nodemon.ts @@ -1,10 +1,10 @@ import { z } from 'zod' export const NodemonSchema = z.object({ - entry: z.string().optional(), + entry: z.string().default('./server/app.js'), configPath: z.string().optional(), - useVault: z.boolean().optional(), - ports: z.number().array().optional() + useVault: z.boolean().default(true), + ports: z.number().array().default([3001, 3002, 3003]) }) export type NodemonOptions = z.infer diff --git a/lib/types/src/schema/prettier.ts b/lib/types/src/schema/prettier.ts index 131ac0005..9746c338f 100644 --- a/lib/types/src/schema/prettier.ts +++ b/lib/types/src/schema/prettier.ts @@ -1,10 +1,16 @@ import { z } from 'zod' export const PrettierSchema = z.object({ - files: z.string().array(), + files: z.string().array().default(['**/*.{js,jsx,ts,tsx}']), configFile: z.string().optional(), - ignoreFile: z.string().optional(), - configOptions: z.record(z.unknown()) + ignoreFile: z.string().default('.prettierignore'), + configOptions: z.record(z.unknown()).default({ + singleQuote: true, + useTabs: true, + bracketSpacing: true, + arrowParens: 'always', + trailingComma: 'none' + }) }) export type PrettierOptions = z.infer diff --git a/lib/types/src/schema/serverless.ts b/lib/types/src/schema/serverless.ts index 9878304da..4bc8018e4 100644 --- a/lib/types/src/schema/serverless.ts +++ b/lib/types/src/schema/serverless.ts @@ -2,8 +2,8 @@ import { z } from 'zod' export const ServerlessSchema = z.object({ configPath: z.string().optional(), - useVault: z.boolean().optional(), - ports: z.number().array().optional() + useVault: z.boolean().default(true), + ports: z.number().array().default([3001, 3002, 3003]) }) export type ServerlessOptions = z.infer diff --git a/lib/types/src/schema/upload-assets-to-s3.ts b/lib/types/src/schema/upload-assets-to-s3.ts index 5bcc7af23..9906ccc50 100644 --- a/lib/types/src/schema/upload-assets-to-s3.ts +++ b/lib/types/src/schema/upload-assets-to-s3.ts @@ -3,15 +3,15 @@ import { z } from 'zod' export const UploadAssetsToS3Schema = z.object({ accessKeyIdEnvVar: z.string().optional(), secretAccessKeyEnvVar: z.string().optional(), - accessKeyId: z.string().optional(), // @deprecated: use accessKeyIdEnvVar instead - secretAccessKey: z.string().optional(), // @deprecated: use secretAccessKeyEnvVar instead - directory: z.string(), - reviewBucket: z.string().array(), - prodBucket: z.string().array(), - region: z.string(), - destination: z.string(), - extensions: z.string(), - cacheControl: z.string() + accessKeyId: z.string().default('aws_access_hashed_assets'), // @deprecated: use accessKeyIdEnvVar instead + secretAccessKey: z.string().default('aws_secret_hashed_assets'), // @deprecated: use secretAccessKeyEnvVar instead + directory: z.string().default('public'), + reviewBucket: z.string().array().default(['ft-next-hashed-assets-preview']), + prodBucket: z.string().array().default(['ft-next-hashed-assets-prod']), + region: z.string().default('eu-west-1'), + destination: z.string().default('hashed-assets/page-kit'), + extensions: z.string().default('js,css,map,gz,br,png,jpg,jpeg,gif,webp,svg,ico,json'), + cacheControl: z.string().default('public, max-age=31536000, stale-while-revalidate=60, stale-if-error=3600') }) export type UploadAssetsToS3Options = z.infer diff --git a/plugins/eslint/src/tasks/eslint.ts b/plugins/eslint/src/tasks/eslint.ts index 2ae771a19..b55f0fea6 100644 --- a/plugins/eslint/src/tasks/eslint.ts +++ b/plugins/eslint/src/tasks/eslint.ts @@ -1,16 +1,12 @@ import { ToolKitError } from '@dotcom-tool-kit/error' import { styles } from '@dotcom-tool-kit/logger' import { Task } from '@dotcom-tool-kit/types' -import { ESLintOptions, ESLintSchema } from '@dotcom-tool-kit/types/lib/schema/eslint' +import { ESLintSchema } from '@dotcom-tool-kit/types/lib/schema/eslint' import { ESLint } from 'eslint' export default class Eslint extends Task { static description = '' - static defaultOptions: ESLintOptions = { - files: ['**/*.js'] - } - async run(files?: string[]): Promise { const eslint = new ESLint(this.options.options) const results = await eslint.lintFiles(files ?? this.options.files) diff --git a/plugins/mocha/src/tasks/mocha.ts b/plugins/mocha/src/tasks/mocha.ts index 438c28a98..6d51c0a18 100644 --- a/plugins/mocha/src/tasks/mocha.ts +++ b/plugins/mocha/src/tasks/mocha.ts @@ -1,7 +1,7 @@ import { hookFork, waitOnExit } from '@dotcom-tool-kit/logger' import { Task } from '@dotcom-tool-kit/types' import { glob } from 'glob' -import { MochaOptions, MochaSchema } from '@dotcom-tool-kit/types/lib/schema/mocha' +import { MochaSchema } from '@dotcom-tool-kit/types/lib/schema/mocha' import { fork } from 'child_process' import { promisify } from 'util' const mochaCLIPath = require.resolve('mocha/bin/mocha') @@ -9,10 +9,6 @@ const mochaCLIPath = require.resolve('mocha/bin/mocha') export default class Mocha extends Task { static description = '' - static defaultOptions: MochaOptions = { - files: 'test/**/*.js' - } - async run(): Promise { const files = await promisify(glob)(this.options.files) diff --git a/plugins/node/src/tasks/node.ts b/plugins/node/src/tasks/node.ts index 48581e5b7..80085aa44 100644 --- a/plugins/node/src/tasks/node.ts +++ b/plugins/node/src/tasks/node.ts @@ -1,5 +1,5 @@ import { Task } from '@dotcom-tool-kit/types' -import { NodeOptions, NodeSchema } from '@dotcom-tool-kit/types/lib/schema/node' +import { NodeSchema } from '@dotcom-tool-kit/types/lib/schema/node' import { ToolKitError } from '@dotcom-tool-kit/error' import { fork } from 'child_process' import { VaultEnvVars } from '@dotcom-tool-kit/vault' @@ -11,12 +11,6 @@ import waitPort from 'wait-port' export default class Node extends Task { static description = '' - static defaultOptions: NodeOptions = { - entry: './server/app.js', - useVault: true, - ports: [3001, 3002, 3003] - } - async run(): Promise { const { entry, args, useVault, ports } = this.options diff --git a/plugins/nodemon/src/tasks/nodemon.ts b/plugins/nodemon/src/tasks/nodemon.ts index 556b0f25b..0e1f5786d 100644 --- a/plugins/nodemon/src/tasks/nodemon.ts +++ b/plugins/nodemon/src/tasks/nodemon.ts @@ -1,7 +1,7 @@ import { ToolKitError } from '@dotcom-tool-kit/error' import { hookFork, styles } from '@dotcom-tool-kit/logger' import { Task } from '@dotcom-tool-kit/types' -import { NodemonOptions, NodemonSchema } from '@dotcom-tool-kit/types/lib/schema/nodemon' +import { NodemonSchema } from '@dotcom-tool-kit/types/lib/schema/nodemon' import { writeState } from '@dotcom-tool-kit/state' import { VaultEnvVars } from '@dotcom-tool-kit/vault' import getPort from 'get-port' @@ -11,12 +11,6 @@ import { Readable } from 'stream' export default class Nodemon extends Task { static description = '' - static defaultOptions: NodemonOptions = { - entry: './server/app.js', - useVault: true, - ports: [3001, 3002, 3003] - } - async run(): Promise { const { entry, configPath, useVault, ports } = this.options diff --git a/plugins/prettier/src/tasks/prettier.ts b/plugins/prettier/src/tasks/prettier.ts index b134885e6..f1df42d51 100644 --- a/plugins/prettier/src/tasks/prettier.ts +++ b/plugins/prettier/src/tasks/prettier.ts @@ -9,18 +9,6 @@ import { ToolKitError } from '@dotcom-tool-kit/error' export default class Prettier extends Task { static description = '' - static defaultOptions: PrettierOptions = { - files: ['**/*.{js,jsx,ts,tsx}'], - ignoreFile: '.prettierignore', - configOptions: { - singleQuote: true, - useTabs: true, - bracketSpacing: true, - arrowParens: 'always', - trailingComma: 'none' - } - } - async run(files?: string[]): Promise { try { const filepaths = await fg(files ?? this.options.files) diff --git a/plugins/serverless/src/tasks/run.ts b/plugins/serverless/src/tasks/run.ts index c427af13d..5c24fdc1f 100644 --- a/plugins/serverless/src/tasks/run.ts +++ b/plugins/serverless/src/tasks/run.ts @@ -1,5 +1,5 @@ import { Task } from '@dotcom-tool-kit/types' -import { ServerlessOptions, ServerlessSchema } from '@dotcom-tool-kit/types/lib/schema/serverless' +import { ServerlessSchema } from '@dotcom-tool-kit/types/lib/schema/serverless' import { spawn } from 'child_process' import { VaultEnvVars } from '@dotcom-tool-kit/vault' import { hookConsole, hookFork } from '@dotcom-tool-kit/logger' @@ -9,11 +9,6 @@ import waitPort from 'wait-port' export default class ServerlessRun extends Task { static description = 'Run serverless functions locally' - static defaultOptions: ServerlessOptions = { - useVault: true, - ports: [3001, 3002, 3003] - } - async run(): Promise { const { useVault, ports, configPath } = this.options diff --git a/plugins/upload-assets-to-s3/src/tasks/upload-assets-to-s3.ts b/plugins/upload-assets-to-s3/src/tasks/upload-assets-to-s3.ts index d8af862d1..e071e2a3b 100644 --- a/plugins/upload-assets-to-s3/src/tasks/upload-assets-to-s3.ts +++ b/plugins/upload-assets-to-s3/src/tasks/upload-assets-to-s3.ts @@ -14,18 +14,6 @@ import { export default class UploadAssetsToS3 extends Task { static description = '' - static defaultOptions: UploadAssetsToS3Options = { - accessKeyId: 'aws_access_hashed_assets', - secretAccessKey: 'aws_secret_hashed_assets', - directory: 'public', - reviewBucket: ['ft-next-hashed-assets-preview'], - prodBucket: ['ft-next-hashed-assets-prod'], - region: 'eu-west-1', - destination: 'hashed-assets/page-kit', - extensions: 'js,css,map,gz,br,png,jpg,jpeg,gif,webp,svg,ico,json', - cacheControl: 'public, max-age=31536000, stale-while-revalidate=60, stale-if-error=3600' - } - async run(): Promise { await this.uploadAssetsToS3(this.options) } @@ -94,10 +82,10 @@ export default class UploadAssetsToS3 extends Task