rman 0.0.3 → 0.2.0
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/README.md +22 -90
- package/bin/rman.js +2 -2
- package/cjs/cli.js +57 -0
- package/cjs/commands/changed-command.js +38 -0
- package/cjs/commands/execute-command.js +79 -0
- package/cjs/commands/info-command.js +66 -0
- package/cjs/commands/list-command.js +141 -0
- package/cjs/commands/multi-task-command.js +70 -0
- package/cjs/commands/publish-command.js +74 -0
- package/cjs/commands/run-command.js +103 -0
- package/cjs/commands/version-command.js +155 -0
- package/cjs/core/command.js +110 -0
- package/cjs/core/config.js +73 -0
- package/cjs/core/logger.js +8 -0
- package/cjs/core/package.js +33 -0
- package/cjs/core/repository.js +133 -0
- package/{dist/workspace → cjs/core}/types.js +0 -0
- package/cjs/debug.js +5 -0
- package/{dist → cjs}/index.js +1 -1
- package/cjs/package.json +3 -0
- package/cjs/utils/constants.js +5 -0
- package/cjs/utils/exec.js +115 -0
- package/cjs/utils/git-utils.js +70 -0
- package/cjs/utils/npm-run-path.js +63 -0
- package/cjs/utils.js +36 -0
- package/esm/cli.d.ts +4 -0
- package/esm/cli.mjs +50 -0
- package/esm/commands/changed-command.d.ts +16 -0
- package/esm/commands/changed-command.mjs +34 -0
- package/esm/commands/execute-command.d.ts +18 -0
- package/esm/commands/execute-command.mjs +72 -0
- package/esm/commands/info-command.d.ts +10 -0
- package/esm/commands/info-command.mjs +59 -0
- package/esm/commands/list-command.d.ts +38 -0
- package/esm/commands/list-command.mjs +134 -0
- package/esm/commands/multi-task-command.d.ts +20 -0
- package/esm/commands/multi-task-command.mjs +63 -0
- package/esm/commands/publish-command.d.ts +17 -0
- package/esm/commands/publish-command.mjs +67 -0
- package/esm/commands/run-command.d.ts +22 -0
- package/esm/commands/run-command.mjs +96 -0
- package/esm/commands/version-command.d.ts +24 -0
- package/esm/commands/version-command.mjs +148 -0
- package/esm/core/command.d.ts +35 -0
- package/esm/core/command.mjs +103 -0
- package/esm/core/config.d.ts +9 -0
- package/esm/core/config.mjs +66 -0
- package/esm/core/logger.d.ts +10 -0
- package/esm/core/logger.mjs +3 -0
- package/esm/core/package.d.ts +11 -0
- package/esm/core/package.mjs +26 -0
- package/esm/core/repository.d.ts +18 -0
- package/esm/core/repository.mjs +126 -0
- package/esm/core/types.d.ts +14 -0
- package/esm/core/types.mjs +1 -0
- package/esm/debug.d.ts +1 -0
- package/esm/debug.mjs +3 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.mjs +1 -0
- package/esm/utils/constants.d.ts +2 -0
- package/esm/utils/constants.mjs +2 -0
- package/{dist/workspace/executor.d.ts → esm/utils/exec.d.ts} +5 -8
- package/esm/utils/exec.mjs +108 -0
- package/esm/utils/git-utils.d.ts +25 -0
- package/esm/utils/git-utils.mjs +63 -0
- package/esm/utils/npm-run-path.d.ts +67 -0
- package/esm/utils/npm-run-path.mjs +55 -0
- package/esm/utils.d.ts +2 -0
- package/esm/utils.mjs +28 -0
- package/package.json +64 -31
- package/bin/debug.ts +0 -4
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -71
- package/dist/index.d.ts +0 -1
- package/dist/workspace/executor.js +0 -120
- package/dist/workspace/package.d.ts +0 -27
- package/dist/workspace/package.js +0 -43
- package/dist/workspace/providers/npm-provider.d.ts +0 -4
- package/dist/workspace/providers/npm-provider.js +0 -16
- package/dist/workspace/types.d.ts +0 -10
- package/dist/workspace/utils.d.ts +0 -6
- package/dist/workspace/utils.js +0 -31
- package/dist/workspace/workspace.d.ts +0 -17
- package/dist/workspace/workspace.js +0 -240
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { AsyncEventEmitter, TypedEventEmitterClass } from 'strict-typed-events';
|
|
2
|
+
import npmlog from 'npmlog';
|
|
3
|
+
import isCi from 'is-ci';
|
|
4
|
+
import merge from 'putil-merge';
|
|
5
|
+
import './logger.mjs';
|
|
6
|
+
import { isTTY } from './../utils/constants.mjs';
|
|
7
|
+
const noOp = () => void (0);
|
|
8
|
+
export class Command extends TypedEventEmitterClass(AsyncEventEmitter) {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super();
|
|
11
|
+
this._started = false;
|
|
12
|
+
this._finished = false;
|
|
13
|
+
this.logger = npmlog;
|
|
14
|
+
this._options = options || {};
|
|
15
|
+
if (isCi)
|
|
16
|
+
this.options.ci = true;
|
|
17
|
+
}
|
|
18
|
+
get options() {
|
|
19
|
+
return this._options;
|
|
20
|
+
}
|
|
21
|
+
get commandName() {
|
|
22
|
+
return Object.getPrototypeOf(this).constructor.commandName;
|
|
23
|
+
}
|
|
24
|
+
async execute() {
|
|
25
|
+
if (this._finished) {
|
|
26
|
+
this._finished = false;
|
|
27
|
+
this._started = false;
|
|
28
|
+
}
|
|
29
|
+
if (!this._started) {
|
|
30
|
+
this._started = true;
|
|
31
|
+
await this.emitAsync('start');
|
|
32
|
+
}
|
|
33
|
+
this._started = true;
|
|
34
|
+
try {
|
|
35
|
+
this.logger.level = this.options.logLevel ||
|
|
36
|
+
(this.options.ci ? 'error' : 'info');
|
|
37
|
+
if (this.options.ci || !isTTY) {
|
|
38
|
+
this.logger.disableColor();
|
|
39
|
+
this.logger.disableUnicode();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.logger.enableColor();
|
|
43
|
+
this.logger.enableUnicode();
|
|
44
|
+
}
|
|
45
|
+
if (this._preExecute)
|
|
46
|
+
await this._preExecute();
|
|
47
|
+
const v = await this._execute();
|
|
48
|
+
if (this._postExecute)
|
|
49
|
+
await this._postExecute();
|
|
50
|
+
await this.emitAsync('finish', undefined, v).catch(noOp);
|
|
51
|
+
this.disableProgress();
|
|
52
|
+
this.logger.resume();
|
|
53
|
+
this.logger.success('', 'Command completed');
|
|
54
|
+
return v;
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
this.disableProgress();
|
|
58
|
+
await this.emitAsync('finish', e).catch(noOp);
|
|
59
|
+
if (this.listenerCount('error'))
|
|
60
|
+
await this.emitAsync('error', e).catch(noOp);
|
|
61
|
+
this.logger.resume();
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
this._finished = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async enableProgress() {
|
|
69
|
+
if (this.options.ci || !isTTY || (this._screen && this._screen.visible))
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
disableProgress() {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
(function (Command) {
|
|
76
|
+
Command.globalOptions = {
|
|
77
|
+
'log-level': {
|
|
78
|
+
defaultDescription: "info",
|
|
79
|
+
describe: "Set log level",
|
|
80
|
+
choices: ['silly', 'verbose', 'info', 'output', 'notice', 'success', 'warn', 'error', 'silent'],
|
|
81
|
+
requiresArg: true
|
|
82
|
+
},
|
|
83
|
+
'json': {
|
|
84
|
+
alias: 'j',
|
|
85
|
+
describe: '# Stream log as json',
|
|
86
|
+
type: 'boolean'
|
|
87
|
+
},
|
|
88
|
+
'ci': {
|
|
89
|
+
hidden: true,
|
|
90
|
+
type: "boolean"
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function composeOptions(commandName, yargArgs, config) {
|
|
94
|
+
const result = merge({}, yargArgs);
|
|
95
|
+
merge(result, config, { filter: (_, key) => key !== 'command' });
|
|
96
|
+
const cfgCmd = config.commans && typeof config.command === 'object' ?
|
|
97
|
+
config.command : undefined;
|
|
98
|
+
if (cfgCmd && typeof cfgCmd === 'object')
|
|
99
|
+
merge(result, cfgCmd);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
Command.composeOptions = composeOptions;
|
|
103
|
+
})(Command || (Command = {}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class Config {
|
|
2
|
+
data: any;
|
|
3
|
+
constructor(data: any);
|
|
4
|
+
getObject(key: string, def?: any): any | undefined;
|
|
5
|
+
getString(key: string, def?: string): string | undefined;
|
|
6
|
+
getNumber(key: string, def?: number): number | undefined;
|
|
7
|
+
get(key: string): any;
|
|
8
|
+
static resolve(dirname: string): Config;
|
|
9
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import merge from 'putil-merge';
|
|
5
|
+
import { getPackageJson } from './../utils.mjs';
|
|
6
|
+
export class Config {
|
|
7
|
+
constructor(data) {
|
|
8
|
+
this.data = data;
|
|
9
|
+
}
|
|
10
|
+
getObject(key, def) {
|
|
11
|
+
const v = this.get(key);
|
|
12
|
+
if (v != null && typeof v === 'object')
|
|
13
|
+
return v;
|
|
14
|
+
return def;
|
|
15
|
+
}
|
|
16
|
+
getString(key, def) {
|
|
17
|
+
const v = this.get(key);
|
|
18
|
+
if (v != null && typeof v !== 'object')
|
|
19
|
+
return '' + v;
|
|
20
|
+
return def;
|
|
21
|
+
}
|
|
22
|
+
getNumber(key, def) {
|
|
23
|
+
const v = this.get(key);
|
|
24
|
+
if (v != null && typeof v !== 'object') {
|
|
25
|
+
const n = parseFloat(v);
|
|
26
|
+
if (!isNaN(n))
|
|
27
|
+
return n;
|
|
28
|
+
}
|
|
29
|
+
return def;
|
|
30
|
+
}
|
|
31
|
+
get(key) {
|
|
32
|
+
const keys = key.split('.');
|
|
33
|
+
let o = this.data;
|
|
34
|
+
for (let i = 0; i < keys.length; i++) {
|
|
35
|
+
const k = keys[i];
|
|
36
|
+
if (o.hasOwnProperty(k)) {
|
|
37
|
+
if (i === keys.length - 1)
|
|
38
|
+
return o[k];
|
|
39
|
+
if (o[k] && typeof o[k] === 'object') {
|
|
40
|
+
o = o[k];
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
static resolve(dirname) {
|
|
48
|
+
const data = {};
|
|
49
|
+
const pkgJson = getPackageJson(dirname);
|
|
50
|
+
if (pkgJson && typeof pkgJson.rman === 'object')
|
|
51
|
+
merge(data, pkgJson.rman, { deep: true });
|
|
52
|
+
let filename = path.resolve(dirname, '.rman.yml');
|
|
53
|
+
if (fs.existsSync(filename)) {
|
|
54
|
+
const obj = yaml.load(fs.readFileSync(filename, 'utf-8'));
|
|
55
|
+
if (obj && typeof obj === 'object')
|
|
56
|
+
merge(data, obj, { deep: true });
|
|
57
|
+
}
|
|
58
|
+
filename = path.resolve(dirname, '.rmanrc');
|
|
59
|
+
if (fs.existsSync(filename)) {
|
|
60
|
+
const obj = JSON.parse(fs.readFileSync(filename, 'utf-8'));
|
|
61
|
+
if (obj && typeof obj === 'object')
|
|
62
|
+
merge(data, obj, { deep: true });
|
|
63
|
+
}
|
|
64
|
+
return new Config(data);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare module 'npmlog' {
|
|
2
|
+
interface Logger {
|
|
3
|
+
output(prefix: string, message: string, ...args: any[]): void;
|
|
4
|
+
success(prefix: string, message: string, ...args: any[]): void;
|
|
5
|
+
disp: Record<string, string>;
|
|
6
|
+
showProgress(): any;
|
|
7
|
+
hideProgress(): any;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export class Package {
|
|
4
|
+
constructor(dirname) {
|
|
5
|
+
this.dirname = dirname;
|
|
6
|
+
this.dependencies = [];
|
|
7
|
+
this.reloadJson();
|
|
8
|
+
}
|
|
9
|
+
get name() {
|
|
10
|
+
return this._json.name;
|
|
11
|
+
}
|
|
12
|
+
get version() {
|
|
13
|
+
return this._json.version;
|
|
14
|
+
}
|
|
15
|
+
get json() {
|
|
16
|
+
return this._json;
|
|
17
|
+
}
|
|
18
|
+
get isPrivate() {
|
|
19
|
+
return !!this._json.private;
|
|
20
|
+
}
|
|
21
|
+
reloadJson() {
|
|
22
|
+
const f = path.join(this.dirname, 'package.json');
|
|
23
|
+
this._json = JSON.parse(fs.readFileSync(f, 'utf-8'));
|
|
24
|
+
return this._json;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Package } from './package';
|
|
2
|
+
export declare class Repository extends Package {
|
|
3
|
+
readonly dirname: string;
|
|
4
|
+
readonly config: any;
|
|
5
|
+
readonly packages: Package[];
|
|
6
|
+
protected constructor(dirname: string, config: any, packages: Package[]);
|
|
7
|
+
getPackages(options?: {
|
|
8
|
+
scope?: string | string[];
|
|
9
|
+
toposort?: boolean;
|
|
10
|
+
}): Package[];
|
|
11
|
+
getPackage(name: string): Package | undefined;
|
|
12
|
+
protected _updateDependencies(): void;
|
|
13
|
+
static create(root?: string, options?: {
|
|
14
|
+
deep?: number;
|
|
15
|
+
}): Repository;
|
|
16
|
+
protected static _resolvePackages(dirname: string, patterns: string[]): Package[];
|
|
17
|
+
protected static _readConfig(dirname: string): any;
|
|
18
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import glob from 'fast-glob';
|
|
4
|
+
import merge from 'putil-merge';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
import { Package } from './package.mjs';
|
|
7
|
+
import { getPackageJson } from './../utils.mjs';
|
|
8
|
+
export class Repository extends Package {
|
|
9
|
+
constructor(dirname, config, packages) {
|
|
10
|
+
super(dirname);
|
|
11
|
+
this.dirname = dirname;
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.packages = packages;
|
|
14
|
+
}
|
|
15
|
+
getPackages(options) {
|
|
16
|
+
const result = [...this.packages];
|
|
17
|
+
if (options?.toposort)
|
|
18
|
+
topoSortPackages(result);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
getPackage(name) {
|
|
22
|
+
return this.packages.find(p => p.name === name);
|
|
23
|
+
}
|
|
24
|
+
_updateDependencies() {
|
|
25
|
+
const deps = {};
|
|
26
|
+
for (const pkg of this.packages) {
|
|
27
|
+
const o = {
|
|
28
|
+
...pkg.json.dependencies,
|
|
29
|
+
...pkg.json.devDependencies,
|
|
30
|
+
...pkg.json.peerDependencies,
|
|
31
|
+
...pkg.json.optionalDependencies
|
|
32
|
+
};
|
|
33
|
+
const dependencies = [];
|
|
34
|
+
for (const k of Object.keys(o)) {
|
|
35
|
+
const p = this.getPackage(k);
|
|
36
|
+
if (p)
|
|
37
|
+
dependencies.push(k);
|
|
38
|
+
}
|
|
39
|
+
deps[pkg.name] = dependencies;
|
|
40
|
+
pkg.dependencies = dependencies;
|
|
41
|
+
}
|
|
42
|
+
let circularCheck;
|
|
43
|
+
const deepFindDependencies = (pkg, target) => {
|
|
44
|
+
if (circularCheck.includes(pkg.name))
|
|
45
|
+
return;
|
|
46
|
+
circularCheck.push(pkg.name);
|
|
47
|
+
for (const s of pkg.dependencies) {
|
|
48
|
+
if (!target.includes(s)) {
|
|
49
|
+
target.push(s);
|
|
50
|
+
const p = this.getPackage(s);
|
|
51
|
+
if (p) {
|
|
52
|
+
deepFindDependencies(p, target);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
for (const pkg of this.packages) {
|
|
58
|
+
circularCheck = [];
|
|
59
|
+
deepFindDependencies(pkg, pkg.dependencies);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
static create(root, options) {
|
|
63
|
+
let dirname = root || process.cwd();
|
|
64
|
+
let deep = options?.deep ?? 10;
|
|
65
|
+
while (deep-- >= 0 && fs.existsSync(dirname)) {
|
|
66
|
+
const f = path.join(dirname, 'package.json');
|
|
67
|
+
if (fs.existsSync(f)) {
|
|
68
|
+
const pkgJson = JSON.parse(fs.readFileSync(f, 'utf-8'));
|
|
69
|
+
if (Array.isArray(pkgJson.workspaces)) {
|
|
70
|
+
const packages = this._resolvePackages(dirname, pkgJson.workspaces);
|
|
71
|
+
const config = this._readConfig(dirname);
|
|
72
|
+
const repo = new Repository(dirname, config, packages);
|
|
73
|
+
repo._updateDependencies();
|
|
74
|
+
return repo;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
dirname = path.resolve(dirname, '..');
|
|
78
|
+
}
|
|
79
|
+
throw new Error('No monorepo project detected');
|
|
80
|
+
}
|
|
81
|
+
static _resolvePackages(dirname, patterns) {
|
|
82
|
+
const packages = [];
|
|
83
|
+
for (const pattern of patterns) {
|
|
84
|
+
const dirs = glob.sync(pattern, {
|
|
85
|
+
cwd: dirname,
|
|
86
|
+
absolute: true,
|
|
87
|
+
deep: 0,
|
|
88
|
+
onlyDirectories: true
|
|
89
|
+
});
|
|
90
|
+
for (const dir of dirs) {
|
|
91
|
+
const f = path.join(dir, 'package.json');
|
|
92
|
+
if (fs.existsSync(f))
|
|
93
|
+
packages.push(new Package(dir));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return packages;
|
|
97
|
+
}
|
|
98
|
+
static _readConfig(dirname) {
|
|
99
|
+
const result = {};
|
|
100
|
+
const pkgJson = getPackageJson(dirname);
|
|
101
|
+
if (pkgJson && typeof pkgJson.rman === 'object')
|
|
102
|
+
merge(result, pkgJson.rman, { deep: true });
|
|
103
|
+
let filename = path.resolve(dirname, '.rman.yml');
|
|
104
|
+
if (fs.existsSync(filename)) {
|
|
105
|
+
const obj = yaml.load(fs.readFileSync(filename, 'utf-8'));
|
|
106
|
+
if (obj && typeof obj === 'object')
|
|
107
|
+
merge(result, obj, { deep: true });
|
|
108
|
+
}
|
|
109
|
+
filename = path.resolve(dirname, '.rmanrc');
|
|
110
|
+
if (fs.existsSync(filename)) {
|
|
111
|
+
const obj = JSON.parse(fs.readFileSync(filename, 'utf-8'));
|
|
112
|
+
if (obj && typeof obj === 'object')
|
|
113
|
+
merge(result, obj, { deep: true });
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function topoSortPackages(packages) {
|
|
119
|
+
packages.sort((a, b) => {
|
|
120
|
+
if (b.dependencies.includes(a.name))
|
|
121
|
+
return -1;
|
|
122
|
+
if (a.dependencies.includes(b.name))
|
|
123
|
+
return 1;
|
|
124
|
+
return 0;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface IWorkspaceProvider {
|
|
2
|
+
parse: (root: string) => IWorkspaceInfo | undefined;
|
|
3
|
+
}
|
|
4
|
+
export interface IWorkspaceInfo {
|
|
5
|
+
dirname: string;
|
|
6
|
+
pkgJson: any;
|
|
7
|
+
packages: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface Logger {
|
|
10
|
+
info: (message?: any, ...optionalParams: any[]) => void;
|
|
11
|
+
error: (message?: any, ...optionalParams: any[]) => void;
|
|
12
|
+
log: (message?: any, ...optionalParams: any[]) => void;
|
|
13
|
+
warn: (message?: any, ...optionalParams: any[]) => void;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/debug.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/debug.mjs
ADDED
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './core/repository';
|
package/esm/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './core/repository.mjs';
|
|
@@ -6,18 +6,15 @@ export interface IExecutorOptions {
|
|
|
6
6
|
argv?: string[];
|
|
7
7
|
env?: Record<string, string | undefined>;
|
|
8
8
|
shell?: boolean;
|
|
9
|
-
color?: boolean;
|
|
10
9
|
onSpawn?: (childProcess: ChildProcess) => void;
|
|
11
10
|
onLine?: (line: string, stdio: 'stderr' | 'stdout') => void;
|
|
12
11
|
onData?: (data: string, stdio: 'stderr' | 'stdout') => void;
|
|
13
|
-
|
|
14
|
-
export interface IRunScriptOptions {
|
|
15
|
-
gauge?: boolean;
|
|
12
|
+
throwOnError?: boolean;
|
|
16
13
|
}
|
|
17
14
|
export interface ExecuteCommandResult {
|
|
18
15
|
code?: number;
|
|
19
|
-
error?:
|
|
20
|
-
stderr
|
|
21
|
-
stdout
|
|
16
|
+
error?: Error;
|
|
17
|
+
stderr?: string;
|
|
18
|
+
stdout?: string;
|
|
22
19
|
}
|
|
23
|
-
export declare function
|
|
20
|
+
export declare function exec(command: string, options?: IExecutorOptions): Promise<ExecuteCommandResult>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import onExit from 'signal-exit';
|
|
3
|
+
import { npmRunPathEnv } from './npm-run-path.mjs';
|
|
4
|
+
const runningChildren = new Map();
|
|
5
|
+
export async function exec(command, options) {
|
|
6
|
+
const opts = {
|
|
7
|
+
shell: true,
|
|
8
|
+
throwOnError: true,
|
|
9
|
+
...options,
|
|
10
|
+
};
|
|
11
|
+
opts.env = {
|
|
12
|
+
...npmRunPathEnv({ cwd: opts.cwd }),
|
|
13
|
+
...opts.env
|
|
14
|
+
};
|
|
15
|
+
opts.cwd = opts.cwd || process.cwd();
|
|
16
|
+
const spawnOptions = {
|
|
17
|
+
stdio: opts.stdio || 'pipe',
|
|
18
|
+
env: opts.env,
|
|
19
|
+
cwd: opts.cwd,
|
|
20
|
+
shell: opts.shell,
|
|
21
|
+
windowsHide: true
|
|
22
|
+
};
|
|
23
|
+
const result = {
|
|
24
|
+
code: undefined,
|
|
25
|
+
stderr: '',
|
|
26
|
+
stdout: ''
|
|
27
|
+
};
|
|
28
|
+
const buffer = {
|
|
29
|
+
stdout: '',
|
|
30
|
+
stderr: ''
|
|
31
|
+
};
|
|
32
|
+
const processData = (data, stdio) => {
|
|
33
|
+
buffer[stdio] += data;
|
|
34
|
+
result[stdio] += data;
|
|
35
|
+
if (opts.onData)
|
|
36
|
+
opts.onData(data, stdio);
|
|
37
|
+
};
|
|
38
|
+
const processLines = (stdio, flush) => {
|
|
39
|
+
let chunk = buffer[stdio];
|
|
40
|
+
let i;
|
|
41
|
+
if (flush && !chunk.endsWith('\n'))
|
|
42
|
+
chunk += '\n';
|
|
43
|
+
while ((i = chunk.indexOf('\n')) >= 0) {
|
|
44
|
+
const line = chunk.substring(0, i);
|
|
45
|
+
chunk = chunk.substring(i + 1);
|
|
46
|
+
if (opts.onLine)
|
|
47
|
+
opts.onLine(line, stdio);
|
|
48
|
+
}
|
|
49
|
+
buffer[stdio] = chunk;
|
|
50
|
+
};
|
|
51
|
+
const child = spawn(command, opts.argv || [], spawnOptions);
|
|
52
|
+
if (child.pid) {
|
|
53
|
+
runningChildren.set(child.pid, child);
|
|
54
|
+
if (opts.onSpawn)
|
|
55
|
+
opts.onSpawn(child);
|
|
56
|
+
}
|
|
57
|
+
child.stdout?.on('data', (data) => {
|
|
58
|
+
processData(data, 'stdout');
|
|
59
|
+
processLines('stdout');
|
|
60
|
+
});
|
|
61
|
+
child.stderr?.on('data', (data) => {
|
|
62
|
+
processData(data, 'stderr');
|
|
63
|
+
processLines('stderr');
|
|
64
|
+
});
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
let resolved;
|
|
67
|
+
child.on('error', (err) => {
|
|
68
|
+
if (child.pid)
|
|
69
|
+
runningChildren.delete(child.pid);
|
|
70
|
+
processLines('stdout', true);
|
|
71
|
+
processLines('stderr', true);
|
|
72
|
+
if (resolved)
|
|
73
|
+
return;
|
|
74
|
+
result.code = err.code || 1;
|
|
75
|
+
if (!err) {
|
|
76
|
+
const text = `Command failed (${result.code})`;
|
|
77
|
+
err = new Error(text);
|
|
78
|
+
}
|
|
79
|
+
if (typeof err === 'string')
|
|
80
|
+
err = new Error(err);
|
|
81
|
+
result.error = err;
|
|
82
|
+
resolved = true;
|
|
83
|
+
if (opts.throwOnError)
|
|
84
|
+
return reject(result.error);
|
|
85
|
+
resolve(result);
|
|
86
|
+
});
|
|
87
|
+
child.on('close', (code) => {
|
|
88
|
+
if (child.pid)
|
|
89
|
+
runningChildren.delete(child.pid);
|
|
90
|
+
processLines('stdout', true);
|
|
91
|
+
processLines('stderr', true);
|
|
92
|
+
if (resolved)
|
|
93
|
+
return;
|
|
94
|
+
result.code = code;
|
|
95
|
+
resolved = true;
|
|
96
|
+
if (code) {
|
|
97
|
+
const text = `Command failed (${result.code})`;
|
|
98
|
+
result.error = new Error(text);
|
|
99
|
+
}
|
|
100
|
+
return resolve(result);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
onExit(() => {
|
|
105
|
+
runningChildren.forEach((child) => {
|
|
106
|
+
child.kill();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare namespace GitHelper {
|
|
2
|
+
interface FileStatus {
|
|
3
|
+
filename: string;
|
|
4
|
+
status: string;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export interface GitOptions {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class GitHelper {
|
|
11
|
+
cwd: string;
|
|
12
|
+
constructor(options?: GitOptions);
|
|
13
|
+
listDirtyFileStatus(options?: {
|
|
14
|
+
absolute?: boolean;
|
|
15
|
+
}): Promise<GitHelper.FileStatus[]>;
|
|
16
|
+
listDirtyFiles(options?: {
|
|
17
|
+
absolute?: boolean;
|
|
18
|
+
}): Promise<string[]>;
|
|
19
|
+
listCommitSha(): Promise<string[]>;
|
|
20
|
+
listCommittedFiles(options?: {
|
|
21
|
+
absolute?: boolean;
|
|
22
|
+
commits?: string | string[];
|
|
23
|
+
}): Promise<string[]>;
|
|
24
|
+
readFileLastPublished(filePath: string, commitSha?: string): Promise<string>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { exec } from './exec.mjs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export class GitHelper {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.cwd = options?.cwd || process.cwd();
|
|
6
|
+
}
|
|
7
|
+
async listDirtyFileStatus(options) {
|
|
8
|
+
const x = await exec('git', {
|
|
9
|
+
cwd: this.cwd,
|
|
10
|
+
argv: ['status', '--porcelain']
|
|
11
|
+
});
|
|
12
|
+
const result = [];
|
|
13
|
+
const files = x.stdout ? x.stdout.trim().split(/\s*\n\s*/) : [];
|
|
14
|
+
for (const f of files) {
|
|
15
|
+
const m = f.match(/^(\w+) (.+)$/);
|
|
16
|
+
if (m) {
|
|
17
|
+
result.push({
|
|
18
|
+
filename: options?.absolute ? path.join(this.cwd, m[2]) : m[2],
|
|
19
|
+
status: m[1]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
async listDirtyFiles(options) {
|
|
26
|
+
return (await this.listDirtyFileStatus(options)).map(x => x.filename);
|
|
27
|
+
}
|
|
28
|
+
async listCommitSha() {
|
|
29
|
+
const x = await exec('git', {
|
|
30
|
+
cwd: this.cwd,
|
|
31
|
+
argv: ['cherry']
|
|
32
|
+
});
|
|
33
|
+
const matches = x.stdout ? x.stdout.matchAll(/([a-f0-9]+)/gi) : [];
|
|
34
|
+
const result = [];
|
|
35
|
+
for (const m of matches) {
|
|
36
|
+
result.push(m[1]);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
async listCommittedFiles(options) {
|
|
41
|
+
const shaArr = options?.commits
|
|
42
|
+
? (Array.isArray(options?.commits) ? options?.commits : [options?.commits])
|
|
43
|
+
: await this.listCommitSha();
|
|
44
|
+
let result = [];
|
|
45
|
+
for (const s of shaArr) {
|
|
46
|
+
const x = await exec('git', {
|
|
47
|
+
cwd: this.cwd,
|
|
48
|
+
argv: ['show', s, '--name-only', '--pretty="format:"']
|
|
49
|
+
});
|
|
50
|
+
result.push(...(x.stdout ? x.stdout.trim().split(/\s*\n\s*/) : []));
|
|
51
|
+
}
|
|
52
|
+
if (options?.absolute)
|
|
53
|
+
result = result.map(f => path.join(this.cwd, f));
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
async readFileLastPublished(filePath, commitSha) {
|
|
57
|
+
const x = await exec('git', {
|
|
58
|
+
cwd: this.cwd,
|
|
59
|
+
argv: ['show', (commitSha || 'HEAD') + ':"' + filePath + '"']
|
|
60
|
+
});
|
|
61
|
+
return x.stdout || '';
|
|
62
|
+
}
|
|
63
|
+
}
|