Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(cordova-serve): support --consolelogs option (#100)
  • Loading branch information
tlancina authored and imhoffd committed Mar 21, 2019
1 parent a52e9bd commit 07af906
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,3 +5,4 @@ node_modules
!builders/**/schema.d.ts
!schematics/**/schema.d.ts
!schematics/*/files/**/*
.vscode
Empty file added assets/consolelog-config.js
Empty file.
73 changes: 73 additions & 0 deletions assets/consolelogs.js
@@ -0,0 +1,73 @@
// Script injected by @ionic/angular-toolkit to send console logs back
// to a websocket server so they can be printed to the terminal
window.Ionic = window.Ionic || {}; window.Ionic.ConsoleLogServer = {
start: function(config) {
var self = this;

this.socket = new WebSocket('ws://' + window.location.hostname + ':' + String(config.wsPort));
this.msgQueue = [];

this.socket.onopen = function() {
self.socketReady = true;

self.socket.onclose = function() {
self.socketReady = false;
console.warn('Console log server closed');
};
};

this.patchConsole();
},

queueMessageSend: function(msg) {
this.msgQueue.push(msg);
this.drainMessageQueue();
},

drainMessageQueue: function() {
var msg;
while (msg = this.msgQueue.shift()) {
if (this.socketReady) {
try {
this.socket.send(JSON.stringify(msg));
} catch(e) {
if (!(e instanceof TypeError)) {
console.error('ws error: ' + e);
}
}
}
}
},

patchConsole: function() {
var self = this;

function _patchConsole(consoleType) {
console[consoleType] = (function() {
var orgConsole = console[consoleType];
return function() {
orgConsole.apply(console, arguments);
var msg = {
category: 'console',
type: consoleType,
data: []
};
for (var i = 0; i < arguments.length; i++) {
msg.data.push(arguments[i]);
}
if (msg.data.length) {
self.queueMessageSend(msg);
}
};
})();
}

// https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-console/#supported-methods
var consoleFns = ['log', 'error', 'exception', 'warn', 'info', 'debug', 'assert', 'dir', 'dirxml', 'time', 'timeEnd', 'table'];
for (var i in consoleFns) {
_patchConsole(consoleFns[i]);
}
},
};

Ionic.ConsoleLogServer.start(Ionic.ConsoleLogServerConfig || {});
File renamed without changes.
21 changes: 20 additions & 1 deletion builders/cordova-build/index.ts
@@ -1,6 +1,7 @@
import { BuildEvent, Builder, BuilderConfiguration, BuilderContext, BuilderDescription } from '@angular-devkit/architect';
import { BrowserBuilderSchema } from '@angular-devkit/build-angular/src/browser/schema';
import { getSystemPath, join, normalize } from '@angular-devkit/core';
import { writeFileSync } from 'fs';
import { Observable, of } from 'rxjs';
import { concatMap, tap } from 'rxjs/operators';

Expand Down Expand Up @@ -54,9 +55,27 @@ export class CordovaBuildBuilder implements Builder<CordovaBuildBuilderSchema> {
// by default. Let's keep it around.
browserOptions.deleteOutputPath = false;

if (options.consolelogs) {
// Write the config to a file, and then include that in the bundle so it loads on window
const configPath = getSystemPath(join(normalize(__dirname), '../../assets', normalize('consolelog-config.js')));
writeFileSync(configPath, `window.Ionic = window.Ionic || {}; Ionic.ConsoleLogServerConfig = { wsPort: ${options.consolelogsPort} }`);

browserOptions.scripts.push({
input: configPath,
bundleName: 'consolelogs',
lazy: false,
});

browserOptions.scripts.push({
input: getSystemPath(join(normalize(__dirname), '../../assets', normalize('consolelogs.js'))),
bundleName: 'consolelogs',
lazy: false,
});
}

if (options.cordovaMock) {
browserOptions.scripts.push({
input: getSystemPath(join(normalize(__dirname), normalize('cordova.js'))),
input: getSystemPath(join(normalize(__dirname), '../../assets', normalize('cordova.js'))),
bundleName: 'cordova',
lazy: false,
});
Expand Down
2 changes: 2 additions & 0 deletions builders/cordova-build/schema.d.ts
Expand Up @@ -5,4 +5,6 @@ export interface CordovaBuildBuilderSchema {
sourceMap?: boolean;
cordovaAssets?: boolean;
cordovaMock?: boolean;
consolelogs?: boolean;
consolelogsPort?: number;
}
9 changes: 9 additions & 0 deletions builders/cordova-build/schema.json
Expand Up @@ -7,6 +7,15 @@
"type": "string",
"description": "Target to build."
},
"consolelogs": {
"type": "boolean",
"description": "Inject script to print console logs to the terminal."
},
"consolelogsPort": {
"type": "number",
"description": "Port for console log server.",
"default": 53703
},
"platform": {
"type": "string",
"description": "Cordova platform to use during build."
Expand Down
15 changes: 12 additions & 3 deletions builders/cordova-serve/index.ts
Expand Up @@ -3,11 +3,12 @@ import { NormalizedBrowserBuilderSchema } from '@angular-devkit/build-angular/sr
import { DevServerBuilder, DevServerBuilderOptions } from '@angular-devkit/build-angular/src/dev-server';
import { Path, virtualFs } from '@angular-devkit/core';
import * as fs from 'fs';
import { Observable, of } from 'rxjs';
import { Observable, from, of } from 'rxjs';
import { concatMap, tap } from 'rxjs/operators';

import { CordovaBuildBuilder, CordovaBuildBuilderSchema } from '../cordova-build';

import { createConsoleLogServer } from './log-server';
import { CordovaServeBuilderSchema } from './schema';

export class CordovaServeBuilder implements Builder<CordovaServeBuilderSchema> {
Expand Down Expand Up @@ -36,9 +37,9 @@ export class CordovaServeBuilder implements Builder<CordovaServeBuilderSchema> {
}

protected _getCordovaBuildConfig(cordovaServeOptions: CordovaServeBuilderSchema): Observable<BuilderConfiguration<CordovaBuildBuilderSchema>> {
const { platform, cordovaBasePath, cordovaAssets, cordovaMock } = cordovaServeOptions;
const { platform, cordovaBasePath, cordovaAssets, cordovaMock, consolelogs, consolelogsPort } = cordovaServeOptions;
const [ project, target, configuration ] = cordovaServeOptions.cordovaBuildTarget.split(':');
const cordovaBuildTargetSpec = { project, target, configuration, overrides: { platform, cordovaBasePath, cordovaAssets, cordovaMock } };
const cordovaBuildTargetSpec = { project, target, configuration, overrides: { platform, cordovaBasePath, cordovaAssets, cordovaMock, consolelogs, consolelogsPort } };
const cordovaBuildTargetConfig = this.context.architect.getBuilderConfiguration<CordovaBuildBuilderSchema>(cordovaBuildTargetSpec);

return this.context.architect.getBuilderDescription(cordovaBuildTargetConfig).pipe(
Expand All @@ -52,6 +53,14 @@ class CordovaDevServerBuilder extends DevServerBuilder {
super(context);
}

run(builderConfig: BuilderConfiguration<DevServerBuilderOptions>): Observable<BuildEvent> {
if (this.cordovaBuildOptions.consolelogs && this.cordovaBuildOptions.consolelogsPort) {
return from(createConsoleLogServer(builderConfig.options.host, this.cordovaBuildOptions.consolelogsPort))
.pipe(_ => super.run(builderConfig));
}
return super.run(builderConfig);
}

buildWebpackConfig(root: Path, projectRoot: Path, host: virtualFs.Host<fs.Stats>, browserOptions: NormalizedBrowserBuilderSchema) {
const builder = new CordovaBuildBuilder(this.context);
builder.validateBuilderConfig(this.cordovaBuildOptions);
Expand Down
78 changes: 78 additions & 0 deletions builders/cordova-serve/log-server.ts
@@ -0,0 +1,78 @@
import { terminal } from '@angular-devkit/core';
import * as util from 'util';
import * as WebSocket from 'ws';

export interface ConsoleLogServerMessage {
category: 'console';
type: string;
data: any[];
}

export interface ConsoleLogServerOptions {
consolelogs: boolean;
consolelogsPort: number;
}

export function isConsoleLogServerMessage(m: any): m is ConsoleLogServerMessage {
return m
&& typeof m.category === 'string'
&& typeof m.type === 'string'
&& m.data && typeof m.data.length === 'number';
}

export async function createConsoleLogServer(host: string, port: number): Promise<WebSocket.Server> {
const wss = new WebSocket.Server({ host, port });

wss.on('connection', ws => {
ws.on('message', data => {
let msg;

try {
data = data.toString();
msg = JSON.parse(data);
} catch (e) {
process.stderr.write(`Error parsing JSON message from client: "${data}" ${terminal.red(e.stack ? e.stack : e)}\n`);
return;
}

if (!isConsoleLogServerMessage(msg)) {
const m = util.inspect(msg, { colors: true });
process.stderr.write(`Bad format in client message: ${m}\n`);
return;
}

if (msg.category === 'console') {
let status: ((_: string) => string) | undefined;

if (msg.type === 'info' || msg.type === 'log') {
status = terminal.reset;
} else if (msg.type === 'error') {
status = terminal.red;
} else if (msg.type === 'warn') {
status = terminal.yellow;
}

// pretty print objects and arrays (no newlines for arrays)
msg.data = msg.data.map(d => JSON.stringify(d, undefined, d && d.length ? '' : ' '));

if (status) {
process.stdout.write(`[${status('console.' + msg.type)}]: ${msg.data.join(' ')}\n`);
} else {
process.stdout.write(`[console]: ${msg.data.join(' ')}\n`);
}
}
});

ws.on('error', (err: NodeJS.ErrnoException) => {
if (err && err.code !== 'ECONNRESET') {
process.stderr.write(`There was an error with the logging stream: ${JSON.stringify(err)}\n`);
}
});
});

wss.on('error', (err: NodeJS.ErrnoException) => {
process.stderr.write(`There was an error with the logging websocket: ${JSON.stringify(err)}\n`);
});

return wss;
}
2 changes: 2 additions & 0 deletions builders/cordova-serve/schema.d.ts
Expand Up @@ -9,4 +9,6 @@ export interface CordovaServeBuilderSchema {
sourceMap?: boolean;
cordovaAssets?: boolean;
cordovaMock?: boolean;
consolelogs?: boolean;
consolelogsPort?: number;
}
9 changes: 9 additions & 0 deletions builders/cordova-serve/schema.json
Expand Up @@ -7,6 +7,15 @@
"type": "string",
"description": "Target to use for build."
},
"consolelogs": {
"type": "boolean",
"description": "Print console logs to the terminal."
},
"consolelogsPort": {
"type": "number",
"description": "Port for console log server.",
"default": 53703
},
"devServerTarget": {
"type": "string",
"description": "Target to use for serve."
Expand Down
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -32,7 +32,8 @@
"dependencies": {
"@schematics/angular": "^7.0.3",
"tslib": "^1.9.0",
"typescript": "^3.2.4"
"typescript": "^3.2.4",
"ws": "^6.1.4"
},
"devDependencies": {
"@angular-devkit/architect": "0.13.1",
Expand All @@ -46,6 +47,7 @@
"@types/node": "^8.10.34",
"@types/webpack": "^4.4.14",
"@types/webpack-dev-server": "^3.1.1",
"@types/ws": "^6.0.1",
"commitizen": "^3.0.2",
"cz-conventional-changelog": "^2.1.0",
"husky": "^1.1.1",
Expand Down

0 comments on commit 07af906

Please sign in to comment.