Skip to content

Commit 144e4ff

Browse files
committed
feat(create): pick an action when current directory is not empty
1 parent 5c169d6 commit 144e4ff

File tree

6 files changed

+88
-71
lines changed

6 files changed

+88
-71
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fbi",
3-
"description": "A workflow tool in command-line",
3+
"description": "A workflow tool in the command line",
44
"version": "4.0.4",
55
"author": "shaw @neikvon",
66
"main": "lib/index.js",

src/commands/create.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { sep } from 'path'
12
import { Fbi } from '../fbi'
23
import { Factory } from '../core/Factory'
34
import { Command } from '../core/command'
@@ -7,7 +8,7 @@ import { groupBy, flatten, isValidArray } from '../utils'
78
export default class CommandCreate extends Command {
89
id = 'create'
910
alias = ''
10-
args = '[templatefactory] [project]'
11+
args = '[template|factory]'
1112
description = `create a project via template or factory`
1213
flags = [
1314
['-p, --package-manager <name>', 'Specifying a package manager. e.g. pnpm/yarn/npm', 'npm']
@@ -17,10 +18,9 @@ export default class CommandCreate extends Command {
1718
super()
1819
}
1920

20-
async run(inputTemplate: any, project: any, flags: any) {
21+
async run(inputTemplate: any, flags: any) {
2122
this.debug(`Running command "${this.id}" from factory "${this.factory.id}" with options:`, {
2223
inputTemplate,
23-
project,
2424
flags
2525
})
2626
const factories = this.factory.createAllFactories()
@@ -32,29 +32,44 @@ export default class CommandCreate extends Command {
3232
if (usingFactory?.id) {
3333
this.debug(`current project using factory "${usingFactory.id}"`)
3434
const factory = this.factory.resolveFactory(usingFactory.id)
35-
// console.log({ factory })
3635
if (usingFactory.template) {
3736
const template = factory?.resolveTemplate(usingFactory.template)
38-
// console.log({ template })
3937
subTemplates = template?.templates
4038
}
4139
}
42-
// console.log({ subTemplates })
4340

41+
let subDirectory = false
4442
if (usingFactory) {
4543
this.log()
4644
this.log(
47-
`current project is using template "${usingFactory.template}" from factory "${usingFactory.id}"`
45+
`current project is using template ${this.style.cyan(
46+
usingFactory.template
47+
)} from ${this.style.cyan(usingFactory.id)}`
4848
)
4949
this.log(`you can only use sub-templates`)
5050
if (!isValidArray(subTemplates)) {
5151
this.warn(`but there are no sub-templates`).exit()
5252
}
5353
this.log()
5454
}
55+
// check current dir is empty or not
56+
else if (await this.fs.pathExists(process.cwd())) {
57+
const { selectedAction } = (await this.prompt({
58+
type: 'select',
59+
name: 'selectedAction',
60+
message: `Current directory is not empty. Pick an action:`,
61+
hint: 'Use arrow-keys, <return> to submit',
62+
choices: ['Overwrite', 'New subdirectory', 'Cancel']
63+
})) as any
64+
if (selectedAction === 'Cancel') {
65+
this.exit()
66+
}
67+
if (selectedAction === 'New subdirectory') {
68+
subDirectory = true
69+
}
70+
}
5571

5672
// get all templates
57-
// const factories = this.factory.createAllFactories()
5873
let templates = subTemplates || flatten(factories.map((f: Factory) => f.templates))
5974

6075
let templateInstances
@@ -107,6 +122,8 @@ export default class CommandCreate extends Command {
107122
(t: Template) => t.id === selected.templateId && t.factory.id === selected.factoryId
108123
)
109124

125+
const projectName = process.cwd().split(sep).pop()
126+
110127
if (selectedTemplate) {
111128
// set init data
112129
const factoryInfo = this.store.get(selected.factoryId)
@@ -119,8 +136,9 @@ export default class CommandCreate extends Command {
119136
template: selected.templateId
120137
},
121138
project: {
122-
name: project
123-
}
139+
name: projectName
140+
},
141+
subDirectory
124142
},
125143
flags
126144
)

src/commands/list.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,11 @@ export default class CommandList extends Command {
9999
const index = showIdx ? this.style.bold(symbols.numbers[idx as number]) : ' '
100100
const title = this.style.bold(obj.id)
101101
const version = isCurrent && current.version ? this.style.italic(`@${current.version}`) : ''
102-
const path = this.style.dim(
103-
obj.rootDir
104-
? obj.rootDir.startsWith(process.cwd())
105-
? obj.rootDir.replace(process.cwd() + '/', '')
106-
: obj.rootDir
107-
: ''
108-
)
102+
const rootDir =
103+
obj.options &&
104+
obj.options.rootDir &&
105+
obj.options.rootDir.replace(this.context.get('env.home'), '~')
106+
const path = this.style.dim(rootDir)
109107
let txt = `\n${index} ${title}${version} ${path}`
110108

111109
if (obj.description) {

src/core/template.ts

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,36 @@ export abstract class Template extends BaseClass {
3030
protected targetDir = process.cwd()
3131
protected _debugPrefix = ''
3232
private rootPath = ''
33+
private subDirectory = false
3334

3435
constructor() {
3536
super()
36-
37+
}
38+
39+
// public methods
40+
public resolveTemplate(templateId: string) {
41+
const template = this.templates.find((x) => x.id === templateId)
42+
if (!template) {
43+
this.debug(
44+
`Template (${this.id}${this.factory ? `:${this.factory.id}` : ''}):`,
45+
`template "${templateId}" not found`
46+
)
47+
} else {
48+
this.debug(
49+
`Template (${this.id}${this.factory ? `:${this.factory.id}` : ''}):`,
50+
`found template "${templateId}"`
51+
)
52+
}
53+
54+
return template
3755
}
3856

3957
public async run(data: Record<string, any>, flags: any): Promise<any> {
40-
this.prepare(data)
58+
if (data?.subDirectory) {
59+
this.subDirectory = data.subDirectory
60+
}
61+
62+
await this.prepare(data)
4163

4264
// 1. gathering: this.data
4365
this.debug(`${this._debugPrefix} run gathering`)
@@ -73,74 +95,55 @@ export abstract class Template extends BaseClass {
7395
}
7496
}
7597

76-
public resolveTemplate(templateId: string) {
77-
const template = this.templates.find(x => x.id === templateId)
78-
if (!template) {
79-
this.debug(
80-
`Template (${this.id}${this.factory ? `:${this.factory.id}` : ''}):`,
81-
`template "${templateId}" not found`
82-
)
83-
} else {
84-
this.debug(
85-
`Template (${this.id}${this.factory ? `:${this.factory.id}` : ''}):`,
86-
`found template "${templateId}"`
87-
)
98+
// processes
99+
private async prepare(data?: any) {
100+
this._debugPrefix = `Template "${this.id}"`
101+
102+
if (data && isValidObject(data)) {
103+
this.data = data
88104
}
89105

90-
return template
91-
}
106+
if (!this.data.factory || !this.data?.factory?.path) {
107+
this.error(`need path of factory`)
108+
return this.exit()
109+
}
92110

111+
this.rootPath = join(this.data.factory.path, this.path)
112+
}
93113
protected async gathering(flags: any): Promise<any> {}
94-
protected async checking(): Promise<any> {}
95-
protected async writing(): Promise<any> {}
96-
protected async installing(flags: any): Promise<any> {}
97-
protected async ending(): Promise<any> {}
98-
99114
private async afterGathering() {
100-
const { project } = this.data
101-
this.targetDir = join(process.cwd(), (project && project.name) || '')
102-
this.debug(`${this._debugPrefix} rootPath: ${this.rootPath} targetDir: ${this.targetDir}`)
115+
if (this.subDirectory) {
116+
this.targetDir = join(this.targetDir, this.data?.project?.name || '')
117+
}
118+
this.debug(`${this._debugPrefix} rootPath: ${this.rootPath}; targetDir: ${this.targetDir}`)
103119
}
120+
protected async checking(): Promise<any> {}
104121
private async afterChecking() {}
122+
protected async writing(): Promise<any> {}
105123
private async afterWriting() {
106124
if (this.files.copy && isValidArray(this.files.copy)) {
107125
this.debug(`${this._debugPrefix} start copy`, this.files.copy)
108126
await this.copy(this.files.copy)
109127
}
110128

111129
if (isFunction(this.renderer) && this.files.render && isValidArray(this.files.render)) {
112-
this.debug(`${this._debugPrefix} start render`, this.files.render)
113-
await this.render(this.files.render, this.data, this.renderOptions)
130+
this.debug(`${this._debugPrefix} start render`, this.files.render, this.files?.renderOptions)
131+
await this.render(this.files.render, this.data, this.files?.renderOptions)
114132
}
115133
}
134+
protected async installing(flags: any): Promise<any> {}
116135
private async afterInstalling() {}
136+
protected async ending(): Promise<any> {}
117137
private async afterEnding() {}
118138

119-
private prepare(data?: any) {
120-
this._debugPrefix = `Template "${this.id}"`
121-
122-
if (data && isValidObject(data)) {
123-
this.data = data
124-
}
125-
126-
if (!this.data.factory || !this.data.factory.path) {
127-
this.error(`need path of factory`)
128-
return this.exit()
129-
}
130-
131-
this.rootPath = join(this.data.factory.path, this.path)
132-
}
133-
139+
// utils
134140
private async copy(fileMaps: StringOrFileMap[]) {
135141
const maps: FileMap[] = this.foramtFileMaps(fileMaps)
136142
for (const map of maps) {
137143
const paths = await this.globFile(map)
138144
const replace = map.to.split('/').filter(Boolean)
139145
for (const p of paths) {
140-
const rest = p
141-
.split('/')
142-
.filter(Boolean)
143-
.slice(replace.length)
146+
const rest = p.split('/').filter(Boolean).slice(replace.length)
144147
const src = join(this.rootPath, p)
145148
if (!(await this.fs.pathExists(src))) {
146149
this.warn(`${src} not found`)
@@ -161,7 +164,7 @@ export abstract class Template extends BaseClass {
161164
private async render(
162165
fileMaps: StringOrFileMap[],
163166
data: Record<string | number, any>,
164-
options: [] | {}
167+
options?: [] | {}
165168
) {
166169
if (!this.renderer || !isFunction(this.renderer)) {
167170
return
@@ -171,10 +174,7 @@ export abstract class Template extends BaseClass {
171174
const paths = await this.globFile(map)
172175
const replace = map.to.split('/').filter(Boolean)
173176
for (const p of paths) {
174-
const rest = p
175-
.split('/')
176-
.filter(Boolean)
177-
.slice(replace.length)
177+
const rest = p.split('/').filter(Boolean).slice(replace.length)
178178
const src = join(this.rootPath, p)
179179
const stats = await this.fs.stat(src)
180180
if (stats.isFile()) {

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ export * from './core/factory'
66
export * from './core/command'
77
export * from './core/template'
88
export * from './core/version'
9-
export const utils = require('./utils/')
9+
export * as utils from './utils/'

src/utils/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './type'
55
export * from './format'
66
export * from './version'
77

8+
export const enquirer = require('enquirer')
89
export const symbols: SymbolsType = require('enquirer/lib/symbols')
910

1011
export const timeMeasurement = (
@@ -32,12 +33,12 @@ export function applyMixins(
3233
baseCtors: any[],
3334
exclude: string[] = ['constructor']
3435
) {
35-
baseCtors.forEach(baseCtor => {
36+
baseCtors.forEach((baseCtor) => {
3637
const instance = baseCtor.prototype || baseCtor
3738
const propertyNames = Object.getOwnPropertyNames(instance).filter(
3839
(name: string) => !exclude.includes(name)
3940
)
40-
propertyNames.forEach(name => {
41+
propertyNames.forEach((name) => {
4142
if (exclude.includes(name)) return
4243
const propDesc = Object.getOwnPropertyDescriptor(instance, name) as
4344
| PropertyDescriptor

0 commit comments

Comments
 (0)