frontfire 0.3.4 → 0.7.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/README.md CHANGED
@@ -6,11 +6,11 @@ It is entirely built upon the great esbuild ecosystem.
6
6
 
7
7
  ## Logic
8
8
 
9
- Structure
9
+ Required structure of an InfrontJS project is as follows
10
10
 
11
11
  - src/assets/**/*
12
12
  - src/app/main.js
13
- - src/app/app.css
13
+ - src/app/main.css
14
14
  - src/index.html
15
15
 
16
- Entrypoint for building is main.js and app.css
16
+ Entrypoint for building is main.js and main.css
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontfire",
3
- "version": "0.3.4",
3
+ "version": "0.7.0",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -23,11 +23,13 @@
23
23
  "chalk": "^5.2.0",
24
24
  "commander": "^10.0.0",
25
25
  "conf": "^11.0.1",
26
+ "ejs": "^3.1.10",
26
27
  "esbuild": "^0.19.5",
27
28
  "esbuild-copy-static-files": "^0.1.0",
28
29
  "esbuild-plugin-copy": "^2.0.2",
29
30
  "fs-extra": "^11.1.1",
30
31
  "lodash": "^4.17.21",
32
+ "luxon": "^3.5.0",
31
33
  "php-server": "^1.0.0",
32
34
  "prettier": "^2.8.4"
33
35
  },
@@ -6,8 +6,33 @@ import path from "node:path";
6
6
  import { copy } from "esbuild-plugin-copy";
7
7
  import copyStaticFiles from "esbuild-copy-static-files";
8
8
 
9
+ let currentDir = path.dirname( '.' );
10
+ let rootDir = null;
11
+ const maxCount = 10;
12
+ let currentCount = 0;
13
+ while ( null === rootDir )
14
+ {
15
+ if ( fs.existsSync( currentDir + path.sep + 'package.json' ) )
16
+ {
17
+ rootDir = currentDir;
18
+ }
19
+ else
20
+ {
21
+ currentDir += '/..';
22
+ currentCount++;
23
+ if ( currentCount > maxCount ) {
24
+ break;
25
+ }
26
+ }
27
+ }
9
28
 
10
- const rootFiles = fs.readdirSync( `src${path.sep}`, { withFileTypes : true } );
29
+ if ( rootDir === null )
30
+ {
31
+ console.error( 'Too many recursions. Root directory not found.' );
32
+ exit;
33
+ }
34
+
35
+ const rootFiles = fs.readdirSync( `${rootDir}${path.sep}src${path.sep}`, { withFileTypes : true } );
11
36
  const rootFilesToCopy = [];
12
37
  for ( let ri = 0; ri < rootFiles.length; ri++ )
