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.
- package/bin/confluence-md +4 -0
- package/install.js +265 -0
- package/package.json +40 -0
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
|
+
}
|