gitxplain 0.1.9 → 0.2.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/.github/workflows/ci.yml +2 -0
- package/.github/workflows/release.yml +92 -5
- package/README.md +37 -0
- package/cli/index.js +14 -2
- package/cli/services/mergeService.js +14 -7
- package/cli/services/pipelineService.js +232 -9
- package/package.json +3 -3
- package/packaging/README.md +60 -0
- package/packaging/aur/.SRCINFO +12 -0
- package/packaging/aur/PKGBUILD +22 -0
- package/packaging/homebrew-tap/Formula/gitxplain.rb +19 -0
- package/scripts/build-deb.sh +64 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -2,12 +2,13 @@ name: Release
|
|
|
2
2
|
on:
|
|
3
3
|
push:
|
|
4
4
|
tags:
|
|
5
|
-
- '
|
|
5
|
+
- 'v*'
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write
|
|
6
8
|
jobs:
|
|
7
|
-
|
|
9
|
+
release:
|
|
10
|
+
if: startsWith(github.ref_name, 'v')
|
|
8
11
|
runs-on: ubuntu-latest
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
12
|
steps:
|
|
12
13
|
- name: Checkout
|
|
13
14
|
uses: actions/checkout@v4
|
|
@@ -16,12 +17,98 @@ jobs:
|
|
|
16
17
|
with:
|
|
17
18
|
node-version: '20'
|
|
18
19
|
cache: npm
|
|
19
|
-
registry-url: 'https://registry.npmjs.org
|
|
20
|
+
registry-url: 'https://registry.npmjs.org'
|
|
21
|
+
always-auth: true
|
|
20
22
|
- name: Install dependencies
|
|
21
23
|
run: npm ci
|
|
24
|
+
- name: Derive release metadata
|
|
25
|
+
id: meta
|
|
26
|
+
run: |
|
|
27
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
28
|
+
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
|
|
29
|
+
echo "deb_path=dist/gitxplain_${VERSION}_all.deb" >> "${GITHUB_OUTPUT}"
|
|
22
30
|
- name: Test
|
|
23
31
|
run: npm test
|
|
32
|
+
- name: Build Debian package
|
|
33
|
+
run: ./scripts/build-deb.sh
|
|
34
|
+
- name: Verify npm token
|
|
35
|
+
env:
|
|
36
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
37
|
+
run: |
|
|
38
|
+
if [ -z "${NODE_AUTH_TOKEN}" ]; then
|
|
39
|
+
echo "NPM_TOKEN is not configured for this repository."
|
|
40
|
+
echo "Add it in GitHub: Settings -> Secrets and variables -> Actions -> New repository secret."
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
printf "//registry.npmjs.org/:_authToken=%s\n" "${NODE_AUTH_TOKEN}" > "${HOME}/.npmrc"
|
|
44
|
+
npm whoami
|
|
24
45
|
- name: Publish to npm
|
|
25
46
|
env:
|
|
26
47
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
27
48
|
run: npm publish
|
|
49
|
+
- name: Compute npm tarball SHA-256
|
|
50
|
+
id: npm
|
|
51
|
+
run: |
|
|
52
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
53
|
+
TARBALL_URL="https://registry.npmjs.org/gitxplain/-/gitxplain-${VERSION}.tgz"
|
|
54
|
+
curl -fsSL "${TARBALL_URL}" -o "gitxplain-${VERSION}.tgz"
|
|
55
|
+
SHA256="$(sha256sum "gitxplain-${VERSION}.tgz" | awk '{print $1}')"
|
|
56
|
+
echo "tarball_url=${TARBALL_URL}" >> "${GITHUB_OUTPUT}"
|
|
57
|
+
echo "sha256=${SHA256}" >> "${GITHUB_OUTPUT}"
|
|
58
|
+
- name: Checkout Homebrew tap
|
|
59
|
+
uses: actions/checkout@v4
|
|
60
|
+
with:
|
|
61
|
+
repository: guruswarupa/homebrew-tap
|
|
62
|
+
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
63
|
+
path: homebrew-tap
|
|
64
|
+
- name: Update Homebrew formula
|
|
65
|
+
run: |
|
|
66
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
67
|
+
SHA256="${{ steps.npm.outputs.sha256 }}"
|
|
68
|
+
FORMULA_PATH="homebrew-tap/Formula/gitxplain.rb"
|
|
69
|
+
mkdir -p "$(dirname "${FORMULA_PATH}")"
|
|
70
|
+
cat > "${FORMULA_PATH}" <<EOF
|
|
71
|
+
class Gitxplain < Formula
|
|
72
|
+
desc "AI-powered Git commit explainer CLI"
|
|
73
|
+
homepage "https://github.com/guruswarupa/gitxplain"
|
|
74
|
+
url "https://registry.npmjs.org/gitxplain/-/gitxplain-${VERSION}.tgz"
|
|
75
|
+
sha256 "${SHA256}"
|
|
76
|
+
license "MIT"
|
|
77
|
+
|
|
78
|
+
depends_on "node"
|
|
79
|
+
|
|
80
|
+
def install
|
|
81
|
+
libexec.install Dir["package/*"]
|
|
82
|
+
bin.install_symlink libexec/"cli/index.js" => "gitxplain"
|
|
83
|
+
bin.install_symlink libexec/"cli/index.js" => "gitxplore"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
test do
|
|
87
|
+
assert_match "gitxplain", shell_output("#{bin}/gitxplain --help")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
EOF
|
|
91
|
+
- name: Commit and push Homebrew tap changes
|
|
92
|
+
working-directory: homebrew-tap
|
|
93
|
+
run: |
|
|
94
|
+
git config user.name "github-actions[bot]"
|
|
95
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
96
|
+
git add Formula/gitxplain.rb
|
|
97
|
+
if git diff --cached --quiet; then
|
|
98
|
+
echo "No Homebrew formula changes to commit."
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
git commit -m "gitxplain ${GITHUB_REF_NAME}"
|
|
102
|
+
git push
|
|
103
|
+
- name: Create GitHub release and upload Debian package
|
|
104
|
+
uses: softprops/action-gh-release@v2
|
|
105
|
+
with:
|
|
106
|
+
files: ${{ steps.meta.outputs.deb_path }}
|
|
107
|
+
- name: Print AUR update instructions
|
|
108
|
+
run: |
|
|
109
|
+
VERSION="${{ steps.meta.outputs.version }}"
|
|
110
|
+
SHA256="${{ steps.npm.outputs.sha256 }}"
|
|
111
|
+
echo "Manual AUR update steps:"
|
|
112
|
+
echo "1. Update packaging/aur/PKGBUILD with pkgver=${VERSION} and sha256sums=('${SHA256}')."
|
|
113
|
+
echo "2. Run: makepkg --printsrcinfo > .SRCINFO"
|
|
114
|
+
echo "3. Commit PKGBUILD and .SRCINFO to the AUR git repository and push."
|
package/README.md
CHANGED
|
@@ -46,6 +46,43 @@ Supported providers:
|
|
|
46
46
|
- A Git repository in your current working directory
|
|
47
47
|
- An API key for your chosen provider, or a local Ollama instance
|
|
48
48
|
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Install from npm:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install -g gitxplain
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Install from bun:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bun install -g gitxplain
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Install with Homebrew:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
brew tap guruswarupa/homebrew-tap
|
|
67
|
+
brew install gitxplain
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Install from the AUR:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
yay -S gitxplain
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
paru -S gitxplain
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Install from a Debian package downloaded from GitHub Releases:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
sudo apt install ./gitxplain_<version>_all.deb
|
|
84
|
+
```
|
|
85
|
+
|
|
49
86
|
Optional advanced environment variables:
|
|
50
87
|
|
|
51
88
|
- `LLM_PROVIDER` default: `openai`
|
package/cli/index.js
CHANGED
|
@@ -689,8 +689,16 @@ async function runPipelineCommand(cwd) {
|
|
|
689
689
|
return 0;
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
-
const { writtenFiles, notes } = writePipelineFiles(cwd, analysis, selection);
|
|
693
|
-
|
|
692
|
+
const { writtenFiles, updatedFiles, unchangedFiles, notes } = writePipelineFiles(cwd, analysis, selection);
|
|
693
|
+
|
|
694
|
+
if (updatedFiles.length === 0 && unchangedFiles.length > 0) {
|
|
695
|
+
console.log(`\nWorkflow files already matched the current template: ${unchangedFiles.join(", ")}`);
|
|
696
|
+
} else if (updatedFiles.length > 0 && unchangedFiles.length === 0) {
|
|
697
|
+
console.log(`\nUpdated workflow files: ${updatedFiles.join(", ")}`);
|
|
698
|
+
} else {
|
|
699
|
+
console.log(`\nUpdated workflow files: ${updatedFiles.join(", ")}`);
|
|
700
|
+
console.log(`Unchanged workflow files: ${unchangedFiles.join(", ")}`);
|
|
701
|
+
}
|
|
694
702
|
|
|
695
703
|
if (notes.length > 0) {
|
|
696
704
|
console.log(`\n${notes.join("\n")}`);
|
|
@@ -765,6 +773,10 @@ export async function main(argv = process.argv) {
|
|
|
765
773
|
return 0;
|
|
766
774
|
}
|
|
767
775
|
|
|
776
|
+
if (parsed.pipelineCommand) {
|
|
777
|
+
return runPipelineCommand(cwd);
|
|
778
|
+
}
|
|
779
|
+
|
|
768
780
|
if (
|
|
769
781
|
parsed.addCommand ||
|
|
770
782
|
parsed.removeCommand ||
|
|
@@ -33,6 +33,10 @@ function unique(values) {
|
|
|
33
33
|
return [...new Set(values)];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function formatVersionTag(version) {
|
|
37
|
+
return `v${version}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
function extractVersions(line) {
|
|
37
41
|
return unique(line.match(VERSION_PATTERN) ?? []);
|
|
38
42
|
}
|
|
@@ -319,23 +323,26 @@ export function selectReleaseWindows(sourceCommits, releaseCommits = []) {
|
|
|
319
323
|
export function selectReleaseTags(sourceCommits, existingTagNames = [], existingTagTargets = []) {
|
|
320
324
|
const windows = selectLatestWindowsPerVersion(buildReleaseWindows(sourceCommits));
|
|
321
325
|
const taggedVersions = extractTaggedVersions(existingTagNames);
|
|
322
|
-
const
|
|
326
|
+
const existingTagByVersion = new Map(
|
|
323
327
|
existingTagTargets
|
|
324
328
|
.map((tag) => {
|
|
325
329
|
const version = tag.tagName?.match(TAG_VERSION_PATTERN)?.[1] ?? null;
|
|
326
|
-
return version ? [version, tag
|
|
330
|
+
return version ? [version, tag] : null;
|
|
327
331
|
})
|
|
328
332
|
.filter(Boolean)
|
|
329
333
|
);
|
|
330
334
|
const tags = windows
|
|
331
335
|
.map((window) => {
|
|
332
336
|
const targetCommit = window.commits.at(-1) ?? null;
|
|
333
|
-
const
|
|
337
|
+
const existingTag = existingTagByVersion.get(window.version) ?? null;
|
|
338
|
+
const existingTargetSha = existingTag?.targetSha ?? null;
|
|
334
339
|
const windowCommitShas = new Set(window.commits.map((commit) => commit.sha));
|
|
335
340
|
|
|
336
341
|
return {
|
|
337
342
|
...window,
|
|
338
|
-
|
|
343
|
+
version: window.version,
|
|
344
|
+
tagName: existingTag?.tagName ?? formatVersionTag(window.version),
|
|
345
|
+
existingTagName: existingTag?.tagName ?? null,
|
|
339
346
|
existingTargetSha,
|
|
340
347
|
needsMove:
|
|
341
348
|
existingTargetSha != null &&
|
|
@@ -394,7 +401,7 @@ export function selectReleaseTagsFromReleaseCommits(releaseCommits, existingTagN
|
|
|
394
401
|
.filter((entry) => !taggedVersions.has(entry.version))
|
|
395
402
|
.map(({ commit, version }) => ({
|
|
396
403
|
version,
|
|
397
|
-
tagName: version,
|
|
404
|
+
tagName: formatVersionTag(version),
|
|
398
405
|
startRef: commit.shortSha,
|
|
399
406
|
endRef: commit.shortSha,
|
|
400
407
|
targetSha: commit.sha,
|
|
@@ -827,10 +834,10 @@ export function executeReleaseTagPlan(plan, cwd) {
|
|
|
827
834
|
try {
|
|
828
835
|
for (const tag of plan.tags) {
|
|
829
836
|
if (tag.needsMove) {
|
|
830
|
-
gitDeleteTag(tag.tagName, cwd);
|
|
837
|
+
gitDeleteTag(tag.existingTagName ?? tag.tagName, cwd);
|
|
831
838
|
}
|
|
832
839
|
|
|
833
|
-
gitCreateAnnotatedTag(tag.tagName, tag.targetSha, `release ${tag.tagName}`, cwd);
|
|
840
|
+
gitCreateAnnotatedTag(tag.tagName, tag.targetSha, `release ${tag.version ?? tag.tagName.replace(/^v/, "")}`, cwd);
|
|
834
841
|
createdTags.push(tag.tagName);
|
|
835
842
|
}
|
|
836
843
|
} catch (error) {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
readdirSync,
|
|
6
6
|
writeFileSync
|
|
7
7
|
} from "node:fs";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
8
9
|
import path from "node:path";
|
|
9
10
|
|
|
10
11
|
const WORKFLOW_DIR = ".github/workflows";
|
|
@@ -64,6 +65,56 @@ function detectNodeVersion(cwd, packageJson) {
|
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
function detectGitHubRepository(cwd) {
|
|
69
|
+
try {
|
|
70
|
+
const remote = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
71
|
+
cwd,
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
74
|
+
}).trim();
|
|
75
|
+
|
|
76
|
+
const match =
|
|
77
|
+
remote.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/i) ??
|
|
78
|
+
remote.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
79
|
+
|
|
80
|
+
if (!match) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
owner: match[1],
|
|
86
|
+
repo: match[2],
|
|
87
|
+
slug: `${match[1]}/${match[2]}`
|
|
88
|
+
};
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function detectNodePackaging(cwd, packageJson) {
|
|
95
|
+
const packageName = (packageJson?.name ?? "package").replace(/^@[^/]+\//, "");
|
|
96
|
+
const githubRepository = detectGitHubRepository(cwd);
|
|
97
|
+
const homebrewFormulaPath = `packaging/homebrew-tap/Formula/${packageName}.rb`;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
deb: fileExists(cwd, "scripts/build-deb.sh"),
|
|
101
|
+
aur: fileExists(cwd, "packaging/aur/PKGBUILD"),
|
|
102
|
+
homebrew: fileExists(cwd, homebrewFormulaPath),
|
|
103
|
+
homebrewFormulaPath,
|
|
104
|
+
homebrewTapRepo: githubRepository ? `${githubRepository.owner}/homebrew-tap` : null,
|
|
105
|
+
githubRepository
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function toHomebrewClassName(packageName) {
|
|
110
|
+
return packageName
|
|
111
|
+
.replace(/^@[^/]+\//, "")
|
|
112
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
113
|
+
.filter(Boolean)
|
|
114
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
115
|
+
.join("");
|
|
116
|
+
}
|
|
117
|
+
|
|
67
118
|
function detectNodeProject(cwd) {
|
|
68
119
|
if (!fileExists(cwd, "package.json")) {
|
|
69
120
|
return null;
|
|
@@ -75,12 +126,14 @@ function detectNodeProject(cwd) {
|
|
|
75
126
|
const packageManager = detectPackageManager(cwd);
|
|
76
127
|
const releaseSupported = packageJson.private !== true && typeof packageJson.name === "string";
|
|
77
128
|
const packSupported = packageJson.private !== true || Boolean(packageJson.bin);
|
|
129
|
+
const packaging = detectNodePackaging(cwd, packageJson);
|
|
78
130
|
|
|
79
131
|
return {
|
|
80
132
|
type: "node",
|
|
81
133
|
displayName: packageJson.name || path.basename(cwd),
|
|
82
134
|
packageManager,
|
|
83
135
|
packageJson,
|
|
136
|
+
packaging,
|
|
84
137
|
nodeVersion,
|
|
85
138
|
commands: {
|
|
86
139
|
install:
|
|
@@ -277,7 +330,9 @@ function buildNodeSetupStep(nodeVersion, packageManager = "npm") {
|
|
|
277
330
|
" uses: actions/setup-node@v4",
|
|
278
331
|
" with:",
|
|
279
332
|
" node-version-file: .nvmrc",
|
|
280
|
-
` cache: ${packageManager}
|
|
333
|
+
` cache: ${packageManager}`,
|
|
334
|
+
" registry-url: 'https://registry.npmjs.org'",
|
|
335
|
+
" always-auth: true"
|
|
281
336
|
].join("\n");
|
|
282
337
|
}
|
|
283
338
|
|
|
@@ -286,7 +341,9 @@ function buildNodeSetupStep(nodeVersion, packageManager = "npm") {
|
|
|
286
341
|
" uses: actions/setup-node@v4",
|
|
287
342
|
" with:",
|
|
288
343
|
` node-version: '${nodeVersion.value}'`,
|
|
289
|
-
` cache: ${packageManager}
|
|
344
|
+
` cache: ${packageManager}`,
|
|
345
|
+
" registry-url: 'https://registry.npmjs.org'",
|
|
346
|
+
" always-auth: true"
|
|
290
347
|
].join("\n");
|
|
291
348
|
}
|
|
292
349
|
|
|
@@ -440,8 +497,10 @@ export function inspectRepositoryForPipeline(cwd) {
|
|
|
440
497
|
id: "ci-release",
|
|
441
498
|
label: `CI plus ${primary.release.type} release automation`,
|
|
442
499
|
description:
|
|
443
|
-
primary.type === "node"
|
|
444
|
-
? "Publishes to npm when
|
|
500
|
+
primary.type === "node" && (primary.packaging?.deb || primary.packaging?.homebrew || primary.packaging?.aur)
|
|
501
|
+
? "Publishes to npm on version tags, builds Debian packages, updates Homebrew when configured, and prints AUR update instructions."
|
|
502
|
+
: primary.type === "node"
|
|
503
|
+
? "Publishes to npm when you push a version tag like v1.2.3."
|
|
445
504
|
: primary.type === "python"
|
|
446
505
|
? "Builds and publishes to PyPI when you push a version tag like v1.2.3."
|
|
447
506
|
: "Publishes to crates.io when you push a version tag like v1.2.3.",
|
|
@@ -569,16 +628,154 @@ export function buildReleaseWorkflow(context) {
|
|
|
569
628
|
}
|
|
570
629
|
|
|
571
630
|
if (context.type === "node") {
|
|
631
|
+
const packaging = context.packaging ?? {};
|
|
632
|
+
const githubRepository = packaging.githubRepository?.slug ?? null;
|
|
633
|
+
const homebrewTapRepo = packaging.homebrewTapRepo ?? null;
|
|
634
|
+
const packageName = context.packageJson?.name ?? context.displayName ?? "package";
|
|
635
|
+
const formulaClassName = toHomebrewClassName(packageName);
|
|
636
|
+
const binEntries = Object.entries(context.packageJson?.bin ?? {});
|
|
637
|
+
const executablePath = (binEntries[0]?.[1] ?? "cli/index.js").replace(/^\.\//, "");
|
|
638
|
+
const executableNames = binEntries.length > 0 ? binEntries.map(([name]) => name) : [packageName];
|
|
639
|
+
|
|
640
|
+
if (packaging.deb || packaging.homebrew || packaging.aur) {
|
|
641
|
+
return [
|
|
642
|
+
"name: Release",
|
|
643
|
+
"",
|
|
644
|
+
"on:",
|
|
645
|
+
" push:",
|
|
646
|
+
" tags:",
|
|
647
|
+
" - 'v*'",
|
|
648
|
+
"",
|
|
649
|
+
"permissions:",
|
|
650
|
+
" contents: write",
|
|
651
|
+
"",
|
|
652
|
+
"jobs:",
|
|
653
|
+
" release:",
|
|
654
|
+
" if: startsWith(github.ref_name, 'v')",
|
|
655
|
+
" runs-on: ubuntu-latest",
|
|
656
|
+
"",
|
|
657
|
+
" steps:",
|
|
658
|
+
" - name: Checkout",
|
|
659
|
+
" uses: actions/checkout@v4",
|
|
660
|
+
buildNodeReleaseSetup(context),
|
|
661
|
+
" - name: Derive release metadata",
|
|
662
|
+
" id: meta",
|
|
663
|
+
" run: |",
|
|
664
|
+
" VERSION=\"${GITHUB_REF_NAME#v}\"",
|
|
665
|
+
" echo \"version=${VERSION}\" >> \"${GITHUB_OUTPUT}\"",
|
|
666
|
+
packaging.deb ? ` echo "deb_path=dist/${packageName}_\${VERSION}_all.deb" >> "\${GITHUB_OUTPUT}"` : "",
|
|
667
|
+
context.commands.test ? formatRunStep("Test", context.commands.test) : "",
|
|
668
|
+
context.commands.build ? formatRunStep("Build", context.commands.build) : "",
|
|
669
|
+
packaging.deb ? formatRunStep("Build Debian package", "./scripts/build-deb.sh") : "",
|
|
670
|
+
" - name: Verify npm token",
|
|
671
|
+
" env:",
|
|
672
|
+
" NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}",
|
|
673
|
+
" run: |",
|
|
674
|
+
" if [ -z \"${NODE_AUTH_TOKEN}\" ]; then",
|
|
675
|
+
" echo \"NPM_TOKEN is not configured for this repository.\"",
|
|
676
|
+
" echo \"Add it in GitHub: Settings -> Secrets and variables -> Actions -> New repository secret.\"",
|
|
677
|
+
" exit 1",
|
|
678
|
+
" fi",
|
|
679
|
+
" printf \"//registry.npmjs.org/:_authToken=%s\\n\" \"${NODE_AUTH_TOKEN}\" > \"${HOME}/.npmrc\"",
|
|
680
|
+
" npm whoami",
|
|
681
|
+
" - name: Publish to npm",
|
|
682
|
+
" env:",
|
|
683
|
+
" NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}",
|
|
684
|
+
" run: npm publish",
|
|
685
|
+
" - name: Compute npm tarball SHA-256",
|
|
686
|
+
" id: npm",
|
|
687
|
+
" run: |",
|
|
688
|
+
" VERSION=\"${{ steps.meta.outputs.version }}\"",
|
|
689
|
+
` TARBALL_URL="https://registry.npmjs.org/${packageName}/-/${packageName}-\${VERSION}.tgz"`,
|
|
690
|
+
` curl -fsSL "\${TARBALL_URL}" -o "${packageName}-\${VERSION}.tgz"`,
|
|
691
|
+
` SHA256="$(sha256sum "${packageName}-\${VERSION}.tgz" | awk '{print $1}')"`,
|
|
692
|
+
" echo \"tarball_url=${TARBALL_URL}\" >> \"${GITHUB_OUTPUT}\"",
|
|
693
|
+
" echo \"sha256=${SHA256}\" >> \"${GITHUB_OUTPUT}\"",
|
|
694
|
+
packaging.homebrew && homebrewTapRepo
|
|
695
|
+
? [
|
|
696
|
+
" - name: Checkout Homebrew tap",
|
|
697
|
+
" uses: actions/checkout@v4",
|
|
698
|
+
" with:",
|
|
699
|
+
` repository: ${homebrewTapRepo}`,
|
|
700
|
+
" token: ${{ secrets.HOMEBREW_TAP_TOKEN }}",
|
|
701
|
+
" path: homebrew-tap",
|
|
702
|
+
" - name: Update Homebrew formula",
|
|
703
|
+
" run: |",
|
|
704
|
+
" VERSION=\"${{ steps.meta.outputs.version }}\"",
|
|
705
|
+
" SHA256=\"${{ steps.npm.outputs.sha256 }}\"",
|
|
706
|
+
` FORMULA_PATH="homebrew-tap/Formula/${packageName}.rb"`,
|
|
707
|
+
" mkdir -p \"$(dirname \"${FORMULA_PATH}\")\"",
|
|
708
|
+
" cat > \"${FORMULA_PATH}\" <<EOF",
|
|
709
|
+
` class ${formulaClassName} < Formula`,
|
|
710
|
+
` desc ${JSON.stringify(context.packageJson?.description ?? "")}`,
|
|
711
|
+
` homepage ${JSON.stringify(githubRepository ? `https://github.com/${githubRepository}` : "")}`,
|
|
712
|
+
` url "https://registry.npmjs.org/${packageName}/-/${packageName}-${"${VERSION}"}.tgz"`,
|
|
713
|
+
" sha256 \"${SHA256}\"",
|
|
714
|
+
` license ${JSON.stringify(context.packageJson?.license ?? "MIT")}`,
|
|
715
|
+
"",
|
|
716
|
+
" depends_on \"node\"",
|
|
717
|
+
"",
|
|
718
|
+
" def install",
|
|
719
|
+
" libexec.install Dir[\"package/*\"]",
|
|
720
|
+
...executableNames.map((name) => ` bin.install_symlink libexec/"${executablePath}" => "${name}"`),
|
|
721
|
+
" end",
|
|
722
|
+
"",
|
|
723
|
+
" test do",
|
|
724
|
+
` assert_match ${JSON.stringify(executableNames[0])}, shell_output("#{bin}/${executableNames[0]} --help")`,
|
|
725
|
+
" end",
|
|
726
|
+
" end",
|
|
727
|
+
" EOF",
|
|
728
|
+
" - name: Commit and push Homebrew tap changes",
|
|
729
|
+
" working-directory: homebrew-tap",
|
|
730
|
+
" run: |",
|
|
731
|
+
" git config user.name \"github-actions[bot]\"",
|
|
732
|
+
" git config user.email \"41898282+github-actions[bot]@users.noreply.github.com\"",
|
|
733
|
+
` git add Formula/${packageName}.rb`,
|
|
734
|
+
" if git diff --cached --quiet; then",
|
|
735
|
+
" echo \"No Homebrew formula changes to commit.\"",
|
|
736
|
+
" exit 0",
|
|
737
|
+
" fi",
|
|
738
|
+
" git commit -m \"gitxplain ${GITHUB_REF_NAME}\"",
|
|
739
|
+
" git push"
|
|
740
|
+
].join("\n")
|
|
741
|
+
: "",
|
|
742
|
+
packaging.deb
|
|
743
|
+
? [
|
|
744
|
+
" - name: Create GitHub release and upload Debian package",
|
|
745
|
+
" uses: softprops/action-gh-release@v2",
|
|
746
|
+
" with:",
|
|
747
|
+
" files: ${{ steps.meta.outputs.deb_path }}"
|
|
748
|
+
].join("\n")
|
|
749
|
+
: "",
|
|
750
|
+
packaging.aur
|
|
751
|
+
? [
|
|
752
|
+
" - name: Print AUR update instructions",
|
|
753
|
+
" run: |",
|
|
754
|
+
" VERSION=\"${{ steps.meta.outputs.version }}\"",
|
|
755
|
+
" SHA256=\"${{ steps.npm.outputs.sha256 }}\"",
|
|
756
|
+
" echo \"Manual AUR update steps:\"",
|
|
757
|
+
" echo \"1. Update packaging/aur/PKGBUILD with pkgver=${VERSION} and sha256sums=('${SHA256}').\"",
|
|
758
|
+
" echo \"2. Run: makepkg --printsrcinfo > .SRCINFO\"",
|
|
759
|
+
" echo \"3. Commit PKGBUILD and .SRCINFO to the AUR git repository and push.\""
|
|
760
|
+
].join("\n")
|
|
761
|
+
: ""
|
|
762
|
+
]
|
|
763
|
+
.filter(Boolean)
|
|
764
|
+
.join("\n")
|
|
765
|
+
.concat("\n");
|
|
766
|
+
}
|
|
767
|
+
|
|
572
768
|
return [
|
|
573
769
|
"name: Release",
|
|
574
770
|
"",
|
|
575
771
|
"on:",
|
|
576
772
|
" push:",
|
|
577
773
|
" tags:",
|
|
578
|
-
" - 'v
|
|
774
|
+
" - 'v*'",
|
|
579
775
|
"",
|
|
580
776
|
"jobs:",
|
|
581
777
|
" publish:",
|
|
778
|
+
" if: startsWith(github.ref_name, 'v')",
|
|
582
779
|
" runs-on: ubuntu-latest",
|
|
583
780
|
" permissions:",
|
|
584
781
|
" contents: read",
|
|
@@ -588,6 +785,17 @@ export function buildReleaseWorkflow(context) {
|
|
|
588
785
|
buildNodeReleaseSetup(context),
|
|
589
786
|
context.commands.test ? formatRunStep("Test", context.commands.test) : "",
|
|
590
787
|
context.commands.build ? formatRunStep("Build", context.commands.build) : "",
|
|
788
|
+
" - name: Verify npm token",
|
|
789
|
+
" env:",
|
|
790
|
+
" NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}",
|
|
791
|
+
" run: |",
|
|
792
|
+
" if [ -z \"${NODE_AUTH_TOKEN}\" ]; then",
|
|
793
|
+
" echo \"NPM_TOKEN is not configured for this repository.\"",
|
|
794
|
+
" echo \"Add it in GitHub: Settings -> Secrets and variables -> Actions -> New repository secret.\"",
|
|
795
|
+
" exit 1",
|
|
796
|
+
" fi",
|
|
797
|
+
" printf \"//registry.npmjs.org/:_authToken=%s\\n\" \"${NODE_AUTH_TOKEN}\" > \"${HOME}/.npmrc\"",
|
|
798
|
+
" npm whoami",
|
|
591
799
|
" - name: Publish to npm",
|
|
592
800
|
" env:",
|
|
593
801
|
" NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}",
|
|
@@ -605,7 +813,7 @@ export function buildReleaseWorkflow(context) {
|
|
|
605
813
|
"on:",
|
|
606
814
|
" push:",
|
|
607
815
|
" tags:",
|
|
608
|
-
" - 'v
|
|
816
|
+
" - 'v*'",
|
|
609
817
|
"",
|
|
610
818
|
"jobs:",
|
|
611
819
|
" publish:",
|
|
@@ -635,10 +843,11 @@ export function buildReleaseWorkflow(context) {
|
|
|
635
843
|
"on:",
|
|
636
844
|
" push:",
|
|
637
845
|
" tags:",
|
|
638
|
-
" - 'v
|
|
846
|
+
" - 'v*'",
|
|
639
847
|
"",
|
|
640
848
|
"jobs:",
|
|
641
849
|
" publish:",
|
|
850
|
+
" if: startsWith(github.ref_name, 'v')",
|
|
642
851
|
" runs-on: ubuntu-latest",
|
|
643
852
|
" steps:",
|
|
644
853
|
" - name: Checkout",
|
|
@@ -660,7 +869,7 @@ export function buildContainerWorkflow() {
|
|
|
660
869
|
" push:",
|
|
661
870
|
" branches: [main, master]",
|
|
662
871
|
" tags:",
|
|
663
|
-
" - 'v
|
|
872
|
+
" - 'v*'",
|
|
664
873
|
" pull_request:",
|
|
665
874
|
"",
|
|
666
875
|
"jobs:",
|
|
@@ -784,13 +993,21 @@ export function writePipelineFiles(cwd, analysis, selection) {
|
|
|
784
993
|
mkdirSync(workflowDir, { recursive: true });
|
|
785
994
|
|
|
786
995
|
const writtenFiles = [];
|
|
996
|
+
const updatedFiles = [];
|
|
997
|
+
const unchangedFiles = [];
|
|
787
998
|
const notes = [];
|
|
788
999
|
|
|
789
1000
|
const writeWorkflow = (relativePath, contents) => {
|
|
790
1001
|
const absolutePath = path.join(cwd, relativePath);
|
|
791
1002
|
mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
1003
|
+
const existingContents = existsSync(absolutePath) ? readFileSync(absolutePath, "utf8") : null;
|
|
792
1004
|
writeFileSync(absolutePath, contents, "utf8");
|
|
793
1005
|
writtenFiles.push(relativePath);
|
|
1006
|
+
if (existingContents === contents) {
|
|
1007
|
+
unchangedFiles.push(relativePath);
|
|
1008
|
+
} else {
|
|
1009
|
+
updatedFiles.push(relativePath);
|
|
1010
|
+
}
|
|
794
1011
|
};
|
|
795
1012
|
|
|
796
1013
|
if (selection.id === "ci" || selection.id === "ci-release") {
|
|
@@ -802,6 +1019,12 @@ export function writePipelineFiles(cwd, analysis, selection) {
|
|
|
802
1019
|
|
|
803
1020
|
if (analysis.primary.release.type === "npm") {
|
|
804
1021
|
notes.push("Add an `NPM_TOKEN` repository secret before pushing a release tag.");
|
|
1022
|
+
if (analysis.primary.packaging?.homebrew) {
|
|
1023
|
+
notes.push("Add a `HOMEBREW_TAP_TOKEN` repository secret so CI can update your tap repository.");
|
|
1024
|
+
}
|
|
1025
|
+
if (analysis.primary.packaging?.aur) {
|
|
1026
|
+
notes.push("AUR updates are still manual. The generated release workflow prints the exact PKGBUILD and .SRCINFO refresh steps.");
|
|
1027
|
+
}
|
|
805
1028
|
} else if (analysis.primary.release.type === "pypi") {
|
|
806
1029
|
notes.push("Add a `PYPI_TOKEN` repository secret before pushing a release tag.");
|
|
807
1030
|
} else if (analysis.primary.release.type === "crates") {
|
|
@@ -829,5 +1052,5 @@ export function writePipelineFiles(cwd, analysis, selection) {
|
|
|
829
1052
|
notes.push("This option only creates the container workflow. Run `gitxplain --pipeline` again if you also want CI verification.");
|
|
830
1053
|
}
|
|
831
1054
|
|
|
832
|
-
return { writtenFiles, notes };
|
|
1055
|
+
return { writtenFiles, updatedFiles, unchangedFiles, notes };
|
|
833
1056
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitxplain",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "AI-powered Git commit explainer CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"gitxplain": "
|
|
8
|
-
"gitxplore": "
|
|
7
|
+
"gitxplain": "cli/index.js",
|
|
8
|
+
"gitxplore": "cli/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node ./cli/index.js",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Packaging
|
|
2
|
+
|
|
3
|
+
This directory contains packaging assets for Homebrew, AUR, and Debian releases.
|
|
4
|
+
|
|
5
|
+
## Homebrew tap
|
|
6
|
+
|
|
7
|
+
Template formula: `packaging/homebrew-tap/Formula/gitxplain.rb`
|
|
8
|
+
|
|
9
|
+
Create and publish the tap repository:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
gh repo create guruswarupa/homebrew-tap --public --clone
|
|
13
|
+
cd homebrew-tap
|
|
14
|
+
mkdir -p Formula
|
|
15
|
+
cp /path/to/gitxplain/packaging/homebrew-tap/Formula/gitxplain.rb Formula/gitxplain.rb
|
|
16
|
+
git add Formula/gitxplain.rb
|
|
17
|
+
git commit -m "Add gitxplain formula"
|
|
18
|
+
git push -u origin main
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After each new npm publish, update the formula `url` and replace `"<SHA256_PLACEHOLDER>"` with the tarball SHA-256, then push the change.
|
|
22
|
+
|
|
23
|
+
## AUR
|
|
24
|
+
|
|
25
|
+
Template files:
|
|
26
|
+
|
|
27
|
+
- `packaging/aur/PKGBUILD`
|
|
28
|
+
- `packaging/aur/.SRCINFO`
|
|
29
|
+
|
|
30
|
+
Generate `.SRCINFO` after updating `PKGBUILD`:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd /path/to/gitxplain/packaging/aur
|
|
34
|
+
makepkg --printsrcinfo > .SRCINFO
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Create and publish the AUR package:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone ssh://aur@aur.archlinux.org/gitxplain.git aur-gitxplain
|
|
41
|
+
cd aur-gitxplain
|
|
42
|
+
cp /path/to/gitxplain/packaging/aur/PKGBUILD .
|
|
43
|
+
cp /path/to/gitxplain/packaging/aur/.SRCINFO .
|
|
44
|
+
makepkg --printsrcinfo > .SRCINFO
|
|
45
|
+
git add PKGBUILD .SRCINFO
|
|
46
|
+
git commit -m "Initial gitxplain release"
|
|
47
|
+
git push
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Replace `"<SHA256_PLACEHOLDER>"` with the npm tarball SHA-256 before publishing.
|
|
51
|
+
|
|
52
|
+
## Debian
|
|
53
|
+
|
|
54
|
+
Build the Debian package from this repository:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
./scripts/build-deb.sh
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The package is written to `dist/gitxplain_<version>_all.deb`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pkgbase = gitxplain
|
|
2
|
+
pkgdesc = AI-powered Git commit explainer CLI
|
|
3
|
+
pkgver = 0.1.9
|
|
4
|
+
pkgrel = 1
|
|
5
|
+
url = https://github.com/guruswarupa/gitxplain
|
|
6
|
+
arch = any
|
|
7
|
+
license = MIT
|
|
8
|
+
depends = nodejs
|
|
9
|
+
source = https://registry.npmjs.org/gitxplain/-/gitxplain-0.1.9.tgz
|
|
10
|
+
sha256sums = <SHA256_PLACEHOLDER>
|
|
11
|
+
|
|
12
|
+
pkgname = gitxplain
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
pkgname=gitxplain
|
|
2
|
+
pkgver=0.1.9
|
|
3
|
+
pkgrel=1
|
|
4
|
+
pkgdesc="AI-powered Git commit explainer CLI"
|
|
5
|
+
arch=('any')
|
|
6
|
+
url="https://github.com/guruswarupa/gitxplain"
|
|
7
|
+
license=('MIT')
|
|
8
|
+
depends=('nodejs')
|
|
9
|
+
source=("https://registry.npmjs.org/${pkgname}/-/${pkgname}-${pkgver}.tgz")
|
|
10
|
+
sha256sums=('<SHA256_PLACEHOLDER>')
|
|
11
|
+
|
|
12
|
+
package() {
|
|
13
|
+
install -d "${pkgdir}/usr/lib/${pkgname}"
|
|
14
|
+
tar -xzf "${srcdir}/${pkgname}-${pkgver}.tgz" -C "${srcdir}"
|
|
15
|
+
cp -a "${srcdir}/package/." "${pkgdir}/usr/lib/${pkgname}/"
|
|
16
|
+
|
|
17
|
+
chmod 755 "${pkgdir}/usr/lib/${pkgname}/cli/index.js"
|
|
18
|
+
|
|
19
|
+
install -d "${pkgdir}/usr/bin"
|
|
20
|
+
ln -sf "/usr/lib/${pkgname}/cli/index.js" "${pkgdir}/usr/bin/gitxplain"
|
|
21
|
+
ln -sf "/usr/lib/${pkgname}/cli/index.js" "${pkgdir}/usr/bin/gitxplore"
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Gitxplain < Formula
|
|
2
|
+
desc "AI-powered Git commit explainer CLI"
|
|
3
|
+
homepage "https://github.com/guruswarupa/gitxplain"
|
|
4
|
+
url "https://registry.npmjs.org/gitxplain/-/gitxplain-0.1.9.tgz"
|
|
5
|
+
sha256 "<SHA256_PLACEHOLDER>"
|
|
6
|
+
license "MIT"
|
|
7
|
+
|
|
8
|
+
depends_on "node"
|
|
9
|
+
|
|
10
|
+
def install
|
|
11
|
+
libexec.install Dir["package/*"]
|
|
12
|
+
bin.install_symlink libexec/"cli/index.js" => "gitxplain"
|
|
13
|
+
bin.install_symlink libexec/"cli/index.js" => "gitxplore"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
test do
|
|
17
|
+
assert_match "gitxplain", shell_output("#{bin}/gitxplain --help")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
PACKAGE_JSON="${ROOT_DIR}/package.json"
|
|
7
|
+
VERSION="$(node -p "JSON.parse(require('node:fs').readFileSync(process.argv[1], 'utf8')).version" "${PACKAGE_JSON}")"
|
|
8
|
+
DESCRIPTION="$(node -p "JSON.parse(require('node:fs').readFileSync(process.argv[1], 'utf8')).description" "${PACKAGE_JSON}")"
|
|
9
|
+
PACKAGE_NAME="gitxplain"
|
|
10
|
+
ARCHITECTURE="${DEB_ARCHITECTURE:-all}"
|
|
11
|
+
MAINTAINER="${DEB_MAINTAINER:-Guruswarupa <opensource@local.invalid>}"
|
|
12
|
+
OUTPUT_DIR="${ROOT_DIR}/dist"
|
|
13
|
+
BUILD_ROOT="$(mktemp -d)"
|
|
14
|
+
PACKAGE_ROOT="${BUILD_ROOT}/${PACKAGE_NAME}_${VERSION}"
|
|
15
|
+
INSTALL_ROOT="${PACKAGE_ROOT}/usr/lib/${PACKAGE_NAME}"
|
|
16
|
+
CONTROL_DIR="${PACKAGE_ROOT}/DEBIAN"
|
|
17
|
+
DOC_DIR="${PACKAGE_ROOT}/usr/share/doc/${PACKAGE_NAME}"
|
|
18
|
+
DEB_FILE="${OUTPUT_DIR}/${PACKAGE_NAME}_${VERSION}_${ARCHITECTURE}.deb"
|
|
19
|
+
|
|
20
|
+
cleanup() {
|
|
21
|
+
rm -rf "${BUILD_ROOT}"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
trap cleanup EXIT
|
|
25
|
+
|
|
26
|
+
mkdir -p "${OUTPUT_DIR}" "${INSTALL_ROOT}" "${CONTROL_DIR}" "${DOC_DIR}" "${PACKAGE_ROOT}/usr/bin"
|
|
27
|
+
|
|
28
|
+
cp -a \
|
|
29
|
+
"${ROOT_DIR}/cli" \
|
|
30
|
+
"${ROOT_DIR}/prompts" \
|
|
31
|
+
"${ROOT_DIR}/package.json" \
|
|
32
|
+
"${ROOT_DIR}/README.md" \
|
|
33
|
+
"${INSTALL_ROOT}/"
|
|
34
|
+
|
|
35
|
+
find "${PACKAGE_ROOT}" -type d -exec chmod 755 {} +
|
|
36
|
+
find "${PACKAGE_ROOT}" -type f -exec chmod 644 {} +
|
|
37
|
+
chmod 755 "${INSTALL_ROOT}/cli/index.js"
|
|
38
|
+
ln -s "../lib/${PACKAGE_NAME}/cli/index.js" "${PACKAGE_ROOT}/usr/bin/gitxplain"
|
|
39
|
+
ln -s "../lib/${PACKAGE_NAME}/cli/index.js" "${PACKAGE_ROOT}/usr/bin/gitxplore"
|
|
40
|
+
|
|
41
|
+
cat > "${CONTROL_DIR}/control" <<EOF
|
|
42
|
+
Package: ${PACKAGE_NAME}
|
|
43
|
+
Version: ${VERSION}
|
|
44
|
+
Section: utils
|
|
45
|
+
Priority: optional
|
|
46
|
+
Architecture: ${ARCHITECTURE}
|
|
47
|
+
Maintainer: ${MAINTAINER}
|
|
48
|
+
Depends: nodejs (>= 18)
|
|
49
|
+
Homepage: https://github.com/guruswarupa/gitxplain
|
|
50
|
+
Description: ${DESCRIPTION}
|
|
51
|
+
AI-powered Git commit explainer CLI distributed as a Debian package.
|
|
52
|
+
EOF
|
|
53
|
+
|
|
54
|
+
cat > "${DOC_DIR}/copyright" <<EOF
|
|
55
|
+
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
56
|
+
Upstream-Name: gitxplain
|
|
57
|
+
Source: https://github.com/guruswarupa/gitxplain
|
|
58
|
+
|
|
59
|
+
Files: *
|
|
60
|
+
License: MIT
|
|
61
|
+
EOF
|
|
62
|
+
|
|
63
|
+
dpkg-deb --root-owner-group --build "${PACKAGE_ROOT}" "${DEB_FILE}" >/dev/null
|
|
64
|
+
echo "${DEB_FILE}"
|