@trackseries/cli 0.1.0-preview.1 → 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/README.md +3 -3
- package/bin/trackseries.js +10 -8
- package/package.json +2 -3
- package/scripts/install.js +79 -60
- package/scripts/platform.js +18 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Before the first stable release, install the current preview with:
|
|
|
17
17
|
npm install --global @trackseries/cli@next
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
The
|
|
20
|
+
The first `trackseries` invocation downloads and checksum-verifies the matching release artifact for the current operating system and architecture. Later invocations reuse the versioned executable from the platform-specific TrackSeries data directory. npm installation does not execute lifecycle scripts.
|
|
21
21
|
|
|
22
22
|
Supported targets:
|
|
23
23
|
|
|
@@ -34,10 +34,10 @@ After installation, authenticate and inspect the available commands:
|
|
|
34
34
|
```bash
|
|
35
35
|
trackseries auth login
|
|
36
36
|
trackseries auth status
|
|
37
|
-
trackseries skill
|
|
37
|
+
trackseries --skill
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
`trackseries skill` prints version-matched instructions designed for coding agents and other automated tools.
|
|
40
|
+
`trackseries --skill` prints version-matched instructions designed for coding agents and other automated tools.
|
|
41
41
|
|
|
42
42
|
## Updates
|
|
43
43
|
|
package/bin/trackseries.js
CHANGED
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { ensureBinary } from "../scripts/install.js";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.error("The TrackSeries CLI executable is missing. Reinstall @trackseries/cli.");
|
|
7
|
+
let executablePath;
|
|
8
|
+
try {
|
|
9
|
+
executablePath = await ensureBinary();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
14
12
|
process.exit(1);
|
|
15
13
|
}
|
|
16
14
|
|
|
@@ -19,6 +17,10 @@ const result = spawnSync(executablePath, process.argv.slice(2), {
|
|
|
19
17
|
});
|
|
20
18
|
|
|
21
19
|
if (result.error) {
|
|
20
|
+
if (process.platform === "linux" && result.error.code === "ENOENT" && existsSync(executablePath)) {
|
|
21
|
+
console.error("Unable to start the TrackSeries CLI. NixOS and other non-FHS distributions may require nix-ld.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
22
24
|
console.error(`Unable to start the TrackSeries CLI: ${result.error.message}`);
|
|
23
25
|
process.exit(1);
|
|
24
26
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackseries/cli",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Install and run the TrackSeries CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"trackseries": "
|
|
7
|
+
"trackseries": "bin/trackseries.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/trackseries.js",
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"scripts/platform.js"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"postinstall": "node scripts/install.js",
|
|
16
15
|
"test": "node --test"
|
|
17
16
|
},
|
|
18
17
|
"engines": {
|
package/scripts/install.js
CHANGED
|
@@ -5,22 +5,12 @@ import { dirname, resolve } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { unzipSync } from "fflate";
|
|
7
7
|
import { x as extractTar } from "tar";
|
|
8
|
-
import { assertSupportedRuntime, releaseArtifact } from "./platform.js";
|
|
8
|
+
import { assertSupportedRuntime, binaryDirectory, releaseArtifact } from "./platform.js";
|
|
9
9
|
|
|
10
10
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
11
|
const metadata = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const releaseBaseUrl =
|
|
15
|
-
process.env.TRACKSERIES_CLI_RELEASE_BASE_URL ?? `https://github.com/TrackSeries/cli/releases/download/cli-v${metadata.version}`;
|
|
16
|
-
const temporaryDirectory = await mkdtemp(resolve(tmpdir(), "trackseries-cli-"));
|
|
17
|
-
const archivePath = resolve(temporaryDirectory, artifact);
|
|
18
|
-
const extractionPath = resolve(temporaryDirectory, "extracted");
|
|
19
|
-
const executableName = process.platform === "win32" ? "trackseries.exe" : "trackseries";
|
|
20
|
-
const installedExecutable = resolve(packageRoot, "vendor", executableName);
|
|
21
|
-
const pendingExecutable = `${installedExecutable}.${process.pid}.tmp`;
|
|
22
|
-
|
|
23
|
-
async function download(url) {
|
|
12
|
+
|
|
13
|
+
async function download(url, timeout) {
|
|
24
14
|
let lastError;
|
|
25
15
|
|
|
26
16
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
@@ -30,7 +20,7 @@ async function download(url) {
|
|
|
30
20
|
headers: {
|
|
31
21
|
"User-Agent": `@trackseries/cli/${metadata.version}`
|
|
32
22
|
},
|
|
33
|
-
signal: AbortSignal.timeout(
|
|
23
|
+
signal: AbortSignal.timeout(timeout)
|
|
34
24
|
});
|
|
35
25
|
} catch (error) {
|
|
36
26
|
lastError = error;
|
|
@@ -53,58 +43,87 @@ async function download(url) {
|
|
|
53
43
|
throw new Error(`Unable to download ${url}: ${lastError instanceof Error ? lastError.message : String(lastError)}.`);
|
|
54
44
|
}
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const actualChecksum = createHash("sha256").update(archive).digest("hex");
|
|
63
|
-
|
|
64
|
-
if (!expectedChecksum || actualChecksum !== expectedChecksum) {
|
|
65
|
-
throw new Error(`Checksum verification failed for ${artifact}.`);
|
|
46
|
+
async function isRegularFile(path) {
|
|
47
|
+
try {
|
|
48
|
+
return (await lstat(path)).isFile();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return false;
|
|
51
|
+
throw error;
|
|
66
52
|
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function ensureBinary() {
|
|
56
|
+
assertSupportedRuntime();
|
|
57
|
+
const artifact = releaseArtifact(metadata.version);
|
|
58
|
+
const releaseBaseUrl =
|
|
59
|
+
process.env.TRACKSERIES_CLI_RELEASE_BASE_URL ?? `https://github.com/TrackSeries/cli/releases/download/cli-v${metadata.version}`;
|
|
60
|
+
const executableName = process.platform === "win32" ? "trackseries.exe" : "trackseries";
|
|
61
|
+
const installedExecutable = resolve(binaryDirectory(metadata.version), executableName);
|
|
62
|
+
|
|
63
|
+
if (await isRegularFile(installedExecutable)) return installedExecutable;
|
|
67
64
|
|
|
68
|
-
await
|
|
69
|
-
|
|
65
|
+
const temporaryDirectory = await mkdtemp(resolve(tmpdir(), "trackseries-cli-"));
|
|
66
|
+
const archivePath = resolve(temporaryDirectory, artifact);
|
|
67
|
+
const extractionPath = resolve(temporaryDirectory, "extracted");
|
|
68
|
+
const pendingExecutable = `${installedExecutable}.${process.pid}.tmp`;
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
console.error(`Downloading TrackSeries CLI ${metadata.version} for ${process.platform}-${process.arch}...`);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const [archive, checksumFile] = await Promise.all([
|
|
74
|
+
download(`${releaseBaseUrl}/${artifact}`, 300_000),
|
|
75
|
+
download(`${releaseBaseUrl}/${artifact}.sha256`, 60_000)
|
|
76
|
+
]);
|
|
77
|
+
const expectedChecksum = checksumFile.toString("utf8").trim().split(/\s+/)[0]?.toLowerCase();
|
|
78
|
+
const actualChecksum = createHash("sha256").update(archive).digest("hex");
|
|
79
|
+
|
|
80
|
+
if (!expectedChecksum || actualChecksum !== expectedChecksum) {
|
|
81
|
+
throw new Error(`Checksum verification failed for ${artifact}.`);
|
|
77
82
|
}
|
|
78
|
-
await writeFile(resolve(extractionPath, executableName), executable);
|
|
79
|
-
} else {
|
|
80
|
-
await extractTar({
|
|
81
|
-
cwd: extractionPath,
|
|
82
|
-
file: archivePath,
|
|
83
|
-
filter: (path, entry) => (path === executableName || path === `./${executableName}`) && entry.type === "File"
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
throw new Error(`${artifact} does not contain a regular ${executableName} file.`);
|
|
90
|
-
}
|
|
84
|
+
await mkdir(extractionPath, { recursive: true });
|
|
85
|
+
await writeFile(archivePath, archive);
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
if (process.platform === "win32") {
|
|
88
|
+
const executable = unzipSync(archive, {
|
|
89
|
+
filter: (file) => file.name === executableName
|
|
90
|
+
})[executableName];
|
|
91
|
+
if (!executable) {
|
|
92
|
+
throw new Error(`${artifact} does not contain ${executableName}.`);
|
|
93
|
+
}
|
|
94
|
+
await writeFile(resolve(extractionPath, executableName), executable);
|
|
95
|
+
} else {
|
|
96
|
+
await extractTar({
|
|
97
|
+
cwd: extractionPath,
|
|
98
|
+
file: archivePath,
|
|
99
|
+
filter: (path, entry) => (path === executableName || path === `./${executableName}`) && entry.type === "File"
|
|
100
|
+
});
|
|
101
|
+
}
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
await
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
103
|
+
const extractedExecutable = resolve(extractionPath, executableName);
|
|
104
|
+
if (!(await lstat(extractedExecutable)).isFile()) {
|
|
105
|
+
throw new Error(`${artifact} does not contain a regular ${executableName} file.`);
|
|
106
|
+
}
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
await mkdir(dirname(installedExecutable), { recursive: true });
|
|
109
|
+
await copyFile(extractedExecutable, pendingExecutable);
|
|
110
|
+
|
|
111
|
+
if (process.platform !== "win32") {
|
|
112
|
+
await chmod(pendingExecutable, 0o755);
|
|
113
|
+
} else {
|
|
114
|
+
await rm(installedExecutable, { force: true });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await rename(pendingExecutable, installedExecutable);
|
|
118
|
+
return installedExecutable;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Unable to install TrackSeries CLI ${metadata.version}: ${reason} Download ${artifact} manually from ${releaseBaseUrl}.`,
|
|
123
|
+
{ cause: error }
|
|
124
|
+
);
|
|
125
|
+
} finally {
|
|
126
|
+
await rm(pendingExecutable, { force: true });
|
|
127
|
+
await rm(temporaryDirectory, { force: true, recursive: true });
|
|
128
|
+
}
|
|
110
129
|
}
|
package/scripts/platform.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { posix, win32 } from "node:path";
|
|
3
|
+
|
|
1
4
|
export function releaseArtifact(version, platform = process.platform, architecture = process.arch) {
|
|
2
5
|
const platformNames = {
|
|
3
6
|
darwin: "macos",
|
|
@@ -20,3 +23,18 @@ export function assertSupportedRuntime(platform = process.platform, report = pro
|
|
|
20
23
|
throw new Error("TrackSeries CLI requires a glibc-based Linux distribution. Alpine and other musl-based systems are not yet supported.");
|
|
21
24
|
}
|
|
22
25
|
}
|
|
26
|
+
|
|
27
|
+
export function binaryDirectory(version, platform = process.platform, environment = process.env, home = homedir()) {
|
|
28
|
+
const path = platform === "win32" ? win32 : posix;
|
|
29
|
+
let dataDirectory;
|
|
30
|
+
|
|
31
|
+
if (platform === "win32") {
|
|
32
|
+
dataDirectory = environment.LOCALAPPDATA ?? environment.APPDATA ?? path.join(home, "AppData", "Local");
|
|
33
|
+
} else if (platform === "darwin") {
|
|
34
|
+
dataDirectory = path.join(home, "Library", "Application Support");
|
|
35
|
+
} else {
|
|
36
|
+
dataDirectory = environment.XDG_DATA_HOME ?? path.join(home, ".local", "share");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return path.join(dataDirectory, "trackseries", "bin", version);
|
|
40
|
+
}
|