@ainame/tuzuru 0.0.20

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,34 @@
1
+ @ainame/tuzuru (npm)
2
+
3
+ NPM wrapper for the Tuzuru static blog generator.
4
+
5
+ What it does
6
+ - Installs prebuilt Tuzuru binary for your platform (macOS universal or Linux x64/aarch64) during npm install.
7
+ - Exposes a `tuzuru` executable on your PATH.
8
+ - Sets `TUZURU_RESOURCES` automatically so Tuzuru can locate its resource bundle.
9
+
10
+ Install
11
+ - Latest published: `npm i -g @ainame/tuzuru`
12
+ - Or as a dev dependency: `npm i -D @ainame/tuzuru`
13
+
14
+ Usage
15
+ - `tuzuru --help`
16
+ - `tuzuru generate`
17
+ - `tuzuru init`
18
+
19
+ Version mapping
20
+ - The npm package version should match the Tuzuru GitHub release tag (no `v` prefix). The installer fetches the asset for that tag.
21
+ - If the package version is `0.0.0`, the installer falls back to the latest release via the GitHub API.
22
+
23
+ Maintainers: how to publish
24
+ 1) Create a Tuzuru release and note the version (e.g. `1.2.3`). Ensure assets exist named:
25
+ - `tuzuru-1.2.3-macos-universal.tar.gz`
26
+ - `tuzuru-1.2.3-linux-x86_64.tar.gz`
27
+ - `tuzuru-1.2.3-linux-aarch64.tar.gz`
28
+ 2) Update `npm/package.json` version to the same `1.2.3`.
29
+ 3) From the `npm/` directory, run:
30
+ - `npm publish --access public`
31
+
32
+ CI note
33
+ - This package uses the GitHub REST API unauthenticated during installation to resolve the asset URL. On heavily parallelized CI, consider setting `GITHUB_TOKEN` in the environment to increase rate limits.
34
+
package/bin/tuzuru.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Wrapper executable for Tuzuru binary installed via npm.
4
+ * - Determines the installed vendor directory
5
+ * - Sets TUZURU_RESOURCES for bundle/resources discovery
6
+ * - Spawns the native binary with passed args
7
+ */
8
+
9
+ const { spawn } = require('node:child_process');
10
+ const { existsSync, readdirSync } = require('node:fs');
11
+ const { join } = require('node:path');
12
+
13
+ function fail(msg) {
14
+ console.error(msg);
15
+ process.exit(1);
16
+ }
17
+
18
+ const platform = process.platform; // 'darwin' | 'linux'
19
+ const arch = process.arch; // 'x64' | 'arm64'
20
+
21
+ if (!['darwin', 'linux'].includes(platform)) {
22
+ fail(`Unsupported platform: ${platform}`);
23
+ }
24
+ if (!['x64', 'arm64'].includes(arch)) {
25
+ fail(`Unsupported architecture: ${arch}`);
26
+ }
27
+
28
+ const pkgRoot = join(__dirname, '..');
29
+ const vendorDir = join(pkgRoot, 'vendor', `${platform}-${arch}`);
30
+ const binPath = join(vendorDir, 'tuzuru');
31
+
32
+ if (!existsSync(binPath)) {
33
+ fail('tuzuru binary is not installed. Try reinstalling the package.');
34
+ }
35
+
36
+ // Set resources path to the vendor directory containing the bundle
37
+ const env = { ...process.env };
38
+ // The bundle is extracted alongside the binary, so set the resources path to the vendor directory
39
+ env.TUZURU_RESOURCES = vendorDir;
40
+
41
+ const args = process.argv.slice(2);
42
+ const child = spawn(binPath, args, { stdio: 'inherit', env });
43
+ child.on('exit', (code, signal) => {
44
+ if (signal) {
45
+ process.kill(process.pid, signal);
46
+ } else {
47
+ process.exit(code ?? 0);
48
+ }
49
+ });
50
+
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@ainame/tuzuru",
3
+ "version": "0.0.20",
4
+ "description": "Tuzuru static blog generator (npm wrapper around prebuilt binaries)",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "bin": {
8
+ "tuzuru": "bin/tuzuru.js"
9
+ },
10
+ "os": [
11
+ "darwin",
12
+ "linux"
13
+ ],
14
+ "cpu": [
15
+ "x64",
16
+ "arm64"
17
+ ],
18
+ "engines": {
19
+ "node": ">=16"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/ainame/Tuzuru.git"
24
+ },
25
+ "homepage": "https://github.com/ainame/Tuzuru",
26
+ "bugs": {
27
+ "url": "https://github.com/ainame/Tuzuru/issues"
28
+ },
29
+ "scripts": {
30
+ "postinstall": "node scripts/install.js"
31
+ },
32
+ "files": [
33
+ "bin/",
34
+ "scripts/",
35
+ "vendor/",
36
+ "README.md"
37
+ ]
38
+ }
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Postinstall script to download and extract the correct Tuzuru prebuilt binary
4
+ * matching the npm package version.
5
+ *
6
+ * It resolves:
7
+ * - platform: darwin|linux
8
+ * - arch: x64|arm64
9
+ * and fetches from GitHub Releases:
10
+ * https://api.github.com/repos/ainame/Tuzuru/releases/tags/<version>
11
+ * to find the asset name:
12
+ * tuzuru-<version>-macos-universal.tar.gz
13
+ * tuzuru-<version>-linux-x86_64.tar.gz
14
+ * tuzuru-<version>-linux-aarch64.tar.gz
15
+ */
16
+
17
+ const https = require('node:https');
18
+ const { execSync } = require('node:child_process');
19
+ const { mkdirSync, existsSync, createWriteStream, rmSync } = require('node:fs');
20
+ const { join } = require('node:path');
21
+
22
+ function log(msg) {
23
+ console.log(`[tuzuru-npm] ${msg}`);
24
+ }
25
+ function fail(msg) {
26
+ console.error(`[tuzuru-npm] ${msg}`);
27
+ process.exit(1);
28
+ }
29
+
30
+ const pkg = require('../package.json');
31
+ const version = (pkg.version || '').trim();
32
+ if (!version || version === '0.0.0') {
33
+ log('Package version is 0.0.0; attempting to install latest release via GitHub API');
34
+ }
35
+
36
+ const platform = process.platform; // 'darwin' | 'linux'
37
+ const arch = process.arch; // 'x64' | 'arm64'
38
+ if (!['darwin', 'linux'].includes(platform)) fail(`Unsupported platform: ${platform}`);
39
+ if (!['x64', 'arm64'].includes(arch)) fail(`Unsupported architecture: ${arch}`);
40
+
41
+ const suffix = platform === 'darwin'
42
+ ? 'macos-universal.tar.gz'
43
+ : (arch === 'x64' ? 'linux-x86_64.tar.gz' : 'linux-aarch64.tar.gz');
44
+
45
+ const apiUrl = version && version !== '0.0.0'
46
+ ? `https://api.github.com/repos/ainame/Tuzuru/releases/tags/${encodeURIComponent(version)}`
47
+ : `https://api.github.com/repos/ainame/Tuzuru/releases/latest`;
48
+
49
+ const ua = 'tuzuru-npm-installer (+https://github.com/ainame/Tuzuru)';
50
+
51
+ function httpGetJson(url) {
52
+ return new Promise((resolve, reject) => {
53
+ https.get(url, { headers: { 'User-Agent': ua, 'Accept': 'application/vnd.github+json' } }, res => {
54
+ if (res.statusCode !== 200) {
55
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
56
+ res.resume();
57
+ return;
58
+ }
59
+ let data = '';
60
+ res.setEncoding('utf8');
61
+ res.on('data', chunk => (data += chunk));
62
+ res.on('end', () => {
63
+ try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
64
+ });
65
+ }).on('error', reject);
66
+ });
67
+ }
68
+
69
+ function httpDownload(url, destPath) {
70
+ return new Promise((resolve, reject) => {
71
+ function handleRedirect(url, maxRedirects = 5) {
72
+ if (maxRedirects === 0) {
73
+ reject(new Error('Too many redirects'));
74
+ return;
75
+ }
76
+
77
+ const file = createWriteStream(destPath);
78
+
79
+ https.get(url, { headers: { 'User-Agent': ua } }, res => {
80
+ if (res.statusCode === 301 || res.statusCode === 302) {
81
+ file.close(() => {});
82
+ const location = res.headers.location;
83
+ if (!location) {
84
+ reject(new Error(`Redirect response missing location header`));
85
+ return;
86
+ }
87
+ log(`Following redirect to ${location}`);
88
+ handleRedirect(location, maxRedirects - 1);
89
+ return;
90
+ }
91
+
92
+ if (res.statusCode !== 200) {
93
+ file.close(() => {});
94
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
95
+ res.resume();
96
+ return;
97
+ }
98
+
99
+ res.pipe(file);
100
+ file.on('finish', () => file.close(resolve));
101
+ file.on('error', err => {
102
+ file.close(() => {});
103
+ try { rmSync(destPath, { force: true }); } catch {}
104
+ reject(err);
105
+ });
106
+ }).on('error', err => {
107
+ file.close(() => {});
108
+ try { rmSync(destPath, { force: true }); } catch {}
109
+ reject(err);
110
+ });
111
+ }
112
+
113
+ handleRedirect(url);
114
+ });
115
+ }
116
+
117
+ (async () => {
118
+ try {
119
+ const json = await httpGetJson(apiUrl);
120
+ const assets = json.assets || [];
121
+ const asset = assets.find(a => typeof a.name === 'string' && a.name.endsWith(suffix));
122
+ if (!asset) {
123
+ const assetList = assets.map(a => a.name).join(', ');
124
+ fail(`No matching asset found for suffix ${suffix}. Available: ${assetList}`);
125
+ }
126
+
127
+ const downloadUrl = asset.browser_download_url;
128
+ const root = join(__dirname, '..');
129
+ const outDir = join(root, 'vendor', `${platform}-${arch}`);
130
+ const tarPath = join(outDir, 'tuzuru.tar.gz');
131
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
132
+
133
+ log(`Downloading ${downloadUrl}`);
134
+ await httpDownload(downloadUrl, tarPath);
135
+
136
+ // Extract using system tar
137
+ log('Extracting archive');
138
+ execSync(`tar -xzf "${tarPath}" -C "${outDir}"`, { stdio: 'inherit' });
139
+
140
+ // Ensure binary is executable
141
+ execSync(`chmod +x "${join(outDir, 'tuzuru')}"`);
142
+
143
+ // Cleanup
144
+ rmSync(tarPath, { force: true });
145
+ log('Installation completed');
146
+ } catch (err) {
147
+ fail(`Install failed: ${err.message}`);
148
+ }
149
+ })();
150
+