Doug Lauder
2023-03-22 17:22:27 -04:00
коммит произвёл GitHub
родитель b61c096497
Коммит c943ed6859
13276 изменённых файлов: 1695615 добавлений и 223189 удалений

73
webapp/scripts/build.js Обычный файл
Просмотреть файл

@@ -0,0 +1,73 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-console */
const chalk = require('chalk');
const concurrently = require('concurrently');
const {getPlatformCommands} = require('./utils.js');
async function buildAll() {
console.log(chalk.inverse.bold('Building subpackages...') + '\n');
try {
const {result} = concurrently(
getPlatformCommands('build'),
{
killOthers: 'failure',
},
);
await result;
} catch (e) {
console.error(chalk.inverse.bold.red('Failed to build subpackages'), e);
return;
}
console.log('\n' + chalk.inverse.bold('Subpackages built! Building web app...') + '\n');
// It's not necessary to run these commands through concurrently, but it makes the output consistent
try {
const {result} = concurrently([
{command: 'npm:build --workspace=channels', name: 'webapp', prefixColor: 'cyan'},
]);
await result;
} catch (e) {
console.error(chalk.inverse.bold.red('Failed to build web app'), e);
return;
}
console.log('\n' + chalk.inverse.bold('Web app built! '));
console.log(chalk.inverse.bold('Building Boards...') + '\n');
try {
const {result} = concurrently([
{command: 'npm:build --workspace=boards', name: 'boards', prefixColor: 'blue'},
]);
await result;
} catch (e) {
console.error(chalk.inverse.bold.red('Failed to build Boards'), e);
return;
}
console.log('\n' + chalk.inverse.bold('Boards built! '));
console.log(chalk.inverse.bold('Building Playbooks...') + '\n');
try {
const {result} = concurrently([
{command: 'npm:build --workspace=playbooks', name: 'playbooks', prefixColor: 'red'},
]);
await result;
} catch (e) {
console.error(chalk.inverse.bold.red('Failed to build Playbooks'), e);
return;
}
console.log('\n' + chalk.inverse.bold('Playbooks built! '));
}
buildAll();

33
webapp/scripts/dev-server.js Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-console */
const chalk = require('chalk');
const concurrently = require('concurrently');
const {getWorkspaceCommands} = require('./utils.js');
async function watchAllWithDevServer() {
console.log(chalk.inverse.bold('Watching web app and all subpackages...'));
const commands = [
{command: 'npm:dev-server:webapp', name: 'webapp', prefixColor: 'cyan'},
{command: 'npm:start:product --workspace=boards', name: 'boards', prefixColor: 'blue'},
{command: 'npm:start:product --workspace=playbooks', name: 'playbooks', prefixColor: 'red'},
];
commands.push(...getWorkspaceCommands('run'));
console.log('\n');
const {result} = concurrently(
commands,
{
killOthers: 'failure',
},
);
await result;
}
watchAllWithDevServer();

51
webapp/scripts/run.js Обычный файл
Просмотреть файл

@@ -0,0 +1,51 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-console, no-process-env */
const chalk = require('chalk');
const concurrently = require('concurrently');
const {makeRunner} = require('./runner.js');
const {getPlatformCommands} = require('./utils.js');
async function watchAll(useRunner) {
if (!useRunner) {
console.log(chalk.inverse.bold('Watching web app and all subpackages...'));
}
const commands = [
{command: 'npm:run --workspace=channels', name: 'webapp', prefixColor: 'cyan'},
{command: 'npm:start:product --workspace=boards', name: 'boards', prefixColor: 'blue'},
{command: 'npm:start:product --workspace=playbooks', name: 'playbooks', prefixColor: 'red'},
];
commands.push(...getPlatformCommands('run'));
let runner;
if (useRunner) {
runner = makeRunner(commands);
}
console.log('\n');
const {result, commands: runningCommands} = concurrently(
commands,
{
killOthers: 'failure',
outputStream: runner?.getOutputStream(),
},
);
runner?.addCloseListener(() => {
for (const command of runningCommands) {
command.kill('SIGINT');
}
});
await result;
}
const useRunner = process.argv[2] === '--runner' || process.env.MM_USE_WEBAPP_RUNNER;
watchAll(useRunner);

238
webapp/scripts/runner.js Обычный файл
Просмотреть файл

