From dddde3488d96a58d06451b870eefe58ade45c190 Mon Sep 17 00:00:00 2001 From: Matthias Giger Date: Sun, 21 Apr 2024 11:19:41 +0200 Subject: [PATCH] refactor: improve types, add helpers and separate state --- configuration/index.ts | 15 ++++++++++++++ configuration/license.ts | 21 ++++++++++---------- configuration/playwright.ts | 2 +- configuration/prettier.ts | 2 +- configuration/vscode.ts | 2 +- helper.ts | 39 ++++++++++++++++++++++++++++++++++++- index.ts | 38 +++++++----------------------------- parse.ts | 6 +++--- state.ts | 7 +++++++ types.ts | 20 +++++++++++++++---- 10 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 state.ts diff --git a/configuration/index.ts b/configuration/index.ts index 498a6c6..df008ad 100644 --- a/configuration/index.ts +++ b/configuration/index.ts @@ -12,6 +12,21 @@ import * as vscode from './vscode' export { ignore } +export type ConfigurationKeys = + | 'typescript' + | 'tsconfig' + | 'biome' + | 'eslint' + | 'prettier' + | 'vscode' + | 'playwright' + | 'vite' + | 'rsbuild' + | 'license' + // Require separate logic, not found in configuraitons below. + | 'ignore' + | 'gitignore' + export const configurations: Configuration[] = [ { name: 'typescript', diff --git a/configuration/license.ts b/configuration/license.ts index cd9c148..1b14f14 100644 --- a/configuration/license.ts +++ b/configuration/license.ts @@ -1,20 +1,21 @@ -import type { PackageJson, Template } from '../types' +import { state } from '../state' +import type { Template } from '../types' -const getNameFromPackageJson = (packageJson: PackageJson) => { - if (typeof packageJson.author === 'string') { - return packageJson.author +const getNameFromPackageJson = () => { + if (typeof state.packageJson.author === 'string') { + return state.packageJson.author } - if (typeof packageJson.author === 'object' && typeof packageJson.author.name === 'string') { - return packageJson.author.name + if (typeof state.packageJson.author === 'object' && typeof state.packageJson.author.name === 'string') { + return state.packageJson.author.name } - return packageJson.name + return state.packageJson.name } -const mitLicense = (packageJson: PackageJson) => `MIT License +const mitLicense = () => `MIT License -Copyright (c) ${new Date().getFullYear()} ${getNameFromPackageJson(packageJson)} +Copyright (c) ${new Date().getFullYear()} ${getNameFromPackageJson()} Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,7 +35,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.` -export const templates: Template<(packageJson: PackageJson) => string> = { +export const templates: Template = { mit: mitLicense, // biome-ignore lint/style/useNamingConvention: Alias for upper case typed license. MIT: mitLicense, diff --git a/configuration/playwright.ts b/configuration/playwright.ts index 0dbed76..c57f626 100644 --- a/configuration/playwright.ts +++ b/configuration/playwright.ts @@ -1,6 +1,6 @@ import type { Template } from '../types' -export const templates: Template = {} // Has no templates, highly customizable. +export const templates: Template = {} // Has no templates, highly customizable. export function createFile() { return { diff --git a/configuration/prettier.ts b/configuration/prettier.ts index 4116cd1..03f2f4d 100644 --- a/configuration/prettier.ts +++ b/configuration/prettier.ts @@ -1,7 +1,7 @@ import type { Template } from '../types' /** @type {import("prettier").Config} */ -export const templates: Template = { +export const templates: Template = { recommended: { semi: false, singleQuote: true, diff --git a/configuration/vscode.ts b/configuration/vscode.ts index 523504b..74b1823 100644 --- a/configuration/vscode.ts +++ b/configuration/vscode.ts @@ -1,6 +1,6 @@ import type { Template } from '../types' -export const templates: Template = { +export const templates: Template = { recommended: { 'editor.defaultFormatter': 'biomejs.biome', 'editor.codeActionsOnSave': { diff --git a/helper.ts b/helper.ts index 87c50be..ef0c742 100644 --- a/helper.ts +++ b/helper.ts @@ -1,6 +1,11 @@ +import { existsSync } from 'node:fs' +import { join } from 'node:path' +import { it } from 'avait' +import Bun from 'bun' import { create } from 'logua' import { z } from 'zod' -import { configurations } from './configuration' +import { configurations, ignore } from './configuration' +import { state } from './state' export const log = create('zero-configuration', 'blue') @@ -21,3 +26,35 @@ export const validate = (configuration: unknown) => { log(`Invalid configuration provided: ${error}`, 'error') } } + +export async function findConfiguration() { + const packageJson = await Bun.file('./package.json').json() + const { value: moduleContents } = await it(import(join(process.cwd(), './configuration.ts'))) + + if (!(moduleContents || Object.hasOwn(packageJson, 'configuration'))) { + log('No configurations detected', 'error') + } + + const userConfiguration = packageJson.configuration ?? moduleContents + + validate(userConfiguration) + + state.options = userConfiguration +} + +export async function writeGitIgnore(ignores: string[]) { + let userIgnores = state.options.ignore ?? state.options.gitignore ?? [] + + if (typeof userIgnores === 'string' && Object.hasOwn(ignore.templates, userIgnores)) { + userIgnores = ignore.templates[userIgnores] + } + + if (userIgnores && Array.isArray(userIgnores)) { + userIgnores.push(...ignores) + } + + if (!existsSync('./.gitignore')) { + const file = ignore.createFile(userIgnores as string[]) + await Bun.write(file.name, file.contents) + } +} diff --git a/index.ts b/index.ts index 5dfddd1..9d7eb27 100644 --- a/index.ts +++ b/index.ts @@ -1,29 +1,18 @@ #!/usr/bin/env bun -import { existsSync } from 'node:fs' -import { join } from 'node:path' -import { it } from 'avait' import Bun from 'bun' -import { configurations, ignore } from './configuration' -import { log, validate } from './helper' +import { configurations } from './configuration' +import { findConfiguration, log, writeGitIgnore } from './helper' import { parse } from './parse' +import { state } from './state' const ignores: string[] = [] -const packageJson = await Bun.file('./package.json').json() -const { value: moduleContents } = await it(import(join(process.cwd(), './configuration.ts'))) - -if (!(moduleContents || Object.hasOwn(packageJson, 'configuration'))) { - log('No configurations detected', 'error') -} - -const userConfiguration = packageJson.configuration ?? moduleContents - -validate(userConfiguration) +findConfiguration() for (const { name, alias, configuration } of configurations) { - const value = userConfiguration[name] ?? (alias && userConfiguration[alias]) + const value = state.options[name] ?? (alias && state.options[alias]) if (!value) continue - const file = await parse(value, configuration, packageJson) + const file = await parse(value, configuration) if (!file) continue await Bun.write(file.name, file.contents) ignores.push(file.name) @@ -33,17 +22,4 @@ if (ignores.length === 0) { log('No configurations detected', 'error') } -let userIgnores: string[] = userConfiguration.ignore ?? userConfiguration.gitignore ?? [] - -if (typeof userIgnores === 'string' && Object.hasOwn(ignore.templates, userIgnores)) { - userIgnores = ignore.templates[userIgnores] -} - -if (userIgnores && Array.isArray(userIgnores)) { - userIgnores.push(...ignores) -} - -if (!existsSync('./.gitignore')) { - const file = ignore.createFile(userIgnores) - await Bun.write(file.name, file.contents) -} +writeGitIgnore(ignores) diff --git a/parse.ts b/parse.ts index 7f28ce3..bf1cc44 100644 --- a/parse.ts +++ b/parse.ts @@ -1,7 +1,7 @@ import { existsSync } from 'node:fs' import { join } from 'node:path' import { it } from 'avait' -import type { Configuration, PackageJson } from './types' +import type { Configuration, Options } from './types' const isExtension = async (value: string) => { // NOTE dynamic import in tests will resolve relative to project node_modules and not fixture. @@ -13,11 +13,11 @@ const isExtension = async (value: string) => { return false } -export async function parse(value: string | object | boolean, configuration: Configuration['configuration'], packageJson: PackageJson) { +export async function parse(value: Options, configuration: Configuration['configuration']) { // Template. if (typeof value === 'string' && configuration.templates && Object.hasOwn(configuration.templates, value)) { const template = configuration.templates[value as keyof typeof configuration.templates] - const configurationTemplate = typeof template === 'function' ? (template as (value: PackageJson) => string)(packageJson) : template + const configurationTemplate = typeof template === 'function' ? template() : template return configuration.createFile(configurationTemplate) } diff --git a/state.ts b/state.ts new file mode 100644 index 0000000..74ddc46 --- /dev/null +++ b/state.ts @@ -0,0 +1,7 @@ +import type { State } from './types' + +export const state: State = { + options: {}, + language: 'json', + packageJson: { name: 'missing-package-name' }, +} diff --git a/types.ts b/types.ts index 12f4bac..b2c0aa6 100644 --- a/types.ts +++ b/types.ts @@ -1,14 +1,26 @@ -export type Template = { [key: string]: T } +import type { ConfigurationKeys } from './configuration' + +export type Template = { [key: string]: T | (() => T) } export type PackageJson = { name: string; author?: string | { name: string } } export type Configuration = { - name: string - alias?: string + name: ConfigurationKeys + alias?: ConfigurationKeys configuration: { - templates?: Template | ((packageJson: PackageJson) => string) + templates?: Template // biome-ignore lint/suspicious/noExplicitAny: Will be specified in file explicitly. createFile: (value?: any) => { name: string; contents: string } extension?: (path: string) => object } } + +export type Options = string | object | true + +export interface State { + options: { [Key in ConfigurationKeys]?: Options } + // Where does the configuration come from package.json => configuration: JSON + // configuration.js: JavaScript, configuration.ts: TypeScript + language: 'json' | 'javascript' | 'typescript' + packageJson: PackageJson +}