@silexlabs/silex 3.0.0-alpha.21
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/.dockerignore +5 -0
- package/.editorconfig +14 -0
- package/.eslintrc.json +43 -0
- package/.github/FUNDING.yml +13 -0
- package/.github/workflows/docker-hub-publish.yml +36 -0
- package/.github/workflows/npm-publish.yml +19 -0
- package/.github/workflows/test.yml +26 -0
- package/.nvmrc +1 -0
- package/.silex.js +19 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +108 -0
- package/Dockerfile +15 -0
- package/LICENSE +203 -0
- package/app.json +106 -0
- package/directus.silex.js +7 -0
- package/dist/client/css/admin.css +4350 -0
- package/dist/client/index.html +9 -0
- package/dist/client/js/main.js +282 -0
- package/dist/client/js/main.js.LICENSE.txt +52 -0
- package/dist/server/config.js +50 -0
- package/dist/server/events.js +5 -0
- package/dist/server/index.js +29 -0
- package/dist/server/start.js +18 -0
- package/jest.config.js +10 -0
- package/logos/logo_by_ceubri/silex-icon-2018@200px.png +0 -0
- package/logos/logo_by_ceubri/silex-icon-2018@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white-transp.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018-white@4x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.ai +2520 -6
- package/logos/logo_by_ceubri/silex-logo-2018.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.pdf +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.5x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@0.75x.png +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.jpg +0 -0
- package/logos/logo_by_ceubri/silex-logo-2018@4x.png +0 -0
- package/logos/silexlabs.png +0 -0
- package/package.json +170 -0
- package/public/assets/container.png +0 -0
- package/public/assets/favicon.png +0 -0
- package/public/assets/logo-silex-small.png +0 -0
- package/public/example-config.js +31 -0
- package/public/robots.txt +2 -0
- package/readme.md +51 -0
- package/src/css/_mixins.scss +8 -0
- package/src/css/_variables.scss +33 -0
- package/src/css/dialog.scss +34 -0
- package/src/css/grapesjs.scss +44 -0
- package/src/css/index.scss +22 -0
- package/src/css/loader.scss +10 -0
- package/src/css/page-panel.scss +65 -0
- package/src/css/project-bar.scss +102 -0
- package/src/css/silex.scss +183 -0
- package/src/html/debug.pug +8 -0
- package/src/html/index.pug +39 -0
- package/src/html/templates/includes.pug +2 -0
- package/src/html/templates/workspace.pug +2 -0
- package/src/plugins/AuthPlugin.js +21 -0
- package/src/plugins/Cache.js +11 -0
- package/src/plugins/DefaultBackend.js +113 -0
- package/src/plugins/ExpressPlugin.js +30 -0
- package/src/plugins/HooksPlugin.js +58 -0
- package/src/plugins/PublishPlugin.js +69 -0
- package/src/plugins/SslPlugin.js +66 -0
- package/src/plugins/StaticPlugin.js +48 -0
- package/src/plugins/WebsitePlugin.js +149 -0
- package/src/ts/client/config.ts +21 -0
- package/src/ts/client/expose.ts +4 -0
- package/src/ts/client/grapesjs/blocks.ts +88 -0
- package/src/ts/client/grapesjs/eleventy.ts +21 -0
- package/src/ts/client/grapesjs/index.ts +255 -0
- package/src/ts/client/grapesjs/internal-links.test.ts +87 -0
- package/src/ts/client/grapesjs/internal-links.ts +210 -0
- package/src/ts/client/grapesjs/new-page-dialog.ts +73 -0
- package/src/ts/client/grapesjs/openImport.ts +59 -0
- package/src/ts/client/grapesjs/page-panel.ts +101 -0
- package/src/ts/client/grapesjs/project-bar.ts +92 -0
- package/src/ts/client/grapesjs/publish.ts +386 -0
- package/src/ts/client/grapesjs/rich-text.ts +16 -0
- package/src/ts/client/grapesjs/semantic.ts +96 -0
- package/src/ts/client/grapesjs/settings.ts +190 -0
- package/src/ts/client/grapesjs/symbolDialogs.ts +21 -0
- package/src/ts/client/grapesjs/template.ts +166 -0
- package/src/ts/client/index.ts +38 -0
- package/src/ts/client/utils.ts +9 -0
- package/src/ts/externs.ts +1 -0
- package/src/ts/page.ts +14 -0
- package/src/ts/server/cli.ts +23 -0
- package/src/ts/server/config.ts +62 -0
- package/src/ts/server/events.ts +2 -0
- package/src/ts/server/index.ts +35 -0
- package/src/ts/server/start.ts +18 -0
- package/src/ts/server/utils/BackwardCompat.ts +534 -0
- package/src/ts/types.ts +138 -0
- package/tsconfig-server-debug.json +20 -0
- package/tsconfig-server-release.json +19 -0
- package/tsconfig.jest.json +9 -0
- package/tsconfig.json +27 -0
- package/webpack.config.js +29 -0
- package/webpack.debug.config.js +8 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as grapesjs from 'grapesjs/dist/grapes.min.js'
|
|
2
|
+
import { cmdAddSymbol } from '@silexlabs/grapesjs-symbols/src'
|
|
3
|
+
|
|
4
|
+
export const cmdPromptAddSymbol = 'symbol-prompt-add'
|
|
5
|
+
|
|
6
|
+
export default grapesjs.plugins.add(name, (editor, opts) => {
|
|
7
|
+
function getNext(prefix) {
|
|
8
|
+
let idx = 1
|
|
9
|
+
while(editor.Symbols.find(s => s.get('label') === prefix + idx)) {
|
|
10
|
+
idx++
|
|
11
|
+
}
|
|
12
|
+
return prefix + idx
|
|
13
|
+
}
|
|
14
|
+
editor.Commands.add(cmdPromptAddSymbol, {
|
|
15
|
+
run: (_, sender) => {
|
|
16
|
+
const label = prompt('Label', getNext('Symbol '))
|
|
17
|
+
const icon = prompt('Icon', 'fa-list')
|
|
18
|
+
editor.runCommand(cmdAddSymbol, { label, icon })
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {html, render} from 'lit-html'
|
|
2
|
+
import {styleMap} from 'lit-html/directives/style-map.js'
|
|
3
|
+
import grapesjs from 'grapesjs/dist/grapes.min.js'
|
|
4
|
+
import { onAll } from '../utils'
|
|
5
|
+
|
|
6
|
+
const pluginName = 'template'
|
|
7
|
+
const templateType = 'templateType'
|
|
8
|
+
const templateKey = 'template'
|
|
9
|
+
|
|
10
|
+
export const templatePlugin = grapesjs.plugins.add(pluginName, (editor, opts) => {
|
|
11
|
+
// Add the new trait to all component types
|
|
12
|
+
editor.DomComponents.getTypes().map(type => {
|
|
13
|
+
editor.DomComponents.addType(type.id, {
|
|
14
|
+
model: {
|
|
15
|
+
defaults: {
|
|
16
|
+
traits: [
|
|
17
|
+
// Keep the type original traits
|
|
18
|
+
...editor.DomComponents.getType(type.id).model.prototype.defaults.traits,
|
|
19
|
+
// Add the new trait
|
|
20
|
+
{
|
|
21
|
+
label: false,
|
|
22
|
+
type: templateType,
|
|
23
|
+
name: pluginName,
|
|
24
|
+
},
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function doRender(el) {
|
|
32
|
+
const template = editor.getSelected()?.get(templateKey) || {}
|
|
33
|
+
const taStyle = opts.styles?.textarea ?? styleMap({
|
|
34
|
+
backgroundColor: 'var(--darkerPrimaryColor)',
|
|
35
|
+
})
|
|
36
|
+
const sepStyle = opts.styles?.sep ?? styleMap({ height: '10px' })
|
|
37
|
+
const labels = {
|
|
38
|
+
before: html`<strong>Before</strong> the element`,
|
|
39
|
+
replace: html`<strong>Replace</strong> the element's children`,
|
|
40
|
+
after: html`<strong>After</strong> the element`,
|
|
41
|
+
attributes: html`HTML attributes`,
|
|
42
|
+
classname: html`CSS classes`,
|
|
43
|
+
style: html`CSS styles`,
|
|
44
|
+
}
|
|
45
|
+
render(html`
|
|
46
|
+
<div>
|
|
47
|
+
<h3>Template</h3>
|
|
48
|
+
<p>This will be inserted in the published version</p>
|
|
49
|
+
</div>
|
|
50
|
+
${['classname', 'attributes', 'style'].map(id => html`
|
|
51
|
+
<label>
|
|
52
|
+
${labels[id]}
|
|
53
|
+
<input
|
|
54
|
+
id="template-${id}"
|
|
55
|
+
.value=${template[id] || ''}
|
|
56
|
+
style=${taStyle}
|
|
57
|
+
/>
|
|
58
|
+
</label>
|
|
59
|
+
`)}
|
|
60
|
+
${['before', 'replace', 'after'].map(id => html`
|
|
61
|
+
<label
|
|
62
|
+
for="template-${id}"
|
|
63
|
+
>${labels[id]}</label>
|
|
64
|
+
<textarea
|
|
65
|
+
id="template-${id}"
|
|
66
|
+
style=${taStyle}
|
|
67
|
+
.value=${template[id] || ''}
|
|
68
|
+
></textarea>
|
|
69
|
+
`)}
|
|
70
|
+
`, el)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
editor.TraitManager.addType(templateType, {
|
|
74
|
+
createInput({ trait }) {
|
|
75
|
+
// Create a new element container and add some content
|
|
76
|
+
const el = document.createElement('div')
|
|
77
|
+
el.classList.add('gjs-one-bg')
|
|
78
|
+
// update the UI when a page is added/renamed/removed
|
|
79
|
+
editor.on('page', () => doRender(el))
|
|
80
|
+
doRender(el)
|
|
81
|
+
// this will be the element passed to onEvent and onUpdate
|
|
82
|
+
return el
|
|
83
|
+
},
|
|
84
|
+
// Update the component based on UI changes
|
|
85
|
+
// `elInput` is the result HTMLElement you get from `createInput`
|
|
86
|
+
onEvent({ elInput, component, event }) {
|
|
87
|
+
const template = {
|
|
88
|
+
before: elInput.querySelector('#template-before').value,
|
|
89
|
+
replace: elInput.querySelector('#template-replace').value,
|
|
90
|
+
after: elInput.querySelector('#template-after').value,
|
|
91
|
+
attributes: elInput.querySelector('#template-attributes').value,
|
|
92
|
+
classname: elInput.querySelector('#template-classname').value,
|
|
93
|
+
style: elInput.querySelector('#template-style').value,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Store the new template
|
|
97
|
+
if(Object.values(template).filter(val => !!val && !!cleanup(val)).length > 0) {
|
|
98
|
+
component.set(templateKey, template)
|
|
99
|
+
} else {
|
|
100
|
+
component.set(templateKey)
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
// Update UI on the component change
|
|
104
|
+
onUpdate({ elInput, component }) {
|
|
105
|
+
doRender(elInput)
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Make html attribute
|
|
110
|
+
// Quote strings, no values for boolean
|
|
111
|
+
function makeAttribute(key, value) {
|
|
112
|
+
switch(typeof value) {
|
|
113
|
+
case 'boolean': return value ? key : ''
|
|
114
|
+
default: return `${key}="${value}"`
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Remove empty lines in templates
|
|
118
|
+
function cleanup(template) {
|
|
119
|
+
return template
|
|
120
|
+
// split in lines
|
|
121
|
+
.split('\n')
|
|
122
|
+
// remove lines with only spaces
|
|
123
|
+
.map(line => line.trim())
|
|
124
|
+
.filter(line => !!line)
|
|
125
|
+
// put back together
|
|
126
|
+
.join('\n')
|
|
127
|
+
}
|
|
128
|
+
editor.on(opts.eventStart || 'publish:before', () => {
|
|
129
|
+
// Insert templates
|
|
130
|
+
onAll(editor, c => {
|
|
131
|
+
const template = c.get(templateKey)
|
|
132
|
+
const toHTML = c.toHTML
|
|
133
|
+
const classes = c.getClasses()
|
|
134
|
+
const before = cleanup(template?.before || '')
|
|
135
|
+
const replace = cleanup(template?.replace || '')
|
|
136
|
+
const after = cleanup(template?.after || '')
|
|
137
|
+
const classname = cleanup(template?.classname || '')
|
|
138
|
+
const style = cleanup(template?.style || '')
|
|
139
|
+
const attributes = cleanup(template?.attributes || '')
|
|
140
|
+
// Store the initial method
|
|
141
|
+
if(!c.has('tmpHtml')) c.set('tmpHtml', toHTML)
|
|
142
|
+
// Override the method
|
|
143
|
+
c.toHTML = () => {
|
|
144
|
+
return `${
|
|
145
|
+
before
|
|
146
|
+
}${ c.get('tagName') ? `<${c.get('tagName')}
|
|
147
|
+
${Object.entries(c.get('attributes')).map(([key, value]) => makeAttribute(key, value)).join(' ')}
|
|
148
|
+
${classes.length || classname ? `class="${classes.join(' ')} ${classname}"` : ''}
|
|
149
|
+
${attributes}
|
|
150
|
+
${style ? `style="${style}"` : ''}
|
|
151
|
+
>` : '' }${
|
|
152
|
+
replace || c.getInnerHTML()
|
|
153
|
+
}${ c.get('tagName') ? `</${c.get('tagName')}>` : '' }${
|
|
154
|
+
after
|
|
155
|
+
}`
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
editor.on(opts.eventStop || 'publish:stop', () => {
|
|
160
|
+
onAll(editor, c => {
|
|
161
|
+
// Restore the original method
|
|
162
|
+
c.toHTML = c.get('tmpHtml')
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* Defines the entry point of Silex client side application
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Get env vars from webpack (see plugin in webpack.config.js)
|
|
8
|
+
declare const CONFIG_URL: string
|
|
9
|
+
|
|
10
|
+
import { SilexConfig } from './config'
|
|
11
|
+
import { initEditor, getEditor } from './grapesjs/index'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Start Silex, called from host HTML page with window.silex.start()
|
|
15
|
+
*/
|
|
16
|
+
export async function start(options = {}) {
|
|
17
|
+
const config = new SilexConfig()
|
|
18
|
+
Object.assign(config, options)
|
|
19
|
+
|
|
20
|
+
// Initial config file
|
|
21
|
+
if(CONFIG_URL) {
|
|
22
|
+
await config.addPlugin(CONFIG_URL, {})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Debug mode
|
|
26
|
+
if (config.debug) {
|
|
27
|
+
console.warn('Silex starting in debug mode.', {config})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Start grapesjs
|
|
31
|
+
initEditor(config.editor)
|
|
32
|
+
|
|
33
|
+
// End of loading
|
|
34
|
+
getEditor().on('load', () => {
|
|
35
|
+
document.querySelector('.silex-loader').classList.add('silex-dialog-hide')
|
|
36
|
+
document.querySelector('#gjs').classList.remove('silex-dialog-hide')
|
|
37
|
+
})
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/src/ts/page.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Page related functions
|
|
2
|
+
// This is used on the client and the server
|
|
3
|
+
export function getPageSlug(pageName = 'index') {
|
|
4
|
+
return pageName
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.replace(/[^a-z0-9 -]/g, '')
|
|
7
|
+
// Collapse whitespace and replace by -
|
|
8
|
+
.replace(/\s+/g, '-')
|
|
9
|
+
// Collapse dashes
|
|
10
|
+
.replace(/-+/g, '-')
|
|
11
|
+
}
|
|
12
|
+
export function getPageLink(pageName = 'index') {
|
|
13
|
+
return `./${getPageSlug(pageName)}.html`
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { parse } from 'cli'
|
|
2
|
+
|
|
3
|
+
const options = parse({
|
|
4
|
+
config: [ 'c', 'Path for the server side config file to load at startup', 'file'],
|
|
5
|
+
port: [ 'p', 'Port to listen to', 'int'],
|
|
6
|
+
'ssl-port': [ false, 'Port to listen to for SSL/HTTPS', 'int'],
|
|
7
|
+
'force-https': [ false, 'Force HTTPS', 'true'],
|
|
8
|
+
'ssl-private-key': [ false, 'Path to private key for SSL', 'file'],
|
|
9
|
+
'ssl-certificate': [ false, 'Path to SSL certificate', 'file'],
|
|
10
|
+
'force-https-trust-xfp-header': [ false, 'Sets the trustXFPHeader param of the express module "cookie-session". Use only with --force-https', 'true'],
|
|
11
|
+
'session-secret': ['s', 'Session secret', 'string'],
|
|
12
|
+
}, {})
|
|
13
|
+
|
|
14
|
+
if(options.config) process.env.CONFIG = options.config
|
|
15
|
+
if(options.port) process.env.PORT = options.port
|
|
16
|
+
if(options['ssl-port']) process.env.SSL_PORT = options['ssl-port']
|
|
17
|
+
if(options['force-https']) process.env.SILEX_FORCE_HTTPS = options['force-https']
|
|
18
|
+
if(options['ssl-private-key']) process.env.SILEX_SSL_PRIVATE_KEY = options['ssl-private-key']
|
|
19
|
+
if(options['ssl-certificate']) process.env.SILEX_SSL_CERTIFICATE = options['ssl-certificate']
|
|
20
|
+
if(options['force-https-trust-xfp-header']) process.env.SILEX_FORCE_HTTPS_TRUST_XFP_HEADER = options['force-https-trust-xfp-header']
|
|
21
|
+
if(options['session-secret']) process.env.SILEX_SESSION_SECRET = options['session-secret']
|
|
22
|
+
|
|
23
|
+
import('./index')
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview This is where the default config is defined. Also use this object as an event emitter to listen to Silex events
|
|
3
|
+
* The values can be overriden with env vars or before passing the config to Silex
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @class default config for Silex server
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Config, Plugin } from '@silexlabs/silex-plugins'
|
|
11
|
+
import { resolve } from 'path'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Config types definitions
|
|
15
|
+
*/
|
|
16
|
+
export class ServerConfig extends Config {
|
|
17
|
+
constructor(
|
|
18
|
+
public url: string,
|
|
19
|
+
public debug: boolean,
|
|
20
|
+
public port: string,
|
|
21
|
+
public apiPath: string
|
|
22
|
+
) {
|
|
23
|
+
super()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get config async function
|
|
28
|
+
export default async function(): Promise<ServerConfig> {
|
|
29
|
+
const port = process.env.PORT || '6805' // 6805 is the date of sexual revolution started in paris france 8-)
|
|
30
|
+
const debug = process.env.SILEX_DEBUG === 'true'
|
|
31
|
+
|
|
32
|
+
const url = process.env.SERVER_URL || `http://localhost:${port}`
|
|
33
|
+
const config = new ServerConfig(url, debug, port, '/api')
|
|
34
|
+
|
|
35
|
+
// Config file in root folder
|
|
36
|
+
const configFilePath: Plugin = resolve(__dirname, '../../.silex.js')
|
|
37
|
+
console.log('Loading config', configFilePath)
|
|
38
|
+
try {
|
|
39
|
+
// Initiate the process with the config file which is just another plugin
|
|
40
|
+
await config.addPlugin(configFilePath, {})
|
|
41
|
+
} catch(e) {
|
|
42
|
+
if(e.code === 'MODULE_NOT_FOUND') {
|
|
43
|
+
console.info('No config found', configFilePath)
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error(`Error in config file ${configFilePath}: ${e.message}`)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Optional config file
|
|
49
|
+
if (process.env.CONFIG) {
|
|
50
|
+
const userConfigPath: Plugin = process.env.CONFIG
|
|
51
|
+
console.log('Loading user config', userConfigPath)
|
|
52
|
+
try {
|
|
53
|
+
// Initiate the process with the config file which is just another plugin
|
|
54
|
+
await config.addPlugin(userConfigPath, {})
|
|
55
|
+
} catch (e) {
|
|
56
|
+
throw new Error(`User config file ${userConfigPath} not found: ${e.message}`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Return the config file
|
|
61
|
+
return config
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import getConfig from './config'
|
|
2
|
+
import start from './start'
|
|
3
|
+
|
|
4
|
+
// Main app
|
|
5
|
+
async function main() {
|
|
6
|
+
// Get the default config object
|
|
7
|
+
const config = await getConfig()
|
|
8
|
+
|
|
9
|
+
// Here one can mutate the config object
|
|
10
|
+
// Check the docs, look for the "add silex to your project as an npm dependency"
|
|
11
|
+
|
|
12
|
+
// start silex
|
|
13
|
+
const app = await start(config)
|
|
14
|
+
|
|
15
|
+
console.info(`
|
|
16
|
+
I'm ready, listening to port ${config.port}
|
|
17
|
+
`)
|
|
18
|
+
if (config.debug) {
|
|
19
|
+
startLiverReload()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
main()
|
|
23
|
+
|
|
24
|
+
// livereload
|
|
25
|
+
import { createServer } from 'livereload'
|
|
26
|
+
import { resolve } from 'path'
|
|
27
|
+
function startLiverReload() {
|
|
28
|
+
const dist = resolve(__dirname, '../client')
|
|
29
|
+
const server = createServer({
|
|
30
|
+
delay: 0,
|
|
31
|
+
}, () => {
|
|
32
|
+
console.info(`\nDebug mode\nLive reload server is running.\nWatching ${dist}`)
|
|
33
|
+
})
|
|
34
|
+
server.watch(dist)
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import express, { Application } from 'express'
|
|
2
|
+
|
|
3
|
+
import { ServerConfig } from './config'
|
|
4
|
+
import { EVENT_STARTUP_START, EVENT_STARTUP_END } from './events'
|
|
5
|
+
|
|
6
|
+
export default async function(config: ServerConfig): Promise<Application> {
|
|
7
|
+
// Plugins hook to create API routes
|
|
8
|
+
const app = express()
|
|
9
|
+
await config.emit(EVENT_STARTUP_START, { app })
|
|
10
|
+
|
|
11
|
+
// Start server
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
app.listen(config.port, () => {
|
|
14
|
+
config.emit(EVENT_STARTUP_END, { app })
|
|
15
|
+
resolve(app)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
}
|