13
38
  {
@@ -25,34 +50,22 @@ const buildDir = `build`;
25
50
  const entryPoints = [
26
51
  /*
27
52
  "src/app/main.js",
28
- "src/app/app.css"
53
+ "src/app/main.css"
29
54
  */
30
55
  /*
31
56
  path.resolve( './src/app/main.js' ),
32
- path.resolve( './src/app/app.css' )
57
+ path.resolve( './src/app/main.css' )
33
58
  */
34
- `.${path.sep}src${path.sep}app${path.sep}main.js`,
35
- `.${path.sep}src${path.sep}app${path.sep}app.css`
59
+ `./src/app/main.js`,
60
+ `./src/app/main.css`
36
61
  ];
37
62
 
38
63
  const outDirDebug = `${buildDir}/debug/app/`
39
64
  const outDirRelease = `${buildDir}/release/app`;
40
65
 
41
- console.log( outDirRelease );
42
-
43
66
  const staticAssetsDestDebug = `${buildDir}/debug/assets`;
44
67
  const staticAssetsDestRelease = `${buildDir}/release/assets`;
45
68
 
46
- /*
47
- console.log( "Build folder:" );
48
- console.log( buildDir );
49
-
50
-
51
- console.log( "Release folder:" );
52
- console.log( outDirRelease );
53
-
54
- console.log( "First entry point", entryPoints[ 0 ] );
55
- */
56
69
  export default {
57
70
  "buildDir" : buildDir,
58
71
  "debug" : {
package/src/FrontFire.js CHANGED
@@ -6,6 +6,8 @@ import fse from "fs-extra";
6
6
  import path from "node:path";
7
7
  import * as child from "child_process";
8
8
 
9
+ import { DateTime } from "luxon";
10
+
9
11
  import DEFAULT_CONFIG from "./DefaultConfig.js";
10
12
 
11
13
  export default async function frontFire( isWatch, cfg = {} )
@@ -42,10 +44,12 @@ export default async function frontFire( isWatch, cfg = {} )
42
44
  }
43
45
 
44
46
  let esbuildOpts = null;
47
+ let serverOpts = null;
45
48
 
46
49
  if ( true === isWatch )
47
50
  {
48
51
  esbuildOpts = _.get( config, 'debug.esbuild' );
52
+ serverOpts = _.get( config, 'debug.server' );
49
53
  }
50
54
  else
51
55
  {
@@ -56,6 +60,8 @@ export default async function frontFire( isWatch, cfg = {} )
56
60
 
57
61
  if ( true === isWatch )
58
62
  {
63
+ const outerPort = serverOpts && serverOpts.hasOwnProperty( 'port' ) ? +serverOpts.port : 3000;
64
+
59
65
  fs.watchFile( `src${path.sep}index.html`, async ( curr, prev ) =>
60
66
  {
61
67
  const result = await ctx.rebuild();
@@ -110,13 +116,18 @@ export default async function frontFire( isWatch, cfg = {} )
110
116
  // Forward the body of the request to esbuild
111
117
  req.pipe(proxyReq, { end: true })
112
118
 
113
- }).listen(3000);
119
+ }).listen( outerPort );
114
120
 
115
- console.log( `> InfrontJS:http://localhost:${port}` );
121
+ console.log( `> InfrontJS:http://localhost:${outerPort}` );
116
122
  }
117
123
  else
118
124
  {
119
125
  await ctx.rebuild();
120
126
  ctx.dispose();
127
+
128
+ // CACHEBREAK
129
+ let indexContent = fs.readFileSync( `${rootBuildDir}${path.sep}release${path.sep}index.html`, { encoding: 'utf8', flag: 'r' } );
130
+ const changedContent = indexContent.replace( /IFJSCACHEBREAK/g, (DateTime.now()).valueOf() );
131
+ fs.writeFileSync( `${rootBuildDir}${path.sep}release${path.sep}index.html`, changedContent );
121
132
  }
122
133
  };
package/src/index.js CHANGED
@@ -1,14 +1,25 @@
1
1
  #! /usr/bin/env node
2
2
 
3
3
  import fs from "node:fs";
4
+ import { readdir, readFile } from 'node:fs/promises';
5
+ import ejs from "ejs";
6
+ import chalk from "chalk";
4
7
 
5
8
  import _ from "lodash";
6
9
  import prettier from "prettier";
7
10
  import { program } from "commander";
11
+ import path from "node:path";
8
12
 
9
13
  import frontFire from "./FrontFire.js";
10
14
  import defaultConfig from "./DefaultConfig.js";
11
15
 
16
+ import wcIndex from "./templates/wc-index.js";
17
+ import wcTemplate from "./templates/wc-template.html.js";
18
+ import stateClass from "./templates/state-class.js";
19
+ import stateTemplate from "./templates/state-template.html.js";
20
+ import poIndex from "./templates/po-index.js";
21
+ import dictTemplate from "./templates/dictionary.js";
22
+
12
23
  async function performInit()
13
24
  {
14
25
  // Deep Copy
@@ -28,7 +39,237 @@ async function performInit()
28
39
  )
29
40
  );
30
41
 
