Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(serve): support --consolelogs option #100

Merged
merged 5 commits into from
Mar 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export interface CordovaBuildBuilderSchema {
sourceMap?: boolean;
cordovaAssets?: boolean;
cordovaMock?: boolean;
consolelogs?: boolean;
consolelogsPort?: number;
tlancina marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 9 additions & 0 deletions builders/cordova-build/schema.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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