reslop 0.1.4 → 0.1.6
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/CHANGELOG.md +19 -1
- package/README.md +19 -20
- package/lib/cli.js +20 -16
- package/lib/detect.js +2 -40
- package/lib/diff.js +71 -2
- package/lib/files.js +5 -1
- package/lib/git.js +322 -12
- package/lib/highlight/bash.js +228 -0
- package/lib/highlight/core.js +194 -0
- package/lib/highlight/css.js +76 -0
- package/lib/highlight/csv.js +66 -0
- package/lib/highlight/dart.js +261 -0
- package/lib/highlight/dot.js +54 -0
- package/lib/highlight/html.js +90 -0
- package/lib/highlight/js.js +571 -0
- package/lib/highlight/json.js +54 -0
- package/lib/highlight/log.js +60 -0
- package/lib/highlight/txt.js +41 -0
- package/lib/highlight.js +32 -1299
- package/lib/keys.js +98 -26
- package/lib/render.js +574 -84
- package/lib/review.js +145 -3
- package/lib/select.js +7 -12
- package/lib/session.js +759 -50
- package/lib/sys.js +4 -0
- package/lib/update.js +302 -0
- package/package.json +1 -1
package/lib/git.js
CHANGED
|
@@ -7,7 +7,7 @@ const path = require('node:path');
|
|
|
7
7
|
const diff = require('./diff.js');
|
|
8
8
|
const { parseDiff, synthesizeNewFile, itemsFromFiles } = diff;
|
|
9
9
|
const { itemPath, isPathInScope, REVIEW_DIR } = require('./files.js');
|
|
10
|
-
const { npmBin, spawnBase } = require('./sys.js');
|
|
10
|
+
const { npmBin, spawnBase, npmOpts } = require('./sys.js');
|
|
11
11
|
const deps = require('./deps.js');
|
|
12
12
|
const { foldDepItems, mergeDepFile, depFileMeta } = deps;
|
|
13
13
|
const { collectUsedNames, parseAuditReport, parseOutdatedReport } = deps;
|
|
@@ -25,6 +25,23 @@ const GIT_MAX_BUFFER = 32 * 1024 * 1024;
|
|
|
25
25
|
const BINARY_SIZE = 1000000;
|
|
26
26
|
const AUDIT_MS = 15000;
|
|
27
27
|
const INSTALL_MS = 120000;
|
|
28
|
+
const BRANCH_FORMAT = [
|
|
29
|
+
'%(HEAD)',
|
|
30
|
+
'%(refname:short)',
|
|
31
|
+
'%(objectname:short)',
|
|
32
|
+
'%(committerdate:relative)',
|
|
33
|
+
'%(upstream:track)',
|
|
34
|
+
'%(subject)',
|
|
35
|
+
].join('%09');
|
|
36
|
+
const TRACK_AHEAD = /ahead (\d+)/;
|
|
37
|
+
const TRACK_BEHIND = /behind (\d+)/;
|
|
38
|
+
const ORIGIN_HEAD = 'refs/remotes/origin/HEAD';
|
|
39
|
+
const DEFAULT_BRANCHES = ['main', 'master'];
|
|
40
|
+
const PUSH_REJECTED = [
|
|
41
|
+
'non-fast-forward',
|
|
42
|
+
'failed to push some refs',
|
|
43
|
+
'updates were rejected',
|
|
44
|
+
];
|
|
28
45
|
|
|
29
46
|
const oneLine = (text) => {
|
|
30
47
|
const line = text.trim().split('\n')[0];
|
|
@@ -77,7 +94,15 @@ const runProc = (cmd, args, options = {}) =>
|
|
|
77
94
|
let stderr = '';
|
|
78
95
|
let settled = false;
|
|
79
96
|
let timer = null;
|
|
80
|
-
const
|
|
97
|
+
const spawnOpts = { cwd, env };
|
|
98
|
+
if (options.shell) spawnOpts.shell = true;
|
|
99
|
+
let child;
|
|
100
|
+
try {
|
|
101
|
+
child = spawn(cmd, args, spawnBase(spawnOpts));
|
|
102
|
+
} catch (error) {
|
|
103
|
+
resolve({ status: null, error, stdout: '', stderr: '' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
81
106
|
const finish = (result) => {
|
|
82
107
|
if (settled) return;
|
|
83
108
|
settled = true;
|
|
@@ -284,7 +309,7 @@ const runNpmJson = (top, args, timeout) => {
|
|
|
284
309
|
const result = spawnSync(
|
|
285
310
|
npmBin(),
|
|
286
311
|
args,
|
|
287
|
-
|
|
312
|
+
npmOpts({
|
|
288
313
|
cwd: top,
|
|
289
314
|
encoding: GIT_ENCODING,
|
|
290
315
|
maxBuffer: GIT_MAX_BUFFER,
|
|
@@ -302,6 +327,7 @@ const runNpmJsonAsync = async (top, args, timeout, signal) => {
|
|
|
302
327
|
maxBuffer: GIT_MAX_BUFFER,
|
|
303
328
|
timeout,
|
|
304
329
|
signal,
|
|
330
|
+
shell: npmOpts().shell,
|
|
305
331
|
});
|
|
306
332
|
if (result.error) return '';
|
|
307
333
|
return result.stdout || '';
|
|
@@ -456,7 +482,7 @@ const runNpm = (cwd, args) =>
|
|
|
456
482
|
spawnSync(
|
|
457
483
|
npmBin(),
|
|
458
484
|
args,
|
|
459
|
-
|
|
485
|
+
npmOpts({
|
|
460
486
|
cwd,
|
|
461
487
|
encoding: GIT_ENCODING,
|
|
462
488
|
maxBuffer: GIT_MAX_BUFFER,
|
|
@@ -464,6 +490,15 @@ const runNpm = (cwd, args) =>
|
|
|
464
490
|
}),
|
|
465
491
|
);
|
|
466
492
|
|
|
493
|
+
const runNpmAsync = (cwd, args) =>
|
|
494
|
+
runProc(npmBin(), args, {
|
|
495
|
+
cwd,
|
|
496
|
+
encoding: GIT_ENCODING,
|
|
497
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
498
|
+
timeout: INSTALL_MS,
|
|
499
|
+
shell: npmOpts().shell,
|
|
500
|
+
});
|
|
501
|
+
|
|
467
502
|
const firstDepRel = (files, kind) => {
|
|
468
503
|
for (const rel of files) {
|
|
469
504
|
const meta = depFileMeta(rel);
|
|
@@ -472,7 +507,7 @@ const firstDepRel = (files, kind) => {
|
|
|
472
507
|
return '';
|
|
473
508
|
};
|
|
474
509
|
|
|
475
|
-
const
|
|
510
|
+
const proposedNpmPlan = (top, item) => {
|
|
476
511
|
const change = depChange(item);
|
|
477
512
|
const origin = item.origin;
|
|
478
513
|
const files = depPaths(item);
|
|
@@ -483,7 +518,7 @@ const applyProposedUpdate = (top, item) => {
|
|
|
483
518
|
const picked = lockOnly ? lockRel : manifest;
|
|
484
519
|
const target = picked || files[0] || '';
|
|
485
520
|
const meta = depFileMeta(target);
|
|
486
|
-
if (!meta) return;
|
|
521
|
+
if (!meta) return null;
|
|
487
522
|
let saved = '';
|
|
488
523
|
if (!lockOnly && !removing && manifest) {
|
|
489
524
|
const worktree = worktreeText(top, manifest);
|
|
@@ -503,9 +538,23 @@ const applyProposedUpdate = (top, item) => {
|
|
|
503
538
|
let npmArgs = ['i'];
|
|
504
539
|
if (removing) npmArgs = ['uninstall', change.name];
|
|
505
540
|
else if (lockOnly) npmArgs = ['audit', 'fix'];
|
|
506
|
-
const install = item.dep.install ?? ((dir) => runNpm(dir, npmArgs));
|
|
507
|
-
const result = install(cwd);
|
|
508
541
|
const fail = removing ? 'npm uninstall failed' : 'npm i failed';
|
|
542
|
+
const dirLock = meta.dir === '.' ? LOCKFILE : `${meta.dir}/${LOCKFILE}`;
|
|
543
|
+
const lockPath = lockRel || dirLock;
|
|
544
|
+
return {
|
|
545
|
+
cwd,
|
|
546
|
+
npmArgs,
|
|
547
|
+
saved,
|
|
548
|
+
manifest,
|
|
549
|
+
lockOnly,
|
|
550
|
+
lockPath,
|
|
551
|
+
fail,
|
|
552
|
+
runner: item.dep.install,
|
|
553
|
+
};
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const finishProposedNpm = (top, plan, result) => {
|
|
557
|
+
const { saved, manifest, lockOnly, lockPath, fail } = plan;
|
|
509
558
|
if (result && (result.error || result.status !== 0)) {
|
|
510
559
|
if (saved && manifest) writeWorktreeFile(top, manifest, saved);
|
|
511
560
|
if (result.error) {
|
|
@@ -517,12 +566,26 @@ const applyProposedUpdate = (top, item) => {
|
|
|
517
566
|
}
|
|
518
567
|
const toAdd = [];
|
|
519
568
|
if (!lockOnly && manifest) toAdd.push(manifest);
|
|
520
|
-
const dirLock = meta.dir === '.' ? LOCKFILE : `${meta.dir}/${LOCKFILE}`;
|
|
521
|
-
const lockPath = lockRel || dirLock;
|
|
522
569
|
if (fs.existsSync(path.join(top, lockPath))) toAdd.push(lockPath);
|
|
523
570
|
if (toAdd.length) requireOk(runGit(['add', '--', ...toAdd], top));
|
|
524
571
|
};
|
|
525
572
|
|
|
573
|
+
const applyProposedUpdate = (top, item) => {
|
|
574
|
+
const plan = proposedNpmPlan(top, item);
|
|
575
|
+
if (!plan) return;
|
|
576
|
+
const install = plan.runner ?? ((dir) => runNpm(dir, plan.npmArgs));
|
|
577
|
+
finishProposedNpm(top, plan, install(plan.cwd));
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const applyProposedUpdateAsync = async (top, item) => {
|
|
581
|
+
const plan = proposedNpmPlan(top, item);
|
|
582
|
+
if (!plan) return;
|
|
583
|
+
const run = plan.runner
|
|
584
|
+
? (dir) => Promise.resolve(plan.runner(dir))
|
|
585
|
+
: (dir) => runNpmAsync(dir, plan.npmArgs);
|
|
586
|
+
finishProposedNpm(top, plan, await run(plan.cwd));
|
|
587
|
+
};
|
|
588
|
+
|
|
526
589
|
const applyDepChange = (top, item, mode) => {
|
|
527
590
|
const change = depChange(item);
|
|
528
591
|
if (change && change.propose) {
|
|
@@ -579,6 +642,15 @@ const commitDiffArgs = (commit, parent, pathArgs) => {
|
|
|
579
642
|
|
|
580
643
|
const pathArgsOf = (paths) => (paths.length ? ['--', ...paths] : []);
|
|
581
644
|
|
|
645
|
+
const currentBranch = (top) => {
|
|
646
|
+
const args = ['rev-parse', '--abbrev-ref', 'HEAD'];
|
|
647
|
+
const result = runGit(args, top);
|
|
648
|
+
if (result.status !== 0) return '';
|
|
649
|
+
const name = result.stdout.trim();
|
|
650
|
+
if (!name || name === 'HEAD') return '';
|
|
651
|
+
return name;
|
|
652
|
+
};
|
|
653
|
+
|
|
582
654
|
const snapshotWorktree = (top, stagedOut, unstagedOut, names) => {
|
|
583
655
|
const stagedDiff = parseDiff(stagedOut);
|
|
584
656
|
const unstagedDiff = parseDiff(unstagedOut);
|
|
@@ -587,14 +659,16 @@ const snapshotWorktree = (top, stagedOut, unstagedOut, names) => {
|
|
|
587
659
|
const untracked = untrackedItemsFrom(top, names);
|
|
588
660
|
const combined = mergeItems([staged, unstaged, untracked]);
|
|
589
661
|
const parsed = dropReviewItems(combined);
|
|
590
|
-
|
|
662
|
+
const branch = currentBranch(top);
|
|
663
|
+
return { top, parsed, branch };
|
|
591
664
|
};
|
|
592
665
|
|
|
593
666
|
const snapshotCommit = (top, commit, files) => {
|
|
594
667
|
const commitItems = itemsFromFiles(files, 'commit');
|
|
595
668
|
const parsed = dropReviewItems(commitItems);
|
|
596
669
|
const revShort = shortRev(top, commit);
|
|
597
|
-
|
|
670
|
+
const branch = currentBranch(top);
|
|
671
|
+
return { top, parsed, rev: commit, revShort, branch };
|
|
598
672
|
};
|
|
599
673
|
|
|
600
674
|
const loadCommit = (top, commit, extra) => {
|
|
@@ -746,6 +820,16 @@ const addItem = (top, item) => {
|
|
|
746
820
|
applyPatch(top, item.patchAdd, ['--cached']);
|
|
747
821
|
};
|
|
748
822
|
|
|
823
|
+
const addItemAsync = async (top, item) => {
|
|
824
|
+
if (item.origin === 'staged' || item.origin === 'commit') return;
|
|
825
|
+
const change = depChange(item);
|
|
826
|
+
if (change && change.propose) {
|
|
827
|
+
await applyProposedUpdateAsync(top, item);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
addItem(top, item);
|
|
831
|
+
};
|
|
832
|
+
|
|
749
833
|
const unstageItem = (top, item) => {
|
|
750
834
|
if (item.origin !== 'staged') return;
|
|
751
835
|
if (depChange(item)) {
|
|
@@ -845,6 +929,203 @@ const revertItem = (top, item) => {
|
|
|
845
929
|
}
|
|
846
930
|
};
|
|
847
931
|
|
|
932
|
+
const lastMessage = (top) => {
|
|
933
|
+
const result = runGit(['log', '-1', '--format=%B'], top);
|
|
934
|
+
if (result.status !== 0) return '';
|
|
935
|
+
return (result.stdout ?? '').replace(/\s+$/, '');
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
const parseTrack = (text) => {
|
|
939
|
+
const raw = `${text ?? ''}`.trim().replace(/^\[|\]$/g, '');
|
|
940
|
+
if (!raw) return { ahead: 0, behind: 0, gone: false };
|
|
941
|
+
if (raw === 'gone') return { ahead: 0, behind: 0, gone: true };
|
|
942
|
+
const aheadMatch = TRACK_AHEAD.exec(raw);
|
|
943
|
+
const behindMatch = TRACK_BEHIND.exec(raw);
|
|
944
|
+
const ahead = aheadMatch ? Number(aheadMatch[1]) : 0;
|
|
945
|
+
const behind = behindMatch ? Number(behindMatch[1]) : 0;
|
|
946
|
+
return { ahead, behind, gone: false };
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
const parseBranchLine = (line) => {
|
|
950
|
+
const parts = line.split('\t');
|
|
951
|
+
if (parts.length < 2) return null;
|
|
952
|
+
const name = (parts[1] ?? '').trim();
|
|
953
|
+
if (!name) return null;
|
|
954
|
+
const mark = parts[0] ?? '';
|
|
955
|
+
const sha = (parts[2] ?? '').trim();
|
|
956
|
+
const date = (parts[3] ?? '').trim();
|
|
957
|
+
const track = parseTrack(parts[4]);
|
|
958
|
+
const subject = parts.slice(5).join('\t').trim();
|
|
959
|
+
return {
|
|
960
|
+
name,
|
|
961
|
+
current: mark.includes('*'),
|
|
962
|
+
isDefault: false,
|
|
963
|
+
sha,
|
|
964
|
+
date,
|
|
965
|
+
subject,
|
|
966
|
+
...track,
|
|
967
|
+
};
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
const originHead = (top) => {
|
|
971
|
+
const args = ['symbolic-ref', '--short', ORIGIN_HEAD];
|
|
972
|
+
const result = runGit(args, top);
|
|
973
|
+
if (result.status !== 0) return '';
|
|
974
|
+
const ref = result.stdout.trim();
|
|
975
|
+
const prefix = 'origin/';
|
|
976
|
+
if (ref.startsWith(prefix)) return ref.slice(prefix.length);
|
|
977
|
+
return ref;
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const defaultBranch = (top, names) => {
|
|
981
|
+
const remote = originHead(top);
|
|
982
|
+
if (names.has(remote)) return remote;
|
|
983
|
+
for (const name of DEFAULT_BRANCHES) {
|
|
984
|
+
if (names.has(name)) return name;
|
|
985
|
+
}
|
|
986
|
+
return '';
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
const listBranches = (top) => {
|
|
990
|
+
const args = ['for-each-ref', `--format=${BRANCH_FORMAT}`, 'refs/heads'];
|
|
991
|
+
const result = requireOk(runGit(args, top), 'branch failed');
|
|
992
|
+
const branches = [];
|
|
993
|
+
for (const line of result.stdout.split('\n')) {
|
|
994
|
+
if (!line) continue;
|
|
995
|
+
const entry = parseBranchLine(line);
|
|
996
|
+
if (entry) branches.push(entry);
|
|
997
|
+
}
|
|
998
|
+
const names = new Set();
|
|
999
|
+
for (const entry of branches) names.add(entry.name);
|
|
1000
|
+
const primary = defaultBranch(top, names);
|
|
1001
|
+
for (const entry of branches) {
|
|
1002
|
+
entry.isDefault = entry.name === primary;
|
|
1003
|
+
}
|
|
1004
|
+
return branches;
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
const namedBranch = (name) => {
|
|
1008
|
+
const branch = `${name ?? ''}`.trim();
|
|
1009
|
+
if (!branch) throw new Error('empty branch name');
|
|
1010
|
+
return branch;
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
const checkoutBranch = (top, name) => {
|
|
1014
|
+
const branch = namedBranch(name);
|
|
1015
|
+
requireOk(runGit(['checkout', branch], top), 'checkout failed');
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
const checkoutBranchAsync = async (top, name) => {
|
|
1019
|
+
const branch = namedBranch(name);
|
|
1020
|
+
requireOk(await runGitAsync(['checkout', branch], top), 'checkout failed');
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
const createBranch = (top, name) => {
|
|
1024
|
+
const branch = namedBranch(name);
|
|
1025
|
+
const args = ['checkout', '-b', branch];
|
|
1026
|
+
requireOk(runGit(args, top), 'checkout failed');
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
const createBranchAsync = async (top, name) => {
|
|
1030
|
+
const branch = namedBranch(name);
|
|
1031
|
+
const args = ['checkout', '-b', branch];
|
|
1032
|
+
requireOk(await runGitAsync(args, top), 'checkout failed');
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
const pullChanges = (top) => {
|
|
1036
|
+
requireOk(runGit(['pull'], top), 'pull failed');
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
const pullChangesAsync = async (top) => {
|
|
1040
|
+
requireOk(await runGitAsync(['pull'], top), 'pull failed');
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
const isRejectedPush = (msg) => {
|
|
1044
|
+
const text = `${msg ?? ''}`.toLowerCase();
|
|
1045
|
+
for (const token of PUSH_REJECTED) {
|
|
1046
|
+
if (text.includes(token)) return true;
|
|
1047
|
+
}
|
|
1048
|
+
return false;
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
const pushError = (msg) => {
|
|
1052
|
+
const text = `${msg ?? ''}`;
|
|
1053
|
+
const rejected = isRejectedPush(text);
|
|
1054
|
+
const error = new Error(rejected ? 'push rejected' : oneLine(text));
|
|
1055
|
+
error.rejected = rejected;
|
|
1056
|
+
return error;
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const pushCmd = (force) => {
|
|
1060
|
+
if (force) return ['push', '--force-with-lease'];
|
|
1061
|
+
return ['push'];
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
const pushUpstreamCmd = (force) => {
|
|
1065
|
+
if (force) return ['push', '--force-with-lease', '-u', 'origin', 'HEAD'];
|
|
1066
|
+
return ['push', '-u', 'origin', 'HEAD'];
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const finishPush = (result, force, run) => {
|
|
1070
|
+
if (result.status === 0) return;
|
|
1071
|
+
const msg = result.stderr || result.stdout || 'push failed';
|
|
1072
|
+
if (!msg.toLowerCase().includes('no upstream')) {
|
|
1073
|
+
throw pushError(msg);
|
|
1074
|
+
}
|
|
1075
|
+
const next = run(pushUpstreamCmd(force));
|
|
1076
|
+
if (next.status !== 0) {
|
|
1077
|
+
throw pushError(next.stderr || next.stdout || 'push failed');
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const pushChanges = (top, force) => {
|
|
1082
|
+
const useForce = force === true;
|
|
1083
|
+
const result = runGit(pushCmd(useForce), top);
|
|
1084
|
+
finishPush(result, useForce, (args) => runGit(args, top));
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
const pushChangesAsync = async (top, force) => {
|
|
1088
|
+
const useForce = force === true;
|
|
1089
|
+
const result = await runGitAsync(pushCmd(useForce), top);
|
|
1090
|
+
if (result.status === 0) return;
|
|
1091
|
+
const msg = result.stderr || result.stdout || 'push failed';
|
|
1092
|
+
if (!msg.toLowerCase().includes('no upstream')) {
|
|
1093
|
+
throw pushError(msg);
|
|
1094
|
+
}
|
|
1095
|
+
const next = await runGitAsync(pushUpstreamCmd(useForce), top);
|
|
1096
|
+
if (next.status !== 0) {
|
|
1097
|
+
throw pushError(next.stderr || next.stdout || 'push failed');
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
const hasStaged = (top) => {
|
|
1102
|
+
const result = runGit(['diff', '--cached', '--quiet'], top);
|
|
1103
|
+
return result.status === 1;
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
const commitPlan = (kind, message) => {
|
|
1107
|
+
if (kind === 'fixup') {
|
|
1108
|
+
const target = (message ?? '').trim() || 'HEAD';
|
|
1109
|
+
return { args: ['commit', `--fixup=${target}`], body: undefined };
|
|
1110
|
+
}
|
|
1111
|
+
const args = ['commit'];
|
|
1112
|
+
if (kind === 'amend') args.push('--amend');
|
|
1113
|
+
args.push('-F', '-');
|
|
1114
|
+
const text = message ?? '';
|
|
1115
|
+
const body = text.endsWith('\n') ? text : `${text}\n`;
|
|
1116
|
+
return { args, body };
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
const commitChanges = (top, kind, message) => {
|
|
1120
|
+
const plan = commitPlan(kind, message);
|
|
1121
|
+
requireOk(runGit(plan.args, top, plan.body));
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
const commitChangesAsync = async (top, kind, message) => {
|
|
1125
|
+
const plan = commitPlan(kind, message);
|
|
1126
|
+
requireOk(await runGitAsync(plan.args, top, { input: plan.body }));
|
|
1127
|
+
};
|
|
1128
|
+
|
|
848
1129
|
const revertFile = (top, rel, items) => {
|
|
849
1130
|
const paths = new Set([rel]);
|
|
850
1131
|
for (const item of items) {
|
|
@@ -864,9 +1145,24 @@ const createGitRepo = () => ({
|
|
|
864
1145
|
toplevel,
|
|
865
1146
|
resolveRev,
|
|
866
1147
|
add: addItem,
|
|
1148
|
+
addAsync: addItemAsync,
|
|
867
1149
|
unstage: unstageItem,
|
|
868
1150
|
revert: revertItem,
|
|
869
1151
|
revertFile,
|
|
1152
|
+
commit: commitChanges,
|
|
1153
|
+
commitAsync: commitChangesAsync,
|
|
1154
|
+
hasStaged,
|
|
1155
|
+
lastMessage,
|
|
1156
|
+
listBranches,
|
|
1157
|
+
currentBranch,
|
|
1158
|
+
checkout: checkoutBranch,
|
|
1159
|
+
checkoutAsync: checkoutBranchAsync,
|
|
1160
|
+
createBranch,
|
|
1161
|
+
createBranchAsync,
|
|
1162
|
+
pull: pullChanges,
|
|
1163
|
+
pullAsync: pullChangesAsync,
|
|
1164
|
+
push: pushChanges,
|
|
1165
|
+
pushAsync: pushChangesAsync,
|
|
870
1166
|
});
|
|
871
1167
|
|
|
872
1168
|
module.exports = {
|
|
@@ -876,5 +1172,19 @@ module.exports = {
|
|
|
876
1172
|
addItem,
|
|
877
1173
|
unstageItem,
|
|
878
1174
|
revertItem,
|
|
1175
|
+
commitChanges,
|
|
1176
|
+
commitChangesAsync,
|
|
1177
|
+
hasStaged,
|
|
1178
|
+
currentBranch,
|
|
1179
|
+
lastMessage,
|
|
1180
|
+
listBranches,
|
|
1181
|
+
checkoutBranch,
|
|
1182
|
+
checkoutBranchAsync,
|
|
1183
|
+
createBranch,
|
|
1184
|
+
createBranchAsync,
|
|
1185
|
+
pullChanges,
|
|
1186
|
+
pullChangesAsync,
|
|
1187
|
+
pushChanges,
|
|
1188
|
+
pushChangesAsync,
|
|
879
1189
|
createGitRepo,
|
|
880
1190
|
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SQUOTE, emit, wordStyle, readUntilNl } = require('./core.js');
|
|
4
|
+
|
|
5
|
+
const langs = ['bash'];
|
|
6
|
+
|
|
7
|
+
const aliases = {
|
|
8
|
+
sh: 'bash',
|
|
9
|
+
shell: 'bash',
|
|
10
|
+
zsh: 'bash',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BASH_QUOTE = `${SQUOTE}\``;
|
|
14
|
+
|
|
15
|
+
const KEYWORDS = [
|
|
16
|
+
'if',
|
|
17
|
+
'then',
|
|
18
|
+
'else',
|
|
19
|
+
'elif',
|
|
20
|
+
'fi',
|
|
21
|
+
'for',
|
|
22
|
+
'while',
|
|
23
|
+
'until',
|
|
24
|
+
'do',
|
|
25
|
+
'done',
|
|
26
|
+
'case',
|
|
27
|
+
'esac',
|
|
28
|
+
'function',
|
|
29
|
+
'select',
|
|
30
|
+
'in',
|
|
31
|
+
'time',
|
|
32
|
+
'coproc',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const BUILTINS = [
|
|
36
|
+
'echo',
|
|
37
|
+
'printf',
|
|
38
|
+
'cd',
|
|
39
|
+
'pwd',
|
|
40
|
+
'exit',
|
|
41
|
+
'export',
|
|
42
|
+
'source',
|
|
43
|
+
'alias',
|
|
44
|
+
'unalias',
|
|
45
|
+
'local',
|
|
46
|
+
'declare',
|
|
47
|
+
'typeset',
|
|
48
|
+
'readonly',
|
|
49
|
+
'unset',
|
|
50
|
+
'shift',
|
|
51
|
+
'read',
|
|
52
|
+
'test',
|
|
53
|
+
'true',
|
|
54
|
+
'false',
|
|
55
|
+
'exec',
|
|
56
|
+
'eval',
|
|
57
|
+
'set',
|
|
58
|
+
'trap',
|
|
59
|
+
'wait',
|
|
60
|
+
'kill',
|
|
61
|
+
'type',
|
|
62
|
+
'command',
|
|
63
|
+
'builtin',
|
|
64
|
+
'return',
|
|
65
|
+
'break',
|
|
66
|
+
'continue',
|
|
67
|
+
'pushd',
|
|
68
|
+
'popd',
|
|
69
|
+
'dirs',
|
|
70
|
+
'getopts',
|
|
71
|
+
'mapfile',
|
|
72
|
+
'readarray',
|
|
73
|
+
'let',
|
|
74
|
+
'umask',
|
|
75
|
+
'ulimit',
|
|
76
|
+
'fg',
|
|
77
|
+
'bg',
|
|
78
|
+
'jobs',
|
|
79
|
+
'hash',
|
|
80
|
+
'help',
|
|
81
|
+
'enable',
|
|
82
|
+
'caller',
|
|
83
|
+
'bind',
|
|
84
|
+
'compgen',
|
|
85
|
+
'complete',
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const WORD_STYLE = {
|
|
89
|
+
...wordStyle(KEYWORDS, 'control'),
|
|
90
|
+
...wordStyle(BUILTINS, 'function'),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const readBashVar = (source, i) => {
|
|
94
|
+
if (source[i + 1] === '{') {
|
|
95
|
+
let j = i + 2;
|
|
96
|
+
while (j < source.length && source[j] !== '}') j += 1;
|
|
97
|
+
if (j < source.length) j += 1;
|
|
98
|
+
return { text: source.slice(i, j), end: j };
|
|
99
|
+
}
|
|
100
|
+
if (/[0-9#?$!*@-]/.test(source[i + 1] || '')) {
|
|
101
|
+
return { text: source.slice(i, i + 2), end: i + 2 };
|
|
102
|
+
}
|
|
103
|
+
let j = i + 1;
|
|
104
|
+
while (j < source.length && /[A-Za-z0-9_]/.test(source[j])) j += 1;
|
|
105
|
+
if (j === i + 1) return { text: '$', end: i + 1 };
|
|
106
|
+
return { text: source.slice(i, j), end: j };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const takeQuoted = (source, i) => {
|
|
110
|
+
const q = source[i];
|
|
111
|
+
let j = i + 1;
|
|
112
|
+
const n = source.length;
|
|
113
|
+
while (j < n && source[j] !== q) j += 1;
|
|
114
|
+
if (j < n) j += 1;
|
|
115
|
+
return { text: source.slice(i, j), end: j };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const takeBashDouble = (source, i, out) => {
|
|
119
|
+
let j = i + 1;
|
|
120
|
+
let chunk = '"';
|
|
121
|
+
const n = source.length;
|
|
122
|
+
while (j < n && source[j] !== '"') {
|
|
123
|
+
if (source[j] === '\\' && j + 1 < n) {
|
|
124
|
+
chunk += source.slice(j, j + 2);
|
|
125
|
+
j += 2;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (source[j] === '$') {
|
|
129
|
+
emit(out, 'string', chunk);
|
|
130
|
+
chunk = '';
|
|
131
|
+
const { text, end } = readBashVar(source, j);
|
|
132
|
+
emit(out, 'variable', text);
|
|
133
|
+
j = end;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
chunk += source[j];
|
|
137
|
+
j += 1;
|
|
138
|
+
}
|
|
139
|
+
if (j < n) {
|
|
140
|
+
chunk += '"';
|
|
141
|
+
j += 1;
|
|
142
|
+
}
|
|
143
|
+
if (chunk) emit(out, 'string', chunk);
|
|
144
|
+
return j;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const bashWordStyle = (word, nextCh) => {
|
|
148
|
+
const known = WORD_STYLE[word];
|
|
149
|
+
if (known) return known;
|
|
150
|
+
if (nextCh === '=') return 'variable';
|
|
151
|
+
return 'plain';
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const bashOpLen = (ch, next) => ('|&><'.includes(ch) && next === ch ? 2 : 1);
|
|
155
|
+
|
|
156
|
+
const highlight = (source) => {
|
|
157
|
+
const out = [];
|
|
158
|
+
let i = 0;
|
|
159
|
+
const n = source.length;
|
|
160
|
+
while (i < n) {
|
|
161
|
+
const ch = source[i];
|
|
162
|
+
const next = source[i + 1];
|
|
163
|
+
|
|
164
|
+
if (ch === '#' && (i === 0 || /\s/.test(source[i - 1]))) {
|
|
165
|
+
const { text, end } = readUntilNl(source, i);
|
|
166
|
+
emit(out, 'comment', text);
|
|
167
|
+
i = end;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (BASH_QUOTE.includes(ch)) {
|
|
172
|
+
const { text, end } = takeQuoted(source, i);
|
|
173
|
+
emit(out, 'string', text);
|
|
174
|
+
i = end;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (ch === '"') {
|
|
179
|
+
i = takeBashDouble(source, i, out);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (ch === '$') {
|
|
184
|
+
const { text, end } = readBashVar(source, i);
|
|
185
|
+
emit(out, 'variable', text);
|
|
186
|
+
i = end;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (ch === '-' && /[A-Za-z0-9]/.test(next || '')) {
|
|
191
|
+
let j = i + 1;
|
|
192
|
+
while (j < n && /[A-Za-z0-9_-]/.test(source[j])) j += 1;
|
|
193
|
+
emit(out, 'attr', source.slice(i, j));
|
|
194
|
+
i = j;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
199
|
+
let j = i + 1;
|
|
200
|
+
while (j < n && /[A-Za-z0-9_]/.test(source[j])) j += 1;
|
|
201
|
+
const word = source.slice(i, j);
|
|
202
|
+
emit(out, bashWordStyle(word, source[j]), word);
|
|
203
|
+
i = j;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (/[0-9]/.test(ch)) {
|
|
208
|
+
let j = i + 1;
|
|
209
|
+
while (j < n && /[0-9]/.test(source[j])) j += 1;
|
|
210
|
+
emit(out, 'number', source.slice(i, j));
|
|
211
|
+
i = j;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if ('|&;<>(){}'.includes(ch)) {
|
|
216
|
+
const j = i + bashOpLen(ch, next);
|
|
217
|
+
emit(out, 'operator', source.slice(i, j));
|
|
218
|
+
i = j;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
emit(out, 'plain', ch);
|
|
223
|
+
i += 1;
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
module.exports = { langs, aliases, highlight };
|