@akiojin/gwt 9.5.2 → 9.7.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/.husky/pre-push +3 -1
- package/README.ja.md +3 -0
- package/README.md +3 -0
- package/bin/gwt.cjs +31 -59
- package/package.json +2 -2
- package/scripts/check-coverage-threshold.mjs +65 -0
- package/scripts/postinstall.cjs +56 -0
- package/scripts/release-assets.cjs +202 -0
- package/scripts/test_release_assets.cjs +123 -0
- package/scripts/verify-husky-hooks.sh +3 -3
- package/wix/main.wxs +34 -0
- package/scripts/postinstall.js +0 -115
package/.husky/pre-push
CHANGED
|
@@ -6,7 +6,9 @@ echo "Running CI-equivalent lint checks..."
|
|
|
6
6
|
|
|
7
7
|
cargo clippy --all-targets --all-features -- -D warnings
|
|
8
8
|
cargo fmt --all -- --check
|
|
9
|
-
|
|
9
|
+
cargo llvm-cov -p gwt-core -p gwt --all-features --json --summary-only --output-path target/coverage-summary.json
|
|
10
|
+
node scripts/check-coverage-threshold.mjs target/coverage-summary.json 90
|
|
11
|
+
bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md --ignore tasks/todo.md
|
|
10
12
|
pnpm lint:skills
|
|
11
13
|
|
|
12
14
|
echo "All pre-push checks passed."
|
package/README.ja.md
CHANGED
package/README.md
CHANGED
|
@@ -68,6 +68,9 @@ gwt board show
|
|
|
68
68
|
gwt hook workflow-policy
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
Managed hooks and runtime delegation continue to enter through `gwt`. There is
|
|
72
|
+
no separate operator-facing daemon command to start by hand.
|
|
73
|
+
|
|
71
74
|
## Main Workflow
|
|
72
75
|
|
|
73
76
|
1. Open a repository directory or restore the previous project.
|
package/bin/gwt.cjs
CHANGED
|
@@ -8,81 +8,45 @@
|
|
|
8
8
|
const { spawn } = require("child_process");
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const fs = require("fs");
|
|
11
|
-
|
|
12
|
-
const
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
bundleBinaryNamesForPlatform,
|
|
14
|
+
binaryNameForPlatform,
|
|
15
|
+
installReleaseBinary,
|
|
16
|
+
releaseAssetUrl,
|
|
17
|
+
} = require("../scripts/release-assets.cjs");
|
|
13
18
|
|
|
14
19
|
const REPO = "akiojin/gwt";
|
|
15
20
|
const BIN_DIR = __dirname;
|
|
16
|
-
const BIN_NAME = process.platform
|
|
21
|
+
const BIN_NAME = binaryNameForPlatform(process.platform);
|
|
17
22
|
const BIN_PATH = path.join(BIN_DIR, BIN_NAME);
|
|
18
|
-
|
|
19
|
-
function releaseAssetName() {
|
|
20
|
-
const platform = os.platform();
|
|
21
|
-
const arch = os.arch();
|
|
22
|
-
|
|
23
|
-
if (platform === "darwin" && arch === "arm64") return "gwt-macos-aarch64";
|
|
24
|
-
if (platform === "darwin" && arch === "x64") return "gwt-macos-x86_64";
|
|
25
|
-
if (platform === "linux" && arch === "x64") return "gwt-linux-x86_64";
|
|
26
|
-
if (platform === "linux" && arch === "arm64") return "gwt-linux-aarch64";
|
|
27
|
-
if (platform === "win32" && arch === "x64") return "gwt-windows-x86_64.exe";
|
|
28
|
-
|
|
29
|
-
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
23
|
+
const BUNDLE_BINARIES = bundleBinaryNamesForPlatform(process.platform);
|
|
32
24
|
|
|
33
25
|
function readVersion() {
|
|
34
26
|
const pkg = path.join(__dirname, "..", "package.json");
|
|
35
27
|
return JSON.parse(fs.readFileSync(pkg, "utf8")).version;
|
|
36
28
|
}
|
|
37
29
|
|
|
38
|
-
function download(url, dest) {
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
const file = fs.createWriteStream(dest);
|
|
41
|
-
const request = (u) => {
|
|
42
|
-
https
|
|
43
|
-
.get(u, { headers: { "User-Agent": "gwt-postinstall" } }, (res) => {
|
|
44
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
45
|
-
request(res.headers.location);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (res.statusCode !== 200) {
|
|
49
|
-
file.close();
|
|
50
|
-
fs.unlink(dest, () => {});
|
|
51
|
-
reject(new Error(`HTTP ${res.statusCode} for ${u}`));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
res.pipe(file);
|
|
55
|
-
file.on("finish", () => file.close(resolve));
|
|
56
|
-
})
|
|
57
|
-
.on("error", (err) => {
|
|
58
|
-
file.close();
|
|
59
|
-
fs.unlink(dest, () => {});
|
|
60
|
-
reject(err);
|
|
61
|
-
});
|
|
62
|
-
};
|
|
63
|
-
request(url);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
30
|
async function ensureBinary() {
|
|
68
|
-
if (fs.existsSync(
|
|
31
|
+
if (BUNDLE_BINARIES.every((name) => fs.existsSync(path.join(BIN_DIR, name)))) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
69
34
|
|
|
70
35
|
const version = readVersion();
|
|
71
|
-
const
|
|
72
|
-
const tag = `v${version}`;
|
|
73
|
-
const url = `https://github.com/${REPO}/releases/download/${tag}/${asset}`;
|
|
36
|
+
const { url } = releaseAssetUrl(REPO, version, process.platform, process.arch);
|
|
74
37
|
|
|
75
|
-
console.log(`Downloading gwt
|
|
38
|
+
console.log(`Downloading gwt bundle for ${process.platform}-${process.arch}...`);
|
|
76
39
|
console.log(`Downloading from: ${url}`);
|
|
77
40
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
41
|
+
await installReleaseBinary({
|
|
42
|
+
repo: REPO,
|
|
43
|
+
version,
|
|
44
|
+
binDir: BIN_DIR,
|
|
45
|
+
platform: process.platform,
|
|
46
|
+
arch: process.arch,
|
|
47
|
+
});
|
|
84
48
|
|
|
85
|
-
console.log(
|
|
49
|
+
console.log(`gwt bundle installed successfully: ${BUNDLE_BINARIES.join(", ")}`);
|
|
86
50
|
}
|
|
87
51
|
|
|
88
52
|
async function main() {
|
|
@@ -113,4 +77,12 @@ async function main() {
|
|
|
113
77
|
});
|
|
114
78
|
}
|
|
115
79
|
|
|
116
|
-
main
|
|
80
|
+
if (require.main === module) {
|
|
81
|
+
main();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
ensureBinary,
|
|
86
|
+
main,
|
|
87
|
+
readVersion,
|
|
88
|
+
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akiojin/gwt",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.7.0",
|
|
4
4
|
"description": "Desktop GUI for Git worktree management and coding agent launch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"gwt": "bin/gwt.cjs"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"postinstall": "node scripts/postinstall.
|
|
10
|
+
"postinstall": "node scripts/postinstall.cjs",
|
|
11
11
|
"dev": "cargo run -p gwt",
|
|
12
12
|
"build": "cargo build --release -p gwt",
|
|
13
13
|
"test": "cargo test -p gwt-core -p gwt --all-features",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const [summaryPath, thresholdArg] = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
if (!summaryPath || !thresholdArg) {
|
|
9
|
+
console.error(
|
|
10
|
+
"Usage: node scripts/check-coverage-threshold.mjs <summary-json> <threshold>",
|
|
11
|
+
);
|
|
12
|
+
process.exit(2);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const threshold = Number(thresholdArg);
|
|
16
|
+
if (!Number.isFinite(threshold)) {
|
|
17
|
+
console.error(`Invalid threshold: ${thresholdArg}`);
|
|
18
|
+
process.exit(2);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const report = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
|
|
22
|
+
const files = report.data?.flatMap((entry) => entry.files ?? []) ?? [];
|
|
23
|
+
const ignoredFilePatterns = [/(^|[\\/])gwt[\\/]src[\\/]main\.rs$/i];
|
|
24
|
+
|
|
25
|
+
let coveredLines = 0;
|
|
26
|
+
let totalLines = 0;
|
|
27
|
+
const excludedFiles = [];
|
|
28
|
+
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const filename = file.filename ?? "";
|
|
31
|
+
if (ignoredFilePatterns.some((pattern) => pattern.test(filename))) {
|
|
32
|
+
excludedFiles.push(path.relative(process.cwd(), filename));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const lines = file.summary?.lines;
|
|
37
|
+
if (!lines) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
coveredLines += lines.covered;
|
|
42
|
+
totalLines += lines.count;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (totalLines === 0) {
|
|
46
|
+
console.error("Coverage report did not contain any line summary data.");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const percent = (coveredLines / totalLines) * 100;
|
|
51
|
+
console.log(
|
|
52
|
+
`Filtered line coverage: ${percent.toFixed(2)}% (${coveredLines}/${totalLines})`,
|
|
53
|
+
);
|
|
54
|
+
if (excludedFiles.length > 0) {
|
|
55
|
+
console.log(`Excluded from threshold: ${excludedFiles.join(", ")}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (percent + Number.EPSILON < threshold) {
|
|
59
|
+
console.error(
|
|
60
|
+
`Coverage threshold not met: required ${threshold.toFixed(2)}%, got ${percent.toFixed(2)}%`,
|
|
61
|
+
);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`Coverage threshold met: ${threshold.toFixed(2)}%`);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
bundleBinaryNamesForPlatform,
|
|
9
|
+
installReleaseBinary,
|
|
10
|
+
releaseAssetUrl,
|
|
11
|
+
} = require("./release-assets.cjs");
|
|
12
|
+
|
|
13
|
+
const REPO = "akiojin/gwt";
|
|
14
|
+
const BIN_DIR = path.join(__dirname, "..", "bin");
|
|
15
|
+
const BUNDLE_BINARIES = bundleBinaryNamesForPlatform();
|
|
16
|
+
|
|
17
|
+
function readVersion() {
|
|
18
|
+
const pkg = path.join(__dirname, "..", "package.json");
|
|
19
|
+
const data = JSON.parse(fs.readFileSync(pkg, "utf8"));
|
|
20
|
+
return data.version;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
if (process.env.CI) {
|
|
25
|
+
console.log("gwt: skipping binary download in CI");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const version = readVersion();
|
|
30
|
+
const { url } = releaseAssetUrl(REPO, version);
|
|
31
|
+
|
|
32
|
+
console.log(`Downloading gwt bundle for ${process.platform}-${process.arch}...`);
|
|
33
|
+
console.log(`Downloading from: ${url}`);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await installReleaseBinary({
|
|
37
|
+
repo: REPO,
|
|
38
|
+
version,
|
|
39
|
+
binDir: BIN_DIR,
|
|
40
|
+
});
|
|
41
|
+
console.log(`gwt bundle installed successfully: ${BUNDLE_BINARIES.join(", ")}`);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error(`gwt: failed to download binary - ${err.message}`);
|
|
44
|
+
console.error("gwt: you can build from source with: cargo build --release -p gwt");
|
|
45
|
+
process.exitCode = 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (require.main === module) {
|
|
50
|
+
main();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
main,
|
|
55
|
+
readVersion,
|
|
56
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const https = require("https");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { execFileSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
function binaryNameForPlatform(platform = os.platform()) {
|
|
8
|
+
return platform === "win32" ? "gwt.exe" : "gwt";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function daemonBinaryNameForPlatform(platform = os.platform()) {
|
|
12
|
+
return platform === "win32" ? "gwtd.exe" : "gwtd";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function bundleBinaryNamesForPlatform(platform = os.platform()) {
|
|
16
|
+
return [binaryNameForPlatform(platform), daemonBinaryNameForPlatform(platform)];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function releaseAssetName(platform = os.platform(), arch = os.arch()) {
|
|
20
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
21
|
+
return "gwt-macos-arm64.tar.gz";
|
|
22
|
+
}
|
|
23
|
+
if (platform === "darwin" && arch === "x64") {
|
|
24
|
+
return "gwt-macos-x86_64.tar.gz";
|
|
25
|
+
}
|
|
26
|
+
if (platform === "linux" && arch === "x64") {
|
|
27
|
+
return "gwt-linux-x86_64.tar.gz";
|
|
28
|
+
}
|
|
29
|
+
if (platform === "linux" && arch === "arm64") {
|
|
30
|
+
return "gwt-linux-aarch64.tar.gz";
|
|
31
|
+
}
|
|
32
|
+
if (platform === "win32" && arch === "x64") {
|
|
33
|
+
return "gwt-windows-x86_64.zip";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw new Error(`Unsupported platform: ${platform}-${arch}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function releaseAssetUrl(repo, version, platform = os.platform(), arch = os.arch()) {
|
|
40
|
+
const asset = releaseAssetName(platform, arch);
|
|
41
|
+
const tag = `v${version}`;
|
|
42
|
+
return {
|
|
43
|
+
asset,
|
|
44
|
+
tag,
|
|
45
|
+
url: `https://github.com/${repo}/releases/download/${tag}/${asset}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function download(url, dest) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const file = fs.createWriteStream(dest);
|
|
52
|
+
const request = (nextUrl) => {
|
|
53
|
+
https
|
|
54
|
+
.get(nextUrl, { headers: { "User-Agent": "gwt-postinstall" } }, (res) => {
|
|
55
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
56
|
+
request(res.headers.location);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (res.statusCode !== 200) {
|
|
60
|
+
file.close();
|
|
61
|
+
fs.rmSync(dest, { force: true });
|
|
62
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode} for ${nextUrl}`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
res.pipe(file);
|
|
66
|
+
file.on("finish", () => {
|
|
67
|
+
file.close(resolve);
|
|
68
|
+
});
|
|
69
|
+
})
|
|
70
|
+
.on("error", (err) => {
|
|
71
|
+
file.close();
|
|
72
|
+
fs.rmSync(dest, { force: true });
|
|
73
|
+
reject(err);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
request(url);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findFileRecursive(root, fileName) {
|
|
81
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const fullPath = path.join(root, entry.name);
|
|
84
|
+
if (entry.isFile() && entry.name === fileName) {
|
|
85
|
+
return fullPath;
|
|
86
|
+
}
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
const nested = findFileRecursive(fullPath, fileName);
|
|
89
|
+
if (nested) {
|
|
90
|
+
return nested;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function extractArchive(archivePath, extractDir, asset) {
|
|
98
|
+
if (asset.endsWith(".zip")) {
|
|
99
|
+
execFileSync(
|
|
100
|
+
"powershell.exe",
|
|
101
|
+
[
|
|
102
|
+
"-NoProfile",
|
|
103
|
+
"-NonInteractive",
|
|
104
|
+
"-Command",
|
|
105
|
+
`Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${extractDir.replace(/'/g, "''")}' -Force`,
|
|
106
|
+
]
|
|
107
|
+
);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (asset.endsWith(".tar.gz")) {
|
|
112
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", extractDir]);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
throw new Error(`Unsupported archive type: ${asset}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function cleanupTempDir(tempRoot) {
|
|
120
|
+
if (os.platform() === "win32") {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
126
|
+
} catch {
|
|
127
|
+
// Best-effort cleanup only.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function installBundleFromArchive({
|
|
132
|
+
archivePath,
|
|
133
|
+
asset,
|
|
134
|
+
binDir,
|
|
135
|
+
platform = os.platform(),
|
|
136
|
+
binaryNames = bundleBinaryNamesForPlatform(platform),
|
|
137
|
+
}) {
|
|
138
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-extract-"));
|
|
139
|
+
const extractDir = path.join(tempRoot, "extract");
|
|
140
|
+
|
|
141
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
142
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
extractArchive(archivePath, extractDir, asset);
|
|
146
|
+
|
|
147
|
+
const destinations = {};
|
|
148
|
+
for (const binaryName of binaryNames) {
|
|
149
|
+
const extractedBinary = findFileRecursive(extractDir, binaryName);
|
|
150
|
+
if (!extractedBinary) {
|
|
151
|
+
throw new Error(`Extracted archive does not contain ${binaryName}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const dest = path.join(binDir, binaryName);
|
|
155
|
+
fs.copyFileSync(extractedBinary, dest);
|
|
156
|
+
if (platform !== "win32") {
|
|
157
|
+
fs.chmodSync(dest, 0o755);
|
|
158
|
+
}
|
|
159
|
+
destinations[binaryName] = dest;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { asset, destinations };
|
|
163
|
+
} finally {
|
|
164
|
+
cleanupTempDir(tempRoot);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function installReleaseBinary({
|
|
169
|
+
repo,
|
|
170
|
+
version,
|
|
171
|
+
binDir,
|
|
172
|
+
platform = os.platform(),
|
|
173
|
+
arch = os.arch(),
|
|
174
|
+
}) {
|
|
175
|
+
const { asset, url } = releaseAssetUrl(repo, version, platform, arch);
|
|
176
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-"));
|
|
177
|
+
const archivePath = path.join(tempRoot, asset);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await download(url, archivePath);
|
|
181
|
+
return installBundleFromArchive({
|
|
182
|
+
archivePath,
|
|
183
|
+
asset,
|
|
184
|
+
binDir,
|
|
185
|
+
platform,
|
|
186
|
+
});
|
|
187
|
+
} finally {
|
|
188
|
+
cleanupTempDir(tempRoot);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = {
|
|
193
|
+
binaryNameForPlatform,
|
|
194
|
+
bundleBinaryNamesForPlatform,
|
|
195
|
+
daemonBinaryNameForPlatform,
|
|
196
|
+
download,
|
|
197
|
+
installBinaryFromArchive: installBundleFromArchive,
|
|
198
|
+
installBundleFromArchive,
|
|
199
|
+
installReleaseBinary,
|
|
200
|
+
releaseAssetName,
|
|
201
|
+
releaseAssetUrl,
|
|
202
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { execFileSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
binaryNameForPlatform,
|
|
9
|
+
bundleBinaryNamesForPlatform,
|
|
10
|
+
installBundleFromArchive,
|
|
11
|
+
releaseAssetName,
|
|
12
|
+
} = require("./release-assets.cjs");
|
|
13
|
+
const postinstall = require("./postinstall.cjs");
|
|
14
|
+
const launcher = require("../bin/gwt.cjs");
|
|
15
|
+
|
|
16
|
+
let failed = false;
|
|
17
|
+
|
|
18
|
+
function run(name, fn) {
|
|
19
|
+
try {
|
|
20
|
+
fn();
|
|
21
|
+
console.log(`ok - ${name}`);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
failed = true;
|
|
24
|
+
console.error(`not ok - ${name}`);
|
|
25
|
+
console.error(error && error.stack ? error.stack : error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
run("release asset names match the public portable contract", () => {
|
|
30
|
+
assert.equal(releaseAssetName("darwin", "arm64"), "gwt-macos-arm64.tar.gz");
|
|
31
|
+
assert.equal(releaseAssetName("darwin", "x64"), "gwt-macos-x86_64.tar.gz");
|
|
32
|
+
assert.equal(releaseAssetName("linux", "arm64"), "gwt-linux-aarch64.tar.gz");
|
|
33
|
+
assert.equal(releaseAssetName("linux", "x64"), "gwt-linux-x86_64.tar.gz");
|
|
34
|
+
assert.equal(releaseAssetName("win32", "x64"), "gwt-windows-x86_64.zip");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
run("release helper keeps platform binary names stable", () => {
|
|
38
|
+
assert.equal(binaryNameForPlatform("win32"), "gwt.exe");
|
|
39
|
+
assert.equal(binaryNameForPlatform("linux"), "gwt");
|
|
40
|
+
assert.equal(binaryNameForPlatform("darwin"), "gwt");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
run("release helper keeps bundle binary names stable", () => {
|
|
44
|
+
assert.deepEqual(bundleBinaryNamesForPlatform("win32"), ["gwt.exe", "gwtd.exe"]);
|
|
45
|
+
assert.deepEqual(bundleBinaryNamesForPlatform("linux"), ["gwt", "gwtd"]);
|
|
46
|
+
assert.deepEqual(bundleBinaryNamesForPlatform("darwin"), ["gwt", "gwtd"]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
run("installer entrypoints are loadable under package type module", () => {
|
|
50
|
+
assert.equal(typeof postinstall.main, "function");
|
|
51
|
+
assert.equal(typeof launcher.main, "function");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
run("windows installer definition includes the gwtd companion binary", () => {
|
|
55
|
+
const wix = fs.readFileSync(path.join(__dirname, "..", "wix", "main.wxs"), "utf8");
|
|
56
|
+
assert.match(wix, /gwtd\.exe/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
run("release workflow packages gwtd alongside gwt", () => {
|
|
60
|
+
const workflow = fs.readFileSync(
|
|
61
|
+
path.join(__dirname, "..", ".github", "workflows", "release.yml"),
|
|
62
|
+
"utf8"
|
|
63
|
+
);
|
|
64
|
+
assert.match(workflow, /--bin gwt --bin gwtd/);
|
|
65
|
+
assert.match(workflow, /Compress-Archive -Path @\("dist\/gwt\.exe", "dist\/gwtd\.exe"\)/);
|
|
66
|
+
assert.match(workflow, /tar -czf \$\{\{ matrix\.archive_name \}\} gwt gwtd/);
|
|
67
|
+
assert.match(workflow, /Contents\/MacOS\/gwtd/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
run("portable tarball extraction installs the unix bundle", () => {
|
|
71
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
|
|
72
|
+
const sourceDir = path.join(root, "source");
|
|
73
|
+
const binDir = path.join(root, "bin");
|
|
74
|
+
const archivePath = path.join(root, "gwt-linux-x86_64.tar.gz");
|
|
75
|
+
fs.mkdirSync(sourceDir, { recursive: true });
|
|
76
|
+
fs.writeFileSync(path.join(sourceDir, "gwt"), "unix-binary");
|
|
77
|
+
fs.writeFileSync(path.join(sourceDir, "gwtd"), "unix-daemon");
|
|
78
|
+
|
|
79
|
+
execFileSync("tar", ["-czf", archivePath, "-C", sourceDir, "gwt", "gwtd"]);
|
|
80
|
+
|
|
81
|
+
installBundleFromArchive({
|
|
82
|
+
archivePath,
|
|
83
|
+
asset: path.basename(archivePath),
|
|
84
|
+
binDir,
|
|
85
|
+
platform: "linux",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
assert.equal(fs.readFileSync(path.join(binDir, "gwt"), "utf8"), "unix-binary");
|
|
89
|
+
assert.equal(fs.readFileSync(path.join(binDir, "gwtd"), "utf8"), "unix-daemon");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
run("portable zip extraction installs the windows bundle", () => {
|
|
93
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
|
|
94
|
+
const sourceDir = path.join(root, "source");
|
|
95
|
+
const binDir = path.join(root, "bin");
|
|
96
|
+
const archivePath = path.join(root, "gwt-windows-x86_64.zip");
|
|
97
|
+
const sourceBinary = path.join(sourceDir, "gwt.exe");
|
|
98
|
+
const sourceDaemon = path.join(sourceDir, "gwtd.exe");
|
|
99
|
+
fs.mkdirSync(sourceDir, { recursive: true });
|
|
100
|
+
fs.writeFileSync(sourceBinary, "windows-binary");
|
|
101
|
+
fs.writeFileSync(sourceDaemon, "windows-daemon");
|
|
102
|
+
|
|
103
|
+
execFileSync("powershell.exe", [
|
|
104
|
+
"-NoProfile",
|
|
105
|
+
"-NonInteractive",
|
|
106
|
+
"-Command",
|
|
107
|
+
`Compress-Archive -LiteralPath @('${sourceBinary.replace(/'/g, "''")}','${sourceDaemon.replace(/'/g, "''")}') -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
installBundleFromArchive({
|
|
111
|
+
archivePath,
|
|
112
|
+
asset: path.basename(archivePath),
|
|
113
|
+
binDir,
|
|
114
|
+
platform: "win32",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
assert.equal(fs.readFileSync(path.join(binDir, "gwt.exe"), "utf8"), "windows-binary");
|
|
118
|
+
assert.equal(fs.readFileSync(path.join(binDir, "gwtd.exe"), "utf8"), "windows-daemon");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (failed) {
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
@@ -43,7 +43,9 @@ require_contains "$PACKAGE_JSON" "\"lint:husky\": \"bash scripts/verify-husky-ho
|
|
|
43
43
|
require_file "$PRE_PUSH"
|
|
44
44
|
require_contains "$PRE_PUSH" "cargo clippy --all-targets --all-features -- -D warnings"
|
|
45
45
|
require_contains "$PRE_PUSH" "cargo fmt --all -- --check"
|
|
46
|
-
require_contains "$PRE_PUSH" "
|
|
46
|
+
require_contains "$PRE_PUSH" "cargo llvm-cov -p gwt-core -p gwt --all-features --json --summary-only --output-path target/coverage-summary.json"
|
|
47
|
+
require_contains "$PRE_PUSH" "node scripts/check-coverage-threshold.mjs target/coverage-summary.json 90"
|
|
48
|
+
require_contains "$PRE_PUSH" "bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md --ignore tasks/todo.md"
|
|
47
49
|
require_contains "$PRE_PUSH" "pnpm lint:skills"
|
|
48
50
|
|
|
49
51
|
require_file "$COMMIT_MSG"
|
|
@@ -52,8 +54,6 @@ require_contains "$COMMIT_MSG" 'bunx --package @commitlint/cli commitlint --edit
|
|
|
52
54
|
if [ -f "$PRE_COMMIT" ]; then
|
|
53
55
|
require_contains "$PRE_COMMIT" "pnpm lint:skills"
|
|
54
56
|
require_contains "$PRE_COMMIT" "bash scripts/run-local-backend-tests-on-commit.sh"
|
|
55
|
-
require_contains "$PRE_COMMIT" "bash scripts/run-local-e2e-on-commit.sh"
|
|
56
|
-
require_contains "$PRE_COMMIT" "bash scripts/run-local-e2e-coverage-on-commit.sh"
|
|
57
57
|
require_not_contains "$PRE_COMMIT" "cargo clippy --all-targets --all-features -- -D warnings"
|
|
58
58
|
require_not_contains "$PRE_COMMIT" "cargo fmt --all -- --check"
|
|
59
59
|
require_not_contains "$PRE_COMMIT" "markdownlint-cli"
|
package/wix/main.wxs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
|
3
|
+
<Package
|
|
4
|
+
Name="GWT"
|
|
5
|
+
Language="1033"
|
|
6
|
+
Version="$(Version)"
|
|
7
|
+
Manufacturer="GWT Contributors"
|
|
8
|
+
UpgradeCode="55555555-5555-5555-5555-555555555555"
|
|
9
|
+
InstallerVersion="500"
|
|
10
|
+
Scope="perMachine">
|
|
11
|
+
<SummaryInformation Description="Canvas-based floating terminal GUI for Git Worktree Manager" />
|
|
12
|
+
<MajorUpgrade DowngradeErrorMessage="A newer version of GWT is already installed." />
|
|
13
|
+
<MediaTemplate EmbedCab="yes" />
|
|
14
|
+
|
|
15
|
+
<StandardDirectory Id="ProgramFiles6432Folder">
|
|
16
|
+
<Directory Id="INSTALLFOLDER" Name="GWT" />
|
|
17
|
+
</StandardDirectory>
|
|
18
|
+
|
|
19
|
+
<Feature Id="MainFeature" Title="GWT" Level="1">
|
|
20
|
+
<ComponentGroupRef Id="ProductComponents" />
|
|
21
|
+
</Feature>
|
|
22
|
+
</Package>
|
|
23
|
+
|
|
24
|
+
<Fragment>
|
|
25
|
+
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
|
|
26
|
+
<Component>
|
|
27
|
+
<File Id="GwtExecutable" Source="!(bindpath.gwt)gwt.exe" KeyPath="yes" />
|
|
28
|
+
</Component>
|
|
29
|
+
<Component>
|
|
30
|
+
<File Id="GwtdExecutable" Source="!(bindpath.gwt)gwtd.exe" KeyPath="yes" />
|
|
31
|
+
</Component>
|
|
32
|
+
</ComponentGroup>
|
|
33
|
+
</Fragment>
|
|
34
|
+
</Wix>
|
package/scripts/postinstall.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const os = require("os");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const path = require("path");
|
|
7
|
-
const https = require("https");
|
|
8
|
-
|
|
9
|
-
const REPO = "akiojin/gwt";
|
|
10
|
-
const BIN_DIR = path.join(__dirname, "..", "bin");
|
|
11
|
-
const BINARY_NAME = os.platform() === "win32" ? "gwt.exe" : "gwt";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Detect the GitHub Release asset name for the current platform/arch.
|
|
15
|
-
* Matches the naming convention: gwt-{os}-{arch}[.exe]
|
|
16
|
-
*/
|
|
17
|
-
function releaseAssetName() {
|
|
18
|
-
const platform = os.platform();
|
|
19
|
-
const arch = os.arch();
|
|
20
|
-
|
|
21
|
-
if (platform === "darwin" && arch === "arm64") {
|
|
22
|
-
return "gwt-macos-aarch64";
|
|
23
|
-
}
|
|
24
|
-
if (platform === "darwin" && arch === "x64") {
|
|
25
|
-
return "gwt-macos-x86_64";
|
|
26
|
-
}
|
|
27
|
-
if (platform === "linux" && arch === "x64") {
|
|
28
|
-
return "gwt-linux-x86_64";
|
|
29
|
-
}
|
|
30
|
-
if (platform === "linux" && arch === "arm64") {
|
|
31
|
-
return "gwt-linux-aarch64";
|
|
32
|
-
}
|
|
33
|
-
if (platform === "win32" && arch === "x64") {
|
|
34
|
-
return "gwt-windows-x86_64.exe";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
throw new Error(`Unsupported platform: ${platform}-${arch}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Read the target version from package.json.
|
|
42
|
-
*/
|
|
43
|
-
function readVersion() {
|
|
44
|
-
const pkg = path.join(__dirname, "..", "package.json");
|
|
45
|
-
const data = JSON.parse(fs.readFileSync(pkg, "utf8"));
|
|
46
|
-
return data.version;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Follow redirects and download a URL to a local path.
|
|
51
|
-
*/
|
|
52
|
-
function download(url, dest) {
|
|
53
|
-
return new Promise((resolve, reject) => {
|
|
54
|
-
const file = fs.createWriteStream(dest);
|
|
55
|
-
const request = (u) => {
|
|
56
|
-
https
|
|
57
|
-
.get(u, { headers: { "User-Agent": "gwt-postinstall" } }, (res) => {
|
|
58
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
59
|
-
request(res.headers.location);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (res.statusCode !== 200) {
|
|
63
|
-
file.close();
|
|
64
|
-
fs.unlink(dest, () => {});
|
|
65
|
-
reject(new Error(`Download failed: HTTP ${res.statusCode} for ${u}`));
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
res.pipe(file);
|
|
69
|
-
file.on("finish", () => {
|
|
70
|
-
file.close(resolve);
|
|
71
|
-
});
|
|
72
|
-
})
|
|
73
|
-
.on("error", (err) => {
|
|
74
|
-
file.close();
|
|
75
|
-
fs.unlink(dest, () => {});
|
|
76
|
-
reject(err);
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
request(url);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function main() {
|
|
84
|
-
// Skip in CI or when running from source
|
|
85
|
-
if (process.env.CI) {
|
|
86
|
-
console.log("gwt: skipping binary download in CI");
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const version = readVersion();
|
|
91
|
-
const asset = releaseAssetName();
|
|
92
|
-
const tag = `v${version}`;
|
|
93
|
-
const binaryUrl = `https://github.com/${REPO}/releases/download/${tag}/${asset}`;
|
|
94
|
-
|
|
95
|
-
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
96
|
-
|
|
97
|
-
const dest = path.join(BIN_DIR, BINARY_NAME);
|
|
98
|
-
|
|
99
|
-
console.log(`Downloading gwt binary for ${os.platform()}-${os.arch()}...`);
|
|
100
|
-
console.log(`Downloading from: ${binaryUrl}`);
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
await download(binaryUrl, dest);
|
|
104
|
-
if (os.platform() !== "win32") {
|
|
105
|
-
fs.chmodSync(dest, 0o755);
|
|
106
|
-
}
|
|
107
|
-
console.log(`gwt binary installed successfully!`);
|
|
108
|
-
} catch (err) {
|
|
109
|
-
console.error(`gwt: failed to download binary - ${err.message}`);
|
|
110
|
-
console.error("gwt: you can build from source with: cargo build --release -p gwt");
|
|
111
|
-
process.exitCode = 0; // non-fatal so npm install does not fail
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
main();
|