diff --git a/src/commands/create.ts b/src/commands/create.ts index 48b5712..ddb3b38 100644 --- a/src/commands/create.ts +++ b/src/commands/create.ts @@ -1,12 +1,12 @@ import * as fs from 'fs/promises' -import * as path from 'path' +import { join } from 'path' import { Command, Flags } from '@oclif/core' import chalk from 'chalk' import { Listr } from 'listr2' import { rimraf } from 'rimraf' import { kebabCase, startCase, upperFirst } from 'lodash-es' -import { cloneRepo, fileExists, pascalCase } from '../util.js' +import { cloneRepo, fileExists, isDirectory, isEmptyDirectory, pascalCase } from '../util.js' enum Type { plugin = 'plugin', @@ -14,15 +14,11 @@ enum Type { } interface Names { - upper: string pascal: string - title: string kebab: string - short: string - full: string + title: string } - interface Ctx { type: Type dir: string @@ -65,46 +61,57 @@ export default class Create extends Command { } async run(): Promise { - const { flags } = await this.parse(Create) + const { flags: { repo, ...flags } } = await this.parse(Create) + const type = Type[flags.type as keyof typeof Type] - const name = flags.name.replace(/^swup/i, '').replace(/(plugin|theme)$/i, '').trim() - const names: Names = { - upper: upperFirst(name), - pascal: pascalCase(name), - kebab: kebabCase(name), - title: startCase(name), - short: `${pascalCase(name)}${upperFirst(type)}`, - full: `Swup${pascalCase(name)}${upperFirst(type)}`, - } - const ctx: Ctx = { - type, - names, - dir: names.full, - path: path.join(process.cwd(), names.full), - repo: flags.repo - } + const names = this.generateNames(flags.name, type) + const dir = names.kebab + const path = join(process.cwd(), dir) + + const ctx: Ctx = { type, names, dir, path, repo } const tasks = [ - { title: 'Creating directory', task: this.createDirectory }, - { title: 'Cloning repository', task: this.cloneRepository }, - { title: 'Cleaning git directory', task: this.clearGitDirectory }, - { title: 'Updating package.json', task: this.updatePackageJson }, - { title: 'Updating plugin file', task: this.updatePluginFile }, - { title: 'Updating readme', task: this.updateReadme }, + { title: 'Creating directory', task: this.createDirectory.bind(this) }, + { title: 'Cloning repository', task: this.cloneRepository.bind(this) }, + { title: 'Cleaning git directory', task: this.clearGitDirectory.bind(this) }, + { title: 'Updating package.json', task: this.updatePackageJson.bind(this) }, + { title: 'Updating plugin file', task: this.updatePluginFile.bind(this) }, + { title: 'Updating readme', task: this.updateReadme.bind(this) }, ] - try { - this.log(chalk`Creating {magenta ${type}} with name {green ${names.full}}...`) - await new Listr(tasks, { ctx }).run() - this.log(chalk`Created {green ${names.full}}`) - } catch (error) { - this.error(`${error}`) + this.log(chalk`Creating new ${type} {magenta ${names.pascal}}...`) + await new Listr(tasks, { ctx }).run() + this.log(chalk`Created ${type} {green ${names.pascal}}`) + } + + async catch(error: Error) { + // this.error(error as Error) + throw error + } + + generateNames(input: string, type: string): Names { + const base = input.replace(/^swup/i, '').replace(/(plugin|theme)$/i, '').trim() + const name = `Swup${pascalCase(base)}${upperFirst(type)}` + + return { + pascal: name, + kebab: kebabCase(name), + title: startCase(name) } } async createDirectory(ctx: Ctx): Promise { - if (await fileExists(ctx.path)) { - throw new Error(`Directory ${ctx.dir} already exists.`) + const exists = await fileExists(ctx.path) + const dir = exists && await isDirectory(ctx.path) + const empty = dir && await isEmptyDirectory(ctx.path) + if (exists) { + if (!dir) { + throw new Error(`Unable to create directory ${ctx.dir}. A file of the same name already exists.`) + } + if (!empty) { + throw new Error(`Directory ${ctx.dir} already exists and has files in it.`) + } + return } try { await fs.mkdir(ctx.path) @@ -120,7 +127,7 @@ export default class Create extends Command { async clearGitDirectory(ctx: Ctx): Promise { if (!ctx.path) return - await rimraf(path.join(ctx.path, '.git')) + await rimraf(join(ctx.path, '.git')) } async cloneRepository(ctx: Ctx): Promise { @@ -134,11 +141,11 @@ export default class Create extends Command { } async updatePluginFile(ctx: Ctx): Promise { - const pluginPath = path.join(ctx.path, 'src/index.js') + const pluginPath = join(ctx.path, 'src/index.js') try { let data = await fs.readFile(pluginPath, 'utf8') - data = data.replace(/PluginName/g, ctx.names.short) - data = data.replace(/ThemeName/g, ctx.names.short) + data = data.replace(/PluginName/g, ctx.names.pascal) + data = data.replace(/ThemeName/g, ctx.names.pascal) await fs.writeFile(pluginPath, data) } catch (error) { throw new Error(`Error updating plugin file: ${error}`) @@ -146,7 +153,7 @@ export default class Create extends Command { } async updatePackageJson(ctx: Ctx): Promise { - const packagePath = path.join(ctx.path, 'package.json') + const packagePath = join(ctx.path, 'package.json') let pckg: any try { pckg = JSON.parse(await fs.readFile(packagePath, 'utf8')) @@ -155,6 +162,7 @@ export default class Create extends Command { } pckg.name = ctx.names.kebab + pckg.amdName = ctx.names.pascal pckg.version = '0.0.0' pckg.description = '' pckg.author.name = '' @@ -174,14 +182,17 @@ export default class Create extends Command { } async updateReadme(ctx: Ctx): Promise { - const readmePath = path.join(ctx.path, 'readme.md') + const readmePath = join(ctx.path, 'readme.md') try { let data = await fs.readFile(readmePath, 'utf8') data = data.replace(/ *\[comment\]: CLI-remove-start[\s\S]*\[comment\]: CLI-remove-end */g, '') - data = data.replace(/swup-\[(plugin-name|theme-name)\]-plugin/g, ctx.names.kebab) + data = data.replace(/swup-\[(plugin-name|theme-name)\]-(plugin|theme)/g, ctx.names.kebab) + data = data.replace(/Swup \[(Plugin Name|Theme Name)\] (Plugin|Theme)/g, ctx.names.title) data = data.replace(/ \[(Plugin Name|Theme Name)\]/g, ctx.names.title) + data = data.replace(/Swup\[(PluginName|ThemeName)\](Plugin|Theme)/g, ctx.names.pascal) data = data.replace(/\[(PluginName|ThemeName)\]/g, ctx.names.pascal) - data = data.replace(/(SwupNamePlugin|SwupNameTheme)/g, ctx.names.full) + data = data.replace(/(SwupNamePlugin|SwupNameTheme)/g, ctx.names.pascal) + data = `${data.trim()}\n` await fs.writeFile(readmePath, data) } catch (error) { throw new Error(`Error updating readme: ${error}`) diff --git a/src/util.ts b/src/util.ts index 64aa808..9615202 100644 --- a/src/util.ts +++ b/src/util.ts @@ -15,6 +15,25 @@ export async function fileExists(path: string): Promise { } } +export async function isDirectory(path: string): Promise { + try { + return (await fs.lstat(path)).isDirectory() + } catch (error) { + return false + } +} + +export async function isEmptyDirectory(path: string): Promise { + try { + const directory = await fs.opendir(path) + const entry = await directory.read() + await directory.close() + return entry === null + } catch (error) { + return false + } +} + export function cloneRepo(repoUrl: string, destination: string): Promise { return new Promise((resolve, reject) => { exec(`git clone ${repoUrl} ${destination}`, (error, stdout, stderr) => {