@code-company/pulsetray 0.1.2

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 ADDED
@@ -0,0 +1,16 @@
1
+ # PulseTray
2
+
3
+ Este pacote instala a versão portátil do PulseTray a partir da GitHub Release
4
+ correspondente. O download é validado com SHA-256 e não executa instaladores
5
+ administrativos.
6
+
7
+ ```sh
8
+ npm install -g @code-company/pulsetray
9
+ pulsetray
10
+ ```
11
+
12
+ Use `pulsetray --help` e `pulsetray --version` sem iniciar a interface.
13
+
14
+ Variáveis opcionais para mirrors privados: `PULSETRAY_GITHUB_REPO`,
15
+ `PULSETRAY_RELEASE_TAG`, `PULSETRAY_RELEASE_BASE_URL` e
16
+ `PULSETRAY_GITHUB_TOKEN`.
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
6
+ const { spawn } = require("node:child_process");
7
+ const pkg = require("../package.json");
8
+
9
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
10
+ console.log(`PulseTray ${pkg.version}
11
+
12
+ Uso:
13
+ pulsetray Inicia o aplicativo
14
+ pulsetray --help Mostra esta ajuda
15
+ pulsetray --version Mostra a versão`);
16
+ process.exit(0);
17
+ }
18
+ if (process.argv.includes("--version") || process.argv.includes("-v")) {
19
+ console.log(`PulseTray ${pkg.version}`);
20
+ process.exit(0);
21
+ }
22
+
23
+ const executable = process.platform === "darwin"
24
+ ? path.join(__dirname, "..", "native", "PulseTray.app", "Contents", "MacOS", "PulseTray")
25
+ : path.join(__dirname, "..", "native", "PulseTray.exe");
26
+
27
+ if (process.platform !== "darwin" && process.platform !== "win32") {
28
+ console.error(`pulsetray: sistema não suportado: ${process.platform}`);
29
+ process.exit(1);
30
+ }
31
+ if (!fs.existsSync(executable)) {
32
+ console.error("pulsetray: aplicativo portátil ausente. Reinstale o pacote.");
33
+ process.exit(1);
34
+ }
35
+
36
+ const child = spawn(executable, process.argv.slice(2), {
37
+ detached: true,
38
+ stdio: "ignore"
39
+ });
40
+ child.on("error", (error) => {
41
+ console.error(`pulsetray: não foi possível iniciar: ${error.message}`);
42
+ process.exitCode = 1;
43
+ });
44
+ child.unref();
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@code-company/pulsetray",
3
+ "version": "0.1.2",
4
+ "description": "Portable PulseTray installer and launcher",
5
+ "license": "UNLICENSED",
6
+ "homepage": "https://github.com/codecompany/itransform-tray-app#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/codecompany/itransform-tray-app.git",
10
+ "directory": "npm-package"
11
+ },
12
+ "bin": {
13
+ "pulsetray": "bin/pulsetray.cjs"
14
+ },
15
+ "scripts": {
16
+ "postinstall": "node scripts/postinstall.cjs"
17
+ },
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "files": [
22
+ "bin/",
23
+ "scripts/",
24
+ "README.md"
25
+ ]
26
+ }
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const crypto = require("node:crypto");
5
+ const fs = require("node:fs");
6
+ const http = require("node:http");
7
+ const https = require("node:https");
8
+ const path = require("node:path");
9
+ const { spawnSync } = require("node:child_process");
10
+
11
+ function fail(message) {
12
+ console.error(`[pulsetray] erro: ${message}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ function target(platform, arch, version) {
17
+ if (platform === "darwin" && (arch === "x64" || arch === "arm64")) {
18
+ return `PulseTray-${version}-mac-${arch}.zip`;
19
+ }
20
+ if (platform === "win32" && arch === "x64") {
21
+ return `PulseTray-${version}-windows-x64-portable.exe`;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ function headers(url) {
27
+ const token = (
28
+ process.env.PULSETRAY_GITHUB_TOKEN ||
29
+ process.env.GITHUB_TOKEN ||
30
+ process.env.GH_TOKEN ||
31
+ ""
32
+ ).trim();
33
+ const hostname = new URL(url).hostname.toLowerCase();
34
+ const isGitHub = hostname === "github.com" || hostname.endsWith(".github.com");
35
+ return {
36
+ "User-Agent": "@code-company/pulsetray postinstall",
37
+ ...(token && isGitHub ? { Authorization: `Bearer ${token}` } : {})
38
+ };
39
+ }
40
+
41
+ function request(url, redirects = 5) {
42
+ return new Promise((resolve, reject) => {
43
+ const transport = url.startsWith("http://") ? http : https;
44
+ const req = transport.get(url, { headers: headers(url) }, (response) => {
45
+ const status = response.statusCode || 0;
46
+ if (status >= 300 && status < 400 && response.headers.location && redirects > 0) {
47
+ const next = new URL(response.headers.location, url).toString();
48
+ response.resume();
49
+ resolve(request(next, redirects - 1));
50
+ return;
51
+ }
52
+ if (status < 200 || status >= 300) {
53
+ response.resume();
54
+ reject(new Error(`HTTP ${status} para ${url}`));
55
+ return;
56
+ }
57
+ resolve(response);
58
+ });
59
+ req.on("error", reject);
60
+ });
61
+ }
62
+
63
+ async function text(url) {
64
+ const response = await request(url);
65
+ return new Promise((resolve, reject) => {
66
+ let value = "";
67
+ response.setEncoding("utf8");
68
+ response.on("data", (chunk) => { value += chunk; });
69
+ response.on("end", () => resolve(value));
70
+ response.on("error", reject);
71
+ });
72
+ }
73
+
74
+ function checksum(contents, asset) {
75
+ for (const line of contents.split(/\r?\n/)) {
76
+ const match = line.trim().match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
77
+ if (match && path.basename(match[2]) === asset) return match[1].toLowerCase();
78
+ }
79
+ return null;
80
+ }
81
+
82
+ async function download(url, destination, expected) {
83
+ const response = await request(url);
84
+ const temporary = `${destination}.download`;
85
+ const hash = crypto.createHash("sha256");
86
+ const output = fs.createWriteStream(temporary, { mode: 0o755 });
87
+ await new Promise((resolve, reject) => {
88
+ response.on("data", (chunk) => hash.update(chunk));
89
+ response.on("error", reject);
90
+ output.on("error", reject);
91
+ output.on("finish", resolve);
92
+ response.pipe(output);
93
+ });
94
+ const actual = hash.digest("hex");
95
+ if (actual !== expected) {
96
+ await fs.promises.rm(temporary, { force: true });
97
+ throw new Error(`SHA-256 inválido: esperado ${expected}, obtido ${actual}`);
98
+ }
99
+ await fs.promises.rename(temporary, destination);
100
+ }
101
+
102
+ async function install(downloaded, root) {
103
+ const next = `${root}.next-${process.pid}`;
104
+ const previous = `${root}.previous-${process.pid}`;
105
+ await fs.promises.rm(next, { recursive: true, force: true });
106
+ await fs.promises.mkdir(next, { recursive: true });
107
+ if (process.platform === "darwin") {
108
+ const result = spawnSync("/usr/bin/ditto", ["-x", "-k", downloaded, next], { stdio: "inherit" });
109
+ if (result.status !== 0) throw new Error("não foi possível extrair o pacote macOS");
110
+ } else {
111
+ await fs.promises.rename(downloaded, path.join(next, "PulseTray.exe"));
112
+ }
113
+ const executable = process.platform === "darwin"
114
+ ? path.join(next, "PulseTray.app", "Contents", "MacOS", "PulseTray")
115
+ : path.join(next, "PulseTray.exe");
116
+ if (!fs.existsSync(executable)) throw new Error("o artefato portátil não contém o executável PulseTray");
117
+ await fs.promises.rm(previous, { recursive: true, force: true });
118
+ if (fs.existsSync(root)) await fs.promises.rename(root, previous);
119
+ try {
120
+ await fs.promises.rename(next, root);
121
+ await fs.promises.rm(previous, { recursive: true, force: true });
122
+ } catch (error) {
123
+ if (fs.existsSync(previous)) await fs.promises.rename(previous, root);
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ async function main() {
129
+ const pkg = require("../package.json");
130
+ const asset = target(process.platform, process.arch, pkg.version);
131
+ if (!asset) fail(`plataforma/arquitetura não suportada: ${process.platform}/${process.arch}`);
132
+ const repo = process.env.PULSETRAY_GITHUB_REPO || "codecompany/itransform-tray-app";
133
+ const tag = process.env.PULSETRAY_RELEASE_TAG || `v${pkg.version}`;
134
+ const base = process.env.PULSETRAY_RELEASE_BASE_URL ||
135
+ `https://github.com/${repo}/releases/download/${tag}`;
136
+ const packageRoot = path.resolve(__dirname, "..");
137
+ const staging = path.join(packageRoot, `.pulsetray-${process.pid}`);
138
+ await fs.promises.mkdir(staging, { recursive: true });
139
+ const downloaded = path.join(staging, asset);
140
+ console.log(`[pulsetray] baixando ${asset}`);
141
+ try {
142
+ const sums = await text(`${base}/SHA256SUMS.txt`);
143
+ const expected = checksum(sums, asset);
144
+ if (!expected) throw new Error(`checksum ausente para ${asset}`);
145
+ await download(`${base}/${asset}`, downloaded, expected);
146
+ await install(downloaded, path.join(packageRoot, "native"));
147
+ } finally {
148
+ await fs.promises.rm(staging, { recursive: true, force: true });
149
+ }
150
+ console.log("[pulsetray] aplicativo portátil instalado e validado");
151
+ }
152
+
153
+ main().catch((error) => fail(error?.message || String(error)));