@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 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
- bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md
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
@@ -66,6 +66,9 @@ gwt board show
66
66
  gwt hook workflow-policy
67
67
  ```
68
68
 
69
+ managed hook と runtime 委譲の入口も引き続き `gwt` です。利用者が別の daemon
70
+ コマンドを手動起動する必要はありません。
71
+
69
72
  ## 基本フロー
70
73
 
71
74
  1. リポジトリを開く、または前回のプロジェクトを復元する
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
- const https = require("https");
12
- const os = require("os");
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 === "win32" ? "gwt.exe" : "gwt";
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(BIN_PATH)) return;
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 asset = releaseAssetName();
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 binary for ${os.platform()}-${os.arch()}...`);
38
+ console.log(`Downloading gwt bundle for ${process.platform}-${process.arch}...`);
76
39
  console.log(`Downloading from: ${url}`);
77
40
 
78
- fs.mkdirSync(BIN_DIR, { recursive: true });
79
- await download(url, BIN_PATH);
80
-
81
- if (os.platform() !== "win32") {
82
- fs.chmodSync(BIN_PATH, 0o755);
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("gwt binary installed successfully!");
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.5.2",
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.js",
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" "bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md"
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>
@@ -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();