@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.
@@ -2,586 +2,652 @@ import FastGlob from 'fast-glob'
2
2
  import { join, relative, extname, basename } from 'path'
3
3
  import fs from 'fs'
4
4
  import fse from 'fs-extra'
5
- import pc from 'picocolors'
6
- import lodash from 'lodash'
7
5
  import dedent from 'dedent'
6
+ import { merge } from 'es-toolkit'
7
+ import * as child_process from 'node:child_process'
8
+ import { styleText } from 'node:util'
8
9
 
9
10
  let sectionsCount = 0
10
11
  let sectionsFactoryCount = 0
11
12
  let templatesCount = 0
12
13
 
13
14
  function generatePhpType(control, controlValue) {
14
- const type = typeof controlValue
15
- let phpType = type
16
-
17
- if (type === 'object' && Array.isArray(controlValue)) {
18
- phpType = 'array'
19
- } else if (type === 'boolean') {
20
- phpType = 'bool'
21
- } else if (type === 'number') {
22
- phpType = 'int'
23
- }
24
- return phpType
15
+ const type = typeof controlValue
16
+ let phpType = type
17
+
18
+ if (type === 'object' && Array.isArray(controlValue)) {
19
+ phpType = 'array'
20
+ }
21
+ else if (type === 'boolean') {
22
+ phpType = 'bool'
23
+ }
24
+ else if (type === 'number') {
25
+ phpType = 'int'
26
+ }
27
+ if (type === 'string') {
28
+ phpType = 'UserText'
29
+ }
30
+ return phpType
25
31
  }