@@ -0,0 +1,238 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const {Writable} = require('stream');
const blessed = require('blessed');
const stripAnsi = require('strip-ansi');
class Runner {
commands;
filter = '';
ui;
scrollLocked = true;
buffer = [];
partialBuffer = '';
outputStream;
closeListeners = new Set();
constructor(commands) {
this.commands = commands;
this.outputStream = new Writable({
write: (chunk, encoding, callback) => this.writeToStream(chunk, encoding, callback),
});
this.makeUi(commands.map((command) => command.name), this.onFilter);
this.registerHotkeys();
}
// Initialization
makeUi(commandNames) {
// Set up screen and output panes
const screen = blessed.screen({
smartCSR: true,
dockBorders: true,
});
const output = blessed.box({
top: 0,
left: 0,
width: '100%',
height: '100%-3',
content: 'THE END IS NEVER '.repeat(1000),
tags: true,
alwaysScroll: true,
scrollable: true,
scrollbar: {
ch: '#',
style: {},
track: {
ch: '|',
},
},
style: {},
});
screen.append(output);
// Set up the menu bar
const menu = blessed.listbar({
top: '100%-3',
left: 0,
width: '100%',
height: 3,
border: {
type: 'line',
},
style: {
item: {
bg: 'red',
hover: {
bg: 'green',
},
},
selected: {
bg: 'blue',
},
},
tags: true,
autoCommandKeys: true,
mouse: true,
});
menu.add('All', () => this.onFilter(''));
for (const name of commandNames) {
menu.add(name, () => this.onFilter(name));
}
screen.append(menu);
this.ui = {
menu,
output,
screen,
};
}
registerHotkeys() {
this.ui.screen.key(['escape', 'q', 'C-c'], () => {
for (const listener of this.closeListeners) {
listener();
}
});
this.ui.screen.key(['up', 'down'], (char, key) => {
this.scrollDelta(key.name === 'up' ? -1 : 1);
});
this.ui.screen.on('wheelup', () => {
this.scrollDelta(-3);
});
this.ui.screen.on('wheeldown', () => {
this.scrollDelta(3);
});
this.ui.screen.key('end', () => {
this.scrollToBottom();
});
}
// Rendering and internal logic
renderUi() {
const filtered = this.buffer.filter((line) => this.filter === '' || line.tag === this.filter);
this.ui.output.setContent(filtered.map((line) => this.formatLine(line)).join('\n'));
if (this.scrollLocked) {
this.ui.output.scrollbar.style.inverse = true;
this.ui.output.setScrollPerc(100);
} else {
this.ui.output.scrollbar.style.inverse = false;
}
this.ui.screen.render();
}
formatLine(line) {
const color = this.commands.find((command) => command.name === line.tag)?.prefixColor;
return color ? `{bold}{${color}-fg}[${line.tag}]{/} ${line.text}` : `[${line.tag}] ${line.text}`;
}
onFilter(newFilter) {
this.filter = newFilter;
this.scrollLocked = true;
this.renderUi();
}
scrollDelta(delta) {
this.ui.output.scroll(delta);
if (this.ui.output.getScrollPerc() >= 100 || this.ui.output.getScrollHeight() <= this.ui.output.height) {
this.scrollLocked = true;
} else {
this.scrollLocked = false;
}
this.renderUi();
}
scrollToBottom() {
this.scrollLocked = true;
this.renderUi();
}
// Terminal output handling
getOutputStream() {
return this.outputStream;
}
writeToStream(chunk, encoding, callback) {
const str = String(chunk);
if (str.includes('\n')) {
const parts = str.split('\n');
// Add completed lines to buffer
this.appendToBuffer(this.partialBuffer + parts[0]);
for (let i = 1; i < parts.length - 1; i++) {
this.appendToBuffer(parts[i]);
}
// Track partial line
this.partialBuffer = parts[parts.length - 1];
} else {
// Track partial line
this.partialBuffer += str;
}
this.renderUi();
callback();
}
appendToBuffer(line) {
// This regex is more complicated than expected because it
const match = (/^\[([^\]]*)\]\s*(.*)$/).exec(stripAnsi(line));
if (match) {
this.buffer.push({tag: match[1], text: match[2]});
} else {
this.buffer.push({tag: '', text: 'Line not recognized correctly: ' + line});
}
// Keep the buffer from using too much memory by removing the oldest chunk of it every time it goes over 5000 lines
const bufferCapacity = 5000;
const capacityReduction = 1000;
if (this.buffer.length > bufferCapacity) {
this.buffer = this.buffer.slice(this.buffer.length - capacityReduction);
}
}
// Event handlers
addCloseListener(listener) {
this.closeListeners.add(listener);
}
removeCloseListener(listener) {
this.closeListeners.remove(listener);
}
}
function makeRunner(commands) {
const runner = new Runner(commands);
runner.renderUi();
return runner;
}
exports.makeRunner = makeRunner;

14
webapp/scripts/skip_integrity_check.js Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const fs = require('fs');
const content = JSON.parse(fs.readFileSync('package-lock.json', 'utf-8'));
// Skip integrity check for mmjstool, which differs on Apple Silicon M1.
// @see https://github.com/npm/cli/issues/2846
delete content.dependencies.mmjstool.integrity;
delete content.packages['node_modules/mmjstool'].integrity;
delete content.dependencies.marked.integrity;
delete content.packages['node_modules/marked'].integrity;
fs.writeFileSync('package-lock.json', JSON.stringify(content, null, 2) + '\n');

47
webapp/scripts/utils.js Обычный файл
Просмотреть файл

@@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const path = require('path');
const chalk = require('chalk');
const packageJson = require('../package.json');
function getWorkspaces() {
return packageJson.workspaces;
}
function getPlatformPackagesContainingCommand(scriptName) {
return getWorkspaces().filter((workspace) => {
if (!workspace.startsWith('platform/')) {
return false;
}
// eslint-disable-next-line global-require
const workspacePackageJson = require(path.join(__dirname, '..', workspace, 'package.json'));
return workspacePackageJson?.scripts?.[scriptName];
});
}
/**
* Returns an array of concurrently commands to run a given script on every platform workspace that contains it.
*/
function getPlatformCommands(scriptName) {
return getPlatformPackagesContainingCommand(scriptName).map((workspace) => ({
command: `npm:${scriptName} --workspace=${workspace}`,
name: workspace.substring(workspace.lastIndexOf('/') + 1),
prefixColor: getColorForWorkspace(workspace),
}));
}
const workspaceColors = ['green', 'magenta', 'yellow', 'red', 'blue'];
function getColorForWorkspace(workspace) {
const index = getWorkspaces().indexOf(workspace);
return index === -1 ? chalk.white : workspaceColors[index % workspaceColors.length];
}
module.exports = {
getPlatformCommands,
};