diff --git a/scully.config.js b/scully.config.js index d0a8120be..725d89f0a 100644 --- a/scully.config.js +++ b/scully.config.js @@ -6,8 +6,8 @@ require('./extraPlugin/voidPlugin'); exports.config = { /** projectRoot is mandatory! */ projectRoot: './projects/sampleBlog/src/app', - /** outFolder is where the static distribution files end up */ - outFolder: './dist/static', + /** outDir is where the static distribution files end up */ + outDir: './dist/static', routes: { '/demo/:id': { diff --git a/scully/renderPlugins/puppeteerRenderPlugin.ts b/scully/renderPlugins/puppeteerRenderPlugin.ts index e528bb2ef..5685553fe 100644 --- a/scully/renderPlugins/puppeteerRenderPlugin.ts +++ b/scully/renderPlugins/puppeteerRenderPlugin.ts @@ -1,12 +1,15 @@ // tslint:disable: no-string-literal // const puppeteer = require('puppeteer'); +import {readFileSync} from 'fs-extra'; +import {join} from 'path'; import {Browser, Page} from 'puppeteer'; import {HandledRoute} from '../routerPlugins/addOptionalRoutesPlugin'; -import {launchedBrowser} from './launchedBrowser'; import {scullyConfig} from '../utils/config'; import {logError, yellow} from '../utils/log'; +import {launchedBrowser} from './launchedBrowser'; export const puppeteerRender = async (route: HandledRoute): Promise => { + const {version} = JSON.parse(readFileSync(join(__dirname, '../package.json')).toString()) || '0.0.0'; const path = `http://localhost:${scullyConfig.appPort}${route.route}`; let pageHtml: string; let browser: Browser; @@ -25,6 +28,8 @@ export const puppeteerRender = async (route: HandledRoute): Promise => { resolve(); }); + windowSet(page, 'scullyVersion', version); + /** Inject this into the running page, runs in browser*/ await page.evaluateOnNewDocument(() => { /** set "running" mode */ @@ -34,6 +39,7 @@ export const puppeteerRender = async (route: HandledRoute): Promise => { const d = document.createElement('script'); d.innerHTML = `window['ScullyIO']='generated';`; document.head.appendChild(d); + document.body.setAttribute('scully-version', window['scullyVersion']); window['onCustomEvent'](); }); }); @@ -63,3 +69,12 @@ export const puppeteerRender = async (route: HandledRoute): Promise => { function waitForIt(milliSeconds) { return new Promise(resolve => setTimeout(() => resolve(), milliSeconds)); } + +const windowSet = (page, name, value) => + page.evaluateOnNewDocument(` + Object.defineProperty(window, '${name}', { + get() { + return '${value}' + } + }) + `); diff --git a/scully/scully.ts b/scully/scully.ts index 0729c02e1..4ba850698 100644 --- a/scully/scully.ts +++ b/scully/scully.ts @@ -1,180 +1,98 @@ #!/usr/bin/env node -// the above line is needed for node bin running - -import {spawn} from 'child_process'; -import {existsSync} from 'fs-extra'; +import {readFileSync} from 'fs-extra'; import {join} from 'path'; import * as yargs from 'yargs'; import './pluginManagement/systemPlugins'; import {routeContentRenderer} from './renderPlugins/routeContentRenderer'; +import {startBackgroundServer} from './startBackgroundServer'; import {loadConfig} from './utils/config'; -import {checkChangeAngular, existDistAngular, moveDistAngular} from './utils/fsAngular'; -import {checkStaticFolder} from './utils/fsFolder'; +import {moveDistAngular} from './utils/fsAngular'; import {httpGetJson} from './utils/httpGetJson'; -import {RouteTypes, ScullyConfig} from './utils/interfacesandenums'; +import {RouteTypes} from './utils/interfacesandenums'; import {isPortTaken} from './utils/isPortTaken'; import {logError} from './utils/log'; import {startScully} from './utils/startup'; -import {closeExpress, staticServer} from './utils/staticServer'; import {waitForServerToBeAvailable} from './utils/waitForServerToBeAvailable'; +import {bootServe, isBuildThere, watchMode} from './watchMode'; /** the default of 10 is too shallow for generating pages. */ require('events').defaultMaxListeners = 100; let port; // tslint:disable-next-line:variable-name -let _options = {}; +export let _options = {}; + +const {argv: options} = yargs + .option('path', { + alias: 'p', + type: 'string', + description: 'The path to generate', + }) + .option('type', { + alias: 't', + type: 'string', + description: 'The type to generate', + }) + .option('port', { + alias: 'p', + type: 'number', + description: 'The port to run on', + }) + .option('folder', { + type: 'string', + description: 'home folder', + }); + +if (process.argv.includes('version')) { + const {version} = JSON.parse(readFileSync(join(__dirname, './package.json')).toString()); + console.log('version:', version); + process.exit(0); +} (async () => { + if (process.argv.includes('killServer')) { + await httpGetJson('http://localhost:1864/killMe', {suppressErrors: true}); + process.exit(0); + return; + } /** make sure not to do something before the config is ready */ const scullyConfig = await loadConfig; await isBuildThere(scullyConfig); - const {argv: options} = yargs - .option('path', { - alias: 'p', - type: 'string', - description: 'The path to generate', - }) - .option('type', { - alias: 't', - type: 'string', - description: 'The type to generate', - }) - .option('port', { - alias: 'p', - type: 'number', - description: 'The port to run on', - }); - if (process.argv.includes('serve')) { - port = options.path; - console.log('starting static server...'); - process.title = 'ScullyServer'; - checkChangeAngular(options.path); - restartStaticServer(); + await bootServe(scullyConfig); } else { const folder = join(scullyConfig.homeFolder, scullyConfig.distFolder); + /** copy in current buildfile */ + await moveDistAngular(folder, scullyConfig.outDir, {removeStaticDist: true, reset: false}); + + /** server already up and running? */ + const isTaken = await isPortTaken(scullyConfig.staticport); + if (!isTaken) { + startBackgroundServer(scullyConfig); + } else { + // debug only + console.log(`Background servers already running.`); + } - if (!existDistAngular(scullyConfig.homeFolder)) { + if (!(await waitForServerToBeAvailable().catch(e => false))) { + logError('Could not connect to server'); process.exit(15); } - await moveDistAngular(folder, scullyConfig.outDir, {removeStaticDist: true, reset: false}); + console.log('servers available'); + await startScully(); - if (options.path && options.type) { - routeContentRenderer({ - route: options.path, - type: (options.type as unknown) as RouteTypes, - }); - if (process.argv.includes('watch')) { - _options = options; - watchMode(); - } + if (process.argv.includes('watch')) { + _options = options; + watchMode(); } else { - /** server already up and running? */ - const isTaken = await isPortTaken(scullyConfig.staticport); if (!isTaken) { - spawn('node', [join(scullyConfig.homeFolder, './node_modules/.bin/scully'), 'serve'], { - detached: true, - }).on('close', err => { - if (+err > 0) { - spawn( - 'node', - [join(scullyConfig.homeFolder, './node_modules/@scullyio/scully/scully.js'), 'serve'], - { - detached: true, - } - ).on('close', err2 => { - if (+err2 > 0) { - spawn('node', [join(scullyConfig.homeFolder, '/dist/scully/scully'), 'serve'], { - detached: true, - }); - } - }); - } - }); - - console.log('started servers in background'); - } else { - // debug only - console.log(`Background servers already running.`); - } - - if (!(await waitForServerToBeAvailable().catch(e => false))) { - logError('Could not connect to server'); - process.exit(15); + // kill serve ports + await httpGetJson('http://localhost:1864/killMe', {suppressErrors: true}); } - - console.log('servers available'); - await startScully(); - - if (process.argv.includes('watch')) { - _options = options; - watchMode(); - } else { - if (!isTaken) { - // kill serve ports - await httpGetJson('http://localhost:1864/killMe', {suppressErrors: true}); - } - /** done, stop the program */ - process.exit(0); - } - } - } -})(); - -// TODO : we need rewrite this to observables for don't have memory leaks -async function watchMode() { - await checkStaticFolder(); - // g for generate and the q for quit - checkForManualRestart(); - // @ts-ignore - await checkChangeAngular(_options.path, false, true); -} - -export function checkForManualRestart() { - const readline = require('readline').createInterface({ - input: process.stdin, - output: process.stdout, - }); - - readline.question(`Press g for manual regenerate, or q for close the server. \n`, command => { - if (command.toLowerCase() === 'g') { - startScully().then(() => checkForManualRestart()); - } else if (command.toLowerCase() === 'q') { - readline.close(); + /** done, stop the program */ process.exit(0); - } else { - readline.close(); - checkForManualRestart(); } - }); -} - -export function startScullyWatchMode() { - startScully(); -} - -function startStaticServer() { - staticServer(); -} - -let restartTimer: NodeJS.Timer; -export function restartStaticServer() { - // tslint:disable-next-line: no-unused-expression - restartTimer && clearTimeout(restartTimer); - restartTimer = setTimeout(() => { - closeExpress(); - startStaticServer(); - }, 500); -} - -export async function isBuildThere(config: ScullyConfig) { - const dist = join(config.homeFolder, config.distFolder); - if (existsSync(dist) && existsSync(join(dist, 'index.html'))) { - return true; } - logError(`Angular distribution files not found, run "ng build" first`); - process.exit(15); -} +})(); diff --git a/scully/startBackgroundServer.ts b/scully/startBackgroundServer.ts new file mode 100644 index 000000000..28e1be56d --- /dev/null +++ b/scully/startBackgroundServer.ts @@ -0,0 +1,27 @@ +import {spawn} from 'child_process'; +import {existsSync} from 'fs-extra'; +import {join} from 'path'; +import {ScullyConfig} from './utils/interfacesandenums'; +import {logError, log, green} from './utils/log'; + +export function startBackgroundServer(scullyConfig: ScullyConfig) { + const binary = ['/dist/scully/scully', '/node_modules/.bin/scully', '/node_modules/@scullyio/scully/scully'] + .map(p => join(scullyConfig.homeFolder, p + '.js')) + .find(p => existsSync(p)); + + if (!binary) { + logError('Could not find scully binaries'); + process.exit(15); + return; + } + spawn('node', [binary, 'serve'], { + detached: true, + // stdio: 'inherit', + }).on('close', err => { + if (+err > 0) { + logError('Problem starting background servers', err); + process.exit(15); + } + }); + log(` ${green('☺')} Started servers in background`); +} diff --git a/scully/tsconfig.scully.json b/scully/tsconfig.scully.json index b72a3e5e4..df4e81ab9 100644 --- a/scully/tsconfig.scully.json +++ b/scully/tsconfig.scully.json @@ -27,5 +27,5 @@ ] }, "files": ["./index.ts", "scully.ts"], - "exclude": ["bin/**/*","bin/**/*.d.ts", "bin/index.js"] + "exclude": ["bin/**/*", "bin/**/*.d.ts", "bin/index.js", "../dist/**/*"] } diff --git a/scully/utils/fsAngular.ts b/scully/utils/fsAngular.ts index 65c536079..851e1bd7e 100644 --- a/scully/utils/fsAngular.ts +++ b/scully/utils/fsAngular.ts @@ -4,12 +4,11 @@ import {copy, remove} from 'fs-extra'; import {join} from 'path'; import {Observable} from 'rxjs'; import {debounceTime, filter, tap} from 'rxjs/operators'; -import {restartStaticServer, startScullyWatchMode} from '../scully'; +import {restartStaticServer, startScullyWatchMode} from '../watchMode'; import {green, log, logWarn, red} from './log'; import {scullyConfig} from './config'; import {createFolderFor} from './createFolderFor'; - export async function checkChangeAngular( folder = join(scullyConfig.homeFolder, scullyConfig.distFolder) || join(scullyConfig.homeFolder, './dist/browser'), @@ -57,19 +56,6 @@ function watchFolder(folder): Observable<{eventType: string; fileName: string}> }); } -export function existDistAngular(src) { - try { - if (!existsSync(src)) { - log(`${red(`Build not found`)}.`); - log(`Please build first your angular app`); - return false; - } - return true; - } catch (e) { - return false; - } -} - // tslint:disable-next-line:no-shadowed-variable export async function moveDistAngular(src, dest, {reset = true, removeStaticDist = false}, watch = false) { try { diff --git a/scully/utils/fsFolder.ts b/scully/utils/fsFolder.ts index c83c04ecb..150f54463 100644 --- a/scully/utils/fsFolder.ts +++ b/scully/utils/fsFolder.ts @@ -5,12 +5,10 @@ import {filter, throttleTime} from 'rxjs/operators'; import {log, red} from './log'; import {watch} from 'chokidar'; import {scullyConfig} from './config'; -import {startScullyWatchMode} from '../scully'; - +import {startScullyWatchMode} from '../watchMode'; // tslint:disable-next-line:no-shadowed-variable export async function checkStaticFolder() { - try { const config = scullyConfig.routes; // require(join(scullyConfig.homeFolder, 'scully.config.js')); const folder = []; @@ -31,7 +29,7 @@ export async function checkStaticFolder() { } } } catch (e) { - console.log('error into read the config', e); + console.log('error into read the config', e); } } @@ -45,14 +43,14 @@ function reWatch(folder) { throttleTime(3000) ) .subscribe({ - next: (v) => { + next: v => { if (v.eventType !== 'addDir') { console.log('--------------------------------------------------'); console.log(`New ${v.eventType} in ${v.fileName}, re run scully.`); console.log('--------------------------------------------------'); startScullyWatchMode(); } - } + }, }); } diff --git a/scully/utils/httpGetJson.ts b/scully/utils/httpGetJson.ts index 3a0f9a431..ddb00bd47 100644 --- a/scully/utils/httpGetJson.ts +++ b/scully/utils/httpGetJson.ts @@ -31,7 +31,7 @@ export function httpGetJson( ); } if (error) { - console.error(error.message); + // console.error(error.message); // Consume response data to free up memory res.resume(); return reject(error); @@ -52,7 +52,7 @@ export function httpGetJson( }); }).on('error', e => { if (!suppressErrors) { - console.error(`Got error: ${e.message}`); + // console.error(`Got error: ${e.message}`); reject(e); } else { resolve(undefined); diff --git a/scully/utils/isPortTaken.ts b/scully/utils/isPortTaken.ts index 8ecd2ff40..ee532a3c3 100644 --- a/scully/utils/isPortTaken.ts +++ b/scully/utils/isPortTaken.ts @@ -1,6 +1,4 @@ import {createServer} from 'net'; -import {logWarn} from './log'; -// const net = require('net'); export const isPortTaken = (usedPort: number): Promise => new Promise((resolve, reject) => { @@ -23,7 +21,3 @@ export const isPortTaken = (usedPort: number): Promise => }) .listen(usedPort); }); -// .then((r: boolean) => { -// logWarn(`port ${usedPort} is ${r ? 'taken' : 'free'}`); -// return r; -// }); diff --git a/scully/utils/staticServer.ts b/scully/utils/staticServer.ts index a871eaee8..4bc54b37b 100644 --- a/scully/utils/staticServer.ts +++ b/scully/utils/staticServer.ts @@ -35,13 +35,12 @@ export async function staticServer(port?: number) { const angularDistServer = express(); angularDistServer.get('/_pong', (req, res) => { - res.json({res: true}); + res.json({res: true, homeFolder: scullyConfig.homeFolder}); }); - angularDistServer.get('/killMe', (req, res) => { - closeExpress(); - try { - process.exit(0); - } catch (e) { } + angularDistServer.get('/killMe', async (req, res) => { + await res.json({ok: true}); + await closeExpress(); + process.exit(0); }); /** use express to serve all static assets in dist folder. */ angularDistServer.use(express.static(distFolder, options)); diff --git a/scully/utils/waitForServerToBeAvailable.ts b/scully/utils/waitForServerToBeAvailable.ts index 53f6458c9..62011f0a1 100644 --- a/scully/utils/waitForServerToBeAvailable.ts +++ b/scully/utils/waitForServerToBeAvailable.ts @@ -1,4 +1,6 @@ +import {scullyConfig} from './config'; import {httpGetJson} from './httpGetJson'; +import {logWarn} from './log'; /** * Wait until our server is up, and accepting requests */ @@ -7,20 +9,26 @@ export const waitForServerToBeAvailable = () => let tries = 0; const tryServer = () => { ++tries; - if (tries > 150) { + if (tries > 500) { reject(`server didn't respond`); } httpGetJson('http://localhost:1864/_pong', {suppressErrors: true}) .then((res: any) => { - if (res.res) { + if (res && res.res) { + if (res.homeFolder !== scullyConfig.homeFolder) { + logWarn( + '`scully serve` is running in a different project. you can kill it by running `scully killServer`' + ); + process.exit(15); + } resolve(true); return; } - setTimeout(tryServer, 100); + setTimeout(tryServer, 125); }) .catch(e => { - // console.log(e) - setTimeout(tryServer, 100); + console.log(e); + setTimeout(tryServer, 125); }); }; tryServer(); diff --git a/scully/watchMode.ts b/scully/watchMode.ts new file mode 100644 index 000000000..707a67be9 --- /dev/null +++ b/scully/watchMode.ts @@ -0,0 +1,89 @@ +import {existsSync} from 'fs-extra'; +import {join} from 'path'; +import * as yargs from 'yargs'; +import {ScullyConfig, startScully} from '.'; +import {checkChangeAngular} from './utils/fsAngular'; +import {checkStaticFolder} from './utils/fsFolder'; +import {httpGetJson} from './utils/httpGetJson'; +import {logError} from './utils/log'; +import {closeExpress, staticServer} from './utils/staticServer'; + +const {argv: options} = yargs + .option('path', { + alias: 'p', + type: 'string', + description: 'The path to generate', + }) + .option('port', { + alias: 'p', + type: 'number', + description: 'The port to run on', + }); + +export async function bootServe(scullyConfig: ScullyConfig) { + const port = options.path || scullyConfig.staticport; + console.log('starting static server'); + process.title = 'ScullyServer'; + checkChangeAngular(options.path); + console.log(scullyConfig.homeFolder, options.folder); + if (scullyConfig.homeFolder !== options.folder) { + closeExpress(); + await httpGetJson('http://localhost:1864/killMe', {suppressErrors: true}); + } + restartStaticServer(); +} + +// TODO : we need rewrite this to observables for don't have memory leaks +export async function watchMode() { + await checkStaticFolder(); + // g for generate and the q for quit + checkForManualRestart(); + // @ts-ignore + await checkChangeAngular(_options.path, false, true); +} + +export function checkForManualRestart() { + const readline = require('readline').createInterface({ + input: process.stdin, + output: process.stdout, + }); + + readline.question(`Press g for manual regenerate, or q for close the server. \n`, command => { + if (command.toLowerCase() === 'g') { + startScully().then(() => checkForManualRestart()); + } else if (command.toLowerCase() === 'q') { + readline.close(); + process.exit(0); + } else { + readline.close(); + checkForManualRestart(); + } + }); +} + +export function startScullyWatchMode() { + startScully(); +} + +function startStaticServer() { + staticServer(); +} + +let restartTimer: NodeJS.Timer; +export function restartStaticServer() { + // tslint:disable-next-line: no-unused-expression + restartTimer && clearTimeout(restartTimer); + restartTimer = setTimeout(() => { + closeExpress(); + startStaticServer(); + }, 500); +} + +export async function isBuildThere(config: ScullyConfig) { + const dist = join(config.homeFolder, config.distFolder); + if (existsSync(dist) && existsSync(join(dist, 'index.html'))) { + return true; + } + logError(`Angular distribution files not found, run "ng build" first`); + process.exit(15); +} diff --git a/tsconfig.json b/tsconfig.json index 119083dd8..651f19a0a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,28 +12,15 @@ "moduleResolution": "node", "importHelpers": true, "target": "es2015", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2018", - "dom" - ], + "typeRoots": ["node_modules/@types"], + "lib": ["es2018", "dom"], "paths": { - "@scullyio/scully": [ - "scully" - ], - "@scullyio/ng-lib": [ - "dist/scullyio/ng-lib" - ], - "@scullyio/ng-lib/*": [ - "dist/scullyio/ng-lib/*" - ] + "@scullyio/scully": ["scully"], + "@scullyio/ng-lib": ["dist/scullyio/ng-lib"], + "@scullyio/ng-lib/*": ["dist/scullyio/ng-lib/*"] } }, - "exclude": [ - "scully/bin/**/*" - ], + "exclude": ["dist/scully/**/*"], "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true