@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.
@@ -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 prompts from 'prompts'
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
- await fse.move(join(tempDir, path), resolve(process.cwd(), path), options).catch(err => console.log(`${pc.red(err)} - ${path}`))
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
- if (variant === 'cms-web') {
17
- variant = 'newlogic-cms-next-web'
18
- } else if (variant === 'cms-eshop') {
19
- variant = 'newlogic-cms-next-eshop'
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
- const { clone } = await prompts([
23
- {
24
- type: 'select',
25
- name: 'clone',
26
- message: 'Clone with SSH or HTTPS?',
27
- choices: [
28
- { title: 'SSH', value: 'ssh' },
29
- { title: 'HTTPS', value: 'https' }
30
- ]
31
- }
32
- ])
33
-
34
- let url = ''
35
-
36
- if (clone === 'ssh') {
37
- url = 'git@git.newlogic.cz:newlogic-digital'
38
- } else if (clone === 'https') {
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
- execSync(`git clone -b ${branch} --single-branch --depth 1 ${url}/${variant}.git ${path}`)
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
- const { install } = await prompts([
47
- {
48
- type: 'select',
49
- name: 'install',
50
- message: 'Install project?',
51
- choices: [
52
- { title: 'yes', value: 'yes' },
53
- { title: 'no', value: 'no' }
54
- ]
55
- }
56
- ])
57
-
58
- if (install === 'yes') {
59
- execSync(`cd ${name || '.'} && composer install`)
60
- execSync(`cd ${name || '.'} && composer install-${branch || ''}`)
61
-
62
- if (fs.existsSync(resolve(process.cwd(), 'package.json'))) {
63
- execSync(`cd ${name || '.'} && npm i && npx vite build`)
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
- const { install } = await prompts([
70
- {
71
- type: 'select',
72
- name: 'install',
73
- message: 'Start dev server?',
74
- choices: [
75
- { title: 'yes', value: 'yes' },
76
- { title: 'no', value: 'no' }
77
- ]
78
- }
79
- ])
80
-
81
- if (install === 'yes') {
82
- execSync(`cd ${name || '.'} && composer dev-headless`)
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
- const { install } = await prompts([
88
- {
89
- type: 'select',
90
- name: 'install',
91
- message: 'Init phinx migrations and seed?',
92
- choices: [
93
- { title: 'yes', value: 'yes' },
94
- { title: 'no', value: 'no' }
95
- ]
96
- }
97
- ])
98
-
99
- if (install === 'yes') {
100
- execSync(`cd ${name || '.'} && composer phinx-init`)
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
- const { install } = await prompts([
106
- {
107
- type: 'select',
108
- name: 'install',
109
- message: 'Prepare project with templates from frontend?',
110
- choices: [
111
- { title: 'yes', value: 'yes' },
112
- { title: 'no', value: 'no' }
113
- ]
114
- }
115
- ])
116
-
117
- if (install === 'yes') {
118
- execSync('newlogic cms prepare')
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
- if (name) {
124
- if (!fs.existsSync(resolve(process.cwd(), name))) {
125
- await clone(name, { variant, branch })
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
- if (!fs.existsSync(resolve(process.cwd(), 'composer.json'))) {
136
- if (fs.existsSync(tempDir)) {
137
- fse.removeSync(tempDir)
138
- }
139
-
140
- await clone(tempDir, { variant, branch })
141
-
142
- await move('.docker')
143
- await move('app')
144
- await move('bin')
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 install(name, branch)
170
- await prepare()
171
- await dev(name)
172
- await migrations(name)
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 prompts from 'prompts'
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
- const nameAsProject = (project && !project.match(/^(ui|cms)$/))
6
+ async function init(project, name, options = {}) {
7
+ const nameAsProject = (project && !project.match(/^(ui|cms)$/))
8
+ const autoYes = isAutoYes(options)
7
9
 
8
- if (!project || nameAsProject) {
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
- name = nameAsProject ? project : name
27
- project = response.project
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
- const { branch } = await prompts([
31
- {
32
- type: 'select',
33
- name: 'branch',
34
- message: 'Select a git branch',
35
- choices: [
36
- { title: 'main', value: 'main' },
37
- { title: 'dev', value: 'dev' }
38
- ],
39
- onState: (state) => {
40
- if (state.aborted) {
41
- process.exit(1)
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
- if (project === 'ui') {
48
- await ui(name, { branch })
47
+ if (isCancel(branch)) {
48
+ cancel('Operation cancelled.')
49
+ process.exit(1)
50
+ }
49
51
  }
52
+ }
50
53
 
51
- if (project === 'cms') {
52
- const { variant } = await prompts([
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
- await cms(name, { branch, variant })
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 }