watskeburt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016-2022 Sander Verweij
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,47 @@
1
+ # watskeburt
2
+
3
+ get git changed files & their statusses since _any reference_
4
+
5
+ ## what's this do?
6
+
7
+ A micro-lib to retrieve an array of file names that were changed (added,
8
+ modified, renamed, deleted, ...) since the reference it got passed.
9
+
10
+ - :warning: NOT yet published on npm
11
+ - :warning: in the process of getting 'production ready'. It's automatically
12
+ tested + it's using itself + interface is stable - but a bunch of static
13
+ analysis still needs be added, as well as a commonjs version.
14
+ - :warning: currently esm only - commonjs compiledown will follow as part of the
15
+ previous point.
16
+
17
+ ## sample return value
18
+
19
+ ```json
20
+ [
21
+ { "name": "doc/cli.md", "changeType": "modified" },
22
+ {
23
+ "name": "test/report/mermaid/mermaid.spec.mjs",
24
+ "changeType": "renamed",
25
+ "oldName": "test/configs/plugins/mermaid-reporter-plugin/module-level/index.spec.mjs",
26
+ "similarity": 66
27
+ },
28
+ { "name": "src/not-tracked-yet.mjs", "changeType": "untracked" }
29
+ // ...
30
+ ]
31
+ ```
32
+
33
+ ## cli
34
+
35
+ For now there's also a simple command line interface
36
+
37
+ ```
38
+ Usage: watskeburt [options] <reference>
39
+
40
+ lists files since <reference>
41
+
42
+ Options:
43
+ -T, --output-type <type> json,regex (default: "regex")
44
+ --tracked-only only take tracked files into account (default: false)
45
+ -h, --help display help for command
46
+
47
+ ```
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "watskeburt",
3
+ "version": "0.1.0",
4
+ "description": "list files changed since a git reference",
5
+ "bin": "src/cli.mjs",
6
+ "main": "src/main.mjs",
7
+ "module": "src/main.mjs",
8
+ "exports": {
9
+ ".": [
10
+ {
11
+ "import": "./src/main.mjs"
12
+ }
13
+ ]
14
+ },
15
+ "types": "types/watskeburt.d.ts",
16
+ "files": [
17
+ "src",
18
+ "!src/**/*.spec.mjs",
19
+ "!**/*.DS_Store",
20
+ "types",
21
+ "LICENSE",
22
+ "package.json",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "test": "mocha \"src/**/*.spec.mjs\"",
27
+ "test:cover": "c8 --check-coverage --statements 100 --branches 100 --functions 100 --lines 100 --exclude \"**/*.spec.mjs\" --reporter text-summary --reporter html --reporter json-summary npm test",
28
+ "depcruise": "depcruise src --config",
29
+ "depcruise:graph": "depcruise src --include-only '^(src)' --config --output-type dot | dot -T svg | depcruise-wrap-stream-in-html > docs/dependency-graph.html",
30
+ "depcruise:graph:archi": "depcruise src --include-only '^(src)' --config --output-type archi | dot -T svg | depcruise-wrap-stream-in-html > docs/high-level-dependency-graph.html",
31
+ "depcruise:graph:dev": "depcruise src --include-only '^(src)' --prefix vscode://file/$(pwd)/ --config --output-type dot | dot -T svg | depcruise-wrap-stream-in-html | browser",
32
+ "depcruise:graph:diff:dev": "depcruise src --include-only '^(src)' --focus \"$(node src/cli.mjs main -T regex)\" --prefix vscode://file/$(pwd)/ --config --output-type dot | dot -T svg | depcruise-wrap-stream-in-html | browser",
33
+ "depcruise:graph:diff:mermaid": "depcruise src --include-only '^(src)' --config --output-type mermaid --output-to - --focus \"$(node src/cli.mjs $SHA -T regex)\"",
34
+ "depcruise:html": "depcruise src --progress --config --output-type err-html --output-to dependency-violation-report.html",
35
+ "depcruise:text": "depcruise src --progress --config --output-type text",
36
+ "depcruise:focus": "depcruise src --progress --config --output-type text --focus"
37
+ },
38
+ "keywords": [
39
+ "git",
40
+ "diff"
41
+ ],
42
+ "author": "Sander Verweij (https://sverweij.github.io/)",
43
+ "license": "MIT",
44
+ "devDependencies": {
45
+ "c8": "^7.11.3",
46
+ "dependency-cruiser": "^11.11.0",
47
+ "mocha": "^10.0.0"
48
+ },
49
+ "dependencies": {
50
+ "commander": "^9.3.0"
51
+ }
52
+ }
package/src/cli.mjs ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from "commander";
4
+ import { convert } from "./main.mjs";
5
+
6
+ program
7
+ .description("lists files since <reference>")
8
+ .option("-T, --output-type <type>", "json,regex", "regex")
9
+ .option("--tracked-only", "only take tracked files into account", false)
10
+ .arguments("<reference>")
11
+ .parse(process.argv);
12
+
13
+ if (program.args[0]) {
14
+ try {
15
+ console.log(convert(program.args[0], program.opts()));
16
+ } catch (pError) {
17
+ console.error(pError);
18
+ }
19
+ } else {
20
+ program.help();
21
+ }
@@ -0,0 +1,115 @@
1
+ import { EOL } from "node:os";
2
+ const DIFF_NAME_STATUS_LINE_PATTERN = new RegExp(
3
+ "^(?<changeType>[ACDMRTUXB])(?<similarity>[0-9]{3})?[ \t]+(?<name>[^ \t]+)[ \t]*(?<newName>[^ \t]+)?$"
4
+ );
5
+ const DIFF_SHORT_STATUS_LINE_PATTERN = new RegExp(
6
+ "^(?<stagedChangeType>[ ACDMRTUXB?!])(?<unStagedChangeType>[ ACDMRTUXB?!])[ \t]+(?<name>[^ \t]+)(( -> )(?<newName>[^ \t]+))?$"
7
+ );
8
+
9
+ const CHANGE_CHAR_2_CHANGE_TYPE = {
10
+ A: "added",
11
+ C: "copied",
12
+ D: "deleted",
13
+ M: "modified",
14
+ R: "renamed",
15
+ T: "type changed",
16
+ U: "unmerged",
17
+ B: "pairing broken",
18
+ " ": "unmodified",
19
+ "?": "untracked",
20
+ "!": "ignored",
21
+ // X: "unknown"
22
+ };
23
+
24
+ function changeChar2ChangeType(pChar) {
25
+ return CHANGE_CHAR_2_CHANGE_TYPE[pChar] || "unknown";
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @param {string} pString
31
+ * @returns {import('../types/watskeburt').IChange}
32
+ */
33
+ export function convertStatusLine(pString) {
34
+ const lMatchResult = pString.match(DIFF_SHORT_STATUS_LINE_PATTERN);
35
+ /** @type {import('../types/watskeburt').IChange} */
36
+ let lReturnValue = {};
37
+
38
+ if (lMatchResult) {
39
+ const lStagedChangeType = changeChar2ChangeType(
40
+ lMatchResult.groups.stagedChangeType
41
+ );
42
+ const lUnStagedChangeType = changeChar2ChangeType(
43
+ lMatchResult.groups.unStagedChangeType
44
+ );
45
+
46
+ lReturnValue.changeType =
47
+ lStagedChangeType === "unmodified"
48
+ ? lUnStagedChangeType
49
+ : lStagedChangeType;
50
+
51
+ if (lMatchResult.groups.newName) {
52
+ lReturnValue.name = lMatchResult.groups.newName;
53
+ lReturnValue.oldName = lMatchResult.groups.name;
54
+ } else {
55
+ lReturnValue.name = lMatchResult.groups.name;
56
+ }
57
+ }
58
+ return lReturnValue;
59
+ }
60
+
61
+ /**
62
+ *
63
+ * @param {string} pString
64
+ * @returns {import('../types/watskeburt').IChange}
65
+ */
66
+ export function convertDiffLine(pString) {
67
+ const lMatchResult = pString.match(DIFF_NAME_STATUS_LINE_PATTERN);
68
+ /** @type {import('../types/watskeburt').IChange} */
69
+ let lReturnValue = {};
70
+
71
+ if (lMatchResult) {
72
+ lReturnValue.changeType = changeChar2ChangeType(
73
+ lMatchResult.groups.changeType
74
+ );
75
+ if (lMatchResult.groups.similarity) {
76
+ lReturnValue.similarity = Number.parseInt(
77
+ lMatchResult.groups.similarity,
78
+ 10
79
+ );
80
+ }
81
+ if (lMatchResult.groups.newName) {
82
+ lReturnValue.name = lMatchResult.groups.newName;
83
+ lReturnValue.oldName = lMatchResult.groups.name;
84
+ } else {
85
+ lReturnValue.name = lMatchResult.groups.name;
86
+ }
87
+ }
88
+ return lReturnValue;
89
+ }
90
+
91
+ /**
92
+ *
93
+ * @param {string} pString
94
+ * @returns {import('../types/watskeburt').IChange[]}
95
+ */
96
+ export function convertStatusLines(pString) {
97
+ return pString
98
+ .split(EOL)
99
+ .filter(Boolean)
100
+ .map(convertStatusLine)
101
+ .filter(({ changeType }) => Boolean(changeType));
102
+ }
103
+
104
+ /**
105
+ *
106
+ * @param {string} pString
107
+ * @returns {import('../types/watskeburt').IChange[]}
108
+ */
109
+ export function convertDiffLines(pString) {
110
+ return pString
111
+ .split(EOL)
112
+ .filter(Boolean)
113
+ .map(convertDiffLine)
114
+ .filter(({ changeType }) => Boolean(changeType));
115
+ }
@@ -0,0 +1,19 @@
1
+ import formatToRegex from "./regex.mjs";
2
+ import formatToJSON from "./json.mjs";
3
+
4
+ const OUTPUT_TYPE_TO_FUNCTION = {
5
+ regex: formatToRegex,
6
+ json: formatToJSON,
7
+ object: (x) => x,
8
+ };
9
+ const DEFAULT_OUTPUT_TYPE = "object";
10
+
11
+ /**
12
+ *
13
+ * @param {import("../../types/watskeburt.js").IChange[]} pChanges
14
+ * @param {import("../../types/watskeburt.js").outputTypeType} pOutputType
15
+ * @returns {string|import("../../types/watskeburt.js").IChange[]}
16
+ */
17
+ export default function format(pChanges, pOutputType) {
18
+ return OUTPUT_TYPE_TO_FUNCTION[pOutputType || DEFAULT_OUTPUT_TYPE](pChanges);
19
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ *
3
+ * @param {import('../types/watskeburt').IChange[]} pChanges
4
+ * @return {string}
5
+ */
6
+ export default function formatToJSON(pChanges) {
7
+ return JSON.stringify(pChanges, null, 2);
8
+ }
@@ -0,0 +1,23 @@
1
+ import { extname } from "node:path";
2
+
3
+ /**
4
+ *
5
+ * @param {import('../types/watskeburt').IChange[]} pChanges
6
+ * @param {string[]} pExtensions
7
+ * @param {import('../types/watskeburt').changeTypeType[]} pChangeTypes
8
+ * @return {string}
9
+ */
10
+ export default function formatToRegex(
11
+ pChanges,
12
+ pExtensions = [".js", ".ts", ".mjs", ".cjs"],
13
+ pChangeTypes = ["modified", "added", "renamed", "copied", "untracked"]
14
+ ) {
15
+ const lChanges = pChanges
16
+ .filter((pChange) => pChangeTypes.includes(pChange.changeType))
17
+ .map(
18
+ ({ name }) => name //.replace(/\./g, "\\\\.")
19
+ )
20
+ .filter((pName) => pExtensions.includes(extname(pName)))
21
+ .join("|");
22
+ return `^(${lChanges})$`;
23
+ }
@@ -0,0 +1,51 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ function stringifyOutStream(pError) {
4
+ if (pError instanceof Buffer) {
5
+ return pError.toString("utf8");
6
+ } else {
7
+ return pError;
8
+ }
9
+ }
10
+
11
+ /**
12
+ *
13
+ * @param {string[]} pArguments
14
+ * @return {string}
15
+ * @throws {Error}
16
+ */
17
+ function getGitResult(pArguments, pSpawnFunction) {
18
+ const lGitResult = pSpawnFunction("git", pArguments, {
19
+ cwd: process.cwd(),
20
+ env: process.env,
21
+ });
22
+
23
+ if (lGitResult.error) {
24
+ throw new Error(stringifyOutStream(lGitResult.error));
25
+ }
26
+
27
+ if (lGitResult.status === 0) {
28
+ return stringifyOutStream(lGitResult.stdout);
29
+ } else {
30
+ throw new Error(lGitResult.output);
31
+ }
32
+ }
33
+
34
+ /**
35
+ *
36
+ * @returns {string}
37
+ * @throws {Error}
38
+ */
39
+ export function getStatusShort(pSpawnFunction = spawnSync) {
40
+ return getGitResult(["status", "--porcelain"], pSpawnFunction);
41
+ }
42
+
43
+ /**
44
+ *
45
+ * @param {string} pOldThing the target to compare against (e.g. branch name, commit, tag etc)
46
+ * @return {string}
47
+ * @throws {Error}
48
+ */
49
+ export function getDiffLines(pOldThing, pSpawnFunction = spawnSync) {
50
+ return getGitResult(["diff", pOldThing, "--name-status"], pSpawnFunction);
51
+ }
package/src/main.mjs ADDED
@@ -0,0 +1,32 @@
1
+ import {
2
+ convertDiffLines,
3
+ convertStatusLines,
4
+ } from "./convert-to-change-object.mjs";
5
+ import { getDiffLines, getStatusShort } from "./get-diff-lines.mjs";
6
+ import format from "./formatters/format.mjs";
7
+
8
+ /**
9
+ *
10
+ * returns a list of files changed since pOldThing. With pOptions
11
+ * you can
12
+ * - influence whether you want to have the output as an array of
13
+ * IChange-s or one of the other output formats (outputType)
14
+ * - tell whether you want to take untracked files into account as
15
+ * well (by setting trackedOnly to false)
16
+ *
17
+ * @param {string} pOldThing reference to a commit, branch, tag, ...
18
+ * @param {import("../types/watskeburt.js").IOptions} pOptions
19
+ * @returns {string|import("../types/watskeburt.js").IChange[]}
20
+ */
21
+ export function convert(pOldThing, pOptions) {
22
+ let lChanges = convertDiffLines(getDiffLines(pOldThing));
23
+
24
+ if (!pOptions.trackedOnly) {
25
+ lChanges = lChanges.concat(
26
+ convertStatusLines(getStatusShort()).filter(
27
+ ({ changeType }) => changeType === "untracked"
28
+ )
29
+ );
30
+ }
31
+ return format(lChanges, pOptions.outputType);
32
+ }
@@ -0,0 +1,27 @@
1
+ export type changeTypeType =
2
+ | "added"
3
+ | "copied"
4
+ | "deleted"
5
+ | "modified"
6
+ | "renamed"
7
+ | "type changed"
8
+ | "unmerged"
9
+ | "pairing broken"
10
+ | "unknown"
11
+ | "unmodified"
12
+ | "untracked"
13
+ | "ignored";
14
+
15
+ export interface IChange {
16
+ name: string;
17
+ changeType: changeTypeType;
18
+ similarity?: Number;
19
+ oldName?: string;
20
+ }
21
+
22
+ export type outputTypeType = "regex" | "json" | "object";
23
+
24
+ export interface IOptions {
25
+ outputType: outputTypeType;
26
+ trackedOnly: boolean;
27
+ }