@newlogic-digital/cli 1.2.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.mjs +79 -24
- package/package.json +14 -13
- package/src/commands/cms/index.mjs +46 -46
- package/src/commands/cms/prepare.mjs +594 -528
- package/src/commands/init/cms.mjs +195 -140
- package/src/commands/init/index.mjs +70 -58
- package/src/commands/init/options.mjs +47 -0
- package/src/commands/init/ui.mjs +161 -58
- package/src/utils.mjs +18 -17
|
@@ -1,173 +1,228 @@
|
|
|
1
|
+
import { cancel, confirm, isCancel, select } from '@clack/prompts'
|
|
1
2
|
import { execSync } from '../../utils.mjs'
|
|
2
3
|
import { join, resolve } from 'path'
|
|
3
4
|
import fs from 'fs'
|
|
5
|
+
import { styleText } from 'node:util'
|
|
4
6
|
import os from 'os'
|
|
5
|
-
import pc from 'picocolors'
|
|
6
7
|
import fse from 'fs-extra'
|
|
7
|
-
import
|
|
8
|
+
import { isAutoYes, normalizeEnum, normalizeYesNo } from './options.mjs'
|
|
8
9
|
|
|
9
10
|
const tempDir = join(os.tmpdir(), 'newlogic-cms-web')
|
|
10
11
|
|
|
11
12
|
async function move(path, options = {}) {
|
|
12
|
-
|
|
13
|
+
await fse.move(join(tempDir, path), resolve(process.cwd(), path), options).catch(err => console.log(`${styleText('red', err)} - ${path}`))
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
async function clone(path, { variant, branch }) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
async function clone(path, { variant, branch, clone: cloneOption, y, yes } = {}) {
|
|
17
|
+
if (variant === 'cms-web') {
|
|
18
|
+
variant = 'newlogic-cms-next-web'
|
|
19
|
+
}
|
|
20
|
+
else if (variant === 'cms-eshop') {
|
|
21
|
+
variant = 'newlogic-cms-next-eshop'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let cloneMethod = normalizeEnum(cloneOption, ['ssh', 'https'])
|
|
25
|
+
const autoYes = isAutoYes({ y, yes })
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
url = 'https://git.newlogic.cz/newlogic-digital'
|
|
27
|
+
if (!cloneMethod) {
|
|
28
|
+
if (autoYes) {
|
|
29
|
+
cloneMethod = 'https'
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
cloneMethod = await select({
|
|
33
|
+
message: 'Clone with SSH or HTTPS?',
|
|
34
|
+
options: [
|
|
35
|
+
{ label: 'SSH', value: 'ssh' },
|
|
36
|
+
{ label: 'HTTPS', value: 'https' },
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (isCancel(cloneMethod)) {
|
|
41
|
+
cancel('Operation cancelled.')
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
40
44
|
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let url = ''
|
|
48
|
+
|
|
49
|
+
if (cloneMethod === 'ssh') {
|
|
50
|
+
url = 'git@git.newlogic.cz:newlogic-digital'
|
|
51
|
+
}
|
|
52
|
+
else if (cloneMethod === 'https') {
|
|
53
|
+
url = 'https://git.newlogic.cz/newlogic-digital'
|
|
54
|
+
}
|
|
41
55
|
|
|
42
|
-
|
|
56
|
+
execSync(`git clone -b ${branch} --single-branch --depth 1 ${url}/${variant}.git ${path}`)
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
async function install(name, branch) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
async function install(name, branch, { install: installOption, y, yes } = {}) {
|
|
60
|
+
let install = normalizeYesNo(installOption)
|
|
61
|
+
const autoYes = isAutoYes({ y, yes })
|
|
62
|
+
|
|
63
|
+
if (!install) {
|
|
64
|
+
if (autoYes) {
|
|
65
|
+
install = 'yes'
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const response = await confirm({
|
|
69
|
+
message: 'Install project?',
|
|
70
|
+
initialValue: true,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
if (isCancel(response)) {
|
|
74
|
+
cancel('Operation cancelled.')
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
install = response ? 'yes' : 'no'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (install === 'yes') {
|
|
83
|
+
execSync(`cd ${name || '.'} && composer install`)
|
|
84
|
+
execSync(`cd ${name || '.'} && composer install-${branch || ''}`)
|
|
85
|
+
|
|
86
|
+
if (fs.existsSync(resolve(process.cwd(), 'package.json'))) {
|
|
87
|
+
execSync(`cd ${name || '.'} && npm i && npx vite build`)
|
|
65
88
|
}
|
|
89
|
+
}
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
async function dev(name) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
async function dev(name, { dev: devOption, y, yes } = {}) {
|
|
93
|
+
let shouldStartDev = normalizeYesNo(devOption)
|
|
94
|
+
const autoYes = isAutoYes({ y, yes })
|
|
95
|
+
|
|
96
|
+
if (!shouldStartDev) {
|
|
97
|
+
if (autoYes) {
|
|
98
|
+
shouldStartDev = 'no'
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const response = await confirm({
|
|
102
|
+
message: 'Start dev server?',
|
|
103
|
+
initialValue: true,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
if (isCancel(response)) {
|
|
107
|
+
cancel('Operation cancelled.')
|
|
108
|
+
process.exit(1)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
shouldStartDev = response ? 'yes' : 'no'
|
|
83
112
|
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (shouldStartDev === 'yes') {
|
|
116
|
+
execSync(`cd ${name || '.'} && composer dev-headless`)
|
|
117
|
+
}
|
|
84
118
|
}
|
|
85
119
|
|
|
86
|
-
async function migrations(name) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
async function migrations(name, { migrations: migrationsOption, y, yes } = {}) {
|
|
121
|
+
let shouldInitMigrations = normalizeYesNo(migrationsOption)
|
|
122
|
+
const autoYes = isAutoYes({ y, yes })
|
|
123
|
+
|
|
124
|
+
if (!shouldInitMigrations) {
|
|
125
|
+
if (autoYes) {
|
|
126
|
+
shouldInitMigrations = 'no'
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const response = await confirm({
|
|
130
|
+
message: 'Init phinx migrations and seed?',
|
|
131
|
+
initialValue: true,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
if (isCancel(response)) {
|
|
135
|
+
cancel('Operation cancelled.')
|
|
136
|
+
process.exit(1)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
shouldInitMigrations = response ? 'yes' : 'no'
|
|
101
140
|
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (shouldInitMigrations === 'yes') {
|
|
144
|
+
execSync(`cd ${name || '.'} && composer phinx-init`)
|
|
145
|
+
}
|
|
102
146
|
}
|
|
103
147
|
|
|
104
|
-
async function prepare() {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
148
|
+
async function prepare({ prepare: prepareOption, y, yes } = {}) {
|
|
149
|
+
let shouldPrepare = normalizeYesNo(prepareOption)
|
|
150
|
+
const autoYes = isAutoYes({ y, yes })
|
|
151
|
+
|
|
152
|
+
if (!shouldPrepare) {
|
|
153
|
+
if (autoYes) {
|
|
154
|
+
shouldPrepare = 'no'
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const response = await confirm({
|
|
158
|
+
message: 'Prepare project with templates from frontend?',
|
|
159
|
+
initialValue: true,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
if (isCancel(response)) {
|
|
163
|
+
cancel('Operation cancelled.')
|
|
164
|
+
process.exit(1)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
shouldPrepare = response ? 'yes' : 'no'
|
|
119
168
|
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (shouldPrepare === 'yes') {
|
|
172
|
+
execSync('newlogic cms prepare')
|
|
173
|
+
}
|
|
120
174
|
}
|
|
121
175
|
|
|
122
|
-
export default async function cms(name, { variant, branch }) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
await install(name, branch)
|
|
129
|
-
await prepare()
|
|
130
|
-
await dev(name)
|
|
131
|
-
await migrations(name)
|
|
132
|
-
return
|
|
176
|
+
export default async function cms(name, { variant, branch, ...options } = {}) {
|
|
177
|
+
if (name) {
|
|
178
|
+
if (!fs.existsSync(resolve(process.cwd(), name))) {
|
|
179
|
+
await clone(name, { variant, branch, clone: options.clone, y: options.y, yes: options.yes })
|
|
133
180
|
}
|
|
134
181
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
await move('config')
|
|
146
|
-
await move('log')
|
|
147
|
-
await move('public/index.php')
|
|
148
|
-
await move('src/views')
|
|
149
|
-
await move('storage')
|
|
150
|
-
await move('temp')
|
|
151
|
-
await move('tests')
|
|
152
|
-
await move('.gitignore', { overwrite: true })
|
|
153
|
-
await move('README.md', { overwrite: true })
|
|
154
|
-
await move('composer.json')
|
|
155
|
-
await move('composer.lock')
|
|
156
|
-
await move('docker-compose.yml')
|
|
157
|
-
await move('Makefile') // deprecated
|
|
158
|
-
await move('phpstan.neon')
|
|
159
|
-
await move('phpunit.xml')
|
|
160
|
-
await move('pint.json')
|
|
161
|
-
await move('rector.php')
|
|
162
|
-
await fse.move(join(tempDir, '.gitlab-ci.prod.yml'), resolve(process.cwd(), '.gitlab-ci.yml'), { overwrite: true }).catch(err => console.log(`${pc.red(err)} - .gitlab-ci.yml`))
|
|
163
|
-
|
|
164
|
-
fse.removeSync(tempDir)
|
|
165
|
-
} else {
|
|
166
|
-
console.log(pc.yellow('Project files already exist in the directory, skipping copy step (delete composer.json to force)'))
|
|
182
|
+
await install(name, branch, { install: options.install, y: options.y, yes: options.yes })
|
|
183
|
+
await prepare({ prepare: options.prepare, y: options.y, yes: options.yes })
|
|
184
|
+
await dev(name, { dev: options.dev, y: options.y, yes: options.yes })
|
|
185
|
+
await migrations(name, { migrations: options.migrations, y: options.y, yes: options.yes })
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!fs.existsSync(resolve(process.cwd(), 'composer.json'))) {
|
|
190
|
+
if (fs.existsSync(tempDir)) {
|
|
191
|
+
fse.removeSync(tempDir)
|
|
167
192
|
}
|
|
168
193
|
|
|
169
|
-
await
|
|
170
|
-
|
|
171
|
-
await
|
|
172
|
-
await
|
|
194
|
+
await clone(tempDir, { variant, branch, clone: options.clone, y: options.y, yes: options.yes })
|
|
195
|
+
|
|
196
|
+
await move('.docker')
|
|
197
|
+
await move('app')
|
|
198
|
+
await move('bin')
|
|
199
|
+
await move('config')
|
|
200
|
+
await move('log')
|
|
201
|
+
await move('public/index.php')
|
|
202
|
+
await move('src/views')
|
|
203
|
+
await move('storage')
|
|
204
|
+
await move('temp')
|
|
205
|
+
await move('tests')
|
|
206
|
+
await move('.gitignore', { overwrite: true })
|
|
207
|
+
await move('README.md', { overwrite: true })
|
|
208
|
+
await move('composer.json')
|
|
209
|
+
await move('composer.lock')
|
|
210
|
+
await move('docker-compose.yml')
|
|
211
|
+
await move('Makefile') // deprecated
|
|
212
|
+
await move('phpstan.neon')
|
|
213
|
+
await move('phpunit.xml')
|
|
214
|
+
await move('pint.json')
|
|
215
|
+
await move('rector.php')
|
|
216
|
+
await fse.move(join(tempDir, '.gitlab-ci.prod.yml'), resolve(process.cwd(), '.gitlab-ci.yml'), { overwrite: true }).catch(err => console.log(`${styleText('red', err)} - .gitlab-ci.yml`))
|
|
217
|
+
|
|
218
|
+
fse.removeSync(tempDir)
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.log(styleText('yellow', 'Project files already exist in the directory, skipping copy step (delete composer.json to force)'))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await install(name, branch, { install: options.install, y: options.y, yes: options.yes })
|
|
225
|
+
await prepare({ prepare: options.prepare, y: options.y, yes: options.yes })
|
|
226
|
+
await dev(name, { dev: options.dev, y: options.y, yes: options.yes })
|
|
227
|
+
await migrations(name, { migrations: options.migrations, y: options.y, yes: options.yes })
|
|
173
228
|
}
|
|
@@ -1,73 +1,85 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { cancel, isCancel, select } from '@clack/prompts'
|
|
2
2
|
import ui from './ui.mjs'
|
|
3
3
|
import cms from './cms.mjs'
|
|
4
|
+
import { isAutoYes, normalizeEnum } from './options.mjs'
|
|
4
5
|
|
|
5
|
-
async function init(project, name) {
|
|
6
|
-
|
|
6
|
+
async function init(project, name, options = {}) {
|
|
7
|
+
const nameAsProject = (project && !project.match(/^(ui|cms)$/))
|
|
8
|
+
const autoYes = isAutoYes(options)
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
const response = await prompts([
|
|
10
|
-
{
|
|
11
|
-
type: 'select',
|
|
12
|
-
name: 'project',
|
|
13
|
-
message: 'Select a project to init',
|
|
14
|
-
choices: [
|
|
15
|
-
{ title: 'ui', value: 'ui' },
|
|
16
|
-
{ title: 'cms', value: 'cms' }
|
|
17
|
-
],
|
|
18
|
-
onState: (state) => {
|
|
19
|
-
if (state.aborted) {
|
|
20
|
-
process.exit(1)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
])
|
|
10
|
+
name = nameAsProject ? project : name
|
|
25
11
|
|
|
26
|
-
|
|
27
|
-
|
|
12
|
+
if (!project || nameAsProject) {
|
|
13
|
+
if (autoYes) {
|
|
14
|
+
project = 'ui'
|
|
28
15
|
}
|
|
16
|
+
else {
|
|
17
|
+
project = await select({
|
|
18
|
+
message: 'Select a project to init',
|
|
19
|
+
options: [
|
|
20
|
+
{ label: 'ui', value: 'ui' },
|
|
21
|
+
{ label: 'cms', value: 'cms' },
|
|
22
|
+
],
|
|
23
|
+
})
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
if (isCancel(project)) {
|
|
26
|
+
cancel('Operation cancelled.')
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let branch = normalizeEnum(options.branch, ['main', 'dev'])
|
|
33
|
+
|
|
34
|
+
if (!branch) {
|
|
35
|
+
if (autoYes) {
|
|
36
|
+
branch = 'main'
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
branch = await select({
|
|
40
|
+
message: 'Select a git branch',
|
|
41
|
+
options: [
|
|
42
|
+
{ label: 'main', value: 'main' },
|
|
43
|
+
{ label: 'dev', value: 'dev' },
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if (isCancel(branch)) {
|
|
48
|
+
cancel('Operation cancelled.')
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
49
51
|
}
|
|
52
|
+
}
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
type: 'select',
|
|
55
|
-
name: 'variant',
|
|
56
|
-
message: 'Select a variant to install',
|
|
57
|
-
choices: [
|
|
58
|
-
{ title: 'cms-web', value: 'cms-web' },
|
|
59
|
-
{ title: 'cms-eshop', value: 'cms-eshop' }
|
|
60
|
-
],
|
|
61
|
-
onState: (state) => {
|
|
62
|
-
if (state.aborted) {
|
|
63
|
-
process.exit(1)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
])
|
|
54
|
+
if (project === 'ui') {
|
|
55
|
+
await ui(name, { ...options, branch })
|
|
56
|
+
}
|
|
68
57
|
|
|
69
|
-
|
|
58
|
+
if (project === 'cms') {
|
|
59
|
+
let variant = normalizeEnum(options.variant, ['cms-web', 'cms-eshop'])
|
|
60
|
+
|
|
61
|
+
if (!variant) {
|
|
62
|
+
if (autoYes) {
|
|
63
|
+
variant = 'cms-web'
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
variant = await select({
|
|
67
|
+
message: 'Select a variant to install',
|
|
68
|
+
options: [
|
|
69
|
+
{ label: 'cms-web', value: 'cms-web' },
|
|
70
|
+
{ label: 'cms-eshop', value: 'cms-eshop' },
|
|
71
|
+
],
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (isCancel(variant)) {
|
|
75
|
+
cancel('Operation cancelled.')
|
|
76
|
+
process.exit(1)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
70
79
|
}
|
|
80
|
+
|
|
81
|
+
await cms(name, { ...options, branch, variant })
|
|
82
|
+
}
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
export default init
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function normalizeEnum(value, allowed = []) {
|
|
2
|
+
if (typeof value !== 'string') {
|
|
3
|
+
return undefined
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const normalized = value.trim().toLowerCase()
|
|
7
|
+
|
|
8
|
+
if (!allowed.includes(normalized)) {
|
|
9
|
+
return undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return normalized
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeYesNo(value) {
|
|
16
|
+
if (typeof value === 'boolean') {
|
|
17
|
+
return value ? 'yes' : 'no'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof value !== 'string') {
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const normalized = value.trim().toLowerCase()
|
|
25
|
+
|
|
26
|
+
if (['yes', 'y', 'true', '1'].includes(normalized)) {
|
|
27
|
+
return 'yes'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (['no', 'n', 'false', '0'].includes(normalized)) {
|
|
31
|
+
return 'no'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isAutoYes(options = {}) {
|
|
38
|
+
const values = [normalizeYesNo(options.y), normalizeYesNo(options.yes)]
|
|
39
|
+
|
|
40
|
+
if (values.includes('no')) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return values.includes('yes')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { normalizeEnum, normalizeYesNo, isAutoYes }
|