arashi 1.11.2 → 1.12.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/README.md +10 -1
- package/bin/arashi.js +125 -86
- package/bin/install-binary.js +211 -0
- package/package.json +3 -4
- package/scripts/postinstall.js +0 -176
package/README.md
CHANGED
|
@@ -47,13 +47,21 @@ If curl installation fails, or if the smoke test reports a bad release artifact,
|
|
|
47
47
|
npm install -g arashi
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
The npm package is script-free: it does not require package-manager lifecycle scripts or `postinstall` approval. It installs the lightweight JavaScript entrypoint and wrapper files first, then downloads the matching platform binary on first use.
|
|
51
|
+
|
|
52
|
+
To preinstall the binary explicitly, run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
arashi install
|
|
56
|
+
```
|
|
57
|
+
|
|
50
58
|
Verify install:
|
|
51
59
|
|
|
52
60
|
```bash
|
|
53
61
|
arashi --version
|
|
54
62
|
```
|
|
55
63
|
|
|
56
|
-
If npm is unavailable or fails, use the curl installer command above or the manual release instructions in [`docs/INSTALLATION.md`](./docs/INSTALLATION.md).
|
|
64
|
+
If npm is unavailable or binary installation fails, use the curl installer command above or the manual release instructions in [`docs/INSTALLATION.md`](./docs/INSTALLATION.md).
|
|
57
65
|
|
|
58
66
|
### Manual install from GitHub Releases
|
|
59
67
|
|
|
@@ -94,6 +102,7 @@ bun run build
|
|
|
94
102
|
Arashi currently provides these commands:
|
|
95
103
|
|
|
96
104
|
- `arashi init`
|
|
105
|
+
- `arashi install`
|
|
97
106
|
- `arashi add <git-url>`
|
|
98
107
|
- `arashi clone [--all]`
|
|
99
108
|
- `arashi create <branch>`
|
package/bin/arashi.js
CHANGED
|
@@ -1,110 +1,149 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { formatInstallError, getPlatformInfo, installBinary } from "./install-binary.js";
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = dirname(__filename);
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
11
|
+
const currentPlatform = process.platform;
|
|
12
|
+
const currentArch = process.arch;
|
|
13
|
+
const isWindows = currentPlatform === "win32";
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
export function getDefaultBinaryName(platform = currentPlatform) {
|
|
16
|
+
return platform === "win32" ? "arashi.bin.exe" : "arashi.bin";
|
|
17
|
+
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
export function getWrapperName(platform = currentPlatform) {
|
|
20
|
+
return platform === "win32" ? "arashi.bat" : "arashi";
|
|
21
|
+
}
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
export function resolvePlatformBinaryName({ arch = currentArch, platform = currentPlatform } = {}) {
|
|
24
|
+
return getPlatformInfo({ arch, platform }).binaryName;
|
|
25
|
+
}
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
export function resolveBinaryPath(options = {}) {
|
|
28
|
+
const binDir = options.binDir ?? __dirname;
|
|
29
|
+
const platform = options.platform ?? currentPlatform;
|
|
30
|
+
const arch = options.arch ?? currentArch;
|
|
31
|
+
const exists = options.existsSyncImpl ?? existsSync;
|
|
32
|
+
const defaultBinaryPath = join(binDir, getDefaultBinaryName(platform));
|
|
28
33
|
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
34
|
+
if (exists(defaultBinaryPath)) {
|
|
35
|
+
return defaultBinaryPath;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const postinstallPath = join(__dirname, "..", "scripts", "postinstall.js");
|
|
40
|
-
const defaultBinary = isWindows ? "arashi.bin.exe" : "arashi.bin";
|
|
41
|
-
const defaultBinaryPath = join(__dirname, defaultBinary);
|
|
42
|
-
const platformBinary = (() => {
|
|
43
|
-
if (isWindows) {
|
|
44
|
-
return "arashi-windows-x64.exe";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (process.platform === "darwin" && process.arch === "arm64") {
|
|
48
|
-
return "arashi-macos-arm64";
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (process.platform === "linux" && process.arch === "x64") {
|
|
52
|
-
return "arashi-linux-x64";
|
|
53
|
-
}
|
|
38
|
+
try {
|
|
39
|
+
return join(binDir, resolvePlatformBinaryName({ arch, platform }));
|
|
40
|
+
} catch {
|
|
41
|
+
return defaultBinaryPath;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
45
|
+
export function hasRunnableBinary(options = {}) {
|
|
46
|
+
const binDir = options.binDir ?? __dirname;
|
|
47
|
+
const platform = options.platform ?? currentPlatform;
|
|
48
|
+
const arch = options.arch ?? currentArch;
|
|
49
|
+
const exists = options.existsSyncImpl ?? existsSync;
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
if (exists(join(binDir, getDefaultBinaryName(platform)))) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
66
54
|
|
|
55
|
+
try {
|
|
56
|
+
return exists(join(binDir, resolvePlatformBinaryName({ arch, platform })));
|
|
57
|
+
} catch {
|
|
67
58
|
return false;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
if (existsSync(wrapperPath) && binaryExists()) {
|
|
71
|
-
return;
|
|
72
59
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function ensureInstalled(options = {}) {
|
|
63
|
+
const binDir = options.binDir ?? __dirname;
|
|
64
|
+
const rootDir = options.rootDir ?? join(binDir, "..");
|
|
65
|
+
const platform = options.platform ?? currentPlatform;
|
|
66
|
+
const arch = options.arch ?? currentArch;
|
|
67
|
+
const log = options.log ?? console.log;
|
|
68
|
+
const installBinaryImpl = options.installBinaryImpl ?? installBinary;
|
|
69
|
+
|
|
70
|
+
if (hasRunnableBinary({ arch, binDir, existsSyncImpl: options.existsSyncImpl, platform })) {
|
|
71
|
+
return { status: "already-installed" };
|
|
80
72
|
}
|
|
81
73
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
74
|
+
log("arashi binary missing; installing the matching platform binary before continuing.");
|
|
75
|
+
return installBinaryImpl({ ...options, arch, binDir, log, platform, rootDir });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isExplicitInstallCommand(argv) {
|
|
79
|
+
return argv[0] === "install";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function runExplicitInstall(options) {
|
|
83
|
+
const binDir = options.binDir ?? __dirname;
|
|
84
|
+
const rootDir = options.rootDir ?? join(binDir, "..");
|
|
85
|
+
const installBinaryImpl = options.installBinaryImpl ?? installBinary;
|
|
86
|
+
|
|
87
|
+
await installBinaryImpl({ ...options, binDir, rootDir });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function spawnArashi(argv, options = {}) {
|
|
91
|
+
const binDir = options.binDir ?? __dirname;
|
|
92
|
+
const platform = options.platform ?? currentPlatform;
|
|
93
|
+
const windows = platform === "win32";
|
|
94
|
+
const spawnImpl = options.spawnImpl ?? spawn;
|
|
95
|
+
const wrapperPath = join(binDir, getWrapperName(platform));
|
|
96
|
+
const binaryPath = resolveBinaryPath({
|
|
97
|
+
arch: options.arch,
|
|
98
|
+
binDir,
|
|
99
|
+
existsSyncImpl: options.existsSyncImpl,
|
|
100
|
+
platform,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
const child = windows
|
|
105
|
+
? spawnImpl(binaryPath, argv, {
|
|
106
|
+
stdio: "inherit",
|
|
107
|
+
windowsHide: false,
|
|
108
|
+
})
|
|
109
|
+
: spawnImpl(wrapperPath, argv, { stdio: "inherit" });
|
|
110
|
+
|
|
111
|
+
child.on("exit", (code, signal) => {
|
|
112
|
+
if (typeof code === "number") {
|
|
113
|
+
resolve(code);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
resolve(signal ? 1 : 0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
child.on("error", (error) => {
|
|
121
|
+
const errorLog = options.error ?? console.error;
|
|
122
|
+
errorLog(`Failed to start arashi. ${error.message}.`);
|
|
123
|
+
resolve(1);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function runEntrypoint(argv = process.argv.slice(2), options = {}) {
|
|
129
|
+
const errorLog = options.error ?? console.error;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (isExplicitInstallCommand(argv)) {
|
|
133
|
+
await runExplicitInstall(options);
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await ensureInstalled(options);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
errorLog(formatInstallError(error));
|
|
140
|
+
return 1;
|
|
102
141
|
}
|
|
103
142
|
|
|
104
|
-
|
|
105
|
-
}
|
|
143
|
+
return spawnArashi(argv, options);
|
|
144
|
+
}
|
|
106
145
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
process.
|
|
110
|
-
}
|
|
146
|
+
const invokedPath = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
|
|
147
|
+
if (import.meta.url === invokedPath) {
|
|
148
|
+
process.exitCode = await runEntrypoint();
|
|
149
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { chmodSync, createWriteStream } from "node:fs";
|
|
3
|
+
import { access, constants, mkdir, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { get } from "node:https";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
export const PACKAGE_NAME = "arashi";
|
|
12
|
+
export const GITHUB_REPO = "corwinm/arashi";
|
|
13
|
+
export const MANUAL_INSTALL_URL = `https://github.com/${GITHUB_REPO}/releases`;
|
|
14
|
+
|
|
15
|
+
export class UnsupportedPlatformError extends Error {
|
|
16
|
+
constructor(platform = process.platform, arch = process.arch) {
|
|
17
|
+
super(
|
|
18
|
+
`Unsupported platform: ${platform}-${arch}. Please build from source or file an issue at https://github.com/${GITHUB_REPO}/issues`,
|
|
19
|
+
);
|
|
20
|
+
this.name = "UnsupportedPlatformError";
|
|
21
|
+
this.platform = platform;
|
|
22
|
+
this.arch = arch;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getPlatformInfo({ platform = process.platform, arch = process.arch } = {}) {
|
|
27
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
28
|
+
return { arch, binaryName: `${PACKAGE_NAME}-macos-arm64`, isWindows: false, platform };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (platform === "linux" && arch === "x64") {
|
|
32
|
+
return { arch, binaryName: `${PACKAGE_NAME}-linux-x64`, isWindows: false, platform };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (platform === "win32" && arch === "x64") {
|
|
36
|
+
return { arch, binaryName: `${PACKAGE_NAME}-windows-x64.exe`, isWindows: true, platform };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new UnsupportedPlatformError(platform, arch);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildReleaseAssetUrl(version, binaryName, repo = GITHUB_REPO) {
|
|
43
|
+
return `https://github.com/${repo}/releases/download/v${version}/${binaryName}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function readPackageVersion(packageJsonPath, { readFileImpl = readFile } = {}) {
|
|
47
|
+
const packageJson = JSON.parse(await readFileImpl(packageJsonPath, "utf8"));
|
|
48
|
+
return packageJson.version;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function isBinaryInstalled(binaryPath, { accessImpl = access } = {}) {
|
|
52
|
+
try {
|
|
53
|
+
await accessImpl(binaryPath, constants.F_OK);
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function downloadFile(url, dest, options = {}) {
|
|
61
|
+
const {
|
|
62
|
+
createWriteStreamImpl = createWriteStream,
|
|
63
|
+
getImpl = get,
|
|
64
|
+
log = console.log,
|
|
65
|
+
maxRedirects = 5,
|
|
66
|
+
} = options;
|
|
67
|
+
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
if (maxRedirects < 0) {
|
|
70
|
+
reject(new Error("Too many redirects while downloading binary"));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
log(`Downloading ${url}...`);
|
|
75
|
+
|
|
76
|
+
const file = createWriteStreamImpl(dest);
|
|
77
|
+
const request = getImpl(url, (response) => {
|
|
78
|
+
if (
|
|
79
|
+
response.statusCode === 301 ||
|
|
80
|
+
response.statusCode === 302 ||
|
|
81
|
+
response.statusCode === 307 ||
|
|
82
|
+
response.statusCode === 308
|
|
83
|
+
) {
|
|
84
|
+
file.close();
|
|
85
|
+
const location = response.headers.location;
|
|
86
|
+
if (!location) {
|
|
87
|
+
reject(new Error(`Redirect response from ${url} did not include a location header`));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
downloadFile(location, dest, { ...options, maxRedirects: maxRedirects - 1 })
|
|
92
|
+
.then(resolve)
|
|
93
|
+
.catch(reject);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (response.statusCode !== 200) {
|
|
98
|
+
file.close();
|
|
99
|
+
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
response.pipe(file);
|
|
104
|
+
file.on("finish", () => {
|
|
105
|
+
file.close(resolve);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
request.on("error", (error) => {
|
|
110
|
+
file.close();
|
|
111
|
+
reject(error);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
file.on("error", (error) => {
|
|
115
|
+
file.close();
|
|
116
|
+
reject(error);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function verifyBinary(binaryPath, { log = console.log, spawnSyncImpl = spawnSync } = {}) {
|
|
122
|
+
const result = spawnSyncImpl(binaryPath, ["--version"], {
|
|
123
|
+
encoding: "utf8",
|
|
124
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (result.error) {
|
|
128
|
+
throw result.error;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (result.status !== 0) {
|
|
132
|
+
const signalDetail = result.signal ? ` (signal: ${result.signal})` : "";
|
|
133
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Downloaded binary failed smoke test with exit code ${result.status ?? "unknown"}${signalDetail}${output ? `: ${output}` : ""}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const versionOutput = (result.stdout ?? "").trim();
|
|
140
|
+
if (!versionOutput) {
|
|
141
|
+
throw new Error("Downloaded binary returned success but produced no version output");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
log(`✓ Verified ${PACKAGE_NAME} executable (${versionOutput})`);
|
|
145
|
+
return versionOutput;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function installBinary(options = {}) {
|
|
149
|
+
const rootDir = options.rootDir ?? join(__dirname, "..");
|
|
150
|
+
const binDir = options.binDir ?? join(rootDir, "bin");
|
|
151
|
+
const packageJsonPath = options.packageJsonPath ?? join(rootDir, "package.json");
|
|
152
|
+
const log = options.log ?? console.log;
|
|
153
|
+
const accessImpl = options.accessImpl ?? access;
|
|
154
|
+
const chmodImpl = options.chmodImpl ?? chmodSync;
|
|
155
|
+
const downloadFileImpl = options.downloadFileImpl ?? downloadFile;
|
|
156
|
+
const mkdirImpl = options.mkdirImpl ?? mkdir;
|
|
157
|
+
const readFileImpl = options.readFileImpl ?? readFile;
|
|
158
|
+
const rmImpl = options.rmImpl ?? rm;
|
|
159
|
+
const verifyBinaryImpl = options.verifyBinaryImpl ?? verifyBinary;
|
|
160
|
+
|
|
161
|
+
const version = options.version ?? (await readPackageVersion(packageJsonPath, { readFileImpl }));
|
|
162
|
+
const { binaryName, isWindows } = getPlatformInfo({
|
|
163
|
+
arch: options.arch ?? process.arch,
|
|
164
|
+
platform: options.platform ?? process.platform,
|
|
165
|
+
});
|
|
166
|
+
const binaryPath = join(binDir, binaryName);
|
|
167
|
+
const downloadUrl = buildReleaseAssetUrl(version, binaryName, options.repo ?? GITHUB_REPO);
|
|
168
|
+
|
|
169
|
+
if (await isBinaryInstalled(binaryPath, { accessImpl })) {
|
|
170
|
+
log(`✓ Binary already installed at ${binaryPath}`);
|
|
171
|
+
return { binaryName, binaryPath, downloadUrl, status: "already-installed", version };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
await mkdirImpl(binDir, { recursive: true });
|
|
176
|
+
await downloadFileImpl(downloadUrl, binaryPath, { log });
|
|
177
|
+
|
|
178
|
+
if (!isWindows) {
|
|
179
|
+
chmodImpl(binaryPath, 0o755);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
verifyBinaryImpl(binaryPath, { log });
|
|
183
|
+
|
|
184
|
+
log(`✓ Successfully installed ${PACKAGE_NAME} v${version}`);
|
|
185
|
+
log(` Binary location: ${binaryPath}`);
|
|
186
|
+
return { binaryName, binaryPath, downloadUrl, status: "installed", version };
|
|
187
|
+
} catch (error) {
|
|
188
|
+
await rmImpl(binaryPath, { force: true }).catch(() => {});
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function formatInstallError(error) {
|
|
194
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
195
|
+
return [
|
|
196
|
+
`✗ Failed to install ${PACKAGE_NAME}: ${message}`,
|
|
197
|
+
"",
|
|
198
|
+
`You can manually download binaries from: ${MANUAL_INSTALL_URL}`,
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function runInstallCli(options = {}) {
|
|
203
|
+
const errorLog = options.error ?? console.error;
|
|
204
|
+
try {
|
|
205
|
+
await installBinary(options);
|
|
206
|
+
return 0;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
errorLog(formatInstallError(error));
|
|
209
|
+
return 1;
|
|
210
|
+
}
|
|
211
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arashi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Git worktree manager for meta-repositories - The eye of the storm for your development workflow",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"bin/arashi",
|
|
31
31
|
"bin/arashi.js",
|
|
32
|
+
"bin/install-binary.js",
|
|
32
33
|
"bin/arashi.bat",
|
|
33
34
|
"bin/arashi.ps1",
|
|
34
|
-
"scripts/postinstall.js",
|
|
35
35
|
"schema/config.schema.json",
|
|
36
36
|
"README.md",
|
|
37
37
|
"LICENSE"
|
|
@@ -65,7 +65,6 @@
|
|
|
65
65
|
"format": "oxfmt --config .oxfmtrc.json --write .",
|
|
66
66
|
"format:check": "oxfmt --config .oxfmtrc.json --check .",
|
|
67
67
|
"quality:changed": "bun run scripts/quality/changed-files-quality.ts",
|
|
68
|
-
"postinstall": "node scripts/postinstall.js",
|
|
69
68
|
"prepublishOnly": "bun run schema:publish",
|
|
70
69
|
"prepare": "husky"
|
|
71
70
|
},
|
|
@@ -85,7 +84,7 @@
|
|
|
85
84
|
"ora": "^8.1.1",
|
|
86
85
|
"oxfmt": "0.28.0",
|
|
87
86
|
"oxlint": "1.43.0",
|
|
88
|
-
"semantic-release": "^
|
|
87
|
+
"semantic-release": "^25.0.3",
|
|
89
88
|
"ts-json-schema-generator": "2.4.0",
|
|
90
89
|
"typescript": "^5.9.3"
|
|
91
90
|
},
|
package/scripts/postinstall.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Post-install script to download the platform-specific arashi binary from GitHub releases
|
|
5
|
-
* This keeps the npm package size small while providing pre-compiled binaries
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { access, constants, mkdir, readFile, rm } from "node:fs/promises";
|
|
9
|
-
import { chmodSync, createWriteStream } from "node:fs";
|
|
10
|
-
import { dirname, join } from "node:path";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { get } from "node:https";
|
|
13
|
-
import { spawnSync } from "node:child_process";
|
|
14
|
-
|
|
15
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
-
const __dirname = dirname(__filename);
|
|
17
|
-
|
|
18
|
-
const PACKAGE_NAME = "arashi";
|
|
19
|
-
const GITHUB_REPO = "corwinm/arashi";
|
|
20
|
-
|
|
21
|
-
// Determine platform and binary name
|
|
22
|
-
function getPlatformInfo() {
|
|
23
|
-
const { platform } = process;
|
|
24
|
-
const { arch } = process;
|
|
25
|
-
|
|
26
|
-
let binaryName = "";
|
|
27
|
-
if (platform === "darwin" && arch === "arm64") {
|
|
28
|
-
binaryName = `${PACKAGE_NAME}-macos-arm64`;
|
|
29
|
-
} else if (platform === "linux" && arch === "x64") {
|
|
30
|
-
binaryName = `${PACKAGE_NAME}-linux-x64`;
|
|
31
|
-
} else if (platform === "win32" && arch === "x64") {
|
|
32
|
-
binaryName = `${PACKAGE_NAME}-windows-x64.exe`;
|
|
33
|
-
} else {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`Unsupported platform: ${platform}-${arch}. Please build from source or file an issue at https://github.com/${GITHUB_REPO}/issues`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return { binaryName, isWindows: platform === "win32" };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Download file from URL
|
|
43
|
-
function downloadFile(url, dest) {
|
|
44
|
-
return new Promise((resolve, reject) => {
|
|
45
|
-
console.log(`Downloading ${url}...`);
|
|
46
|
-
|
|
47
|
-
const file = createWriteStream(dest);
|
|
48
|
-
const request = get(url, (response) => {
|
|
49
|
-
// Handle redirects
|
|
50
|
-
if (
|
|
51
|
-
response.statusCode === 301 ||
|
|
52
|
-
response.statusCode === 302 ||
|
|
53
|
-
response.statusCode === 307
|
|
54
|
-
) {
|
|
55
|
-
file.close();
|
|
56
|
-
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (response.statusCode !== 200) {
|
|
61
|
-
file.close();
|
|
62
|
-
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
response.pipe(file);
|
|
67
|
-
|
|
68
|
-
file.on("finish", () => {
|
|
69
|
-
file.close(resolve);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
request.on("error", (err) => {
|
|
74
|
-
file.close();
|
|
75
|
-
reject(err);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
file.on("error", (err) => {
|
|
79
|
-
file.close();
|
|
80
|
-
reject(err);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function verifyBinary(binaryPath) {
|
|
86
|
-
const result = spawnSync(binaryPath, ["--version"], {
|
|
87
|
-
encoding: "utf8",
|
|
88
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (result.error) {
|
|
92
|
-
throw result.error;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (result.status !== 0) {
|
|
96
|
-
const signalDetail = result.signal ? ` (signal: ${result.signal})` : "";
|
|
97
|
-
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
98
|
-
throw new Error(
|
|
99
|
-
`Downloaded binary failed smoke test with exit code ${result.status ?? "unknown"}${signalDetail}${output ? `: ${output}` : ""}`,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const versionOutput = (result.stdout ?? "").trim();
|
|
104
|
-
if (!versionOutput) {
|
|
105
|
-
throw new Error("Downloaded binary returned success but produced no version output");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
console.log(`✓ Verified ${PACKAGE_NAME} executable (${versionOutput})`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Main installation logic
|
|
112
|
-
async function install() {
|
|
113
|
-
let binaryPath = "";
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// Skip postinstall in development (when installing from the repo)
|
|
117
|
-
// Check if we're in development by looking for src/ directory
|
|
118
|
-
const srcDir = join(__dirname, "..", "src");
|
|
119
|
-
try {
|
|
120
|
-
await access(srcDir, constants.F_OK);
|
|
121
|
-
console.log("✓ Development environment detected, skipping binary download");
|
|
122
|
-
console.log(" Run 'bun run build' to build binary locally");
|
|
123
|
-
return;
|
|
124
|
-
} catch {
|
|
125
|
-
// Not in development, continue with download
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Get version from package.json
|
|
129
|
-
const packageJsonPath = join(__dirname, "..", "package.json");
|
|
130
|
-
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
131
|
-
const { version } = packageJson;
|
|
132
|
-
|
|
133
|
-
const { binaryName, isWindows } = getPlatformInfo();
|
|
134
|
-
const binDir = join(__dirname, "..", "bin");
|
|
135
|
-
binaryPath = join(binDir, binaryName);
|
|
136
|
-
|
|
137
|
-
// Check if binary already exists
|
|
138
|
-
try {
|
|
139
|
-
await access(binaryPath, constants.F_OK);
|
|
140
|
-
console.log(`✓ Binary already exists at ${binaryPath}`);
|
|
141
|
-
return;
|
|
142
|
-
} catch {
|
|
143
|
-
// Binary doesn't exist, continue with download
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Ensure bin directory exists
|
|
147
|
-
await mkdir(binDir, { recursive: true });
|
|
148
|
-
|
|
149
|
-
// Download binary from GitHub releases
|
|
150
|
-
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${binaryName}`;
|
|
151
|
-
|
|
152
|
-
await downloadFile(downloadUrl, binaryPath);
|
|
153
|
-
|
|
154
|
-
// Make binary executable (Unix-like systems)
|
|
155
|
-
if (!isWindows) {
|
|
156
|
-
chmodSync(binaryPath, 0o755);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
verifyBinary(binaryPath);
|
|
160
|
-
|
|
161
|
-
console.log(`✓ Successfully installed ${PACKAGE_NAME} v${version}`);
|
|
162
|
-
console.log(` Binary location: ${binaryPath}`);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
if (binaryPath) {
|
|
165
|
-
await rm(binaryPath, { force: true }).catch(() => {});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
console.error(`✗ Failed to install ${PACKAGE_NAME}:`, error.message);
|
|
169
|
-
console.error(
|
|
170
|
-
`\nYou can manually download binaries from: https://github.com/${GITHUB_REPO}/releases`,
|
|
171
|
-
);
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
await install();
|