26
32
  function getFieldType(control, controlValue) {
27
- const type = typeof controlValue
28
- if (type === 'number') {
29
- return 'INT'
30
- }
31
-
32
- if (control.startsWith('table')) {
33
- return 'TABLE'
34
- }
35
-
36
- if (control === 'heading' || control === 'title') {
37
- return 'TEXT'
38
- }
39
-
40
- if (control === 'content') {
41
- return 'WSW'
33
+ const type = typeof controlValue
34
+ if (type === 'number') {
35
+ return 'INT'
36
+ }
37
+
38
+ if (control.startsWith('table')) {
39
+ return 'TABLE'
40
+ }
41
+
42
+ if (control === 'heading' || control === 'title') {
43
+ return 'TEXT'
44
+ }
45
+
46
+ if (control === 'content') {
47
+ return 'WSW'
48
+ }
49
+
50
+ if (type === 'string') {
51
+ if (controlValue.startsWith('https://') || controlValue.startsWith('http://') || controlValue.trim().endsWith('.html')) {
52
+ return 'LINK'
42
53
  }
43
-
44
- if (type === 'string') {
45
- if (controlValue.startsWith('https://') || controlValue.startsWith('http://') || controlValue.trim().endsWith('.html')) {
46
- return 'LINK'
47
- }
48
- if (controlValue.trim().startsWith('<') && controlValue.trim().endsWith('/>')) {
49
- return 'WSW'
50
- }
51
- return 'TEXTAREA'
54
+ if (controlValue.trim().startsWith('<') && controlValue.trim().endsWith('/>')) {
55
+ return 'WSW'
52
56
  }
57
+ return 'TEXTAREA'
58
+ }
53
59
 
54
- if (type === 'boolean') {
55
- return 'BOOLEAN'
56
- }
60
+ if (type === 'boolean') {
61
+ return 'BOOLEAN'
62
+ }
57
63
 
58
- if (type === 'object') {
59
- return 'REPEATER'
60
- }
61
- return 'OTHER'
64
+ if (type === 'object') {
65
+ return 'REPEATER'
66
+ }
67
+ return 'OTHER'
62
68
  }
63
69
  function getTitle(control) {
64
- if (control === 'heading') {
65
- return 'Nadpis'
66
- }
70
+ if (control === 'heading') {
71
+ return 'Nadpis'
72
+ }
67
73
 
68
- if (control === 'title') {
69
- return 'Titulek'
70
- }
74
+ if (control === 'title') {
75
+ return 'Titulek'
76
+ }
71
77
 
72
- if (control === 'content') {
73
- return 'Obsah'
74
- }
78
+ if (control === 'content') {
79
+ return 'Obsah'
80
+ }
75
81
 
76
- if (control === 'text') {
77
- return 'Text'
78
- }
82
+ if (control === 'text') {
83
+ return 'Text'
84
+ }
79
85
 
80
- return control[0].toUpperCase() + control.slice(1)
86
+ return control[0].toUpperCase() + control.slice(1)
81
87
  }
82
88
 
83
89
  function buildAttributeInternals(control, controlValue) {
84
- const type = getFieldType(control, controlValue)
85
- const name = getTitle(control)
86
- let subfields = ''
87
- let phpType = generatePhpType(control, controlValue)
88
- if (type === 'REPEATER') {
89
- subfields = ', subfields: [\n'
90
- phpType = 'list<object{'
91
- if (Array.isArray(controlValue)) {
92
- const visited = {}
93
- for (const object in controlValue) {
94
- if (typeof controlValue[object] !== 'object') {
95
- const key = 'SomethingIsWrongHere'
96
- if (visited[key]) {
97
- continue
98
- }
99
- visited[key] = true
100
- const [attribute, type] = buildAttributeInternals('SomethingIsWrongHere', controlValue[object])
101
- subfields += `'SomethingIsWrongHere' => new ${attribute},\n`
102
- phpType += `SomethingIsWrongHere: ${type}, `
103
- continue
104
- }
105
- for (const key in controlValue[object]) {
106
- if (visited[key]) {
107
- continue
108
- }
109
- visited[key] = true
110
- const [attribute, type] = buildAttributeInternals(key, controlValue[object][key])
111
- subfields += `'${key}' => new ${attribute},\n`
112
- phpType += `${key}: ${type}, `
113
- }
114
- }
115
- } else {
116
- for (const key in controlValue) {
117
- const [attribute, type] = buildAttributeInternals(key, controlValue[key])
118
- subfields += `'${key}' => new ${attribute},\n`
119
- phpType += `${key}: ${type}, `
120
- }
90
+ const type = getFieldType(control, controlValue)
91
+ const name = getTitle(control)
92
+ let subfields = ''
93
+ let phpType = generatePhpType(control, controlValue)
94
+ if (type === 'REPEATER') {
95
+ subfields = ', subfields: [\n'
96
+ phpType = 'list<object{'
97
+ if (Array.isArray(controlValue)) {
98
+ const visited = {}
99
+ for (const object in controlValue) {
100
+ if (typeof controlValue[object] !== 'object') {
101
+ const key = 'SomethingIsWrongHere'
102
+ if (visited[key]) {
103
+ continue
104
+ }
105
+ visited[key] = true
106
+ const [attribute, type] = buildAttributeInternals('SomethingIsWrongHere', controlValue[object])
107
+ subfields += `'SomethingIsWrongHere' => new ${attribute},\n`
108
+ phpType += `SomethingIsWrongHere: ${type}, `
109
+ continue
121
110
  }
122
- subfields += ']'
123
- phpType = phpType.slice(0, -2) + '}>'
111
+ for (const key in controlValue[object]) {
112
+ if (visited[key]) {
113
+ continue
114
+ }
115
+ visited[key] = true
116
+ const [attribute, type] = buildAttributeInternals(key, controlValue[object][key])
117
+ subfields += `'${key}' => new ${attribute},\n`
118
+ phpType += `${key}: ${type}, `
119
+ }
120
+ }
121
+ }
122
+ else {
123
+ for (const key in controlValue) {
124
+ const [attribute, type] = buildAttributeInternals(key, controlValue[key])
125
+ subfields += `'${key}' => new ${attribute},\n`
126
+ phpType += `${key}: ${type}, `
127
+ }
124
128
  }
125
- return [`FieldConfig(type: FieldTypeEnum::${type}, name: '${name}'${subfields})`, phpType]
129
+ subfields += ']'
130
+ phpType = phpType.slice(0, -2) + '}>'
131
+ }
132
+ return [`FieldConfig(type: FieldTypeEnum::${type}, name: '${name}'${subfields})`, phpType]
126
133
  }
127
134
  function buildAttribute(control, controlValue) {
128
- const [attribute, type] = buildAttributeInternals(control, controlValue)
129
- if (type.startsWith('list')) {
130
- return `/** @var null|${type} */\n#[${attribute}]`
131
- }
132
- return `#[${attribute}]`
135
+ const [attribute, type] = buildAttributeInternals(control, controlValue)
136
+ if (type.startsWith('list')) {
137
+ return `/** @var null|${type} */\n#[${attribute}]`
138
+ }
139
+ return `#[${attribute}]`
133
140
  }
134
141
 
135
142
  export default function prepare(options) {
136
- return {
137
- async copy(name) {
138
- if (!name || name === 'components') {
139
- if (!fs.existsSync(options.path.src.components)) {
140
- console.log(`${pc.red('✖')} path ${options.path.src.components} doesn't exists`)
141
- process.exit(1)
142
- }
143
-
144
- if (!fs.existsSync(options.path.app.components)) {
145
- console.log(`${pc.red('✖')} path ${options.path.app.components} doesn't exists`)
146
- process.exit(1)
147
- }
148
-
149
- if (!fs.existsSync(options.path.app.componentsFactory)) {
150
- fs.mkdirSync(options.path.app.componentsFactory)
151
- }
152
-
153
- await this.copyComponents(name === 'components')
154
- }
143
+ return {
144
+ async copy(name) {
145
+ if (!name || name === 'components') {
146
+ if (!fs.existsSync(options.path.src.components)) {
147
+ console.log(`${styleText('red', '✖')} path ${options.path.src.components} doesn't exists`)
148
+ process.exit(1)
149
+ }
150
+
151
+ if (!fs.existsSync(options.path.app.components)) {
152
+ console.log(`${styleText('red', '✖')} path ${options.path.app.components} doesn't exists`)
153
+ process.exit(1)
154
+ }
155
155
 
156
- if (name === 'sections') {
157
- if (!fs.existsSync(options.path.src.sections)) {
158
- console.log(`${pc.red('✖')} path ${options.path.src.sections} doesn't exists`)
159
- process.exit(1)
160
- }
156
+ if (!fs.existsSync(options.path.app.componentsFactory)) {
157
+ fs.mkdirSync(options.path.app.componentsFactory)
158
+ }
161
159
 
162
- if (!fs.existsSync(options.path.app.sections)) {
163
- console.log(`${pc.red('✖')} path ${options.path.app.sections} doesn't exists`)
164
- process.exit(1)
165
- }
160
+ await this.copyComponents(name === 'components')
161
+ }
166
162
 
167
- if (!fs.existsSync(options.path.app.sectionsFactory)) {
168
- fs.mkdirSync(options.path.app.sectionsFactory)
169
- }
163
+ if (name === 'sections') {
164
+ if (!fs.existsSync(options.path.src.sections)) {
165
+ console.log(`${styleText('red', '✖')} path ${options.path.src.sections} doesn't exists`)
166
+ process.exit(1)
167
+ }
170
168
 
171
- await this.copySections(name === 'sections')
172
- }
169
+ if (!fs.existsSync(options.path.app.sections)) {
170
+ console.log(`${styleText('red', '✖')} path ${options.path.app.sections} doesn't exists`)
171
+ process.exit(1)
172
+ }
173
173
 
174
- if (!name || name === 'views') {
175
- if (!fs.existsSync(options.path.src.templates)) {
176
- console.log(`${pc.red('✖')} path ${options.path.src.templates} doesn't exists`)
177
- process.exit(1)
178
- }
174
+ if (!fs.existsSync(options.path.app.sectionsFactory)) {
175
+ fs.mkdirSync(options.path.app.sectionsFactory)
176
+ }
179
177
 
180
- if (!fs.existsSync(options.path.app.components)) {
181
- fs.mkdirSync(options.path.app.components)
182
- }
178
+ await this.copySections(name === 'sections')
179
+ }
183
180
 
184
- await this.copyViews(name === 'views')
185
- }
181
+ if (!name || name === 'views') {
182
+ if (!fs.existsSync(options.path.src.templates)) {
183
+ console.log(`${styleText('red', '✖')} path ${options.path.src.templates} doesn't exists`)
184
+ process.exit(1)
185
+ }
186
186
 
187
- if (name === 'templates') {
188
- if (!fs.existsSync(options.path.src.templates)) {
189
- console.log(`${pc.red('✖')} path ${options.path.src.templates} doesn't exists`)
190
- process.exit(1)
191
- }
187
+ if (!fs.existsSync(options.path.app.components)) {
188
+ fs.mkdirSync(options.path.app.components)
189
+ }
190
+
191
+ await this.copyViews(name === 'views')
192
+ }
193
+
194
+ if (name === 'templates') {
195
+ if (!fs.existsSync(options.path.src.templates)) {
196
+ console.log(`${styleText('red', '✖')} path ${options.path.src.templates} doesn't exists`)
197
+ process.exit(1)
198
+ }
192
199
 
193
- if (!fs.existsSync(options.path.app.sections)) {
194
- fs.mkdirSync(options.path.app.sections)
195
- }
200
+ if (!fs.existsSync(options.path.app.sections)) {
201
+ fs.mkdirSync(options.path.app.sections)
202
+ }
196
203
 
197
- await this.copyTemplates(name === 'templates')
204
+ await this.copyTemplates(name === 'templates')
205
+ }
206
+ },
207
+ writeSection(name, path, force) {
208
+ if (!path) {
209
+ path = join(relative(options.path.src.templates, options.path.src.sections), name) + '.latte'
210
+ name = name.replaceAll('/', '')
211
+ }
212
+
213
+ if (!name.match(/(Ui)/)) {
214
+ if (!fs.existsSync(join(options.path.app.sections, `${name}.php`)) || force) {
215
+ const schema = this.getSectionsSchema()
216
+ const controls = schema[path] ?? {}
217
+ let annotations = []
218
+ let objects = []
219
+
220
+ const getField = (control, type) => {
221
+ if (type === 'number') {
222
+ return 'INT'
198
223
  }
199
- },
200
- writeSection(name, path, force) {
201
- if (!path) {
202
- path = join(relative(options.path.src.templates, options.path.src.sections), name) + '.latte'
203
- name = name.replaceAll('/', '')
224
+
225
+ if (control.startsWith('table')) {
226
+ return 'TABLE'
204
227
  }
205
228
 
206
- if (!name.match(/(Ui)/)) {
207
- if (!fs.existsSync(join(options.path.app.sections, `${name}.php`)) || force) {
208
- const schema = this.getSectionsSchema()
209
- const controls = schema[path] ?? {}
210
- let annotations = []
211
- let objects = []
212
-
213
- const getField = (control, type) => {
214
- if (type === 'number') {
215
- return 'INT'
216
- }
217
-
218
- if (control.startsWith('table')) {
219
- return 'TABLE'
220
- }
221
-
222
- if (control === 'heading' || control === 'title') {
223
- return 'TEXT'
224
- }
225
-
226
- if (control === 'content') {
227
- return 'WSW'
228
- }
229
-
230
- if (type === 'string') {
231
- return 'TEXTAREA'
232
- }
233
-
234
- if (type === 'boolean') {
235
- return 'BOOLEAN'
236
- }
237
-
238
- return 'OTHER'
239
- }
240
-
241
- const getTitle = (control) => {
242
- if (control === 'heading') {
243
- return 'Nadpis'
244
- }
245
-
246
- if (control === 'title') {
247
- return 'Titulek'
248
- }
249
-
250
- if (control === 'content') {
251
- return 'Obsah'
252
- }
253
-
254
- if (control === 'text') {
255
- return 'Text'
256
- }
257
-
258
- return control[0].toUpperCase() + control.slice(1)
259
- }
260
-
261
- Object.keys(controls).forEach(control => {
262
- if (control !== 'src') {
263
- const type = typeof controls[control]
264
- const value = typeof controls[control] === 'object' ? JSON.stringify(controls[control]) : controls[control]
265
-
266
- let phpType = type
267
-
268
- if (type === 'object' && Array.isArray(controls[control])) {
269
- phpType = 'array'
270
- } else if (type === 'boolean') {
271
- phpType = 'bool'
272
- } else if (type === 'number') {
273
- phpType = 'int'
274
- }
275
-
276
- annotations.push(`
277
- #[FieldType(FieldType::${getField(control, type)})]
278
- #[FieldName('${getTitle(control)}')]
279
- public ?${phpType} $${control}${type === 'string' ? " = '" + value + "'" : ' = null'};
280
- `)
281
-
282
- if (type === 'object') {
283
- objects.push(`
284
- $this->${control} = json_decode('${value}', false);
285
- `)
286
- }
287
- }
288
- })
289
-
290
- if (annotations.length > 0) {
291
- annotations = annotations.map(item => item).join('')
292
- } else {
293
- annotations = ''
294
- }
295
-
296
- if (objects.length > 0) {
297
- objects = objects.map(item => item).join('')
298
- } else {
299
- objects = ''
300
- }
301
-
302
- fs.writeFileSync(join(options.path.app.sections, `${name}.php`),
303
- dedent`
304
- <?php
305
-
306
- declare(strict_types=1);
307
-
308
- namespace App\Sections;
309
-
310
- use App\Sections\Attributes\FieldName;
311
- use App\Sections\Attributes\FieldType;
312
- use App\Sections\Attributes\SectionName;
313
-
314
- #[SectionName('${name}')]
315
- class ${name} extends BaseSection
316
- {
317
- ${annotations}
318
- public function render() : void
319
- {
320
- ${objects}
321
- $this->template->render(TEMPLATES_DIR . '/${path}');
322
- }
323
- }
324
- `
325
- )
326
-
327
- sectionsCount += 1
328
- }
229
+ if (control === 'heading' || control === 'title') {
230
+ return 'TEXT'
329
231
  }
330
- },
331
- writeSectionFactory(name, force) {
332
- if (!name.match(/(Ui)/)) {
333
- if (!fs.existsSync(join(options.path.app.sectionsFactory, `${name}Factory.php`)) || force) {
334
- fs.writeFileSync(join(options.path.app.sectionsFactory, `${name}Factory.php`),
335
- dedent`
336
- <?php
337
-
338
- declare(strict_types=1);
339
-
340
- namespace App\Sections\Factory;
341
-
342
- use ${'App\\Sections\\'}${name};
343
-
344
- interface ${name}Factory
345
- {
346
- public function create(): ${name};
347
- }
348
- `
349
- )
350
-
351
- sectionsFactoryCount += 1
352
- }
232
+
233
+ if (control === 'content') {
234
+ return 'WSW'
353
235
  }
354
- },
355
- writeComponent(name, path, force) {
356
- if (!path) {
357
- path = name + '.latte'
358
- name = name.replaceAll('/', '')
236
+
237
+ if (type === 'string') {
238
+ return 'TEXTAREA'
359
239
  }
360
240
 
361
- if (!name.match(/(Ui)/)) {
362
- if (!fs.existsSync(join(options.path.app.components, `${name}.php`)) || force) {
363
- const schema = this.getComponentsSchema()
364
- const controls = schema[path] ?? {}
365
- let annotations = []
366
- let objects = []
367
-
368
- Object.keys(controls).forEach(control => {
369
- if (control !== 'src') {
370
- const type = typeof controls[control]
371
- const value = typeof controls[control] === 'object' ? JSON.stringify(controls[control]) : controls[control]
372
-
373
- const phpType = generatePhpType(control, controls[control])
374
-
375
- annotations.push(`
376
- ${buildAttribute(control, controls[control])}
377
- public ?${phpType} $${control} = null;
378
- `)
379
-
380
- if (type === 'object') {
381
- objects.push(`
382
- $this->${control} = json_decode('${value}', false);
383
- `)
384
- }
385
- }
386
- })
387
-
388
- if (annotations.length > 0) {
389
- annotations = annotations.map(item => item).join('')
390
- } else {
391
- annotations = ''
392
- }
393
-
394
- if (objects.length > 0) {
395
- objects = objects.map(item => item).join('')
396
- } else {
397
- objects = ''
398
- }
399
-
400
- fs.writeFileSync(join(options.path.app.components, `${name}.php`),
401
- dedent`
402
- <?php
403
-
404
- declare(strict_types=1);
405
-
406
- namespace App\Components;
407
-
408
- use App\Components\Attributes\ComponentName;
409
- use App\Components\Attributes\FieldTypeEnum;
410
- use App\Components\Attributes\FieldConfig;
411
-
412
- #[ComponentName('${name}')]
413
- class ${name} extends BaseComponent
414
- {
415
- ${annotations}
416
- public function render() : void
417
- {
418
- ${objects}
419
- $this->template->render(TEMPLATES_DIR . '/${path}');
420
- }
421
- }
422
- `
423
- )
424
-
425
- sectionsCount += 1
426
- }
241
+ if (type === 'boolean') {
242
+ return 'BOOLEAN'
427
243
  }
428
- },
429
- writeComponentFactory(name, force) {
430
- if (!name.match(/(Ui)/)) {
431
- if (!fs.existsSync(join(options.path.app.componentsFactory, `${name}Factory.php`)) || force) {
432
- fs.writeFileSync(join(options.path.app.componentsFactory, `${name}Factory.php`),
433
- dedent`
434
- <?php
435
-
436
- declare(strict_types=1);
437
-
438
- namespace App\Components\Factory;
439
-
440
- use ${'App\\Components\\'}${name};
441
-
442
- interface ${name}Factory
443
- {
444
- public function create(): ${name};
445
- }
446
- `
447
- )
448
-
449
- sectionsFactoryCount += 1
450
- }
244
+
245
+ return 'OTHER'
246
+ }
247
+
248
+ const getTitle = (control) => {
249
+ if (control === 'heading') {
250
+ return 'Nadpis'
451
251
  }
452
- },
453
- async copyComponents(force = false) {
454
- const components = FastGlob.sync(join(options.path.src.components, '/**'))
455
-
456
- components.forEach(path => {
457
- const name = basename(relative(options.path.src.components, path), extname(relative(options.path.src.components, path)))
458
-
459
- this.writeComponent(name, relative(options.path.src.templates, path), force)
460
- this.writeComponentFactory(name, force)
461
- })
462
-
463
- console.log(`${pc.green('✔')} ${sectionsCount} components and ${sectionsFactoryCount} factory files copied to app`)
464
- },
465
- async copySections(force = false) {
466
- const sections = FastGlob.sync(join(options.path.src.sections, '/**'))
467
-
468
- sections.forEach(path => {
469
- const name = relative(options.path.src.sections, path).replace(extname(relative(options.path.src.sections, path)), '').replaceAll('/', '')
470
-
471
- this.writeSection(name, relative(options.path.src.templates, path), force)
472
- this.writeSectionFactory(name, force)
473
- })
474
-
475
- console.log(`${pc.green('✔')} ${sectionsCount} sections and ${sectionsFactoryCount} factory files copied to app`)
476
- },
477
- async copyViews(force = false) {
478
- const templates = FastGlob.sync(join(options.path.src.templates, '/**')).filter(entry => {
479
- return !fs.existsSync(join(options.path.src.views, relative(options.path.src.templates, entry))) || force
480
- })
481
-
482
- for (const path of templates) {
483
- const copyAppend = async function(source) {
484
- const dest = join(options.path.src.views, relative(options.path.src.templates, path))
485
- fse.copy(source, dest).then(
486
- () => {
487
- // Prepend data
488
- const data = fs.readFileSync(dest)
489
- const fd = fs.openSync(dest, 'w+')
490
- const base = basename(dest, '.latte')
491
- const componentNameFinal = base.charAt(0).toUpperCase() + base.slice(1)
492
- const insert = Buffer.from(`{varType App\\Components\\${componentNameFinal} $control}\n`)
493
- fs.writeSync(fd, insert, 0, insert.length, 0)
494
- fs.writeSync(fd, data, 0, data.length, insert.length)
495
- fs.close(fd, (err) => {
496
- if (err) throw err
497
- })
498
- }
499
- )
500
- }
501
-
502
- await copyAppend(path)
503
- templatesCount += 1
252
+
253
+ if (control === 'title') {
254
+ return 'Titulek'
504
255
  }
505
256
 
506
- console.log(`${pc.green('')} ${templatesCount} views files copied from templates`)
507
- },
508
- async copyTemplates(force = false) {
509
- const templates = FastGlob.sync(join(options.path.src.templates, '/**')).filter(entry => {
510
- return !fs.existsSync(join(options.path.app.templates, relative(options.path.src.templates, entry))) || force
511
- })
257
+ if (control === 'content') {
258
+ return 'Obsah'
259
+ }
512
260
 
513
- for (const path of templates) {
514
- await fse.copy(path, join(options.path.app.templates, relative(options.path.src.templates, path)))
515
- templatesCount += 1
261
+ if (control === 'text') {
262
+ return 'Text'
516
263
  }
517
264
 
518
- console.log(`${pc.green('✔')} ${templatesCount} template files copied to app`)
519
- },
520
- getSectionsSchema() {
521
- const views = FastGlob.sync(join(options.path.src.views, '/**/*.json'))
522
- const sectionsUnsorted = {}
523
- const sectionsSchema = {}
524
-
525
- views.forEach(path => {
526
- const json = JSON.parse(fs.readFileSync(path).toString())
527
- const sections = [...(json.page.body ?? []), ...(json.page.head ?? []), ...(json.page.foot ?? [])]
528
-
529
- sections.forEach((section) => {
530
- if (section.src) {
531
- if (typeof sectionsUnsorted[section.src] === 'undefined') {
532
- sectionsUnsorted[section.src] = []
533
- sectionsUnsorted[section.src].push(section)
534
- } else {
535
- sectionsUnsorted[section.src].push(section)
536
- }
537
- }
538
- })
539
- })
540
-
541
- Object.keys(sectionsUnsorted).forEach(key => {
542
- const sortedSection = {}
543
-
544
- sectionsUnsorted[key].forEach(section => {
545
- lodash.merge(sortedSection, section)
546
- })
547
-
548
- sectionsSchema[sortedSection.src] = sortedSection
549
- })
550
-
551
- return sectionsSchema
552
- },
553
- getComponentsSchema() {
554
- const views = FastGlob.sync(join(options.path.src.pages, '/**/*.json'))
555
- const sectionsUnsorted = {}
556
- const sectionsSchema = {}
557
-
558
- views.forEach(path => {
559
- const json = JSON.parse(fs.readFileSync(path).toString())
560
- const sections = [...(json.body ?? []), ...(json.head ?? []), ...(json.foot ?? [])]
561
-
562
- sections.forEach((section) => {
563
- if (section.src) {
564
- if (typeof sectionsUnsorted[section.src] === 'undefined') {
565
- sectionsUnsorted[section.src] = []
566
- sectionsUnsorted[section.src].push(section)
567
- } else {
568
- sectionsUnsorted[section.src].push(section)
569
- }
570
- }
571
- })
572
- })
573
-
574
- Object.keys(sectionsUnsorted).forEach(key => {
575
- const sortedSection = {}
576
-
577
- sectionsUnsorted[key].forEach(section => {
578
- lodash.merge(sortedSection, section)
579
- })
580
-
581
- sectionsSchema[sortedSection.src] = sortedSection
582
- })
583
-
584
- return sectionsSchema
265
+ return control[0].toUpperCase() + control.slice(1)
266
+ }
267
+
268
+ Object.keys(controls).forEach((control) => {
269
+ if (control !== 'src') {
270
+ const type = typeof controls[control]
271
+ const value = typeof controls[control] === 'object' ? JSON.stringify(controls[control]) : controls[control]
272
+
273
+ let phpType = type
274
+
275
+ if (type === 'object' && Array.isArray(controls[control])) {
276
+ phpType = 'array'
277
+ }
278
+ else if (type === 'boolean') {
279
+ phpType = 'bool'
280
+ }
281
+ else if (type === 'number') {
282
+ phpType = 'int'
283
+ }
284
+
285
+ annotations.push(`
286
+ #[FieldType(FieldType::${getField(control, type)})]
287
+ #[FieldName('${getTitle(control)}')]
288
+ public ?${phpType} $${control}${type === 'string' ? ' = \'' + value + '\'' : ' = null'};
289
+ `)
290
+
291
+ if (type === 'object') {
292
+ objects.push(`
293
+ $this->${control} = json_decode('${value}', false);
294
+ `)
295
+ }
296
+ }
297
+ })
298
+
299
+ if (annotations.length > 0) {
300
+ annotations = annotations.map(item => item).join('')
301
+ }
302
+ else {
303
+ annotations = ''
304
+ }
305
+
306
+ if (objects.length > 0) {
307
+ objects = objects.map(item => item).join('')
308
+ }
309
+ else {
310
+ objects = ''
311
+ }
312
+
313
+ fs.writeFileSync(join(options.path.app.sections, `${name}.php`),
314
+ dedent`
315
+ <?php
316
+
317
+ declare(strict_types=1);
318
+
319
+ namespace App\Sections;
320
+
321
+ use App\Sections\Attributes\FieldName;
322
+ use App\Sections\Attributes\FieldType;
323
+ use App\Sections\Attributes\SectionName;
324
+
325
+ #[SectionName('${name}')]
326
+ class ${name} extends BaseSection
327
+ {
328
+ ${annotations}
329
+ public function render() : void
330
+ {
331
+ ${objects}
332
+ $this->template->render(TEMPLATES_DIR . '/${path}');
333
+ }
334
+ }
335
+ `,
336
+ )
337
+
338
+ sectionsCount += 1
585
339
  }
586
- }
340
+ }
341
+ },
342
+ writeSectionFactory(name, force) {
343
+ if (!name.match(/(Ui)/)) {
344
+ if (!fs.existsSync(join(options.path.app.sectionsFactory, `${name}Factory.php`)) || force) {
345
+ fs.writeFileSync(join(options.path.app.sectionsFactory, `${name}Factory.php`),
346
+ dedent`
347
+ <?php
348
+
349
+ declare(strict_types=1);
350
+
351
+ namespace App\Sections\Factory;
352
+
353
+ use ${'App\\Sections\\'}${name};
354
+
355
+ interface ${name}Factory
356
+ {
357
+ public function create(): ${name};
358
+ }
359
+ `,
360
+ )
361
+
362
+ sectionsFactoryCount += 1
363
+ }
364
+ }
365
+ },
366
+ writeComponent(name, path, force, imageSubstitutions) {
367
+ if (!path) {
368
+ path = name + '.latte'
369
+ name = name.replaceAll('/', '')
370
+ }
371
+
372
+ if (!name.match(/(Ui)/)) {
373
+ if (!fs.existsSync(join(options.path.app.components, `${name}.php`)) || force) {
374
+ const schema = this.getComponentsSchema()
375
+ const controls = schema[path] ?? {}
376
+ let annotations = []
377
+ let objects = []
378
+
379
+ Object.keys(controls).forEach((control) => {
380
+ if (control !== 'src') {
381
+ const type = typeof controls[control]
382
+ const value = typeof controls[control] === 'object' ? JSON.stringify(controls[control]) : controls[control]
383
+
384
+ const phpType = generatePhpType(control, controls[control])
385
+
386
+ annotations.push(`
387
+ ${buildAttribute(control, controls[control])}
388
+ public ?${phpType} $${control} = null;
389
+ `)
390
+
391
+ if (type === 'object') {
392
+ objects.push(`
393
+ $this->${control} = json_decode('${value}', false);
394
+ `)
395
+ }
396
+ }
397
+ })
398
+ for (const [value, x, y] of (imageSubstitutions ?? [])) {
399
+ annotations.push(`
400
+ #[FieldConfig(type: FieldTypeEnum::FILE, name: 'PLACEHOLDER ${value}', help: '${x} x ${y}')]
401
+ public ?File $${value} = null;
402
+ `)
403
+ }
404
+
405
+ if (annotations.length > 0) {
406
+ annotations = annotations.map(item => item).join('')
407
+ }
408
+ else {
409
+ annotations = ''
410
+ }
411
+
412
+ if (objects.length > 0) {
413
+ objects = objects.map(item => item).join('')
414
+ }
415
+ else {
416
+ objects = ''
417
+ }
418
+
419
+ fs.writeFileSync(join(options.path.app.components, `${name}.php`),
420
+ dedent`
421
+ <?php
422
+
423
+ declare(strict_types=1);
424
+
425
+ namespace App\Components;
426
+
427
+ use App\Components\Attributes\Component\Category;
428
+ use App\Components\Attributes\Component\Focus;
429
+ use App\Components\Attributes\Component\Component;
430
+ use App\Components\Attributes\FieldTypeEnum;
431
+ use App\Components\Attributes\FieldConfig;
432
+ use App\Model\File\File;
433
+ use NewlogicDigital\Cms\Core\Model\UserText;
434
+
435
+ #[Component(name: '${name}', category: Category::Text, focuses: [Focus::Page], cache: true)]
436
+ class ${name} extends BaseComponent
437
+ {
438
+ ${annotations}
439
+ public function render() : void
440
+ {
441
+ ${objects}
442
+ $this->template->render(TEMPLATES_DIR . '/${path}');
443
+ }
444
+ }
445
+ `,
446
+ )
447
+
448
+ sectionsCount += 1
449
+ }
450
+ }
451
+ },
452
+ writeComponentFactory(name, force) {
453
+ if (!name.match(/(Ui)/)) {
454
+ if (!fs.existsSync(join(options.path.app.componentsFactory, `${name}Factory.php`)) || force) {
455
+ fs.writeFileSync(join(options.path.app.componentsFactory, `${name}Factory.php`),
456
+ dedent`
457
+ <?php
458
+
459
+ declare(strict_types=1);
460
+
461
+ namespace App\Components\Factory;
462
+
463
+ use ${'App\\Components\\'}${name};
464
+
465
+ interface ${name}Factory
466
+ {
467
+ public function create(): ${name};
468
+ }
469
+ `,
470
+ )
471
+
472
+ sectionsFactoryCount += 1
473
+ }
474
+ }
475
+ },
476
+ async copyComponents(force = false, buildFactories = false) {
477
+ const components = FastGlob.sync(join(options.path.src.components, '/**'))
478
+
479
+ components.forEach((path) => {
480
+ const name = basename(relative(options.path.src.components, path), extname(relative(options.path.src.components, path)))
481
+ if (name.startsWith('CookieConsent')) {
482
+ return
483
+ }
484
+ const check = child_process.spawn('grep', ['-ri', '{[(include)|(import)][^}]*' + name + '.latte', options.path.src.templates])
485
+ check.stderr.on('data', () => '')
486
+ var buffer = ''
487
+ check.stdout.on('data', function (a) {
488
+ buffer += a
489
+ })
490
+ const ref = this
491
+ check.on('exit', function (code) {
492
+ if (code !== 0) {
493
+ // console.log('Creating:' +name+ ' - buffer: '+ buffer);
494
+ let res = []
495
+ let counter = 0
496
+ let a
497
+ for (a of fs.readFileSync(path).toString().matchAll(/=\(*placeholder\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*\)+/ig)) {
498
+ res.push(['image' + counter, a[1], a[2]])
499
+ counter += 1
500
+ }
501
+
502
+ ref.writeComponent(name, relative(options.path.src.templates, path), force, res)
503
+ if (buildFactories) {
504
+ ref.writeComponentFactory(name, force)
505
+ }
506
+ }
507
+ else {
508
+ console.info('Skiping `' + name + '` grep -ri found ' + buffer)
509
+ }
510
+ })
511
+ })
512
+
513
+ console.log(`${styleText('green', '✔')} ${sectionsCount} components and ${sectionsFactoryCount} factory files copied to app`)
514
+ },
515
+ async copySections(force = false) {
516
+ const sections = FastGlob.sync(join(options.path.src.sections, '/**'))
517
+
518
+ sections.forEach((path) => {
519
+ const name = relative(options.path.src.sections, path).replace(extname(relative(options.path.src.sections, path)), '').replaceAll('/', '')
520
+
521
+ this.writeSection(name, relative(options.path.src.templates, path), force)
522
+ this.writeSectionFactory(name, force)
523
+ })
524
+
525
+ console.log(`${styleText('green', '✔')} ${sectionsCount} sections and ${sectionsFactoryCount} factory files copied to app`)
526
+ },
527
+ async copyViews(force = false) {
528
+ const templates = FastGlob.sync(join(options.path.src.templates, '/**')).filter((entry) => {
529
+ return !fs.existsSync(join(options.path.src.views, relative(options.path.src.templates, entry))) || force
530
+ })
531
+
532
+ for (const path of templates) {
533
+ const copyAppend = async function (source) {
534
+ const dest = join(options.path.src.views, relative(options.path.src.templates, path))
535
+ fse.copy(source, dest).then(
536
+ () => {
537
+ // Prepend data
538
+ let data = fs.readFileSync(dest).toString()
539
+ const fd = fs.openSync(dest, 'w+')
540
+ const base = basename(dest, '.latte')
541
+ const componentNameFinal = base.charAt(0).toUpperCase() + base.slice(1)
542
+ let insert = Buffer.from(`{varType App\\Components\\${componentNameFinal} $control}\n`)
543
+ let full, a, b
544
+ let res
545
+ let count = 0
546
+
547
+ res = data.match(/=\(*placeholder\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*\)+/i)
548
+
549
+ while (res) {
550
+ [full, a, b] = res
551
+ data = data.replace(full, `$control->image${count}|thumb:${a}, ${b},true`)
552
+ count += 1
553
+ res = data.match(/=\(*placeholder\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*\)+/i)
554
+ }
555
+
556
+ data = insert.toString() + data
557
+
558
+ fs.writeSync(fd, data)
559
+ fs.close(fd, (err) => {
560
+ if (err) throw err
561
+ })
562
+ },
563
+ )
564
+ }
565
+
566
+ await copyAppend(path)
567
+ templatesCount += 1
568
+ }
569
+
570
+ console.log(`${styleText('green', '✔')} ${templatesCount} views files copied from templates`)
571
+ },
572
+ async copyTemplates(force = false) {
573
+ const templates = FastGlob.sync(join(options.path.src.templates, '/**')).filter((entry) => {
574
+ return !fs.existsSync(join(options.path.app.templates, relative(options.path.src.templates, entry))) || force
575
+ })
576
+
577
+ for (const path of templates) {
578
+ await fse.copy(path, join(options.path.app.templates, relative(options.path.src.templates, path)))
579
+ templatesCount += 1
580
+ }
581
+
582
+ console.log(`${styleText('green', '✔')} ${templatesCount} template files copied to app`)
583
+ },
584
+ getSectionsSchema() {
585
+ const views = FastGlob.sync(join(options.path.src.views, '/**/*.json'))
586
+ const sectionsUnsorted = {}
587
+ const sectionsSchema = {}
588
+
589
+ views.forEach((path) => {
590
+ const json = JSON.parse(fs.readFileSync(path).toString())
591
+ const sections = [...(json.page.body ?? []), ...(json.page.head ?? []), ...(json.page.foot ?? [])]
592
+
593
+ sections.forEach((section) => {
594
+ if (section.src) {
595
+ if (typeof sectionsUnsorted[section.src] === 'undefined') {
596
+ sectionsUnsorted[section.src] = []
597
+ sectionsUnsorted[section.src].push(section)
598
+ }
599
+ else {
600
+ sectionsUnsorted[section.src].push(section)
601
+ }
602
+ }
603
+ })
604
+ })
605
+
606
+ Object.keys(sectionsUnsorted).forEach((key) => {
607
+ const sortedSection = {}
608
+
609
+ sectionsUnsorted[key].forEach((section) => {
610
+ merge(sortedSection, section)
611
+ })
612
+
613
+ sectionsSchema[sortedSection.src] = sortedSection
614
+ })
615
+
616
+ return sectionsSchema
617
+ },
618
+ getComponentsSchema() {
619
+ const views = FastGlob.sync(join(options.path.src.pages, '/**/*.json'))
620
+ const sectionsUnsorted = {}
621
+ const sectionsSchema = {}
622
+
623
+ views.forEach((path) => {
624
+ const json = JSON.parse(fs.readFileSync(path).toString())
625
+ const sections = [...(json.body ?? []), ...(json.head ?? []), ...(json.foot ?? [])]
626
+
627
+ sections.forEach((section) => {
628
+ if (section.src) {
629
+ if (typeof sectionsUnsorted[section.src] === 'undefined') {
630
+ sectionsUnsorted[section.src] = []
631
+ sectionsUnsorted[section.src].push(section)
632
+ }
633
+ else {
634
+ sectionsUnsorted[section.src].push(section)
635
+ }
636
+ }
637
+ })
638
+ })
639
+
640
+ Object.keys(sectionsUnsorted).forEach((key) => {
641
+ const sortedSection = {}
642
+
643
+ sectionsUnsorted[key].forEach((section) => {
644
+ merge(sortedSection, section)
645
+ })
646
+
647
+ sectionsSchema[sortedSection.src] = sortedSection
648
+ })
649
+
650
+ return sectionsSchema
651
+ },
652
+ }
587
653
  }