Comparing version 0.9.0-beta1 to 0.9.0-beta10
@@ -12,2 +12,9 @@ "use strict"; | ||
exports.DEFAULT_ROWS = 24; | ||
/** | ||
* Default messages to indicate PAUSE/RESUME for automatic flow control. | ||
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh), | ||
* the sequences can be customized in `IPtyForkOptions`. | ||
*/ | ||
var FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF | ||
var FLOW_CONTROL_RESUME = '\x11'; // defaults to XON | ||
var Terminal = /** @class */ (function () { | ||
@@ -32,2 +39,6 @@ function Terminal(opt) { | ||
this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string'); | ||
// setup flow control handling | ||
this.handleFlowControl = !!(opt.handleFlowControl); | ||
this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE; | ||
this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME; | ||
} | ||
@@ -59,2 +70,17 @@ Object.defineProperty(Terminal.prototype, "onData", { | ||
}); | ||
Terminal.prototype.write = function (data) { | ||
if (this.handleFlowControl) { | ||
// PAUSE/RESUME messages are not forwarded to the pty | ||
if (data === this._flowControlPause) { | ||
this.pause(); | ||
return; | ||
} | ||
if (data === this._flowControlResume) { | ||
this.resume(); | ||
return; | ||
} | ||
} | ||
// everything else goes to the real pty | ||
this._writeMethod(data); | ||
}; | ||
Terminal.prototype._forwardEvents = function () { | ||
@@ -61,0 +87,0 @@ var _this = this; |
@@ -6,4 +6,44 @@ "use strict"; | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var assert = require("assert"); | ||
var windowsTerminal_1 = require("./windowsTerminal"); | ||
var unixTerminal_1 = require("./unixTerminal"); | ||
var pollUntil = require("pollUntil"); | ||
var terminalConstructor = (process.platform === 'win32') ? windowsTerminal_1.WindowsTerminal : unixTerminal_1.UnixTerminal; | ||
var SHELL = (process.platform === 'win32') ? 'cmd.exe' : '/bin/bash'; | ||
var terminalCtor; | ||
@@ -22,3 +62,44 @@ if (process.platform === 'win32') { | ||
}); | ||
describe('automatic flow control', function () { | ||
it('should respect ctor flow control options', function () { | ||
var pty = new terminalConstructor(SHELL, [], { handleFlowControl: true, flowControlPause: 'abc', flowControlResume: '123' }); | ||
assert.equal(pty.handleFlowControl, true); | ||
assert.equal(pty._flowControlPause, 'abc'); | ||
assert.equal(pty._flowControlResume, '123'); | ||
}); | ||
it('should do flow control automatically', function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var pty, read; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
this.timeout(10000); | ||
pty = new terminalConstructor(SHELL, [], { handleFlowControl: true, flowControlPause: 'PAUSE', flowControlResume: 'RESUME' }); | ||
read = ''; | ||
pty.on('data', function (data) { return read += data; }); | ||
pty.on('pause', function () { return read += 'paused'; }); | ||
pty.on('resume', function () { return read += 'resumed'; }); | ||
pty.write('1'); | ||
pty.write('PAUSE'); | ||
pty.write('2'); | ||
pty.write('RESUME'); | ||
pty.write('3'); | ||
return [4 /*yield*/, pollUntil(function () { | ||
// important here: no data should be delivered between 'paused' and 'resumed' | ||
if (process.platform === 'win32') { | ||
read.endsWith('1\u001b[0Kpausedresumed2\u001b[0K3\u001b[0K'); | ||
} | ||
else { | ||
read.endsWith('1pausedresumed23'); | ||
} | ||
}, [], 20, 10)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
//# sourceMappingURL=terminal.test.js.map |
@@ -136,2 +136,4 @@ "use strict"; | ||
_this._forwardEvents(); | ||
// attach write method | ||
_this._writeMethod = function (data) { return _this._socket.write(data); }; | ||
return _this; | ||
@@ -163,10 +165,14 @@ } | ||
var rows = opt.rows || terminal_1.DEFAULT_ROWS; | ||
var encoding = opt.encoding ? 'utf8' : opt.encoding; | ||
var encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); | ||
// open | ||
var term = pty.open(cols, rows); | ||
self._master = new PipeSocket(term.master); | ||
self._master.setEncoding(encoding); | ||
if (encoding !== null) { | ||
self._master.setEncoding(encoding); | ||
} | ||
self._master.resume(); | ||
self._slave = new PipeSocket(term.slave); | ||
self._slave.setEncoding(encoding); | ||
if (encoding !== null) { | ||
self._slave.setEncoding(encoding); | ||
} | ||
self._slave.resume(); | ||
@@ -192,5 +198,2 @@ self._socket = self._master; | ||
}; | ||
UnixTerminal.prototype.write = function (data) { | ||
this._socket.write(data); | ||
}; | ||
UnixTerminal.prototype.destroy = function () { | ||
@@ -197,0 +200,0 @@ var _this = this; |
@@ -25,4 +25,5 @@ "use strict"; | ||
var WindowsPtyAgent = /** @class */ (function () { | ||
function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty) { | ||
function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty, conptyInheritCursor) { | ||
var _this = this; | ||
if (conptyInheritCursor === void 0) { conptyInheritCursor = false; } | ||
this._useConpty = _useConpty; | ||
@@ -60,3 +61,3 @@ if (this._useConpty === undefined || this._useConpty === true) { | ||
if (this._useConpty) { | ||
term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName()); | ||
term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor); | ||
} | ||
@@ -175,3 +176,2 @@ else { | ||
// Something went wrong, just send back the shell PID | ||
console.error('Could not fetch console process list'); | ||
agent.kill(); | ||
@@ -178,0 +178,0 @@ resolve([_this._innerPid]); |
@@ -49,3 +49,3 @@ "use strict"; | ||
// Create new termal. | ||
_this._agent = new windowsPtyAgent_1.WindowsPtyAgent(file, args, parsedEnv, cwd, _this._cols, _this._rows, false, opt.experimentalUseConpty); | ||
_this._agent = new windowsPtyAgent_1.WindowsPtyAgent(file, args, parsedEnv, cwd, _this._cols, _this._rows, false, opt.experimentalUseConpty, opt.conptyInheritCursor); | ||
_this._socket = _this._agent.outSocket; | ||
@@ -108,2 +108,4 @@ // Not available until `ready` event emitted. | ||
_this._forwardEvents(); | ||
// attach write method | ||
_this._writeMethod = function (data) { return _this._defer(function () { return _this._agent.inSocket.write(data); }); }; | ||
return _this; | ||
@@ -118,11 +120,2 @@ } | ||
/** | ||
* Events | ||
*/ | ||
WindowsTerminal.prototype.write = function (data) { | ||
var _this = this; | ||
this._defer(function () { | ||
_this._agent.inSocket.write(data); | ||
}); | ||
}; | ||
/** | ||
* TTY | ||
@@ -129,0 +122,0 @@ */ |
@@ -7,3 +7,3 @@ { | ||
}, | ||
"version": "0.9.0-beta1", | ||
"version": "0.9.0-beta10", | ||
"license": "MIT", | ||
@@ -43,3 +43,4 @@ "main": "./lib/index.js", | ||
"test": "cross-env NODE_ENV=test mocha -R spec --exit lib/*.test.js", | ||
"prepublish": "npm run tsc" | ||
"prepare": "npm run tsc", | ||
"prepublishOnly": "npm run tsc" | ||
}, | ||
@@ -46,0 +47,0 @@ "dependencies": { |
@@ -28,2 +28,3 @@ # node-pty | ||
- [Extraterm](http://extraterm.org/) | ||
- [Wetty](https://github.com/krishnasrinivas/wetty) Browser based Terminal over HTTP and HTTPS | ||
@@ -103,2 +104,25 @@ Do you use node-pty in your application as well? Please open a [Pull Request](https://github.com/Tyriar/node-pty/pulls) to include it here. We would love to have it in our list. | ||
## Flow Control | ||
Automatic flow control can be enabled by either providing `handleFlowControl = true` in the constructor options or setting it later on: | ||
```js | ||
const PAUSE = '\x13'; // XOFF | ||
const RESUME = '\x11'; // XON | ||
const ptyProcess = pty.spawn(shell, [], {handleFlowControl: true}); | ||
// flow control in action | ||
ptyProcess.write(PAUSE); // pty will block and pause the slave program | ||
... | ||
ptyProcess.write(RESUME); // pty will enter flow mode and resume the slave program | ||
// temporarily disable/re-enable flow control | ||
ptyProcess.handleFlowControl = false; | ||
... | ||
ptyProcess.handleFlowControl = true; | ||
``` | ||
By default `PAUSE` and `RESUME` are XON/XOFF control codes (as shown above). To avoid conflicts in environments that use these control codes for different purposes the messages can be customized as `flowControlPause: string` and `flowControlResume: string` in the constructor options. `PAUSE` and `RESUME` are not passed to the underlying pseudoterminal if flow control is enabled. | ||
## Troubleshooting | ||
@@ -105,0 +129,0 @@ |
@@ -120,2 +120,5 @@ /** | ||
experimentalUseConpty?: boolean | undefined; | ||
handleFlowControl?: boolean; | ||
flowControlPause?: string; | ||
flowControlResume?: string; | ||
} | ||
@@ -122,0 +125,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
interface IConptyNative { | ||
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string): IConptyProcess; | ||
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean): IConptyProcess; | ||
connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number }; | ||
@@ -9,0 +9,0 @@ resize(ptyId: number, cols: number, rows: number): void; |
@@ -9,3 +9,7 @@ /** | ||
import { UnixTerminal } from './unixTerminal'; | ||
import pollUntil = require('pollUntil'); | ||
const terminalConstructor = (process.platform === 'win32') ? WindowsTerminal : UnixTerminal; | ||
const SHELL = (process.platform === 'win32') ? 'cmd.exe' : '/bin/bash'; | ||
let terminalCtor: WindowsTerminal | UnixTerminal; | ||
@@ -18,2 +22,3 @@ if (process.platform === 'win32') { | ||
describe('Terminal', () => { | ||
@@ -28,2 +33,32 @@ describe('constructor', () => { | ||
}); | ||
describe('automatic flow control', () => { | ||
it('should respect ctor flow control options', () => { | ||
const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'abc', flowControlResume: '123'}); | ||
assert.equal(pty.handleFlowControl, true); | ||
assert.equal((pty as any)._flowControlPause, 'abc'); | ||
assert.equal((pty as any)._flowControlResume, '123'); | ||
}); | ||
it('should do flow control automatically', async function(): Promise<void> { | ||
this.timeout(10000); | ||
const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'PAUSE', flowControlResume: 'RESUME'}); | ||
let read: string = ''; | ||
pty.on('data', data => read += data); | ||
pty.on('pause', () => read += 'paused'); | ||
pty.on('resume', () => read += 'resumed'); | ||
pty.write('1'); | ||
pty.write('PAUSE'); | ||
pty.write('2'); | ||
pty.write('RESUME'); | ||
pty.write('3'); | ||
await (<any>pollUntil)(() => { | ||
// important here: no data should be delivered between 'paused' and 'resumed' | ||
if (process.platform === 'win32') { | ||
read.endsWith('1\u001b[0Kpausedresumed2\u001b[0K3\u001b[0K'); | ||
} else { | ||
read.endsWith('1pausedresumed23'); | ||
} | ||
}, [], 20, 10); | ||
}); | ||
}); | ||
}); |
@@ -16,2 +16,10 @@ /** | ||
/** | ||
* Default messages to indicate PAUSE/RESUME for automatic flow control. | ||
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh), | ||
* the sequences can be customized in `IPtyForkOptions`. | ||
*/ | ||
const FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF | ||
const FLOW_CONTROL_RESUME = '\x11'; // defaults to XON | ||
export abstract class Terminal implements ITerminal { | ||
@@ -32,2 +40,6 @@ protected _socket: Socket; | ||
protected _internalee: EventEmitter; | ||
protected _writeMethod: (data: string) => void; | ||
private _flowControlPause: string; | ||
private _flowControlResume: string; | ||
public handleFlowControl: boolean; | ||
@@ -61,4 +73,25 @@ private _onData = new EventEmitter2<string>(); | ||
this._checkType('encoding', opt.encoding ? opt.encoding : null, 'string'); | ||
// setup flow control handling | ||
this.handleFlowControl = !!(opt.handleFlowControl); | ||
this._flowControlPause = opt.flowControlPause || FLOW_CONTROL_PAUSE; | ||
this._flowControlResume = opt.flowControlResume || FLOW_CONTROL_RESUME; | ||
} | ||
public write(data: string): void { | ||
if (this.handleFlowControl) { | ||
// PAUSE/RESUME messages are not forwarded to the pty | ||
if (data === this._flowControlPause) { | ||
this.pause(); | ||
return; | ||
} | ||
if (data === this._flowControlResume) { | ||
this.resume(); | ||
return; | ||
} | ||
} | ||
// everything else goes to the real pty | ||
this._writeMethod(data); | ||
} | ||
protected _forwardEvents(): void { | ||
@@ -137,3 +170,2 @@ this.on('data', e => this._onData.fire(e)); | ||
public abstract write(data: string): void; | ||
public abstract resize(cols: number, rows: number): void; | ||
@@ -140,0 +172,0 @@ public abstract destroy(): void; |
@@ -9,4 +9,4 @@ /** | ||
export interface IExitEvent { | ||
exitCode: number; | ||
signal: number | undefined; | ||
exitCode: number; | ||
signal: number | undefined; | ||
} | ||
@@ -13,0 +13,0 @@ |
@@ -159,2 +159,5 @@ /** | ||
this._forwardEvents(); | ||
// attach write method | ||
this._writeMethod = (data: string) => this._socket.write(data); | ||
} | ||
@@ -179,3 +182,3 @@ | ||
const rows = opt.rows || DEFAULT_ROWS; | ||
const encoding = opt.encoding ? 'utf8' : opt.encoding; | ||
const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); | ||
@@ -186,7 +189,11 @@ // open | ||
self._master = new PipeSocket(<number>term.master); | ||
self._master.setEncoding(encoding); | ||
if (encoding !== null) { | ||
self._master.setEncoding(encoding); | ||
} | ||
self._master.resume(); | ||
self._slave = new PipeSocket(term.slave); | ||
self._slave.setEncoding(encoding); | ||
if (encoding !== null) { | ||
self._slave.setEncoding(encoding); | ||
} | ||
self._slave.resume(); | ||
@@ -219,6 +226,2 @@ | ||
public write(data: string): void { | ||
this._socket.write(data); | ||
} | ||
public destroy(): void { | ||
@@ -225,0 +228,0 @@ this._close(); |
@@ -54,3 +54,4 @@ /** | ||
debug: boolean, | ||
private _useConpty: boolean | undefined | ||
private _useConpty: boolean | undefined, | ||
conptyInheritCursor: boolean = false | ||
) { | ||
@@ -88,3 +89,3 @@ if (this._useConpty === undefined || this._useConpty === true) { | ||
if (this._useConpty) { | ||
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName()); | ||
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor); | ||
} else { | ||
@@ -182,3 +183,2 @@ term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug); | ||
// Something went wrong, just send back the shell PID | ||
console.error('Could not fetch console process list'); | ||
agent.kill(); | ||
@@ -185,0 +185,0 @@ resolve([ this._innerPid ]); |
@@ -13,2 +13,3 @@ /** | ||
import { assign } from './utils'; | ||
import { IWindowsPtyForkOptions } from 'node-pty'; | ||
@@ -23,3 +24,3 @@ const DEFAULT_FILE = 'cmd.exe'; | ||
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) { | ||
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) { | ||
super(opt); | ||
@@ -51,3 +52,3 @@ | ||
// Create new termal. | ||
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.experimentalUseConpty); | ||
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.experimentalUseConpty, opt.conptyInheritCursor); | ||
this._socket = this._agent.outSocket; | ||
@@ -125,2 +126,5 @@ | ||
this._forwardEvents(); | ||
// attach write method | ||
this._writeMethod = (data: string) => this._defer(() => this._agent.inSocket.write(data)); | ||
} | ||
@@ -137,12 +141,2 @@ | ||
/** | ||
* Events | ||
*/ | ||
public write(data: string): void { | ||
this._defer(() => { | ||
this._agent.inSocket.write(data); | ||
}); | ||
} | ||
/** | ||
* TTY | ||
@@ -149,0 +143,0 @@ */ |
@@ -18,3 +18,3 @@ /** | ||
*/ | ||
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions): IPty; | ||
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty; | ||
@@ -30,2 +30,11 @@ export interface IPtyForkOptions { | ||
encoding?: string; | ||
} | ||
export interface IWindowsPtyForkOptions { | ||
name?: string; | ||
cols?: number; | ||
rows?: number; | ||
cwd?: string; | ||
env?: { [key: string]: string }; | ||
encoding?: string; | ||
/** | ||
@@ -39,2 +48,24 @@ * Whether to use the experimental ConPTY system on Windows. When this is not set, ConPTY will | ||
experimentalUseConpty?: boolean; | ||
/** | ||
* Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty. | ||
* @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole | ||
*/ | ||
conptyInheritCursor?: boolean; | ||
/** | ||
* Whether to enable flow control handling (false by default). If enabled a message of `flowControlPause` | ||
* will pause the socket and thus blocking the slave program execution due to buffer back pressure. | ||
* A message of `flowControlResume` will resume the socket into flow mode. | ||
* For performance reasons only a single message as a whole will match (no message part matching). | ||
* If flow control is enabled the `flowControlPause` and `flowControlResume` messages are not forwarded to | ||
* the underlying pseudoterminal. | ||
*/ | ||
handleFlowControl?: boolean; | ||
/** | ||
* The string that should pause the pty when `handleFlowControl` is true. Default is XOFF ('\x13'). | ||
*/ | ||
flowControlPause?: string; | ||
/** | ||
* The string that should resume the pty when `handleFlowControl` is true. Default is XON ('\x11'). | ||
*/ | ||
flowControlResume?: string; | ||
} | ||
@@ -49,3 +80,3 @@ | ||
*/ | ||
pid: number; | ||
readonly pid: number; | ||
@@ -55,3 +86,3 @@ /** | ||
*/ | ||
cols: number; | ||
readonly cols: number; | ||
@@ -61,3 +92,3 @@ /** | ||
*/ | ||
rows: number; | ||
readonly rows: number; | ||
@@ -67,5 +98,11 @@ /** | ||
*/ | ||
process: string; | ||
readonly process: string; | ||
/** | ||
* Whether to handle flow control. Useful to disable/re-enable flow control during runtime. | ||
* Use this for binary data that is likely to contain the `flowControlPause` string by accident. | ||
*/ | ||
handleFlowControl: boolean; | ||
/** | ||
* Adds an event listener for when a data event fires. This happens when data is returned from | ||
@@ -75,3 +112,3 @@ * the pty. | ||
*/ | ||
onData: IEvent<string>; | ||
readonly onData: IEvent<string>; | ||
@@ -82,3 +119,3 @@ /** | ||
*/ | ||
onExit: IEvent<{ exitCode: number, signal?: number }>; | ||
readonly onExit: IEvent<{ exitCode: number, signal?: number }>; | ||
@@ -85,0 +122,0 @@ /** |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1075834
3375
147