42
+ if ( false === fs.existsSync( './src/assets' ) )
43
+ {
44
+ fs.mkdirSync( './src/assets' );
45
+ }
46
+ }
47
+
48
+ async function createInfrontJsStarter( appName = null )
49
+ {
50
+ // Deep Copy
51
+ const initConfig = JSON.parse( JSON.stringify( defaultConfig ) );
52
+
53
+ _.unset( initConfig, 'debug.esbuild.plugins' );
54
+ _.unset( initConfig, 'release.esbuild.plugins' );
55
+
56
+ if ( null === appName || appName.length === 0 )
57
+ {
58
+ appName = 'InfrontJS Starter';
59
+ }
60
+
61
+ const srcFolder = path.resolve( '.' ) + path.separator + 'src-to-test';
62
+
63
+ if ( true === fs.existsSync( srcFolder ) )
64
+ {
65
+ console.warn( `Sourcefolder ${srcFolder} already exists!` );
66
+ return;
67
+ }
68
+ }
69
+
70
+ async function generatesPathObject( name )
71
+ {
72
+ let fileName = null;
73
+ let poName = null;
74
+
75
+ fileName = name.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
76
+ poName = fileName.charAt( 0 ).toUpperCase() + fileName.slice( 1 );
77
+
78
+ const poFilename = path.resolve( '.' ) + path.sep + poName + '.js';
79
+ if ( true === fs.existsSync( poFilename ) )
80
+ {
81
+ console.error( `File "${poFilename}" already exists!` );
82
+ return false;
83
+ }
84
+
85
+ fs.writeFileSync(
86
+ poFilename,
87
+ ejs.render( poIndex, { poName: poName } )
88
+ );
89
+
90
+ console.log( chalk.green.bold( `PathObject ${poName} successfully generated.`) );
91
+ }
92
+
93
+ async function generatesState( name, options )
94
+ {
95
+ let fileName = null;
96
+ let stateName = null;
97
+ let stateId = null;
98
+
99
+ fileName = name.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
100
+ stateName = fileName.charAt( 0 ).toUpperCase() + fileName.slice( 1 );
101
+ stateId = stateName.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? '-' : '') + match.toLowerCase());
102
+
103
+ const stateFilename = path.resolve( '.' ) + path.sep + stateName + 'State.js';
104
+ if ( true === fs.existsSync( stateFilename ) )
105
+ {
106
+ console.error( `File "${stateFilename}" already exists!` );
107
+ return false;
108
+ }
109
+
110
+ const stateTemplatename = path.resolve( '.' ) + path.sep + stateName + 'Template.html';
111
+ if ( true === options.template && true === fs.existsSync( stateTemplatename ) )
112
+ {
113
+ console.error( `File "${stateTemplatename}" already exists!` );
114
+ return false;
115
+ }
116
+
117
+ fs.writeFileSync(
118
+ stateFilename,
119
+ ejs.render( stateClass, { stateName: stateName, stateId : stateId } )
120
+ );
121
+ fs.writeFileSync(
122
+ stateTemplatename,
123
+ ejs.render( stateTemplate, { stateName: stateName } )
124
+ );
125
+
126
+ console.log( chalk.green.bold( `State ${stateName} successfully generated.`) );
127
+ console.log( chalk.italic( 'Dont forget to add the state to your states instance, e.g.' ) );
128
+ console.log( chalk.italic.bgWhite( `myApp.states.add( ${stateName} );`) );
129
+ }
130
+
131
+ async function generatesWebComponent( name = null )
132
+ {
133
+ let fileName = null;
134
+ let className = null;
135
+ let wcName = null;
136
+
137
+ // Deep Copy
138
+ const initConfig = JSON.parse( JSON.stringify( defaultConfig ) );
139
+
140
+ _.unset( initConfig, 'debug.esbuild.plugins' );
141
+ _.unset( initConfig, 'release.esbuild.plugins' );
142
+
143
+ if ( null === name || name.length === 0 )
144
+ {
145
+ console.error( 'V')
146
+ }
147
+
148
+ fileName = name.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
149
+ className = fileName.charAt( 0 ).toUpperCase() + fileName.slice( 1 );
150
+ wcName = name.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? '-' : '') + match.toLowerCase());
151
+ fileName = fileName.charAt(0).toLowerCase() + fileName.slice(1);
152
+
153
+ const srcFolder = path.resolve( '.' ) + path.sep + wcName;
154
+ if ( true === fs.existsSync( srcFolder ) )
155
+ {
156
+ console.error( `Folder "${srcFolder}" already exists!` );
157
+ return false;
158
+ }
159
+
160
+ fs.mkdirSync ( srcFolder ) ;
161
+ fs.writeFileSync(
162
+ srcFolder + path.sep + 'index.js',
163
+ ejs.render( wcIndex, { wcName: wcName, className: className } )
164
+ );
165
+ fs.writeFileSync(
166
+ srcFolder + path.sep + 'template.html',
167
+ ejs.render( wcTemplate, { wcName: wcName } )
168
+ );
169
+
170
+ console.log( chalk.green.bold( `Web component ${wcName} successfully created.` ) );
171
+ }
172
+
173
+ async function generateDictionary( pathToDictionary, options )
174
+ {
175
+ const defaultLang = 'en';
176
+ const countryCodes = options.countrycodes.split( "," );
177
+ const defaultCountryCode = options.defaulcountrycode;
178
+ const rootFolder = path.resolve( options.rootpath );
179
+ const newDict = [];
180
+ const lKeys = [];
181
+ let currentDict = {};
182
+
183
+ const indexOfDefaultLang = countryCodes.indexOf( defaultLang );
184
+ if ( -1 < indexOfDefaultLang )
185
+ {
186
+ countryCodes.splice( indexOfDefaultLang, 1 );
187
+ }
188
+
189
+ try
190
+ {
191
+ if ( fs.existsSync( pathToDictionary ) )
192
+ {
193
+ let dictContent = fs.readFileSync( pathToDictionary, { encoding: 'utf8' } );
194
+ dictContent = dictContent.replace( "export default", "" );
195
+ dictContent = dictContent.trim();
196
+ dictContent = dictContent.replace( new RegExp(';$', 'gm'), "" );
197
+ try {
198
+ currentDict = JSON.parse( dictContent );
199
+ } catch( e ) {
200
+ console.error( `Cannot parse current dictionary.`, e );
201
+ currentDict = {};
202
+ }
203
+ }
204
+
205
+ const files = await readdir( rootFolder, { recursive : true } );
206
+ for ( let fi = 0; fi < files.length; fi++ )
207
+ {
208
+ const file = files[ fi ];
209
+ if ( false === fs.lstatSync( file ).isFile() )
210
+ {
211
+ continue;
212
+ }
213
+
214
+ const regex = /_lcs\(\s*?[\'|\"|\`](.+)[\'|\"|`]\s*?\)/gm;
215
+ if ( -1 < [ '.html', '.js' ].indexOf( ( '.' + file.split( '.' ).pop() ) ) )
216
+ {
217
+ const str = await readFile( file, 'utf8' );
218
+
219
+ let m;
220
+ while ((m = regex.exec(str)) !== null) {
221
+
222
+ // This is necessary to avoid infinite loops with zero-width matches
223
+ if (m.index === regex.lastIndex) {
224
+ regex.lastIndex++;
225
+ }
226
+
227
+ // The result can be accessed through the `m`-variable.
228
+ m.forEach((match, groupIndex) => {
229
+
230
+ if ( false === match.includes( '_lcs' ) )
231
+ {
232
+ if ( -1 === lKeys.indexOf( match ) )
233
+ {
234
+ lKeys.push( match );
235
+ let newEntry = { "key" : match, trans : [] };
236
+ if ( currentDict.hasOwnProperty( match ) && currentDict[ match ].hasOwnProperty( defaultCountryCode ) && currentDict[ match ][ defaultCountryCode ] !== null )
237
+ {
238
+ newEntry.trans.push( { "cc" : defaultCountryCode, "val" : currentDict[ match ][ defaultCountryCode ] } );
239
+ }
240
+ else
241
+ {
242
+ newEntry.trans.push( { "cc" : defaultCountryCode, "val" : match } );
243
+ }
244
+
245
+ for ( let ci = 0; ci < countryCodes.length; ci++ )
246
+ {
247
+ if ( currentDict.hasOwnProperty( match ) && currentDict[ match ].hasOwnProperty( countryCodes[ ci ] ) && currentDict[ match ][ countryCodes[ ci ] ] !== null )
248
+ {
249
+ newEntry.trans.push( { "cc" : countryCodes[ ci ], "val" : currentDict[ match ][ countryCodes[ ci ] ] } );
250
+ }
251
+ else
252
+ {
253
+ newEntry.trans.push( { "cc" : countryCodes[ ci ], "val" : null } );
254
+ }
255
+ }
256
+ newDict.push( newEntry );
257
+ }
258
+ }
259
+ });
260
+ }
261
+ }
262
+ }
263
+
264
+ const newDictFileContent = ejs.render( dictTemplate, { newDict: newDict } );
265
+ fs.writeFileSync( pathToDictionary, newDictFileContent, { encoding: 'utf8' } );
31
266
 
267
+ console.log( chalk.green.bold( `Dictionary successfully created under ${pathToDictionary}. Total keys: ${newDict.length}.` ) );
268
+ }
269
+ catch( e )
270
+ {
271
+ console.error( e );
272
+ }
32
273
  }
33
274
 
34
275
  // Try to load custom config
@@ -61,6 +302,38 @@ program
61
302
  .description( 'Creates frontfire default configuration file in current directory.' )
62
303
  .action( function() { performInit(); } );
63
304
 
305
+ program
306
+ .command( 'create' )
307
+ .description( 'Creates starter InfrontJS application.' )
308
+ .action( function() { createInfrontJsStarter(); } );
309
+
310
+ program
311
+ .command( 'gwc' )
312
+ .argument( '<name>', 'Name of web component.' )
313
+ .description( 'Generates a webcomponent with specific name.' )
314
+ .action( function( name ) { generatesWebComponent( name ); } );
315
+
316
+ program
317
+ .command( 'gd' )
318
+ .argument( '<pathToDictionary>', 'Path to dictionary file. If it exists, it gets overwritten.' )
319
+ .option( '-cc, --countrycodes <ccodes>', 'Comma seperated list of country codes.', 'en,de' )
320
+ .option( '-dcc, --defaulcountrycode <ccode>', 'Default country code', 'en' )
321
+ .option( '-rp, --rootpath <rootpath>', 'Root path to parse files for translations.', './' )
322
+ .description( 'Generates a dictionary file.' )
323
+ .action( function( pathToDictionary, options ) { generateDictionary( pathToDictionary, options ); } );
324
+
325
+ program
326
+ .command( 'gs' )
327
+ .argument( '<name>', 'Name of state.' )
328
+ .option( '-nt, --no-template', 'No template is generated. By default, an empty template for the State is autogenerated.' )
329
+ .description( 'Generates a new state.' )
330
+ .action( function( name, options ) { generatesState( name, options ); } );
331
+
332
+ program
333
+ .command( 'gpo' )
334
+ .argument( '<name>', 'Name of path object.' )
335
+ .description( 'Generates a new path object.' )
336
+ .action( function( name ) { generatesPathObject( name ); } );
64
337
 
65
338
  program
66
339
  .command( 'build' )
@@ -0,0 +1,16 @@
1
+ export default 'export default {\n'+
2
+ '<% for ( let ki = 0; ki < newDict.length; ki++ ) { %>\n' +
3
+ ' "<%= newDict[ ki ].key %>" : {\n' +
4
+ '<% for( let ti = 0; ti < newDict[ ki ].trans.length; ti++ ) { %>' +
5
+ '<% if ( newDict[ ki ].trans[ ti ].val === null ) { %>' +
6
+ ' "<%= newDict[ ki ].trans[ ti ].cc; %>" : null<%= ((ti+1) < newDict[ ki ].trans.length) ? "," : "" %>' +
7
+ '<% } else { %>' +
8
+ ' "<%= newDict[ ki ].trans[ ti ].cc; %>" : "<%= newDict[ ki ].trans[ ti ].val; %>"<%= ((ti+1) < newDict[ ki ].trans.length) ? "," : "" %>' +
9
+ '<% } %>' +
10
+ '<% if ((ti+1) < newDict[ ki ].trans.length) { %> ' +
11
+ '\n' +
12
+ '<% } %>' +
13
+ '<% } %>\n' +
14
+ ' }<%= ((ki+1) < newDict.length) ? "," : "" %>\n' +
15
+ '<% } %>\n' +
16
+ '};';
@@ -0,0 +1,4 @@
1
+ export default 'import { PathObject } from "infrontjs";\n' +
2
+ 'export class <%= poName %> extends PathObject\n' +
3
+ '{\n' +
4
+ '}';
@@ -0,0 +1,12 @@
1
+ export default 'import { State } from "infrontjs";\n' +
2
+ 'import template from \'./<%= stateName %>Template.html\';\n' +
3
+ 'export class <%= stateName %>State extends State\n' +
4
+ '{\n' +
5
+ ' static ROUTE = "/<%= stateId %>";\n' +
6
+ ' static ID = "<%= stateId %>";\n' +
7
+ '\n' +
8
+ ' async enter()\n' +
9
+ ' {\n' +
10
+ ' console.log( "Hello from state <%= stateName %>" );\n' +
11
+ ' }\n' +
12
+ '}';
@@ -0,0 +1 @@
1
+ export default '<h1>Hello from State <%= stateName %>!</h1>';
@@ -0,0 +1,32 @@
1
+ export default '' +
2
+ '// import * as IF from "infrontjs";\n' +
3
+ 'import template from "./template.html";\n' +
4
+ 'class <%= className %> extends HTMLElement\n' +
5
+ '{\n' +
6
+ ' constructor()\n' +
7
+ ' {\n' +
8
+ ' super();\n' +
9
+ ' this._isConnected = false;\n' +
10
+ ' }\n' +
11
+ '\n' +
12
+ ' connectedCallback()\n' +
13
+ ' {\n' +
14
+ ' if ( false === this._isConnected )\n' +
15
+ ' {\n' +
16
+ ' // Resolve default InfrontJS instance\n' +
17
+ ' // this.app = IF.App.get();\n' +
18
+ ' this.render();\n' +
19
+ '\n' +
20
+ ' this._isConnected = true;\n' +
21
+ ' }\n' +
22
+ ' }\n' +
23
+ '\n' +
24
+ ' render()\n' +
25
+ ' {\n' +
26
+ ' // Eg. Render template\n' +
27
+ ' // this.innerHTML = this.app.view.getHtml( template, { user : this.app.user } );\n' +
28
+ ' this.innerHTML = template;\n' +
29
+ ' }\n' +
30
+ '}\n' +
31
+ '\n' +
32
+ 'customElements.define( "<%= wcName %>", <%= className %> );'
@@ -0,0 +1,2 @@
1
+ export default '' +
2
+ '<strong>Content of web component named:<br /><%= wcName %></strong>';