confluence-md 0.1.1

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.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ # POSIX-compliant shell stub for confluence-md
3
+ basedir=$(cd "$(dirname "$0")" && pwd)
4
+ exec "$basedir/confluence-md-binary" "$@"
package/install.js ADDED
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Postinstall script for confluence-md npm package.
3
+ * Downloads the correct platform-specific binary from GitHub Releases.
4
+ */
5
+
6
+ const {
7
+ createWriteStream,
8
+ unlinkSync,
9
+ chmodSync,
10
+ renameSync,
11
+ existsSync,
12
+ mkdirSync,
13
+ } = require("fs");
14
+ const { readFile } = require("fs/promises");
15
+ const { createHash } = require("crypto");
16
+ const { join, dirname } = require("path");
17
+ const https = require("https");
18
+
19
+ // Package version (updated by CI before publish)
20
+ const packageJson = require("./package.json");
21
+ const VERSION = packageJson.version;
22
+
23
+ // GitHub release base URL
24
+ const GITHUB_RELEASE_URL = `https://github.com/bzoboki/Confluence.md/releases/download/v${VERSION}`;
25
+
26
+ // Binary naming convention
27
+ const PLATFORM_MAP = {
28
+ darwin: {
29
+ x64: "confluence-md-macos-arm64", // x64 uses arm64 binary (Rosetta 2)
30
+ arm64: "confluence-md-macos-arm64",
31
+ },
32
+ linux: { x64: "confluence-md-linux-x64" },
33
+ win32: { x64: "confluence-md-win-x64.exe" },
34
+ };
35
+
36
+ // Retry configuration
37
+ const MAX_RETRIES = 3;
38
+ const INITIAL_RETRY_DELAY_MS = 1000;
39
+
40
+ /**
41
+ * Get the binary name for the current platform.
42
+ */
43
+ function getBinaryName() {
44
+ const platform = process.platform;
45
+ const arch = process.arch;
46
+
47
+ if (!PLATFORM_MAP[platform]) {
48
+ throw new Error(
49
+ `Unsupported platform: ${platform}. ` +
50
+ `Supported platforms: darwin (macOS), linux, win32 (Windows).`,
51
+ );
52
+ }
53
+
54
+ const archMap = PLATFORM_MAP[platform];
55
+ if (!archMap[arch]) {
56
+ const supported = Object.keys(archMap).join(", ");
57
+ throw new Error(
58
+ `Unsupported architecture: ${arch} on ${platform}. ` +
59
+ `Supported architectures for ${platform}: ${supported}.`,
60
+ );
61
+ }
62
+
63
+ return archMap[arch];
64
+ }
65
+
66
+ /**
67
+ * Download a file from URL with retry logic.
68
+ */
69
+ function downloadFile(url, destPath, retryCount = 0) {
70
+ return new Promise((resolve, reject) => {
71
+ const tempPath = destPath + ".tmp";
72
+
73
+ // Ensure directory exists
74
+ const dir = dirname(destPath);
75
+ if (!existsSync(dir)) {
76
+ mkdirSync(dir, { recursive: true });
77
+ }
78
+
79
+ const file = createWriteStream(tempPath);
80
+
81
+ const handleError = (error) => {
82
+ // Clean up temp file
83
+ file.close();
84
+ try {
85
+ unlinkSync(tempPath);
86
+ } catch {}
87
+
88
+ if (retryCount < MAX_RETRIES) {
89
+ const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
90
+ console.log(
91
+ ` Retry ${retryCount + 1}/${MAX_RETRIES} in ${delay}ms...`,
92
+ );
93
+ setTimeout(() => {
94
+ downloadFile(url, destPath, retryCount + 1)
95
+ .then(resolve)
96
+ .catch(reject);
97
+ }, delay);
98
+ } else {
99
+ reject(
100
+ new Error(
101
+ `Failed to download after ${MAX_RETRIES} retries: ${error.message}`,
102
+ ),
103
+ );
104
+ }
105
+ };
106
+
107
+ const request = https.get(url, (response) => {
108
+ // Handle redirects (GitHub releases use redirects)
109
+ if (
110
+ response.statusCode >= 300 &&
111
+ response.statusCode < 400 &&
112
+ response.headers.location
113
+ ) {
114
+ file.close();
115
+ try {
116
+ unlinkSync(tempPath);
117
+ } catch {}
118
+ return downloadFile(response.headers.location, destPath, retryCount)
119
+ .then(resolve)
120
+ .catch(reject);
121
+ }
122
+
123
+ if (response.statusCode !== 200) {
124
+ handleError(
125
+ new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`),
126
+ );
127
+ return;
128
+ }
129
+
130
+ response.pipe(file);
131
+
132
+ file.on("finish", () => {
133
+ file.close(() => {
134
+ // Rename temp to final
135
+ try {
136
+ renameSync(tempPath, destPath);
137
+ resolve();
138
+ } catch (err) {
139
+ try {
140
+ unlinkSync(tempPath);
141
+ } catch {}
142
+ reject(err);
143
+ }
144
+ });
145
+ });
146
+ });
147
+
148
+ request.on("error", handleError);
149
+ file.on("error", handleError);
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Calculate SHA256 hash of a file.
155
+ */
156
+ async function calculateSha256(filePath) {
157
+ const content = await readFile(filePath);
158
+ return createHash("sha256").update(content).digest("hex");
159
+ }
160
+
161
+ /**
162
+ * Download and parse checksums file.
163
+ */
164
+ async function downloadChecksums() {
165
+ const url = `${GITHUB_RELEASE_URL}/checksums.txt`;
166
+ const tempPath = join(__dirname, "bin", "checksums.txt.tmp");
167
+
168
+ await downloadFile(url, tempPath);
169
+
170
+ const content = await readFile(tempPath, "utf-8");
171
+ const checksums = {};
172
+
173
+ for (const line of content.split("\n")) {
174
+ const match = line.match(/^([a-f0-9]{64})\s+(.+)$/i);
175
+ if (match) {
176
+ checksums[match[2].trim()] = match[1].toLowerCase();
177
+ }
178
+ }
179
+
180
+ // Clean up
181
+ try {
182
+ unlinkSync(tempPath);
183
+ } catch {}
184
+
185
+ return checksums;
186
+ }
187
+
188
+ /**
189
+ * Main installation function.
190
+ */
191
+ async function install() {
192
+ console.log("confluence-md: Installing binary...");
193
+
194
+ // Get binary name for this platform
195
+ let binaryName;
196
+ try {
197
+ binaryName = getBinaryName();
198
+ } catch (error) {
199
+ console.error(`\nError: ${error.message}`);
200
+ console.error("\nFor manual installation, download the binary from:");
201
+ console.error(` ${GITHUB_RELEASE_URL.replace(`/v${VERSION}`, "")}`);
202
+ process.exit(1);
203
+ }
204
+
205
+ console.log(` Platform: ${process.platform}-${process.arch}`);
206
+ console.log(` Binary: ${binaryName}`);
207
+
208
+ // Determine local binary path
209
+ const isWindows = process.platform === "win32";
210
+ const localBinaryName = isWindows
211
+ ? "confluence-md-binary.exe"
212
+ : "confluence-md-binary";
213
+ const binDir = join(__dirname, "bin");
214
+ const binaryPath = join(binDir, localBinaryName);
215
+
216
+ try {
217
+ // Download checksums first
218
+ console.log(" Downloading checksums...");
219
+ const checksums = await downloadChecksums();
220
+
221
+ const expectedChecksum = checksums[binaryName];
222
+ if (!expectedChecksum) {
223
+ throw new Error(`Checksum not found for ${binaryName} in checksums.txt`);
224
+ }
225
+
226
+ // Download binary
227
+ const binaryUrl = `${GITHUB_RELEASE_URL}/${binaryName}`;
228
+ console.log(` Downloading binary from GitHub Releases...`);
229
+ await downloadFile(binaryUrl, binaryPath);
230
+
231
+ // Verify checksum
232
+ console.log(" Verifying SHA256 checksum...");
233
+ const actualChecksum = await calculateSha256(binaryPath);
234
+
235
+ if (actualChecksum !== expectedChecksum) {
236
+ unlinkSync(binaryPath);
237
+ throw new Error(
238
+ `Checksum mismatch!\n` +
239
+ ` Expected: ${expectedChecksum}\n` +
240
+ ` Actual: ${actualChecksum}\n` +
241
+ `This could indicate a corrupted download or tampering.`,
242
+ );
243
+ }
244
+
245
+ // Make executable on Unix
246
+ if (!isWindows) {
247
+ chmodSync(binaryPath, 0o755);
248
+ }
249
+
250
+ console.log(" ✓ Installation complete!");
251
+ } catch (error) {
252
+ console.error(`\nInstallation failed: ${error.message}`);
253
+ console.error("\nTroubleshooting:");
254
+ console.error(" - Check your internet connection");
255
+ console.error(
256
+ " - If behind a proxy, set HTTP_PROXY/HTTPS_PROXY environment variables",
257
+ );
258
+ console.error(" - For manual installation, download from:");
259
+ console.error(` ${GITHUB_RELEASE_URL}`);
260
+ process.exit(1);
261
+ }
262
+ }
263
+
264
+ // Run installation
265
+ install();
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "confluence-md",
3
+ "version": "0.1.1",
4
+ "description": "CLI tool to export Confluence pages to Markdown files",
5
+ "bin": {
6
+ "confluence-md": "./bin/confluence-md"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node install.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/bzoboki/Confluence.md"
14
+ },
15
+ "keywords": [
16
+ "confluence",
17
+ "markdown",
18
+ "export",
19
+ "cli",
20
+ "atlassian"
21
+ ],
22
+ "author": "Confluence.md Contributors",
23
+ "license": "MIT",
24
+ "bugs": {
25
+ "url": "https://github.com/bzoboki/Confluence.md/issues"
26
+ },
27
+ "homepage": "https://github.com/bzoboki/Confluence.md#readme",
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "os": [
32
+ "darwin",
33
+ "linux",
34
+ "win32"
35
+ ],
36
+ "cpu": [
37
+ "x64",
38
+ "arm64"
39
+ ]
40
+ }