procoder-cli 0.1.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/AGENTS.md +122 -0
- package/CONTRIBUTORS.md +79 -0
- package/Makefile +82 -0
- package/README.md +125 -0
- package/bin/procoder.js +28 -0
- package/cmd/procoder/main.go +15 -0
- package/cmd/procoder-return/main.go +67 -0
- package/cmd/procoder-return/main_test.go +77 -0
- package/go.mod +3 -0
- package/internal/app/app.go +224 -0
- package/internal/app/app_test.go +253 -0
- package/internal/app/roundtrip_test.go +215 -0
- package/internal/apply/apply.go +1069 -0
- package/internal/apply/apply_test.go +794 -0
- package/internal/errs/errors.go +114 -0
- package/internal/exchange/exchange_test.go +165 -0
- package/internal/exchange/id.go +66 -0
- package/internal/exchange/json.go +64 -0
- package/internal/exchange/types.go +55 -0
- package/internal/gitx/gitx.go +105 -0
- package/internal/gitx/gitx_test.go +51 -0
- package/internal/output/errors.go +49 -0
- package/internal/output/errors_test.go +41 -0
- package/internal/prepare/prepare.go +788 -0
- package/internal/prepare/prepare_test.go +416 -0
- package/internal/returnpkg/returnpkg.go +589 -0
- package/internal/returnpkg/returnpkg_test.go +489 -0
- package/internal/testutil/gitrepo/repo.go +113 -0
- package/package.json +47 -0
- package/scripts/postinstall.js +263 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const https = require("node:https");
|
|
6
|
+
const { spawnSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
const pkg = require("../package.json");
|
|
9
|
+
const cliName = pkg.config?.cliBinaryName || "procoder";
|
|
10
|
+
const helperBinaryName = "procoder-return";
|
|
11
|
+
const helperTarget = Object.freeze({
|
|
12
|
+
goos: "linux",
|
|
13
|
+
goarch: "amd64"
|
|
14
|
+
});
|
|
15
|
+
const helperAssetName = `${helperBinaryName}_${helperTarget.goos}_${helperTarget.goarch}`;
|
|
16
|
+
|
|
17
|
+
const goosMap = {
|
|
18
|
+
darwin: "darwin",
|
|
19
|
+
linux: "linux",
|
|
20
|
+
win32: "windows"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const goarchMap = {
|
|
24
|
+
x64: "amd64",
|
|
25
|
+
arm64: "arm64"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function parseGitHubRepo(repositoryURL) {
|
|
29
|
+
const match = (repositoryURL || "").match(/github\.com[:/](.+?)\/(.+?)(?:\.git)?$/);
|
|
30
|
+
if (!match) {
|
|
31
|
+
throw new Error("package.json repository.url must point to a GitHub repo.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
owner: match[1],
|
|
36
|
+
name: match[2]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveHostTarget(platform, arch) {
|
|
41
|
+
const goos = goosMap[platform];
|
|
42
|
+
const goarch = goarchMap[arch];
|
|
43
|
+
if (!goos || !goarch) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { goos, goarch };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function installedCliBinaryName(platform, binaryName) {
|
|
51
|
+
return platform === "win32" ? `${binaryName}.exe` : `${binaryName}-bin`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildReleaseAssetName(binaryName, goos, goarch) {
|
|
55
|
+
const suffix = goos === "windows" ? ".exe" : "";
|
|
56
|
+
return `${binaryName}_${goos}_${goarch}${suffix}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildReleaseAssetURL({ repoOwner, repoName, version, assetName }) {
|
|
60
|
+
return `https://github.com/${repoOwner}/${repoName}/releases/download/v${version}/${assetName}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildInstallPlan({
|
|
64
|
+
pkg,
|
|
65
|
+
cliName,
|
|
66
|
+
platform = process.platform,
|
|
67
|
+
arch = process.arch,
|
|
68
|
+
rootDir = path.resolve(__dirname, "..")
|
|
69
|
+
}) {
|
|
70
|
+
const repo = parseGitHubRepo(pkg.repository?.url || "");
|
|
71
|
+
const hostTarget = resolveHostTarget(platform, arch);
|
|
72
|
+
const binDir = path.join(rootDir, "bin");
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
rootDir,
|
|
76
|
+
binDir,
|
|
77
|
+
platform,
|
|
78
|
+
arch,
|
|
79
|
+
version: pkg.version,
|
|
80
|
+
repo,
|
|
81
|
+
hostTarget,
|
|
82
|
+
cli: {
|
|
83
|
+
name: cliName,
|
|
84
|
+
assetName: hostTarget ? buildReleaseAssetName(cliName, hostTarget.goos, hostTarget.goarch) : "",
|
|
85
|
+
destination: path.join(binDir, installedCliBinaryName(platform, cliName))
|
|
86
|
+
},
|
|
87
|
+
helper: {
|
|
88
|
+
name: helperBinaryName,
|
|
89
|
+
assetName: helperAssetName,
|
|
90
|
+
destination: path.join(binDir, helperAssetName)
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function main() {
|
|
96
|
+
const plan = buildInstallPlan({ pkg, cliName });
|
|
97
|
+
fs.mkdirSync(plan.binDir, { recursive: true });
|
|
98
|
+
|
|
99
|
+
if (!plan.hostTarget) {
|
|
100
|
+
console.warn(`Unsupported platform/arch for release asset download: ${plan.platform}/${plan.arch}`);
|
|
101
|
+
fallbackBuildOrExit(plan);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const installed = await installReleaseAssets(plan);
|
|
107
|
+
console.log(`Installed ${cliName} from ${installed.cliURL}`);
|
|
108
|
+
console.log(`Installed ${helperBinaryName} from ${installed.helperURL}`);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.warn(`Failed to download prebuilt binaries: ${err.message}`);
|
|
111
|
+
fallbackBuildOrExit(plan);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function installReleaseAssets(plan) {
|
|
116
|
+
const cliURL = buildReleaseAssetURL({
|
|
117
|
+
repoOwner: plan.repo.owner,
|
|
118
|
+
repoName: plan.repo.name,
|
|
119
|
+
version: plan.version,
|
|
120
|
+
assetName: plan.cli.assetName
|
|
121
|
+
});
|
|
122
|
+
await downloadToFile(cliURL, plan.cli.destination);
|
|
123
|
+
chmodIfNeeded(plan.cli.destination, plan.platform);
|
|
124
|
+
|
|
125
|
+
const helperURL = buildReleaseAssetURL({
|
|
126
|
+
repoOwner: plan.repo.owner,
|
|
127
|
+
repoName: plan.repo.name,
|
|
128
|
+
version: plan.version,
|
|
129
|
+
assetName: plan.helper.assetName
|
|
130
|
+
});
|
|
131
|
+
await downloadToFile(helperURL, plan.helper.destination);
|
|
132
|
+
chmodIfNeeded(plan.helper.destination, plan.platform);
|
|
133
|
+
|
|
134
|
+
return { cliURL, helperURL };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function fallbackBuildOrExit(plan) {
|
|
138
|
+
const hasGo = spawnSync("go", ["version"], { stdio: "ignore" }).status === 0;
|
|
139
|
+
const hasSource = [
|
|
140
|
+
path.join(plan.rootDir, "cmd", plan.cli.name, "main.go"),
|
|
141
|
+
path.join(plan.rootDir, "cmd", plan.helper.name, "main.go")
|
|
142
|
+
].every((sourcePath) => fs.existsSync(sourcePath));
|
|
143
|
+
|
|
144
|
+
if (!hasGo || !hasSource) {
|
|
145
|
+
console.error("Unable to install procoder binaries. Missing release assets and local Go build fallback.");
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const build of buildFallbackBuilds(plan)) {
|
|
150
|
+
const result = spawnSync("go", build.args, {
|
|
151
|
+
cwd: plan.rootDir,
|
|
152
|
+
stdio: "inherit",
|
|
153
|
+
env: {
|
|
154
|
+
...process.env,
|
|
155
|
+
...build.env
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (result.status !== 0) {
|
|
160
|
+
process.exit(result.status || 1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
chmodIfNeeded(build.destination, plan.platform);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`Installed ${plan.cli.name} and ${plan.helper.name} by building from source.`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function buildFallbackBuilds(plan) {
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
destination: plan.cli.destination,
|
|
173
|
+
env: {},
|
|
174
|
+
args: [
|
|
175
|
+
"build",
|
|
176
|
+
"-trimpath",
|
|
177
|
+
'-ldflags=-s -w',
|
|
178
|
+
"-o",
|
|
179
|
+
plan.cli.destination,
|
|
180
|
+
`./cmd/${plan.cli.name}`
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
destination: plan.helper.destination,
|
|
185
|
+
env: {
|
|
186
|
+
CGO_ENABLED: "0",
|
|
187
|
+
GOOS: helperTarget.goos,
|
|
188
|
+
GOARCH: helperTarget.goarch
|
|
189
|
+
},
|
|
190
|
+
args: [
|
|
191
|
+
"build",
|
|
192
|
+
"-trimpath",
|
|
193
|
+
'-ldflags=-s -w',
|
|
194
|
+
"-o",
|
|
195
|
+
plan.helper.destination,
|
|
196
|
+
`./cmd/${plan.helper.name}`
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function chmodIfNeeded(targetPath, platform) {
|
|
203
|
+
if (platform !== "win32") {
|
|
204
|
+
fs.chmodSync(targetPath, 0o755);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function downloadToFile(url, destinationPath) {
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
const request = https.get(url, (response) => {
|
|
211
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
212
|
+
response.resume();
|
|
213
|
+
downloadToFile(response.headers.location, destinationPath).then(resolve).catch(reject);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (response.statusCode !== 200) {
|
|
218
|
+
response.resume();
|
|
219
|
+
reject(new Error(`HTTP ${response.statusCode}`));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const file = fs.createWriteStream(destinationPath);
|
|
224
|
+
response.pipe(file);
|
|
225
|
+
|
|
226
|
+
file.on("finish", () => {
|
|
227
|
+
file.close((err) => {
|
|
228
|
+
if (err) {
|
|
229
|
+
reject(err);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
resolve();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
file.on("error", (err) => {
|
|
237
|
+
fs.rmSync(destinationPath, { force: true });
|
|
238
|
+
reject(err);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
request.on("error", reject);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
buildFallbackBuilds,
|
|
248
|
+
buildInstallPlan,
|
|
249
|
+
buildReleaseAssetName,
|
|
250
|
+
buildReleaseAssetURL,
|
|
251
|
+
helperAssetName,
|
|
252
|
+
helperBinaryName,
|
|
253
|
+
installedCliBinaryName,
|
|
254
|
+
parseGitHubRepo,
|
|
255
|
+
resolveHostTarget
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if (require.main === module) {
|
|
259
|
+
main().catch((err) => {
|
|
260
|
+
console.error(err.message);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|
|
263
|
+
}
|