@newlogic-digital/cli 0.1.0 → 1.0.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 CHANGED
@@ -23,10 +23,13 @@ if (!command) {
23
23
  ${pc.green('newlogic init cms')} ${pc.yellow('<directory>')} - Creates a new ${pc.blue('@newlogic-digital/cms')} project in new directory with the name ${pc.yellow('<directory>')}
24
24
 
25
25
  -- cms --
26
- ${pc.green('newlogic cms prepare')} - Copies templates and sections from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')}
27
- ${pc.green('newlogic cms prepare templates')} - Copies templates from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')} even if they already exists
28
- ${pc.green('newlogic cms prepare sections')} - Copies sections from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')} even if they already exists
29
- ${pc.green('newlogic cms new-section')} ${pc.yellow('<name>')} - Creates a new ${pc.blue('@newlogic-digital/cms')} section with name ${pc.yellow('<name>')}
26
+ ${pc.green('newlogic cms prepare')} - Copies templates and components from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')}
27
+ ${pc.green('newlogic cms prepare views')} - Copies views from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')} even if they already exists
28
+ ${pc.green('newlogic cms prepare components')} - Copies components from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')} even if they already exists
29
+ ${pc.green('newlogic cms new-component')} ${pc.yellow('<name>')} - Creates a new ${pc.blue('@newlogic-digital/cms')} section with name ${pc.yellow('<name>')}
30
+ ${pc.red('newlogic cms prepare templates')} - (deprecated) Copies templates from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')} even if they already exists
31
+ ${pc.red('newlogic cms prepare sections')} - (deprecated) Copies sections from ${pc.blue('@newlogic-digital/ui')} project to ${pc.blue('@newlogic-digital/cms')} even if they already exists
32
+ ${pc.red('newlogic cms new-section')} ${pc.yellow('<name>')} - (deprecated) Creates a new ${pc.blue('@newlogic-digital/cms')} section with name ${pc.yellow('<name>')}
30
33
  `)
31
34
  }
32
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newlogic-digital/cli",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "main": "index.mjs",
5
5
  "bin": {
6
6
  "newlogic-cli": "index.mjs",
@@ -12,14 +12,13 @@
12
12
  "dependencies": {
13
13
  "lodash": "^4.17.21",
14
14
  "prompts": "^2.4.2",
15
- "fs-extra": "^11.1.1",
16
- "picocolors": "^1.0.0",
17
- "fast-glob": "^3.2.12",
18
- "dedent": "^0.7.0"
15
+ "fs-extra": "^11.2.0",
16
+ "picocolors": "^1.1.1",
17
+ "fast-glob": "^3.3.2",
18
+ "dedent": "^1.5.3"
19
19
  },
20
20
  "devDependencies": {
21
- "eslint": "^8.39.0",
22
- "eslint-config-standard": "^17.0.0"
21
+ "eslint-config-standard": "^17.1.0"
23
22
  },
24
23
  "files": [
25
24
  "index.js",
@@ -8,11 +8,15 @@ export const options = {
8
8
  path: {
9
9
  src: {
10
10
  templates: resolve(process.cwd(), 'src/templates'),
11
+ components: resolve(process.cwd(), 'src/templates/components'),
11
12
  sections: resolve(process.cwd(), 'src/templates/Sections'),
12
- views: resolve(process.cwd(), 'src/views')
13
+ views: resolve(process.cwd(), 'src/views'),
14
+ pages: resolve(process.cwd(), 'src/pages')
13
15
  },
14
16
  app: {
15
17
  templates: resolve(process.cwd(), 'app/Templates'),
18
+ components: resolve(process.cwd(), 'app/Components'),
19
+ componentsFactory: resolve(process.cwd(), 'app/Components/Factory'),
16
20
  sections: resolve(process.cwd(), 'app/Sections'),
17
21
  sectionsFactory: resolve(process.cwd(), 'app/Sections/Factory'),
18
22
  tempCache: resolve(process.cwd(), 'temp/cache')
@@ -25,6 +29,24 @@ export default async function cms(action, name) {
25
29
  await prepare(options).copy(name)
26
30
  }
27
31
 
32
+ if (action === 'new-component') {
33
+ const nameFormatted = name.replaceAll('/', '')
34
+
35
+ if (fs.existsSync(join(options.path.app.components, `${nameFormatted}.php`))) {
36
+ console.log(`${pc.red('✖')} section ${nameFormatted} already exists`)
37
+ process.exit(1)
38
+ }
39
+
40
+ prepare(options).writeComponent(name)
41
+ prepare(options).writeComponentFactory(nameFormatted)
42
+
43
+ if (fs.existsSync(options.path.app.tempCache)) {
44
+ fse.removeSync(options.path.app.tempCache)
45
+ }
46
+
47
+ console.log(`${pc.green('✔')} component ${nameFormatted} created`)
48
+ }
49
+
28
50
  if (action === 'new-section') {
29
51
  const nameFormatted = name.replaceAll('/', '')
30
52
 
@@ -1,5 +1,5 @@
1
1
  import FastGlob from 'fast-glob'
2
- import { join, relative, extname } from 'path'
2
+ import { join, relative, extname, basename } from 'path'
3
3
  import fs from 'fs'
4
4
  import fse from 'fs-extra'
5
5
  import pc from 'picocolors'
@@ -13,7 +13,25 @@ let templatesCount = 0
13
13
  export default function prepare(options) {
14
14
  return {
15
15
  async copy(name) {
16
- if (!name || name === 'sections') {
16
+ if (!name || name === 'components') {
17
+ if (!fs.existsSync(options.path.src.components)) {
18
+ console.log(`${pc.red('✖')} path ${options.path.src.components} doesn't exists`)
19
+ process.exit(1)
20
+ }
21
+
22
+ if (!fs.existsSync(options.path.app.components)) {
23
+ console.log(`${pc.red('✖')} path ${options.path.app.components} doesn't exists`)
24
+ process.exit(1)
25
+ }
26
+
27
+ if (!fs.existsSync(options.path.app.componentsFactory)) {
28
+ fs.mkdirSync(options.path.app.componentsFactory)
29
+ }
30
+
31
+ await this.copyComponents(name === 'components')
32
+ }
33
+
34
+ if (name === 'sections') {
17
35
  if (!fs.existsSync(options.path.src.sections)) {
18
36
  console.log(`${pc.red('✖')} path ${options.path.src.sections} doesn't exists`)
19
37
  process.exit(1)
@@ -31,7 +49,20 @@ export default function prepare(options) {
31
49
  await this.copySections(name === 'sections')
32
50
  }
33
51
 
34
- if (!name || name === 'templates') {
52
+ if (!name || name === 'views') {
53
+ if (!fs.existsSync(options.path.src.templates)) {
54
+ console.log(`${pc.red('✖')} path ${options.path.src.templates} doesn't exists`)
55
+ process.exit(1)
56
+ }
57
+
58
+ if (!fs.existsSync(options.path.app.components)) {
59
+ fs.mkdirSync(options.path.app.components)
60
+ }
61
+
62
+ await this.copyViews(name === 'views')
63
+ }
64
+
65
+ if (name === 'templates') {
35
66
  if (!fs.existsSync(options.path.src.templates)) {
36
67
  console.log(`${pc.red('✖')} path ${options.path.src.templates} doesn't exists`)
37
68
  process.exit(1)
@@ -59,30 +90,30 @@ export default function prepare(options) {
59
90
 
60
91
  const getField = (control, type) => {
61
92
  if (type === 'number') {
62
- return 'int'
93
+ return 'INT'
63
94
  }
64
95
 
65
96
  if (control.startsWith('table')) {
66
- return 'table'
97
+ return 'TABLE'
67
98
  }
68
99
 
69
100
  if (control === 'heading' || control === 'title') {
70
- return 'text'
101
+ return 'TEXT'
71
102
  }
72
103
 
73
104
  if (control === 'content') {
74
- return 'wsw'
105
+ return 'WSW'
75
106
  }
76
107
 
77
108
  if (type === 'string') {
78
- return 'textarea'
109
+ return 'TEXTAREA'
79
110
  }
80
111
 
81
112
  if (type === 'boolean') {
82
- return type
113
+ return 'BOOLEAN'
83
114
  }
84
115
 
85
- return type
116
+ return 'OTHER'
86
117
  }
87
118
 
88
119
  const getTitle = (control) => {
@@ -121,12 +152,9 @@ export default function prepare(options) {
121
152
  }
122
153
 
123
154
  annotations.push(`
124
- /**
125
- * @field ${getField(control, type)}
126
- * @title ${getTitle(control)}
127
- * @value ${value}
128
- */
129
- public ${phpType} $${control}${type === 'string' ? " = '" + value + "'" : ''};
155
+ #[FieldType(FieldType::${getField(control, type)})]
156
+ #[FieldName('${getTitle(control)}')]
157
+ public ?${phpType} $${control}${type === 'string' ? " = '" + value + "'" : ' = null'};
130
158
  `)
131
159
 
132
160
  if (type === 'object') {
@@ -152,16 +180,23 @@ export default function prepare(options) {
152
180
  fs.writeFileSync(join(options.path.app.sections, `${name}.php`),
153
181
  dedent`
154
182
  <?php
155
-
183
+
184
+ declare(strict_types=1);
185
+
156
186
  namespace App\Sections;
157
187
 
188
+ use App\Sections\Attributes\FieldName;
189
+ use App\Sections\Attributes\FieldType;
190
+ use App\Sections\Attributes\SectionName;
191
+
192
+ #[SectionName('${name}')]
158
193
  class ${name} extends BaseSection
159
194
  {
160
195
  ${annotations}
161
196
  public function render() : void
162
197
  {
163
198
  ${objects}
164
- $this->getTemplate()->render(TEMPLATES_DIR . '/${path}');
199
+ $this->template->render(TEMPLATES_DIR . '/${path}');
165
200
  }
166
201
  }
167
202
  `
@@ -177,6 +212,8 @@ export default function prepare(options) {
177
212
  fs.writeFileSync(join(options.path.app.sectionsFactory, `${name}Factory.php`),
178
213
  dedent`
179
214
  <?php
215
+
216
+ declare(strict_types=1);
180
217
 
181
218
  namespace App\Sections\Factory;
182
219
 
@@ -193,6 +230,173 @@ export default function prepare(options) {
193
230
  }
194
231
  }
195
232
  },
233
+ writeComponent(name, path, force) {
234
+ if (!path) {
235
+ path = name + '.latte'
236
+ name = name.replaceAll('/', '')
237
+ }
238
+
239
+ if (!name.match(/(Ui)/)) {
240
+ if (!fs.existsSync(join(options.path.app.components, `${name}.php`)) || force) {
241
+ const schema = this.getComponentsSchema()
242
+ const controls = schema[path] ?? {}
243
+ let annotations = []
244
+ let objects = []
245
+
246
+ const getField = (control, type) => {
247
+ if (type === 'number') {
248
+ return 'INT'
249
+ }
250
+
251
+ if (control.startsWith('table')) {
252
+ return 'TABLE'
253
+ }
254
+
255
+ if (control === 'heading' || control === 'title') {
256
+ return 'TEXT'
257
+ }
258
+
259
+ if (control === 'content') {
260
+ return 'WSW'
261
+ }
262
+
263
+ if (type === 'string') {
264
+ return 'TEXTAREA'
265
+ }
266
+
267
+ if (type === 'boolean') {
268
+ return 'BOOLEAN'
269
+ }
270
+
271
+ return 'OTHER'
272
+ }
273
+
274
+ const getTitle = (control) => {
275
+ if (control === 'heading') {
276
+ return 'Nadpis'
277
+ }
278
+
279
+ if (control === 'title') {
280
+ return 'Titulek'
281
+ }
282
+
283
+ if (control === 'content') {
284
+ return 'Obsah'
285
+ }
286
+
287
+ if (control === 'text') {
288
+ return 'Text'
289
+ }
290
+
291
+ return control[0].toUpperCase() + control.slice(1)
292
+ }
293
+
294
+ Object.keys(controls).forEach(control => {
295
+ if (control !== 'src') {
296
+ const type = typeof controls[control]
297
+ const value = typeof controls[control] === 'object' ? JSON.stringify(controls[control]) : controls[control]
298
+
299
+ let phpType = type
300
+
301
+ if (type === 'object' && Array.isArray(controls[control])) {
302
+ phpType = 'array'
303
+ } else if (type === 'boolean') {
304
+ phpType = 'bool'
305
+ } else if (type === 'number') {
306
+ phpType = 'int'
307
+ }
308
+
309
+ annotations.push(`
310
+ #[FieldType(FieldType::${getField(control, type)})]
311
+ #[FieldName('${getTitle(control)}')]
312
+ public ?${phpType} $${control}${type === 'string' ? " = '" + value + "'" : ' = null'};
313
+ `)
314
+
315
+ if (type === 'object') {
316
+ objects.push(`
317
+ $this->${control} = json_decode('${value}', false);
318
+ `)
319
+ }
320
+ }
321
+ })
322
+
323
+ if (annotations.length > 0) {
324
+ annotations = annotations.map(item => item).join('')
325
+ } else {
326
+ annotations = ''
327
+ }
328
+
329
+ if (objects.length > 0) {
330
+ objects = objects.map(item => item).join('')
331
+ } else {
332
+ objects = ''
333
+ }
334
+
335
+ fs.writeFileSync(join(options.path.app.components, `${name}.php`),
336
+ dedent`
337
+ <?php
338
+
339
+ declare(strict_types=1);
340
+
341
+ namespace App\Components;
342
+
343
+ use App\Components\Attributes\FieldName;
344
+ use App\Components\Attributes\FieldType;
345
+ use App\Components\Attributes\SectionName;
346
+
347
+ #[ComponentName('${name}')]
348
+ class ${name} extends BaseComponent
349
+ {
350
+ ${annotations}
351
+ public function render() : void
352
+ {
353
+ ${objects}
354
+ $this->template->render(TEMPLATES_DIR . '/${path}');
355
+ }
356
+ }
357
+ `
358
+ )
359
+
360
+ sectionsCount += 1
361
+ }
362
+ }
363
+ },
364
+ writeComponentFactory(name, force) {
365
+ if (!name.match(/(Ui)/)) {
366
+ if (!fs.existsSync(join(options.path.app.componentsFactory, `${name}Factory.php`)) || force) {
367
+ fs.writeFileSync(join(options.path.app.componentsFactory, `${name}Factory.php`),
368
+ dedent`
369
+ <?php
370
+
371
+ declare(strict_types=1);
372
+
373
+ namespace App\SComponents\Factory;
374
+
375
+ use ${'App\\Components\\'}${name};
376
+
377
+ interface ${name}Factory
378
+ {
379
+ public function create(): ${name};
380
+ }
381
+ `
382
+ )
383
+
384
+ sectionsFactoryCount += 1
385
+ }
386
+ }
387
+ },
388
+ async copyComponents(force = false) {
389
+ const components = FastGlob.sync(join(options.path.src.components, '/**'))
390
+
391
+ components.forEach(path => {
392
+ const name = basename(relative(options.path.src.components, path), extname(relative(options.path.src.components, path)))
393
+
394
+ this.writeComponent(name, relative(options.path.src.templates, path), force)
395
+ this.writeComponentFactory(name, force)
396
+ })
397
+
398
+ console.log(`${pc.green('✔')} ${sectionsCount} components and ${sectionsFactoryCount} factory files copied to app`)
399
+ },
196
400
  async copySections(force = false) {
197
401
  const sections = FastGlob.sync(join(options.path.src.sections, '/**'))
198
402
 
@@ -205,6 +409,18 @@ export default function prepare(options) {
205
409
 
206
410
  console.log(`${pc.green('✔')} ${sectionsCount} sections and ${sectionsFactoryCount} factory files copied to app`)
207
411
  },
412
+ async copyViews(force = false) {
413
+ const templates = FastGlob.sync(join(options.path.src.templates, '/**')).filter(entry => {
414
+ return !fs.existsSync(join(options.path.src.views, relative(options.path.src.templates, entry))) || force
415
+ })
416
+
417
+ for (const path of templates) {
418
+ await fse.copy(path, join(options.path.src.views, relative(options.path.src.templates, path)))
419
+ templatesCount += 1
420
+ }
421
+
422
+ console.log(`${pc.green('✔')} ${templatesCount} views files copied from templates`)
423
+ },
208
424
  async copyTemplates(force = false) {
209
425
  const templates = FastGlob.sync(join(options.path.src.templates, '/**')).filter(entry => {
210
426
  return !fs.existsSync(join(options.path.app.templates, relative(options.path.src.templates, entry))) || force
@@ -248,6 +464,39 @@ export default function prepare(options) {
248
464
  sectionsSchema[sortedSection.src] = sortedSection
249
465
  })
250
466
 
467
+ return sectionsSchema
468
+ },
469
+ getComponentsSchema() {
470
+ const views = FastGlob.sync(join(options.path.src.pages, '/**/*.json'))
471
+ const sectionsUnsorted = {}
472
+ const sectionsSchema = {}
473
+
474
+ views.forEach(path => {
475
+ const json = JSON.parse(fs.readFileSync(path).toString())
476
+ const sections = [...(json.body ?? []), ...(json.head ?? []), ...(json.foot ?? [])]
477
+
478
+ sections.forEach((section) => {
479
+ if (section.src) {
480
+ if (typeof sectionsUnsorted[section.src] === 'undefined') {
481
+ sectionsUnsorted[section.src] = []
482
+ sectionsUnsorted[section.src].push(section)
483
+ } else {
484
+ sectionsUnsorted[section.src].push(section)
485
+ }
486
+ }
487
+ })
488
+ })
489
+
490
+ Object.keys(sectionsUnsorted).forEach(key => {
491
+ const sortedSection = {}
492
+
493
+ sectionsUnsorted[key].forEach(section => {
494
+ lodash.merge(sortedSection, section)
495
+ })
496
+
497
+ sectionsSchema[sortedSection.src] = sortedSection
498
+ })
499
+
251
500
  return sectionsSchema
252
501
  }
253
502
  }
@@ -76,16 +76,17 @@ export default async function cms(name, { variant, branch }) {
76
76
  await move('app')
77
77
  await move('config')
78
78
  await move('log')
79
- await move('public/.htaccess')
80
79
  await move('public/index.php')
80
+ await move('src/views')
81
81
  await move('temp')
82
+ await move('tests')
82
83
  await move('.gitignore', { overwrite: true })
83
- await move('.htaccess')
84
- await move('Makefile')
85
84
  await move('composer.json')
86
85
  await move('composer.lock')
87
86
  await move('docker-compose.yml')
87
+ await move('Makefile')
88
88
  await move('phpstan.neon')
89
+ await move('phpunit.xml')
89
90
  await move('pint.json')
90
91
  await move('rector.php')
91
92
  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`))