rman 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/bin/debug.ts +4 -0
- package/bin/rman.js +5 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +51 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/workspace/executor.d.ts +23 -0
- package/dist/workspace/executor.js +107 -0
- package/dist/workspace/package.d.ts +27 -0
- package/dist/workspace/package.js +36 -0
- package/dist/workspace/providers/npm-provider.d.ts +4 -0
- package/dist/workspace/providers/npm-provider.js +12 -0
- package/dist/workspace/types.d.ts +11 -0
- package/dist/workspace/types.js +1 -0
- package/dist/workspace/utils.d.ts +6 -0
- package/dist/workspace/utils.js +22 -0
- package/dist/workspace/workspace.d.ts +16 -0
- package/dist/workspace/workspace.js +227 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 sqbjs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
## postgresql-client
|
|
2
|
+
|
|
3
|
+
[![NPM Version][npm-image]][npm-url]
|
|
4
|
+
[![NPM Downloads][downloads-image]][downloads-url]
|
|
5
|
+
[![Build Status][travis-image]][travis-url]
|
|
6
|
+
[![Test Coverage][coveralls-image]][coveralls-url]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Professional PostgreSQL client written in TypeScript.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Pure JavaScript library completely written in TypeScript
|
|
14
|
+
- Supports both single connection and pooling
|
|
15
|
+
- Named Prepared Statements
|
|
16
|
+
- Extended cursor support with fast double-link cache
|
|
17
|
+
- Extensible data-types and type mapping
|
|
18
|
+
- Extended bind parameters
|
|
19
|
+
- Multidimensional arrays with fast binary encoding/decoding
|
|
20
|
+
- Low memory utilization and boosted performance with Shared Buffers
|
|
21
|
+
- Full binary and text wire protocol support for all data types
|
|
22
|
+
- Supports Clear text, MD5 and SASL password algorithms
|
|
23
|
+
- Can return both array and object rows
|
|
24
|
+
- Asynchronous Promise based api
|
|
25
|
+
- Strictly typed
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
$ npm install postgresql-client --save
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Documentation
|
|
34
|
+
Documentation for detailed usage is [here](DOCUMENTATION.md)
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import {Connection} from 'postgresql-client';
|
|
38
|
+
|
|
39
|
+
const connection = new Connection('postgres://localhost');
|
|
40
|
+
await connection.connect();
|
|
41
|
+
const result = await connection.query(
|
|
42
|
+
'select * from cities where name like $1',
|
|
43
|
+
{params: ['%york%']});
|
|
44
|
+
const rows = result.rows;
|
|
45
|
+
await connection.close(); // Disconnect
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import {Pool} from 'postgresql-client';
|
|
50
|
+
|
|
51
|
+
const db = new Pool({
|
|
52
|
+
host: 'postgres://localhost',
|
|
53
|
+
pool: {
|
|
54
|
+
min: 1,
|
|
55
|
+
max: 10,
|
|
56
|
+
idleTimeoutMillis: 5000
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const result = await db.query(
|
|
61
|
+
'select * from cities where name like $1',
|
|
62
|
+
{params: ['%york%'], cursor: true});
|
|
63
|
+
const cursor = result.cursor;
|
|
64
|
+
let row;
|
|
65
|
+
while ((row = cursor.next())) {
|
|
66
|
+
console.log(row);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await db.close(); // Disconnect all connections and shutdown pool
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Support
|
|
74
|
+
You can report bugs and discuss features on the [GitHub issues](https://github.com/panates/postgresql-client/issues) page
|
|
75
|
+
When you open an issue please provide version of NodeJS and PostgreSQL server.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Node Compatibility
|
|
79
|
+
|
|
80
|
+
- node >= 10.x
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### License
|
|
84
|
+
postgresql-client is available under [MIT](LICENSE) license.
|
|
85
|
+
|
|
86
|
+
[npm-image]: https://img.shields.io/npm/v/postgresql-client.svg
|
|
87
|
+
[npm-url]: https://npmjs.org/package/postgresql-client
|
|
88
|
+
[travis-image]: https://img.shields.io/travis/panates/postgresql-client/master.svg
|
|
89
|
+
[travis-url]: https://travis-ci.com/panates/postgresql-client
|
|
90
|
+
[coveralls-image]: https://img.shields.io/coveralls/panates/postgresql-client/master.svg
|
|
91
|
+
[coveralls-url]: https://coveralls.io/r/panates/postgresql-client
|
|
92
|
+
[downloads-image]: https://img.shields.io/npm/dm/postgresql-client.svg
|
|
93
|
+
[downloads-url]: https://npmjs.org/package/postgresql-client
|
|
94
|
+
[gitter-image]: https://badges.gitter.im/panates/postgresql-client.svg
|
|
95
|
+
[gitter-url]: https://gitter.im/panates/postgresql-client?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
|
96
|
+
[dependencies-image]: https://david-dm.org/panates/postgresql-client/status.svg
|
|
97
|
+
[dependencies-url]:https://david-dm.org/panates/postgresql-client
|
|
98
|
+
[devdependencies-image]: https://david-dm.org/panates/postgresql-client/dev-status.svg
|
|
99
|
+
[devdependencies-url]:https://david-dm.org/panates/postgresql-client?type=dev
|
|
100
|
+
[quality-image]: http://npm.packagequality.com/shield/postgresql-client.png
|
|
101
|
+
[quality-url]: http://packagequality.com/#?package=postgresql-client
|
package/bin/debug.ts
ADDED
package/bin/rman.js
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(argv?: string[]): Promise<void>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { URL } from 'url';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { program } from "commander";
|
|
5
|
+
import { Workspace } from './workspace/workspace.js';
|
|
6
|
+
export async function run(argv = process.argv) {
|
|
7
|
+
const pkgJson = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
8
|
+
program.version(pkgJson.version || '');
|
|
9
|
+
program
|
|
10
|
+
.command('run <script>')
|
|
11
|
+
.description('Executes given script for every package in repository')
|
|
12
|
+
.action(async (script) => runScript(script))
|
|
13
|
+
.allowUnknownOption();
|
|
14
|
+
program
|
|
15
|
+
.command('build')
|
|
16
|
+
.description('Executes "build" script for every package in repository')
|
|
17
|
+
.action(async () => runScript('build'))
|
|
18
|
+
.allowUnknownOption();
|
|
19
|
+
program
|
|
20
|
+
.command('lint')
|
|
21
|
+
.description('Executes "lint" script for every package in repository')
|
|
22
|
+
.action(async () => runScript('lint'))
|
|
23
|
+
.allowUnknownOption();
|
|
24
|
+
program
|
|
25
|
+
.command('test')
|
|
26
|
+
.description('Executes "test" script for every package in repository')
|
|
27
|
+
.action(async () => runScript('lint'))
|
|
28
|
+
.allowUnknownOption();
|
|
29
|
+
program.parse(argv);
|
|
30
|
+
}
|
|
31
|
+
async function runScript(script) {
|
|
32
|
+
const workspace = await Workspace.resolve();
|
|
33
|
+
const result = await workspace.runScript(script, {
|
|
34
|
+
gauge: true
|
|
35
|
+
});
|
|
36
|
+
if (result.errorCount) {
|
|
37
|
+
console.error('\n' + chalk.yellow(result.errorCount) + chalk.white(' error(s)'));
|
|
38
|
+
let s = '';
|
|
39
|
+
for (let i = 0; i < result.commands.length; i++) {
|
|
40
|
+
const cmd = result.commands[i];
|
|
41
|
+
if (cmd.error) {
|
|
42
|
+
s += '\n' + (i + 1) + ') ' +
|
|
43
|
+
chalk.cyanBright(cmd.package) + '\n' +
|
|
44
|
+
chalk.white(cmd.command) + '\n' +
|
|
45
|
+
chalk.red('Error: ' + cmd.error.message) + '\n' +
|
|
46
|
+
chalk.red(cmd.stderr) + '\n';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.error(s);
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './workspace/workspace';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './workspace/workspace';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { ChildProcess } from 'child_process';
|
|
3
|
+
export interface IExecutorOptions {
|
|
4
|
+
stdio?: 'inherit' | 'pipe';
|
|
5
|
+
cwd?: string;
|
|
6
|
+
argv?: string[];
|
|
7
|
+
env?: Record<string, string | undefined>;
|
|
8
|
+
shell?: boolean;
|
|
9
|
+
color?: boolean;
|
|
10
|
+
onSpawn?: (childProcess: ChildProcess) => void;
|
|
11
|
+
onLine?: (line: string, stdio: 'stderr' | 'stdout') => void;
|
|
12
|
+
onData?: (data: string, stdio: 'stderr' | 'stdout') => void;
|
|
13
|
+
}
|
|
14
|
+
export interface IRunScriptOptions {
|
|
15
|
+
gauge?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ExecuteCommandResult {
|
|
18
|
+
code?: number;
|
|
19
|
+
error?: any;
|
|
20
|
+
stderr: string;
|
|
21
|
+
stdout: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function executeCommand(command: string, options?: IExecutorOptions): Promise<ExecuteCommandResult>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import npmRubPath from 'npm-run-path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { onProcessExit } from './utils.js';
|
|
5
|
+
const runningChildren = new Map();
|
|
6
|
+
export async function executeCommand(command, options) {
|
|
7
|
+
const opts = {
|
|
8
|
+
...options
|
|
9
|
+
};
|
|
10
|
+
opts.env = {
|
|
11
|
+
...npmRubPath.env({ cwd: options?.cwd }),
|
|
12
|
+
// FORCE_COLOR: `${chalk.level}`,
|
|
13
|
+
...opts.env
|
|
14
|
+
};
|
|
15
|
+
opts.cwd = opts.cwd || process.cwd();
|
|
16
|
+
opts.color = opts.color == null ? true : opts.color;
|
|
17
|
+
const spawnOptions = {
|
|
18
|
+
stdio: opts.stdio || 'pipe',
|
|
19
|
+
env: opts.env,
|
|
20
|
+
cwd: opts.cwd,
|
|
21
|
+
shell: options?.shell,
|
|
22
|
+
windowsHide: true
|
|
23
|
+
};
|
|
24
|
+
const result = {
|
|
25
|
+
code: undefined,
|
|
26
|
+
stderr: '',
|
|
27
|
+
stdout: ''
|
|
28
|
+
};
|
|
29
|
+
const buffer = {
|
|
30
|
+
stdout: '',
|
|
31
|
+
stderr: ''
|
|
32
|
+
};
|
|
33
|
+
const processData = (data, stdio) => {
|
|
34
|
+
buffer[stdio] += data;
|
|
35
|
+
result[stdio] += data;
|
|
36
|
+
if (opts.onData)
|
|
37
|
+
opts.onData(data, stdio);
|
|
38
|
+
};
|
|
39
|
+
const processLines = (stdio, flush) => {
|
|
40
|
+
let chunk = buffer[stdio];
|
|
41
|
+
let i;
|
|
42
|
+
if (flush && !chunk.endsWith('\n'))
|
|
43
|
+
chunk += '\n';
|
|
44
|
+
while ((i = chunk.indexOf('\n')) >= 0) {
|
|
45
|
+
const line = chunk.substring(0, i);
|
|
46
|
+
chunk = chunk.substring(i + 1);
|
|
47
|
+
if (opts.onLine)
|
|
48
|
+
opts.onLine(line, stdio);
|
|
49
|
+
}
|
|
50
|
+
buffer[stdio] = chunk;
|
|
51
|
+
};
|
|
52
|
+
const child = spawn(command, opts.argv || [], spawnOptions);
|
|
53
|
+
if (child.pid) {
|
|
54
|
+
runningChildren.set(child.pid, child);
|
|
55
|
+
if (opts.onSpawn)
|
|
56
|
+
opts.onSpawn(child);
|
|
57
|
+
}
|
|
58
|
+
child.stdout?.on('data', (data) => {
|
|
59
|
+
processData(data, 'stdout');
|
|
60
|
+
processLines('stdout');
|
|
61
|
+
});
|
|
62
|
+
child.stderr?.on('data', (data) => {
|
|
63
|
+
processData(data, 'stderr');
|
|
64
|
+
processLines('stderr');
|
|
65
|
+
});
|
|
66
|
+
return new Promise(resolve => {
|
|
67
|
+
let resolved;
|
|
68
|
+
child.on('error', (err) => {
|
|
69
|
+
if (child.pid)
|
|
70
|
+
runningChildren.delete(child.pid);
|
|
71
|
+
processLines('stdout', true);
|
|
72
|
+
processLines('stderr', true);
|
|
73
|
+
if (resolved)
|
|
74
|
+
return;
|
|
75
|
+
result.code = err.code || 1;
|
|
76
|
+
result.error = err;
|
|
77
|
+
if (!result.error) {
|
|
78
|
+
const text = `Command failed (${result.code})`;
|
|
79
|
+
result.error =
|
|
80
|
+
new Error((opts.color ? chalk.red(text) : text) + '\n ' +
|
|
81
|
+
opts.color ? chalk.white(err.message) : err.message);
|
|
82
|
+
}
|
|
83
|
+
resolved = true;
|
|
84
|
+
resolve(result);
|
|
85
|
+
});
|
|
86
|
+
child.on('close', (code) => {
|
|
87
|
+
if (child.pid)
|
|
88
|
+
runningChildren.delete(child.pid);
|
|
89
|
+
processLines('stdout', true);
|
|
90
|
+
processLines('stderr', true);
|
|
91
|
+
if (resolved)
|
|
92
|
+
return;
|
|
93
|
+
result.code = code;
|
|
94
|
+
resolved = true;
|
|
95
|
+
if (code) {
|
|
96
|
+
const text = `Command failed (${result.code})`;
|
|
97
|
+
result.error = new Error((opts.color ? chalk.red(text) : text));
|
|
98
|
+
}
|
|
99
|
+
return resolve(result);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
onProcessExit(() => {
|
|
104
|
+
runningChildren.forEach((child) => {
|
|
105
|
+
child.kill();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface RunScriptResult {
|
|
2
|
+
script: string;
|
|
3
|
+
code?: number;
|
|
4
|
+
errorCount: number;
|
|
5
|
+
commands: CommandResult[];
|
|
6
|
+
}
|
|
7
|
+
export interface ScriptCommand {
|
|
8
|
+
name: string;
|
|
9
|
+
command: string;
|
|
10
|
+
step: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CommandResult {
|
|
13
|
+
package: string;
|
|
14
|
+
command: string;
|
|
15
|
+
code: number;
|
|
16
|
+
error: any;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class Package {
|
|
21
|
+
readonly dirname: string;
|
|
22
|
+
readonly def: any;
|
|
23
|
+
dependencies: string[];
|
|
24
|
+
constructor(dirname: string, def: any);
|
|
25
|
+
get name(): string;
|
|
26
|
+
getScriptCommands(script: string): ScriptCommand[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import parseNpmScript from '@netlify/parse-npm-script';
|
|
2
|
+
export class Package {
|
|
3
|
+
constructor(dirname, def) {
|
|
4
|
+
this.dirname = dirname;
|
|
5
|
+
this.dependencies = [];
|
|
6
|
+
Object.defineProperty(this, 'def', {
|
|
7
|
+
enumerable: false,
|
|
8
|
+
value: def,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
get name() {
|
|
12
|
+
return this.def.name;
|
|
13
|
+
}
|
|
14
|
+
getScriptCommands(script) {
|
|
15
|
+
const result = [];
|
|
16
|
+
let scriptInfo;
|
|
17
|
+
try {
|
|
18
|
+
scriptInfo = parseNpmScript(this.def, 'npm run ' + script);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
if (scriptInfo && scriptInfo.raw)
|
|
24
|
+
for (const step of scriptInfo.steps) {
|
|
25
|
+
const parsed = Array.isArray(step.parsed) ? step.parsed : [step.parsed];
|
|
26
|
+
for (const cmd of parsed) {
|
|
27
|
+
result.push({
|
|
28
|
+
name: cmd.split(' ')[0],
|
|
29
|
+
command: cmd,
|
|
30
|
+
step: step.name
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getPackageJson } from '../utils.js';
|
|
2
|
+
export class NpmProvider {
|
|
3
|
+
async parse(root) {
|
|
4
|
+
const pkg = getPackageJson(root);
|
|
5
|
+
if (pkg && typeof pkg.workspaces === 'object') {
|
|
6
|
+
if (Array.isArray(pkg.workspaces))
|
|
7
|
+
return { root, packages: pkg.workspaces };
|
|
8
|
+
if (Array.isArray(pkg.workspaces.packages))
|
|
9
|
+
return { root, packages: pkg.workspaces.packages };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface IWorkspaceOptions {
|
|
2
|
+
packageOrder?: string[];
|
|
3
|
+
scripts?: Record<string, {}>;
|
|
4
|
+
}
|
|
5
|
+
export interface IWorkspaceProvider {
|
|
6
|
+
parse: (root: string) => Promise<IParsedWorkspaceInfo | undefined>;
|
|
7
|
+
}
|
|
8
|
+
export interface IParsedWorkspaceInfo {
|
|
9
|
+
root: string;
|
|
10
|
+
packages: string[];
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
declare type ExitListener = (signal: NodeJS.Signals | "exit") => void;
|
|
3
|
+
export declare function getPackageJson(dirname: string): any;
|
|
4
|
+
export declare function onProcessExit(listener: ExitListener, forceExit?: boolean): void;
|
|
5
|
+
export declare function setFind<T>(set: Set<T>, cb: (item: T) => boolean): T | undefined;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
export function getPackageJson(dirname) {
|
|
4
|
+
const f = path.resolve(dirname, 'package.json');
|
|
5
|
+
if (!fs.existsSync(f))
|
|
6
|
+
return;
|
|
7
|
+
return JSON.parse(fs.readFileSync(f, 'utf-8'));
|
|
8
|
+
}
|
|
9
|
+
export function onProcessExit(listener, forceExit = true) {
|
|
10
|
+
["SIGTERM", "SIGINT"].forEach((event) => process.once(event, (signal) => {
|
|
11
|
+
listener(signal);
|
|
12
|
+
if (forceExit)
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}));
|
|
15
|
+
process.once("exit", () => listener("exit"));
|
|
16
|
+
}
|
|
17
|
+
export function setFind(set, cb) {
|
|
18
|
+
for (const e of set) {
|
|
19
|
+
if (cb(e))
|
|
20
|
+
return e;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { IRunScriptOptions } from './executor.js';
|
|
2
|
+
import { Package, RunScriptResult } from './package.js';
|
|
3
|
+
import { IWorkspaceOptions } from './types.js';
|
|
4
|
+
export declare class Workspace {
|
|
5
|
+
readonly root: string;
|
|
6
|
+
readonly packages: Package[];
|
|
7
|
+
private _options;
|
|
8
|
+
constructor(root: string, packages: Package[], options?: IWorkspaceOptions);
|
|
9
|
+
getPackage(name: string): Package | undefined;
|
|
10
|
+
runScript(script: string, options?: IRunScriptOptions): Promise<RunScriptResult>;
|
|
11
|
+
private _determineDependencies;
|
|
12
|
+
private _getSortedPackages;
|
|
13
|
+
static resolve(root?: string, options?: {
|
|
14
|
+
deep?: number;
|
|
15
|
+
}): Promise<Workspace>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import glob from 'fast-glob';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { MultiBar, Presets } from 'cli-progress';
|
|
6
|
+
import { getPackageJson, setFind } from './utils.js';
|
|
7
|
+
import { executeCommand } from './executor.js';
|
|
8
|
+
import { Package } from './package.js';
|
|
9
|
+
import { NpmProvider } from './providers/npm-provider.js';
|
|
10
|
+
const providers = [
|
|
11
|
+
new NpmProvider()
|
|
12
|
+
];
|
|
13
|
+
export class Workspace {
|
|
14
|
+
constructor(root, packages, options) {
|
|
15
|
+
this.root = root;
|
|
16
|
+
this.packages = packages;
|
|
17
|
+
this._options = { ...options };
|
|
18
|
+
this._determineDependencies();
|
|
19
|
+
}
|
|
20
|
+
getPackage(name) {
|
|
21
|
+
return this.packages.find(p => p.name === name);
|
|
22
|
+
}
|
|
23
|
+
async runScript(script, options = {}) {
|
|
24
|
+
const packages = {};
|
|
25
|
+
const result = {
|
|
26
|
+
script,
|
|
27
|
+
errorCount: 0,
|
|
28
|
+
commands: []
|
|
29
|
+
};
|
|
30
|
+
options.gauge = options.gauge == null ? true : options.gauge;
|
|
31
|
+
const progressBars = options.gauge && new MultiBar({
|
|
32
|
+
format: '[' + chalk.cyan('{bar}') + '] {percentage}% | {value}/{total} | ' +
|
|
33
|
+
chalk.yellowBright('{package}') + ' | ' + chalk.yellow('{command}'),
|
|
34
|
+
barsize: 30,
|
|
35
|
+
hideCursor: true
|
|
36
|
+
}, Presets.rect);
|
|
37
|
+
const overallProgress = progressBars && progressBars.create(0, 0);
|
|
38
|
+
let totalCommands = 0;
|
|
39
|
+
for (const p of this._getSortedPackages()) {
|
|
40
|
+
const commands = p.getScriptCommands(script);
|
|
41
|
+
const progress = progressBars && progressBars.create(commands.length, 0);
|
|
42
|
+
packages[p.name] = {
|
|
43
|
+
package: p,
|
|
44
|
+
commands: [...commands],
|
|
45
|
+
progress
|
|
46
|
+
};
|
|
47
|
+
totalCommands += commands.length;
|
|
48
|
+
if (progress)
|
|
49
|
+
progress.start(commands.length, 0, {
|
|
50
|
+
package: p.name,
|
|
51
|
+
command: 'Waiting'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (overallProgress) {
|
|
55
|
+
overallProgress.start(totalCommands, 0, { package: 'Overall', command: '' });
|
|
56
|
+
}
|
|
57
|
+
const t = Date.now();
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const remaining = new Set(Object.keys(packages));
|
|
60
|
+
const runScripts = () => {
|
|
61
|
+
for (const pkgName of remaining) {
|
|
62
|
+
const pkgInfo = packages[pkgName];
|
|
63
|
+
const pkg = pkgInfo.package;
|
|
64
|
+
const progress = pkgInfo.progress;
|
|
65
|
+
for (let k = 0; k < pkgInfo.commands.length; k++) {
|
|
66
|
+
const cmd = pkgInfo.commands[k];
|
|
67
|
+
if (cmd.status === 'running')
|
|
68
|
+
break;
|
|
69
|
+
if (!cmd.status) {
|
|
70
|
+
const concurrent = cmd.step.startsWith('pre');
|
|
71
|
+
if (!concurrent &&
|
|
72
|
+
pkg.dependencies.find(dep => setFind(remaining, p => p === dep))) {
|
|
73
|
+
cmd.status = '';
|
|
74
|
+
if (progress)
|
|
75
|
+
progress.update({ command: chalk.bgYellow.white('Waiting dependencies') });
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
cmd.status = 'running';
|
|
79
|
+
if (progress)
|
|
80
|
+
progress.update({ command: cmd.name });
|
|
81
|
+
else
|
|
82
|
+
console.log('[' + chalk.whiteBright(pkg.name) + '] ' +
|
|
83
|
+
chalk.yellow(cmd.command), (chalk.cyanBright(' +' + (Date.now() - t))));
|
|
84
|
+
void executeCommand(cmd.command, {
|
|
85
|
+
...options,
|
|
86
|
+
cwd: pkg.dirname,
|
|
87
|
+
shell: true
|
|
88
|
+
}).then(r => {
|
|
89
|
+
if (overallProgress)
|
|
90
|
+
overallProgress.increment(1);
|
|
91
|
+
if (progress)
|
|
92
|
+
progress.increment(1);
|
|
93
|
+
const cr = {
|
|
94
|
+
package: pkg.name,
|
|
95
|
+
command: cmd,
|
|
96
|
+
code: r.code || 1,
|
|
97
|
+
error: r.error,
|
|
98
|
+
stdout: r.stdout,
|
|
99
|
+
stderr: r.stderr
|
|
100
|
+
};
|
|
101
|
+
result.code = result.code || r.code;
|
|
102
|
+
if (r.error)
|
|
103
|
+
result.errorCount++;
|
|
104
|
+
result.commands.push(cr);
|
|
105
|
+
cmd.status = 'done';
|
|
106
|
+
if (r.error || k === pkgInfo.commands.length - 1) {
|
|
107
|
+
if (progress) {
|
|
108
|
+
if (r.error)
|
|
109
|
+
progress.update({ command: chalk.yellow(cmd.name) + chalk.red(' Filed!') });
|
|
110
|
+
else
|
|
111
|
+
progress.update({ command: chalk.green(' Completed!') });
|
|
112
|
+
}
|
|
113
|
+
remaining.delete(pkg.name);
|
|
114
|
+
}
|
|
115
|
+
if (!remaining.size) {
|
|
116
|
+
if (progressBars)
|
|
117
|
+
progressBars.stop();
|
|
118
|
+
return resolve(result);
|
|
119
|
+
}
|
|
120
|
+
if (!result.errorCount)
|
|
121
|
+
setTimeout(runScripts, 1);
|
|
122
|
+
});
|
|
123
|
+
if (!concurrent)
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
runScripts();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
_determineDependencies() {
|
|
133
|
+
const deps = {};
|
|
134
|
+
for (const pkg of this.packages) {
|
|
135
|
+
const o = {
|
|
136
|
+
...pkg.def.dependencies,
|
|
137
|
+
...pkg.def.devDependencies,
|
|
138
|
+
...pkg.def.peerDependencies,
|
|
139
|
+
...pkg.def.optionalDependencies
|
|
140
|
+
};
|
|
141
|
+
const dependencies = [];
|
|
142
|
+
for (const k of Object.keys(o)) {
|
|
143
|
+
const p = this.getPackage(k);
|
|
144
|
+
if (p)
|
|
145
|
+
dependencies.push(k);
|
|
146
|
+
}
|
|
147
|
+
deps[pkg.name] = dependencies;
|
|
148
|
+
pkg.dependencies = dependencies;
|
|
149
|
+
}
|
|
150
|
+
let circularCheck;
|
|
151
|
+
const deepFindDependencies = (pkg, target) => {
|
|
152
|
+
if (circularCheck.includes(pkg.name))
|
|
153
|
+
return;
|
|
154
|
+
circularCheck.push(pkg.name);
|
|
155
|
+
for (const s of pkg.dependencies) {
|
|
156
|
+
if (!target.includes(s)) {
|
|
157
|
+
target.push(s);
|
|
158
|
+
const p = this.getPackage(s);
|
|
159
|
+
if (p) {
|
|
160
|
+
deepFindDependencies(p, target);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
for (const pkg of this.packages) {
|
|
166
|
+
circularCheck = [];
|
|
167
|
+
deepFindDependencies(pkg, pkg.dependencies);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
_getSortedPackages() {
|
|
171
|
+
const packages = [...this.packages];
|
|
172
|
+
const packageOrder = this._options.packageOrder;
|
|
173
|
+
packages.sort((a, b) => {
|
|
174
|
+
if (packageOrder) {
|
|
175
|
+
const a1 = packageOrder.indexOf(a.name);
|
|
176
|
+
const b1 = packageOrder.indexOf(b.name);
|
|
177
|
+
const i = (a1 >= 0 ? a1 : Number.MAX_SAFE_INTEGER) - (b1 >= 0 ? b1 : Number.MAX_SAFE_INTEGER);
|
|
178
|
+
if (i !== 0)
|
|
179
|
+
return i;
|
|
180
|
+
}
|
|
181
|
+
if (b.dependencies.includes(a.name))
|
|
182
|
+
return -1;
|
|
183
|
+
if (a.dependencies.includes(b.name))
|
|
184
|
+
return 1;
|
|
185
|
+
return 0;
|
|
186
|
+
});
|
|
187
|
+
return packages;
|
|
188
|
+
}
|
|
189
|
+
static async resolve(root, options) {
|
|
190
|
+
root = root || process.cwd();
|
|
191
|
+
let deep = options?.deep || 0;
|
|
192
|
+
while (deep-- >= 0 && fs.existsSync(root)) {
|
|
193
|
+
for (let i = providers.length - 1; i >= 0; i--) {
|
|
194
|
+
const provider = providers[i];
|
|
195
|
+
const inf = await provider.parse(root);
|
|
196
|
+
if (!inf)
|
|
197
|
+
continue;
|
|
198
|
+
const pkgJson = getPackageJson(inf.root);
|
|
199
|
+
if (!pkgJson)
|
|
200
|
+
continue;
|
|
201
|
+
const packages = [];
|
|
202
|
+
for (const pattern of inf.packages) {
|
|
203
|
+
const dirs = await glob(pattern, {
|
|
204
|
+
cwd: inf.root,
|
|
205
|
+
absolute: true,
|
|
206
|
+
deep: 0,
|
|
207
|
+
onlyDirectories: true
|
|
208
|
+
});
|
|
209
|
+
for (const dir of dirs) {
|
|
210
|
+
const p = await detectPackage(dir);
|
|
211
|
+
if (p && !packages.find(x => x.name === p.name))
|
|
212
|
+
packages.push(p);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return new Workspace(inf.root, packages, pkgJson.rman);
|
|
216
|
+
}
|
|
217
|
+
root = path.resolve(root, '..');
|
|
218
|
+
}
|
|
219
|
+
throw new Error('No project workspace detected');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function detectPackage(dirname) {
|
|
223
|
+
const pkgJson = getPackageJson(dirname);
|
|
224
|
+
if (pkgJson && pkgJson.name) {
|
|
225
|
+
return new Package(dirname, pkgJson);
|
|
226
|
+
}
|
|
227
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rman",
|
|
3
|
+
"description": "Monorepo repository manager",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"author": "Panates",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Eray Hanoglu <e.hanoglu@panates.com>"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/panates/rman.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"javascript",
|
|
16
|
+
"typescript",
|
|
17
|
+
"monorepo",
|
|
18
|
+
"repository",
|
|
19
|
+
"build",
|
|
20
|
+
"lerna"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"rman": "bin/rman.js"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"module": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chalk": "^4.1.2",
|
|
30
|
+
"commander": "^8.3.0",
|
|
31
|
+
"fast-glob": "^3.2.7",
|
|
32
|
+
"npm-run-path": "^4.0.1",
|
|
33
|
+
"@netlify/parse-npm-script": "^0.1.2",
|
|
34
|
+
"cli-progress": "^3.9.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/mocha": "^9.0.0",
|
|
38
|
+
"@types/node": "^16.4.8",
|
|
39
|
+
"@types/cli-progress": "^3.9.2",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
41
|
+
"@typescript-eslint/parser": "^5.0.0",
|
|
42
|
+
"@babel/eslint-parser": "^7.16.3",
|
|
43
|
+
"eslint": "^8.0.0",
|
|
44
|
+
"eslint-config-google": "^0.14.0",
|
|
45
|
+
"mocha": "^9.0.1",
|
|
46
|
+
"nyc": "^15.1.0",
|
|
47
|
+
"ts-cleanup": "^0.2.2",
|
|
48
|
+
"ts-loader": "^9.0.0",
|
|
49
|
+
"ts-node": "^10.0.0",
|
|
50
|
+
"tsconfig-paths": "^3.9.0",
|
|
51
|
+
"typescript": "^4.5.2"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">= 14.0"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist/",
|
|
58
|
+
"bin/",
|
|
59
|
+
"LICENSE",
|
|
60
|
+
"README.md"
|
|
61
|
+
],
|
|
62
|
+
"scripts": {
|
|
63
|
+
"test": "TS_NODE_PROJECT='./test/tsconfig.json' mocha -r ts-node/register --reporter spec test/**/*.spec.ts",
|
|
64
|
+
"cover": "nyc --reporter=cobertura --reporter html --reporter text npm run test",
|
|
65
|
+
"clean": "npm run clean:src && npm run clean:dist",
|
|
66
|
+
"clean:dist": "ts-cleanup -d dist --remove-dirs --all",
|
|
67
|
+
"clean:src": "ts-cleanup -s src --all | ts-cleanup -s test",
|
|
68
|
+
"prebuild": "npm run clean && npm run lint",
|
|
69
|
+
"build": "tsc -b tsconfig.build.json",
|
|
70
|
+
"compile": "tsc -b tsconfig.json",
|
|
71
|
+
"lint": "eslint src/** --no-error-on-unmatched-pattern",
|
|
72
|
+
"travis-cover": "nyc --reporter lcovonly npm run test"
|
|
73
|
+
},
|
|
74
|
+
"nyc": {
|
|
75
|
+
"temp-dir": "./coverage/.nyc_output"
|
|
76
|
+
}
|
|
77
|
+
}
|