@akiojin/gwt 9.6.0 → 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 +7 -4
- package/package.json +1 -1
- package/scripts/check-coverage-threshold.mjs +65 -0
- package/scripts/postinstall.cjs +4 -5
- package/scripts/release-assets.cjs +28 -15
- package/scripts/test_release_assets.cjs +35 -9
- package/scripts/verify-husky-hooks.sh +3 -3
- package/wix/main.wxs +3 -0
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
|
@@ -10,6 +10,7 @@ const path = require("path");
|
|
|
10
10
|
const fs = require("fs");
|
|
11
11
|
|
|
12
12
|
const {
|
|
13
|
+
bundleBinaryNamesForPlatform,
|
|
13
14
|
binaryNameForPlatform,
|
|
14
15
|
installReleaseBinary,
|
|
15
16
|
releaseAssetUrl,
|
|
@@ -19,6 +20,7 @@ const REPO = "akiojin/gwt";
|
|
|
19
20
|
const BIN_DIR = __dirname;
|
|
20
21
|
const BIN_NAME = binaryNameForPlatform(process.platform);
|
|
21
22
|
const BIN_PATH = path.join(BIN_DIR, BIN_NAME);
|
|
23
|
+
const BUNDLE_BINARIES = bundleBinaryNamesForPlatform(process.platform);
|
|
22
24
|
|
|
23
25
|
function readVersion() {
|
|
24
26
|
const pkg = path.join(__dirname, "..", "package.json");
|
|
@@ -26,24 +28,25 @@ function readVersion() {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
async function ensureBinary() {
|
|
29
|
-
if (fs.existsSync(
|
|
31
|
+
if (BUNDLE_BINARIES.every((name) => fs.existsSync(path.join(BIN_DIR, name)))) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
30
34
|
|
|
31
35
|
const version = readVersion();
|
|
32
36
|
const { url } = releaseAssetUrl(REPO, version, process.platform, process.arch);
|
|
33
37
|
|
|
34
|
-
console.log(`Downloading gwt
|
|
38
|
+
console.log(`Downloading gwt bundle for ${process.platform}-${process.arch}...`);
|
|
35
39
|
console.log(`Downloading from: ${url}`);
|
|
36
40
|
|
|
37
41
|
await installReleaseBinary({
|
|
38
42
|
repo: REPO,
|
|
39
43
|
version,
|
|
40
44
|
binDir: BIN_DIR,
|
|
41
|
-
binaryName: BIN_NAME,
|
|
42
45
|
platform: process.platform,
|
|
43
46
|
arch: process.arch,
|
|
44
47
|
});
|
|
45
48
|
|
|
46
|
-
console.log(
|
|
49
|
+
console.log(`gwt bundle installed successfully: ${BUNDLE_BINARIES.join(", ")}`);
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
async function main() {
|
package/package.json
CHANGED
|
@@ -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)}%`);
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -5,14 +5,14 @@ const fs = require("fs");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
|
-
|
|
8
|
+
bundleBinaryNamesForPlatform,
|
|
9
9
|
installReleaseBinary,
|
|
10
10
|
releaseAssetUrl,
|
|
11
11
|
} = require("./release-assets.cjs");
|
|
12
12
|
|
|
13
13
|
const REPO = "akiojin/gwt";
|
|
14
14
|
const BIN_DIR = path.join(__dirname, "..", "bin");
|
|
15
|
-
const
|
|
15
|
+
const BUNDLE_BINARIES = bundleBinaryNamesForPlatform();
|
|
16
16
|
|
|
17
17
|
function readVersion() {
|
|
18
18
|
const pkg = path.join(__dirname, "..", "package.json");
|
|
@@ -29,7 +29,7 @@ async function main() {
|
|
|
29
29
|
const version = readVersion();
|
|
30
30
|
const { url } = releaseAssetUrl(REPO, version);
|
|
31
31
|
|
|
32
|
-
console.log(`Downloading gwt
|
|
32
|
+
console.log(`Downloading gwt bundle for ${process.platform}-${process.arch}...`);
|
|
33
33
|
console.log(`Downloading from: ${url}`);
|
|
34
34
|
|
|
35
35
|
try {
|
|
@@ -37,9 +37,8 @@ async function main() {
|
|
|
37
37
|
repo: REPO,
|
|
38
38
|
version,
|
|
39
39
|
binDir: BIN_DIR,
|
|
40
|
-
binaryName: BINARY_NAME,
|
|
41
40
|
});
|
|
42
|
-
console.log(
|
|
41
|
+
console.log(`gwt bundle installed successfully: ${BUNDLE_BINARIES.join(", ")}`);
|
|
43
42
|
} catch (err) {
|
|
44
43
|
console.error(`gwt: failed to download binary - ${err.message}`);
|
|
45
44
|
console.error("gwt: you can build from source with: cargo build --release -p gwt");
|
|
@@ -8,6 +8,14 @@ function binaryNameForPlatform(platform = os.platform()) {
|
|
|
8
8
|
return platform === "win32" ? "gwt.exe" : "gwt";
|
|
9
9
|
}
|
|
10
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
|
+
|
|
11
19
|
function releaseAssetName(platform = os.platform(), arch = os.arch()) {
|
|
12
20
|
if (platform === "darwin" && arch === "arm64") {
|
|
13
21
|
return "gwt-macos-arm64.tar.gz";
|
|
@@ -120,16 +128,15 @@ function cleanupTempDir(tempRoot) {
|
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
function
|
|
131
|
+
function installBundleFromArchive({
|
|
124
132
|
archivePath,
|
|
125
133
|
asset,
|
|
126
134
|
binDir,
|
|
127
|
-
binaryName = binaryNameForPlatform(),
|
|
128
135
|
platform = os.platform(),
|
|
136
|
+
binaryNames = bundleBinaryNamesForPlatform(platform),
|
|
129
137
|
}) {
|
|
130
138
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-extract-"));
|
|
131
139
|
const extractDir = path.join(tempRoot, "extract");
|
|
132
|
-
const dest = path.join(binDir, binaryName);
|
|
133
140
|
|
|
134
141
|
fs.mkdirSync(binDir, { recursive: true });
|
|
135
142
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
@@ -137,17 +144,22 @@ function installBinaryFromArchive({
|
|
|
137
144
|
try {
|
|
138
145
|
extractArchive(archivePath, extractDir, asset);
|
|
139
146
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
}
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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;
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
return { asset,
|
|
162
|
+
return { asset, destinations };
|
|
151
163
|
} finally {
|
|
152
164
|
cleanupTempDir(tempRoot);
|
|
153
165
|
}
|
|
@@ -157,7 +169,6 @@ async function installReleaseBinary({
|
|
|
157
169
|
repo,
|
|
158
170
|
version,
|
|
159
171
|
binDir,
|
|
160
|
-
binaryName = binaryNameForPlatform(),
|
|
161
172
|
platform = os.platform(),
|
|
162
173
|
arch = os.arch(),
|
|
163
174
|
}) {
|
|
@@ -167,11 +178,10 @@ async function installReleaseBinary({
|
|
|
167
178
|
|
|
168
179
|
try {
|
|
169
180
|
await download(url, archivePath);
|
|
170
|
-
return
|
|
181
|
+
return installBundleFromArchive({
|
|
171
182
|
archivePath,
|
|
172
183
|
asset,
|
|
173
184
|
binDir,
|
|
174
|
-
binaryName,
|
|
175
185
|
platform,
|
|
176
186
|
});
|
|
177
187
|
} finally {
|
|
@@ -181,8 +191,11 @@ async function installReleaseBinary({
|
|
|
181
191
|
|
|
182
192
|
module.exports = {
|
|
183
193
|
binaryNameForPlatform,
|
|
194
|
+
bundleBinaryNamesForPlatform,
|
|
195
|
+
daemonBinaryNameForPlatform,
|
|
184
196
|
download,
|
|
185
|
-
installBinaryFromArchive,
|
|
197
|
+
installBinaryFromArchive: installBundleFromArchive,
|
|
198
|
+
installBundleFromArchive,
|
|
186
199
|
installReleaseBinary,
|
|
187
200
|
releaseAssetName,
|
|
188
201
|
releaseAssetUrl,
|
|
@@ -6,7 +6,8 @@ const { execFileSync } = require("child_process");
|
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
binaryNameForPlatform,
|
|
9
|
-
|
|
9
|
+
bundleBinaryNamesForPlatform,
|
|
10
|
+
installBundleFromArchive,
|
|
10
11
|
releaseAssetName,
|
|
11
12
|
} = require("./release-assets.cjs");
|
|
12
13
|
const postinstall = require("./postinstall.cjs");
|
|
@@ -39,57 +40,82 @@ run("release helper keeps platform binary names stable", () => {
|
|
|
39
40
|
assert.equal(binaryNameForPlatform("darwin"), "gwt");
|
|
40
41
|
});
|
|
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
|
+
|
|
42
49
|
run("installer entrypoints are loadable under package type module", () => {
|
|
43
50
|
assert.equal(typeof postinstall.main, "function");
|
|
44
51
|
assert.equal(typeof launcher.main, "function");
|
|
45
52
|
});
|
|
46
53
|
|
|
47
|
-
run("
|
|
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", () => {
|
|
48
71
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
|
|
49
72
|
const sourceDir = path.join(root, "source");
|
|
50
73
|
const binDir = path.join(root, "bin");
|
|
51
74
|
const archivePath = path.join(root, "gwt-linux-x86_64.tar.gz");
|
|
52
75
|
fs.mkdirSync(sourceDir, { recursive: true });
|
|
53
76
|
fs.writeFileSync(path.join(sourceDir, "gwt"), "unix-binary");
|
|
77
|
+
fs.writeFileSync(path.join(sourceDir, "gwtd"), "unix-daemon");
|
|
54
78
|
|
|
55
|
-
execFileSync("tar", ["-czf", archivePath, "-C", sourceDir, "gwt"]);
|
|
79
|
+
execFileSync("tar", ["-czf", archivePath, "-C", sourceDir, "gwt", "gwtd"]);
|
|
56
80
|
|
|
57
|
-
|
|
81
|
+
installBundleFromArchive({
|
|
58
82
|
archivePath,
|
|
59
83
|
asset: path.basename(archivePath),
|
|
60
84
|
binDir,
|
|
61
|
-
binaryName: "gwt",
|
|
62
85
|
platform: "linux",
|
|
63
86
|
});
|
|
64
87
|
|
|
65
88
|
assert.equal(fs.readFileSync(path.join(binDir, "gwt"), "utf8"), "unix-binary");
|
|
89
|
+
assert.equal(fs.readFileSync(path.join(binDir, "gwtd"), "utf8"), "unix-daemon");
|
|
66
90
|
});
|
|
67
91
|
|
|
68
|
-
run("portable zip extraction installs the windows
|
|
92
|
+
run("portable zip extraction installs the windows bundle", () => {
|
|
69
93
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
|
|
70
94
|
const sourceDir = path.join(root, "source");
|
|
71
95
|
const binDir = path.join(root, "bin");
|
|
72
96
|
const archivePath = path.join(root, "gwt-windows-x86_64.zip");
|
|
73
97
|
const sourceBinary = path.join(sourceDir, "gwt.exe");
|
|
98
|
+
const sourceDaemon = path.join(sourceDir, "gwtd.exe");
|
|
74
99
|
fs.mkdirSync(sourceDir, { recursive: true });
|
|
75
100
|
fs.writeFileSync(sourceBinary, "windows-binary");
|
|
101
|
+
fs.writeFileSync(sourceDaemon, "windows-daemon");
|
|
76
102
|
|
|
77
103
|
execFileSync("powershell.exe", [
|
|
78
104
|
"-NoProfile",
|
|
79
105
|
"-NonInteractive",
|
|
80
106
|
"-Command",
|
|
81
|
-
`Compress-Archive -LiteralPath '${sourceBinary.replace(/'/g, "''")}' -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
|
|
107
|
+
`Compress-Archive -LiteralPath @('${sourceBinary.replace(/'/g, "''")}','${sourceDaemon.replace(/'/g, "''")}') -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
|
|
82
108
|
]);
|
|
83
109
|
|
|
84
|
-
|
|
110
|
+
installBundleFromArchive({
|
|
85
111
|
archivePath,
|
|
86
112
|
asset: path.basename(archivePath),
|
|
87
113
|
binDir,
|
|
88
|
-
binaryName: "gwt.exe",
|
|
89
114
|
platform: "win32",
|
|
90
115
|
});
|
|
91
116
|
|
|
92
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");
|
|
93
119
|
});
|
|
94
120
|
|
|
95
121
|
if (failed) {
|
|
@@ -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
CHANGED
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
<Component>
|
|
27
27
|
<File Id="GwtExecutable" Source="!(bindpath.gwt)gwt.exe" KeyPath="yes" />
|
|
28
28
|
</Component>
|
|
29
|
+
<Component>
|
|
30
|
+
<File Id="GwtdExecutable" Source="!(bindpath.gwt)gwtd.exe" KeyPath="yes" />
|
|
31
|
+
</Component>
|
|
29
32
|
</ComponentGroup>
|
|
30
33
|
</Fragment>
|
|
31
34
|
</Wix>
|