@ulinkly/cli 1.2.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 +41 -0
- package/bin/ulink.js +130 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @ulinkly/cli
|
|
2
|
+
|
|
3
|
+
npm distribution of the **ULink CLI** — verify universal links & app links configuration for [ULink](https://ulink.ly) projects.
|
|
4
|
+
|
|
5
|
+
The CLI is a native binary (compiled from Dart). This package is a thin launcher: on first run it downloads the binary matching your platform from the [GitHub Releases](https://github.com/FlywheelStudio/ulink_cli/releases), verifies its checksum, caches it under `~/.ulink/npm/`, and runs it.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Zero-install (recommended for one-off checks / CI):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @ulinkly/cli --help
|
|
13
|
+
npx @ulinkly/cli verify
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or install globally:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @ulinkly/cli
|
|
20
|
+
ulink --version
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Supported platforms
|
|
24
|
+
|
|
25
|
+
| OS | Arch |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| macOS | arm64, x64 |
|
|
28
|
+
| Linux | x64 |
|
|
29
|
+
| Windows | x64 |
|
|
30
|
+
|
|
31
|
+
On other platforms, use the [install script or manual binaries](https://github.com/FlywheelStudio/ulink_cli/blob/main/INSTALL.md).
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- The npm version maps 1:1 to a CLI release tag (e.g. `1.2.0` → `v1.2.0`).
|
|
36
|
+
- The binary is cached per-version; upgrading the package re-downloads on next run.
|
|
37
|
+
- Set `ULINK_CLI_VERSION` (e.g. `v1.1.3`) to override the downloaded release.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/bin/ulink.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Thin npm launcher for the ULink CLI.
|
|
5
|
+
//
|
|
6
|
+
// The CLI itself is a Dart program compiled to native binaries and published
|
|
7
|
+
// as GitHub Release assets on FlywheelStudio/ulink_cli. This package does not
|
|
8
|
+
// bundle those binaries; on first run it downloads the one matching the host
|
|
9
|
+
// platform + this package's version, verifies its checksum, caches it under
|
|
10
|
+
// ~/.ulink/npm/<version>/, then execs it transparently with the user's args.
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const https = require('https');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
const { spawnSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
const pkg = require('../package.json');
|
|
20
|
+
|
|
21
|
+
const REPO = 'FlywheelStudio/ulink_cli';
|
|
22
|
+
// The npm version maps 1:1 to a release tag (v1.2.0). Override for testing.
|
|
23
|
+
const VERSION = process.env.ULINK_CLI_VERSION || `v${pkg.version}`;
|
|
24
|
+
|
|
25
|
+
// Map host platform/arch to the release asset name produced by release.yml.
|
|
26
|
+
function assetName() {
|
|
27
|
+
const platform = os.platform();
|
|
28
|
+
const arch = os.arch();
|
|
29
|
+
const table = {
|
|
30
|
+
'darwin:arm64': 'ulink-macos-arm64',
|
|
31
|
+
'darwin:x64': 'ulink-macos-x64',
|
|
32
|
+
'linux:x64': 'ulink-linux-x64',
|
|
33
|
+
'win32:x64': 'ulink-windows-x64.exe',
|
|
34
|
+
};
|
|
35
|
+
const asset = table[`${platform}:${arch}`];
|
|
36
|
+
if (!asset) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Unsupported platform: ${platform}/${arch}.\n` +
|
|
39
|
+
`Supported: macOS (arm64/x64), Linux (x64), Windows (x64).\n` +
|
|
40
|
+
`See ${pkg.homepage} for manual installation.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return asset;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const isWindows = os.platform() === 'win32';
|
|
47
|
+
const binDir = path.join(os.homedir(), '.ulink', 'npm', VERSION);
|
|
48
|
+
const binPath = path.join(binDir, isWindows ? 'ulink.exe' : 'ulink');
|
|
49
|
+
|
|
50
|
+
// GET with redirect-following (GitHub release URLs 302 to a CDN host).
|
|
51
|
+
function download(url, redirects = 0) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
if (redirects > 5) return reject(new Error('Too many redirects'));
|
|
54
|
+
https
|
|
55
|
+
.get(url, { headers: { 'User-Agent': '@ulinkly/cli' } }, (res) => {
|
|
56
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
57
|
+
res.resume();
|
|
58
|
+
return resolve(download(res.headers.location, redirects + 1));
|
|
59
|
+
}
|
|
60
|
+
if (res.statusCode !== 200) {
|
|
61
|
+
res.resume();
|
|
62
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
63
|
+
}
|
|
64
|
+
const chunks = [];
|
|
65
|
+
res.on('data', (c) => chunks.push(c));
|
|
66
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
67
|
+
res.on('error', reject);
|
|
68
|
+
})
|
|
69
|
+
.on('error', reject);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sha256(buf) {
|
|
74
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// checksums.txt lines look like: "<hex> ulink-macos-arm64"
|
|
78
|
+
function expectedChecksum(text, asset) {
|
|
79
|
+
for (const line of text.split('\n')) {
|
|
80
|
+
const m = line.trim().match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
|
|
81
|
+
if (m && m[2].trim() === asset) return m[1].toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function ensureBinary() {
|
|
87
|
+
if (fs.existsSync(binPath)) return binPath;
|
|
88
|
+
|
|
89
|
+
const asset = assetName();
|
|
90
|
+
const base = `https://github.com/${REPO}/releases/download/${VERSION}`;
|
|
91
|
+
process.stderr.write(`Fetching ULink CLI ${VERSION} (${asset})…\n`);
|
|
92
|
+
|
|
93
|
+
const bin = await download(`${base}/${asset}`);
|
|
94
|
+
|
|
95
|
+
// Verify against the published checksums when available; fail closed on a
|
|
96
|
+
// genuine mismatch, but tolerate a missing/unreachable checksums.txt.
|
|
97
|
+
try {
|
|
98
|
+
const sums = await download(`${base}/checksums.txt`);
|
|
99
|
+
const expected = expectedChecksum(sums.toString('utf8'), asset);
|
|
100
|
+
if (expected) {
|
|
101
|
+
const actual = sha256(bin);
|
|
102
|
+
if (actual !== expected) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Checksum mismatch for ${asset}\n expected ${expected}\n got ${actual}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (/Checksum mismatch/.test(err.message)) throw err;
|
|
110
|
+
process.stderr.write(`Warning: skipping checksum verification (${err.message})\n`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
114
|
+
const tmp = `${binPath}.${process.pid}.tmp`;
|
|
115
|
+
fs.writeFileSync(tmp, bin);
|
|
116
|
+
fs.chmodSync(tmp, 0o755);
|
|
117
|
+
fs.renameSync(tmp, binPath); // atomic publish into the cache
|
|
118
|
+
return binPath;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
ensureBinary()
|
|
122
|
+
.then((bin) => {
|
|
123
|
+
const res = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' });
|
|
124
|
+
if (res.error) throw res.error;
|
|
125
|
+
process.exit(res.status === null ? 1 : res.status);
|
|
126
|
+
})
|
|
127
|
+
.catch((err) => {
|
|
128
|
+
process.stderr.write(`ulink: ${err.message}\n`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ulinkly/cli",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "ULink CLI — verify universal links & app links configuration for ULink projects",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ulink": "./bin/ulink.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/ulink.js",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://ulink.ly",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/FlywheelStudio/ulink_cli",
|
|
16
|
+
"directory": "npm"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"os": [
|
|
23
|
+
"darwin",
|
|
24
|
+
"linux",
|
|
25
|
+
"win32"
|
|
26
|
+
],
|
|
27
|
+
"cpu": [
|
|
28
|
+
"x64",
|
|
29
|
+
"arm64"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"ulink",
|
|
33
|
+
"ulinkly",
|
|
34
|
+
"deeplink",
|
|
35
|
+
"deep-linking",
|
|
36
|
+
"universal-links",
|
|
37
|
+
"app-links",
|
|
38
|
+
"cli"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|