Skip to content

Commit

Permalink
refactor: improve types, add helpers and separate state
Browse files Browse the repository at this point in the history
  • Loading branch information
tobua committed Apr 21, 2024
1 parent bcdf754 commit dddde34
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 52 deletions.
15 changes: 15 additions & 0 deletions configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
21 changes: 11 additions & 10 deletions configuration/license.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<string> = {
mit: mitLicense,
// biome-ignore lint/style/useNamingConvention: Alias for upper case typed license.
MIT: mitLicense,
Expand Down
2 changes: 1 addition & 1 deletion configuration/playwright.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Template } from '../types'

export const templates: Template = {} // Has no templates, highly customizable.
export const templates: Template<object> = {} // Has no templates, highly customizable.

export function createFile() {
return {
Expand Down
2 changes: 1 addition & 1 deletion configuration/prettier.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Template } from '../types'

/** @type {import("prettier").Config} */
export const templates: Template = {
export const templates: Template<object> = {
recommended: {
semi: false,
singleQuote: true,
Expand Down
2 changes: 1 addition & 1 deletion configuration/vscode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Template } from '../types'

export const templates: Template = {
export const templates: Template<object> = {
recommended: {
'editor.defaultFormatter': 'biomejs.biome',
'editor.codeActionsOnSave': {
Expand Down
39 changes: 38 additions & 1 deletion helper.ts
Original file line number Diff line number Diff line change
@@ -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')

Expand All @@ -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)
}
}
38 changes: 7 additions & 31 deletions index.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
6 changes: 3 additions & 3 deletions parse.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
}

Expand Down
7 changes: 7 additions & 0 deletions state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { State } from './types'

export const state: State = {
options: {},
language: 'json',
packageJson: { name: 'missing-package-name' },
}
20 changes: 16 additions & 4 deletions types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
export type Template<T = object> = { [key: string]: T }
import type { ConfigurationKeys } from './configuration'

export type Template<T> = { [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<string | object | string[]>
// 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
}

0 comments on commit dddde34

Please sign in to comment.