@sinch/cli 0.4.7 → 0.4.8
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/platform-resolver.js +61 -0
- package/bin/sinch +40 -17
- package/package.json +3 -3
- package/scripts/postinstall.js +104 -123
- package/bin/credman.exe +0 -0
- package/bin/s +0 -4
- package/bin/sinch.cmd +0 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const PLATFORM_BINARIES = {
|
|
5
|
+
'darwin-arm64': { artifactName: 'sinch-darwin-arm64' },
|
|
6
|
+
'darwin-x64': { artifactName: 'sinch-darwin-x64' },
|
|
7
|
+
'linux-arm64': { artifactName: 'sinch-linux-arm64' },
|
|
8
|
+
'linux-x64': { artifactName: 'sinch-linux-x64' },
|
|
9
|
+
'win32-arm64': { artifactName: 'sinch-windows-x64.exe' },
|
|
10
|
+
'win32-x64': { artifactName: 'sinch-windows-x64.exe' },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function getPlatformKey(platform = process.platform, arch = process.arch) {
|
|
14
|
+
return `${platform}-${arch}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getPlatformBinaryInfo(platform = process.platform, arch = process.arch) {
|
|
18
|
+
return PLATFORM_BINARIES[getPlatformKey(platform, arch)] ?? null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildMissingBinaryMessage(platform, arch, binaryPath) {
|
|
22
|
+
const binaryHint = binaryPath
|
|
23
|
+
? `Expected binary at ${binaryPath}.`
|
|
24
|
+
: 'No binary mapping is defined for this platform.';
|
|
25
|
+
|
|
26
|
+
return [
|
|
27
|
+
`Sinch CLI does not have an installed binary for ${platform}-${arch}.`,
|
|
28
|
+
binaryHint,
|
|
29
|
+
'Common causes: unsupported platform, install scripts were disabled, or the binary download failed.',
|
|
30
|
+
'Try reinstalling with: npm install -g @sinch/cli',
|
|
31
|
+
].join(' ');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveInstalledBinaryPath(
|
|
35
|
+
packageRoot = path.join(__dirname, '..'),
|
|
36
|
+
platform = process.platform,
|
|
37
|
+
arch = process.arch
|
|
38
|
+
) {
|
|
39
|
+
const binaryInfo = getPlatformBinaryInfo(platform, arch);
|
|
40
|
+
if (!binaryInfo) {
|
|
41
|
+
throw new Error(buildMissingBinaryMessage(platform, arch, null));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const binaryPath = path.join(packageRoot, 'dist', binaryInfo.artifactName);
|
|
45
|
+
if (!fs.existsSync(binaryPath)) {
|
|
46
|
+
throw new Error(buildMissingBinaryMessage(platform, arch, binaryPath));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
binaryPath,
|
|
51
|
+
artifactName: binaryInfo.artifactName,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
PLATFORM_BINARIES,
|
|
57
|
+
getPlatformKey,
|
|
58
|
+
getPlatformBinaryInfo,
|
|
59
|
+
buildMissingBinaryMessage,
|
|
60
|
+
resolveInstalledBinaryPath,
|
|
61
|
+
};
|
package/bin/sinch
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
#!/bin/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const { resolveInstalledBinaryPath } = require('./platform-resolver');
|
|
5
|
+
|
|
6
|
+
function main(argv = process.argv.slice(2)) {
|
|
7
|
+
let resolved;
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
resolved = resolveInstalledBinaryPath();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(error.message);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const child = spawn(resolved.binaryPath, argv, {
|
|
17
|
+
stdio: 'inherit',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
child.on('error', (error) => {
|
|
21
|
+
console.error(`Failed to launch ${resolved.artifactName}: ${error.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
child.on('exit', (code, signal) => {
|
|
26
|
+
if (signal) {
|
|
27
|
+
process.kill(process.pid, signal);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
process.exit(code ?? 0);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (require.main === module) {
|
|
35
|
+
main();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
main,
|
|
40
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sinch/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Official Sinch CLI - Manage all Sinch products from your terminal",
|
|
5
|
+
"main": "bin/sinch",
|
|
5
6
|
"bin": {
|
|
6
|
-
"sinch": "bin/sinch"
|
|
7
|
-
"s": "bin/s"
|
|
7
|
+
"sinch": "bin/sinch"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
package/scripts/postinstall.js
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* postinstall.js — runs on `npm install -g @sinch/cli`
|
|
5
5
|
*
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
8
|
-
*
|
|
9
|
-
* Silent failure on ALL errors — must never break `npm install`.
|
|
6
|
+
* 1. Downloads the platform binary from GitLab Generic Packages
|
|
7
|
+
* 2. Writes ~/.sinch/completions.json (command tree for PowerShell)
|
|
8
|
+
* 3. Installs shell completion into the user's profile (PowerShell, zsh, or bash)
|
|
10
9
|
*
|
|
11
10
|
* Keep COMPLETION_COMMANDS in sync with src/index.ts.
|
|
12
11
|
*/
|
|
@@ -15,13 +14,12 @@ const fs = require('fs');
|
|
|
15
14
|
const path = require('path');
|
|
16
15
|
const os = require('os');
|
|
17
16
|
const https = require('https');
|
|
18
|
-
const { spawn } = require('child_process');
|
|
17
|
+
const { execSync, spawn } = require('child_process');
|
|
18
|
+
const { getPlatformBinaryInfo } = require('../bin/platform-resolver');
|
|
19
19
|
|
|
20
20
|
const SINCH_DIR = path.join(os.homedir(), '.sinch');
|
|
21
21
|
const PROJECT_ID = '72974711';
|
|
22
22
|
|
|
23
|
-
// Keep in sync with COMPLETION_COMMANDS in src/index.ts.
|
|
24
|
-
// The postAction hook overwrites completions.json on every CLI run, so drift self-heals.
|
|
25
23
|
const COMPLETION_COMMANDS = {
|
|
26
24
|
functions: ['init', 'list', 'deploy', 'download', 'dev', 'status', 'logs', 'delete', 'docs'],
|
|
27
25
|
templates: ['list', 'show', 'node', 'csharp', 'python'],
|
|
@@ -90,20 +88,41 @@ Register-ArgumentCompleter -Native -CommandName sinch -ScriptBlock {
|
|
|
90
88
|
${SENTINEL_END}`;
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
// --- Profile installation helpers ---
|
|
94
|
-
|
|
95
91
|
function shellEscapePath(p) {
|
|
96
92
|
return "'" + p.replace(/'/g, "'\\''") + "'";
|
|
97
93
|
}
|
|
98
94
|
|
|
95
|
+
function stripManagedBlocks(content) {
|
|
96
|
+
let nextContent = content;
|
|
97
|
+
let startIndex = nextContent.indexOf(SENTINEL_START);
|
|
98
|
+
|
|
99
|
+
while (startIndex !== -1) {
|
|
100
|
+
const lineStart = nextContent.lastIndexOf('\n', startIndex - 1) + 1;
|
|
101
|
+
const endIndex = nextContent.indexOf(SENTINEL_END, startIndex);
|
|
102
|
+
let removalEnd = endIndex === -1 ? nextContent.length : endIndex + SENTINEL_END.length;
|
|
103
|
+
|
|
104
|
+
if (nextContent.slice(removalEnd, removalEnd + 2) === '\r\n') {
|
|
105
|
+
removalEnd += 2;
|
|
106
|
+
} else if (nextContent[removalEnd] === '\n') {
|
|
107
|
+
removalEnd += 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
nextContent = nextContent.slice(0, lineStart) + nextContent.slice(removalEnd);
|
|
111
|
+
startIndex = nextContent.indexOf(SENTINEL_START);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return nextContent.replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
115
|
+
}
|
|
116
|
+
|
|
99
117
|
function upsertBlock(content, block) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
const normalizedBlock = block.trim();
|
|
119
|
+
const strippedContent = stripManagedBlocks(content);
|
|
120
|
+
|
|
121
|
+
if (!strippedContent) {
|
|
122
|
+
return normalizedBlock + '\n';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return `${strippedContent}\n\n${normalizedBlock}\n`;
|
|
107
126
|
}
|
|
108
127
|
|
|
109
128
|
function tryShell(cmd) {
|
|
@@ -125,7 +144,6 @@ async function installPowerShellCompletion() {
|
|
|
125
144
|
const profilePath = (await tryShell('pwsh')) ?? (await tryShell('powershell'));
|
|
126
145
|
if (!profilePath) return;
|
|
127
146
|
|
|
128
|
-
// Validate profile path is within user's home directory
|
|
129
147
|
const resolved = path.resolve(profilePath);
|
|
130
148
|
if (!resolved.startsWith(os.homedir())) return;
|
|
131
149
|
|
|
@@ -134,9 +152,7 @@ async function installPowerShellCompletion() {
|
|
|
134
152
|
let content = '';
|
|
135
153
|
try {
|
|
136
154
|
content = fs.readFileSync(resolved, 'utf8');
|
|
137
|
-
} catch {
|
|
138
|
-
// Profile doesn't exist yet — will be created
|
|
139
|
-
}
|
|
155
|
+
} catch {}
|
|
140
156
|
|
|
141
157
|
const sourceLine = `. ${shellEscapePath(completionFile.replace(/\\/g, '\\\\'))}`;
|
|
142
158
|
const block = `${SENTINEL_START}\n${sourceLine}\n${SENTINEL_END}`;
|
|
@@ -166,57 +182,67 @@ function installBashZshCompletion() {
|
|
|
166
182
|
let content = '';
|
|
167
183
|
try {
|
|
168
184
|
content = fs.readFileSync(rcFile, 'utf8');
|
|
169
|
-
} catch {
|
|
170
|
-
// File doesn't exist yet — will be created
|
|
171
|
-
}
|
|
185
|
+
} catch {}
|
|
172
186
|
fs.writeFileSync(rcFile, upsertBlock(content, block));
|
|
173
187
|
}
|
|
174
188
|
}
|
|
175
189
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const PLATFORM_MAP = {
|
|
179
|
-
'linux-x64': 'sinch-linux-x64',
|
|
180
|
-
'linux-arm64': 'sinch-linux-arm64',
|
|
181
|
-
'darwin-x64': 'sinch-darwin-x64',
|
|
182
|
-
'darwin-arm64': 'sinch-darwin-arm64',
|
|
183
|
-
'win32-x64': 'sinch-windows-x64.exe',
|
|
184
|
-
'win32-arm64': 'sinch-windows-x64.exe',
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
function getTokenFromNpmrc() {
|
|
188
|
-
// Read GitLab token from global .npmrc (same token used for registry auth)
|
|
190
|
+
function getTokenFromNpmConfig() {
|
|
189
191
|
try {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
);
|
|
196
|
-
|
|
192
|
+
const configPaths = [
|
|
193
|
+
process.env.npm_config_userconfig,
|
|
194
|
+
process.env.NPM_CONFIG_USERCONFIG,
|
|
195
|
+
execSync('npm config get userconfig', { encoding: 'utf8' }).trim(),
|
|
196
|
+
execSync('npm config get globalconfig', { encoding: 'utf8' }).trim(),
|
|
197
|
+
].filter(Boolean);
|
|
198
|
+
|
|
199
|
+
for (const configPath of configPaths) {
|
|
200
|
+
if (!fs.existsSync(configPath)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
205
|
+
const match = content.match(
|
|
206
|
+
new RegExp(`//gitlab\\.com/api/v4/projects/${PROJECT_ID}/packages/npm/:_authToken=(.+)`)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (match?.[1]?.trim()) {
|
|
210
|
+
return match[1].trim();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
197
213
|
} catch {
|
|
198
214
|
return '';
|
|
199
215
|
}
|
|
216
|
+
|
|
217
|
+
return '';
|
|
200
218
|
}
|
|
201
219
|
|
|
202
220
|
function downloadFile(url, dest, token) {
|
|
203
221
|
return new Promise((resolve, reject) => {
|
|
204
222
|
const headers = {};
|
|
205
|
-
if (token)
|
|
223
|
+
if (token) {
|
|
224
|
+
headers['PRIVATE-TOKEN'] = token;
|
|
225
|
+
}
|
|
206
226
|
|
|
207
|
-
const get = (
|
|
227
|
+
const get = (requestUrl) => {
|
|
208
228
|
https
|
|
209
|
-
.get(
|
|
210
|
-
if (
|
|
211
|
-
|
|
229
|
+
.get(requestUrl, { headers }, (response) => {
|
|
230
|
+
if (
|
|
231
|
+
response.statusCode >= 300 &&
|
|
232
|
+
response.statusCode < 400 &&
|
|
233
|
+
response.headers.location
|
|
234
|
+
) {
|
|
235
|
+
get(response.headers.location);
|
|
212
236
|
return;
|
|
213
237
|
}
|
|
214
|
-
|
|
215
|
-
|
|
238
|
+
|
|
239
|
+
if (response.statusCode !== 200) {
|
|
240
|
+
reject(new Error(`Download failed: ${response.statusCode} from ${requestUrl}`));
|
|
216
241
|
return;
|
|
217
242
|
}
|
|
243
|
+
|
|
218
244
|
const file = fs.createWriteStream(dest);
|
|
219
|
-
|
|
245
|
+
response.pipe(file);
|
|
220
246
|
file.on('finish', () => {
|
|
221
247
|
file.close();
|
|
222
248
|
resolve();
|
|
@@ -225,89 +251,46 @@ function downloadFile(url, dest, token) {
|
|
|
225
251
|
})
|
|
226
252
|
.on('error', reject);
|
|
227
253
|
};
|
|
254
|
+
|
|
228
255
|
get(url);
|
|
229
256
|
});
|
|
230
257
|
}
|
|
231
258
|
|
|
232
259
|
async function downloadBinary(version) {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
260
|
+
const binaryInfo = getPlatformBinaryInfo();
|
|
261
|
+
if (!binaryInfo) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
236
264
|
|
|
237
265
|
const pkgRoot = path.join(__dirname, '..');
|
|
238
266
|
const distDir = path.join(pkgRoot, 'dist');
|
|
239
|
-
const dest = path.join(distDir,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
267
|
+
const dest = path.join(distDir, binaryInfo.artifactName);
|
|
268
|
+
if (fs.existsSync(dest)) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
243
271
|
|
|
244
272
|
fs.mkdirSync(distDir, { recursive: true });
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
273
|
+
const token =
|
|
274
|
+
process.env.SINCH_GITLAB_TOKEN || process.env.GITLAB_TOKEN || getTokenFromNpmConfig();
|
|
275
|
+
const url = `https://gitlab.com/api/v4/projects/${PROJECT_ID}/packages/generic/sinch-cli/${version}/${binaryInfo.artifactName}`;
|
|
248
276
|
|
|
249
277
|
try {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// Make executable on Unix
|
|
254
|
-
if (process.platform !== 'win32') {
|
|
278
|
+
await downloadFile(url, dest, token);
|
|
279
|
+
if (!dest.endsWith('.exe')) {
|
|
255
280
|
fs.chmodSync(dest, 0o755);
|
|
256
281
|
}
|
|
257
|
-
|
|
258
|
-
} catch (err) {
|
|
259
|
-
// Clean up partial download
|
|
282
|
+
} catch (error) {
|
|
260
283
|
try {
|
|
261
|
-
fs.
|
|
284
|
+
fs.rmSync(dest, { force: true });
|
|
262
285
|
} catch {}
|
|
263
|
-
console.error(`Failed to download Sinch CLI binary: ${
|
|
286
|
+
console.error(`Failed to download Sinch CLI binary: ${error.message}`);
|
|
264
287
|
console.error(
|
|
265
|
-
|
|
288
|
+
'You can download the binary manually from GitLab release assets or the generic package registry.'
|
|
266
289
|
);
|
|
267
290
|
}
|
|
268
291
|
}
|
|
269
292
|
|
|
270
|
-
/**
|
|
271
|
-
* On Windows, npm generates .cmd and .ps1 shims that try to run bin/sinch via
|
|
272
|
-
* /bin/sh (broken on Windows). Replace them with our native .cmd launcher that
|
|
273
|
-
* execs the Bun binary directly — zero Node.js overhead.
|
|
274
|
-
*/
|
|
275
|
-
function fixWindowsShims() {
|
|
276
|
-
if (process.platform !== 'win32') return;
|
|
277
|
-
try {
|
|
278
|
-
const prefix = process.env.npm_config_prefix;
|
|
279
|
-
if (!prefix || !path.isAbsolute(prefix)) return;
|
|
280
|
-
|
|
281
|
-
const globalBinDir = prefix;
|
|
282
|
-
const pkgRoot = path.join(__dirname, '..');
|
|
283
|
-
const binaryPath = path.join(pkgRoot, 'dist', 'sinch-windows-x64.exe');
|
|
284
|
-
|
|
285
|
-
// Write a .cmd that uses the absolute path to the binary inside the npm package.
|
|
286
|
-
// Can't copy bin/sinch.cmd because %~dp0 would resolve to the global bin dir,
|
|
287
|
-
// not the package dir where the binary lives.
|
|
288
|
-
fs.writeFileSync(path.join(globalBinDir, 'sinch.cmd'), `@echo off\r\n"${binaryPath}" %*\r\n`);
|
|
289
|
-
|
|
290
|
-
// Remove .ps1 shim (broken — tries /bin/sh) and bash shim (not needed on Windows)
|
|
291
|
-
try {
|
|
292
|
-
fs.unlinkSync(path.join(globalBinDir, 'sinch.ps1'));
|
|
293
|
-
} catch {}
|
|
294
|
-
try {
|
|
295
|
-
fs.unlinkSync(path.join(globalBinDir, 'sinch'));
|
|
296
|
-
} catch {}
|
|
297
|
-
|
|
298
|
-
// Clean up orphaned sinch.exe from previous installToGlobalBin versions
|
|
299
|
-
try {
|
|
300
|
-
fs.unlinkSync(path.join(globalBinDir, 'sinch.exe'));
|
|
301
|
-
} catch {}
|
|
302
|
-
} catch {
|
|
303
|
-
// Non-fatal — npm's .cmd shim may still work for node-based fallback
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// --- Main ---
|
|
308
|
-
|
|
309
293
|
async function main() {
|
|
310
|
-
// Only run on global installs or when forced by setup-dev.js
|
|
311
294
|
if (process.env.npm_config_global !== 'true' && !process.env.SINCH_FORCE_POSTINSTALL) {
|
|
312
295
|
return;
|
|
313
296
|
}
|
|
@@ -316,14 +299,7 @@ async function main() {
|
|
|
316
299
|
fs.mkdirSync(SINCH_DIR, { recursive: true });
|
|
317
300
|
|
|
318
301
|
const version = getVersion();
|
|
319
|
-
|
|
320
|
-
// Download the platform binary
|
|
321
302
|
await downloadBinary(version);
|
|
322
|
-
|
|
323
|
-
// Replace npm's broken Windows shims with native .cmd launcher
|
|
324
|
-
fixWindowsShims();
|
|
325
|
-
|
|
326
|
-
// Set up shell completions
|
|
327
303
|
writeCompletionsJson(version);
|
|
328
304
|
|
|
329
305
|
if (process.platform === 'win32') {
|
|
@@ -331,9 +307,14 @@ async function main() {
|
|
|
331
307
|
} else {
|
|
332
308
|
installBashZshCompletion();
|
|
333
309
|
}
|
|
334
|
-
} catch {
|
|
335
|
-
// Silent failure — never break npm install
|
|
336
|
-
}
|
|
310
|
+
} catch {}
|
|
337
311
|
}
|
|
338
312
|
|
|
339
|
-
main
|
|
313
|
+
if (require.main === module) {
|
|
314
|
+
main();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
module.exports = {
|
|
318
|
+
stripManagedBlocks,
|
|
319
|
+
upsertBlock,
|
|
320
|
+
};
|
package/bin/credman.exe
DELETED
|
Binary file
|
package/bin/s
DELETED
package/bin/sinch.cmd
DELETED