@webority/ensemble 0.5.0 → 0.5.2
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/ensemble.js +32 -17
- package/lib/core.js +42 -3
- package/lib/postinstall.js +22 -0
- package/package.json +4 -1
package/bin/ensemble.js
CHANGED
|
@@ -26,41 +26,54 @@ function reportSetup(wired) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Browser sign-in (device flow) — no token to copy. The recommended path.
|
|
29
|
+
// Order matters: AUTHENTICATE FIRST (browser approval), and only download the runtime once the
|
|
30
|
+
// machine is approved — so a cancelled or denied login never pulls ~90 MB for nothing.
|
|
29
31
|
async function login(args) {
|
|
30
32
|
const api = args.api || core.DEFAULT_API;
|
|
31
33
|
const name = args.name || os.hostname();
|
|
32
|
-
core.
|
|
33
|
-
core.log('
|
|
34
|
-
const { runnerPath } = await core.ensureBinaries();
|
|
34
|
+
core.assertSupported();
|
|
35
|
+
core.log('Ensemble — sign in for "' + name + '" via ' + api);
|
|
35
36
|
const grant = await core.startDevice(api, name);
|
|
36
|
-
core.log('\n Approve this machine in your browser:\n
|
|
37
|
-
'\n
|
|
37
|
+
core.log('\n 1) Approve this machine in your browser:\n ' + grant.verificationUriComplete +
|
|
38
|
+
'\n Confirmation code: ' + grant.userCode + '\n');
|
|
38
39
|
core.openBrowser(grant.verificationUriComplete);
|
|
39
|
-
core.log('• waiting for approval …');
|
|
40
|
+
core.log('• waiting for your approval …');
|
|
41
|
+
const token = await waitForApproval(api, grant);
|
|
42
|
+
core.log('\n✓ Approved. Connecting this machine …');
|
|
43
|
+
const { runnerPath } = core.requireRuntime();
|
|
44
|
+
reportSetup(core.finishSetup(api, token, runnerPath));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function waitForApproval(api, grant) {
|
|
40
48
|
const deadline = Date.now() + grant.expiresIn * 1000;
|
|
41
|
-
let token = null;
|
|
42
49
|
while (Date.now() < deadline) {
|
|
43
50
|
await core.sleep((grant.interval || 5) * 1000);
|
|
44
51
|
const r = await core.pollDevice(api, grant.deviceCode);
|
|
45
|
-
if (r.status === 'approved')
|
|
46
|
-
if (r.status === 'denied') core.die('approval was denied in the browser.');
|
|
52
|
+
if (r.status === 'approved') return r.token;
|
|
53
|
+
if (r.status === 'denied') core.die('approval was denied in the browser — nothing was installed.');
|
|
47
54
|
if (r.status === 'expired') core.die('the login request expired — run `ensemble login` again.');
|
|
48
55
|
}
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
core.die('timed out waiting for approval — nothing was installed. Run `ensemble login` again.');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Explicit (re)install of the runner + bus. Normally runs automatically at `npm install`.
|
|
60
|
+
async function install() {
|
|
61
|
+
core.assertSupported();
|
|
62
|
+
await core.ensureBinaries();
|
|
63
|
+
core.log('✓ Ensemble runtime installed. Next: ensemble login');
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
// Headless / CI path: enrol with a copy-pasted enrollment token.
|
|
66
|
+
// Headless / CI path: enrol with a copy-pasted enrollment token. Auth only — the runtime is
|
|
67
|
+
// installed at `npm install` (or `ensemble install`).
|
|
54
68
|
async function enroll(args) {
|
|
55
69
|
const token = args.token || process.env.ENSEMBLE_ENROLL_TOKEN;
|
|
56
70
|
if (!token) core.die('missing --token <enrollment-token> — get it from your Ensemble portal → Connect Runner, or use `ensemble login` for browser sign-in');
|
|
57
71
|
const api = args.api || core.DEFAULT_API;
|
|
58
72
|
const name = args.name || os.hostname();
|
|
59
73
|
core.log('Ensemble — enrolling "' + name + '" with ' + api);
|
|
60
|
-
core.log('•
|
|
61
|
-
const { runnerPath } = await core.ensureBinaries();
|
|
62
|
-
core.log('• exchanging enrollment token for a per-machine token');
|
|
74
|
+
core.log('• exchanging the enrollment token for a per-machine token');
|
|
63
75
|
const machineToken = await core.enrollMachine(api, token, name);
|
|
76
|
+
const { runnerPath } = core.requireRuntime();
|
|
64
77
|
reportSetup(core.finishSetup(api, machineToken, runnerPath));
|
|
65
78
|
}
|
|
66
79
|
|
|
@@ -84,9 +97,10 @@ function usage() {
|
|
|
84
97
|
`ensemble — connect this machine to your Ensemble control plane
|
|
85
98
|
|
|
86
99
|
ensemble login [--api <url>] [--name <machine>]
|
|
87
|
-
sign in via your browser
|
|
100
|
+
sign in via your browser + connect this machine (runtime is installed at npm install)
|
|
88
101
|
ensemble enroll --token <enrollment-token> [--api <url>] [--name <machine>]
|
|
89
102
|
headless / CI: enrol with an enrollment token from the portal
|
|
103
|
+
ensemble install (re)download the runner + bus for this platform
|
|
90
104
|
ensemble status show connection + your org's live session mailboxes
|
|
91
105
|
ensemble hooks (re)wire ensemble-bus into installed coding CLIs
|
|
92
106
|
ensemble version
|
|
@@ -101,8 +115,9 @@ Run \`ensemble login\` and approve the machine in your browser.`);
|
|
|
101
115
|
try {
|
|
102
116
|
if (cmd === 'login') await login(args);
|
|
103
117
|
else if (cmd === 'enroll') await enroll(args);
|
|
118
|
+
else if (cmd === 'install') await install();
|
|
104
119
|
else if (cmd === 'status') await status(args);
|
|
105
|
-
else if (cmd === 'hooks') {
|
|
120
|
+
else if (cmd === 'hooks') { core.log('wired: ' + (core.wireHooks().join(', ') || 'none')); }
|
|
106
121
|
else if (cmd === 'version' || cmd === '--version' || cmd === '-v') core.log(require('../package.json').version);
|
|
107
122
|
else usage();
|
|
108
123
|
} catch (e) {
|
package/lib/core.js
CHANGED
|
@@ -11,8 +11,12 @@ const ENSEMBLE_DIR = path.join(HOME, '.ensemble');
|
|
|
11
11
|
const BIN_DIR = path.join(ENSEMBLE_DIR, 'bin');
|
|
12
12
|
const RUNNER_DIR = path.join(ENSEMBLE_DIR, 'runner');
|
|
13
13
|
const DEFAULT_API = process.env.ENSEMBLE_API || 'https://api.ensemble.host';
|
|
14
|
-
// Public host for the prebuilt binaries (runner + bus).
|
|
15
|
-
|
|
14
|
+
// Public host for the prebuilt binaries (runner + bus). Branded CDN in front of the blob
|
|
15
|
+
// (Cloudflare Worker → ensembledl blob; the raw blob URL still works as a fallback). Overridable.
|
|
16
|
+
const DL_BASE = process.env.ENSEMBLE_DL_BASE || 'https://storage.ensemble.host/cli';
|
|
17
|
+
// RIDs we currently publish a prebuilt runner + bus for on DL_BASE. Expand as more are uploaded
|
|
18
|
+
// (osx-x64 for Intel Macs, linux-arm64, win-arm64). Keep in sync with what the build pipeline ships.
|
|
19
|
+
const SUPPORTED_PLATFORMS = ['win-x64', 'osx-arm64', 'osx-x64', 'linux-x64', 'linux-arm64'];
|
|
16
20
|
|
|
17
21
|
function log(msg) { process.stdout.write(msg + '\n'); }
|
|
18
22
|
function warn(msg) { process.stderr.write(msg + '\n'); }
|
|
@@ -64,9 +68,43 @@ function downloadTo(url, dest) {
|
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
// Throws (not die) so the npm postinstall can catch and not fail the whole install on an
|
|
72
|
+
// unsupported OS; the CLI dispatcher catches the throw and prints it cleanly.
|
|
73
|
+
function assertSupported() {
|
|
74
|
+
const pf = platform();
|
|
75
|
+
if (!SUPPORTED_PLATFORMS.includes(pf.key)) {
|
|
76
|
+
throw new Error(`no prebuilt Ensemble runtime for "${pf.key}" (${process.platform}/${process.arch}) — ` +
|
|
77
|
+
`supported: ${SUPPORTED_PLATFORMS.join(', ')}. Ask us to add ${pf.key}, or build from source.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function runtimePaths() {
|
|
82
|
+
const pf = platform();
|
|
83
|
+
return {
|
|
84
|
+
pf,
|
|
85
|
+
busPath: path.join(BIN_DIR, 'ensemble-bus' + pf.exe),
|
|
86
|
+
runnerPath: path.join(RUNNER_DIR, 'Ensemble.Runner' + pf.exe),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The runner + bus are downloaded at `npm install` (postinstall) or `ensemble install` — NOT at login.
|
|
91
|
+
function runtimeInstalled() {
|
|
92
|
+
const p = runtimePaths();
|
|
93
|
+
const ok = (f) => fs.existsSync(f) && fs.statSync(f).size > 0;
|
|
94
|
+
return ok(p.busPath) && ok(p.runnerPath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function requireRuntime() {
|
|
98
|
+
if (!runtimeInstalled()) {
|
|
99
|
+
die('the Ensemble runtime is not installed — run `ensemble install` (or reinstall @webority/ensemble).');
|
|
100
|
+
}
|
|
101
|
+
return runtimePaths();
|
|
102
|
+
}
|
|
103
|
+
|
|
67
104
|
async function ensureBinaries() {
|
|
68
105
|
ensureDirs();
|
|
69
106
|
const pf = platform();
|
|
107
|
+
assertSupported();
|
|
70
108
|
const busPath = path.join(BIN_DIR, 'ensemble-bus' + pf.exe);
|
|
71
109
|
const runnerPath = path.join(RUNNER_DIR, 'Ensemble.Runner' + pf.exe);
|
|
72
110
|
const targets = [
|
|
@@ -276,5 +314,6 @@ function finishSetup(api, machineToken, runnerPath) {
|
|
|
276
314
|
module.exports = {
|
|
277
315
|
log, warn, die, platform, DEFAULT_API, ENSEMBLE_DIR, RUNNER_DIR, BIN_DIR,
|
|
278
316
|
ensureBinaries, enrollMachine, writeRunnerConfig, wireHooks, installAutostart, startRunner, request,
|
|
279
|
-
sleep, openBrowser, startDevice, pollDevice, finishSetup,
|
|
317
|
+
sleep, openBrowser, startDevice, pollDevice, finishSetup, assertSupported,
|
|
318
|
+
runtimePaths, runtimeInstalled, requireRuntime,
|
|
280
319
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Runs at `npm install` (postinstall). Downloads the runner + bus for this platform so that
|
|
3
|
+
// `ensemble login` is auth-only. Resilient by design: any failure (offline, unsupported OS,
|
|
4
|
+
// --ignore-scripts) warns and exits 0 so it never breaks `npm install` — `ensemble install`
|
|
5
|
+
// (or the next install) can retry.
|
|
6
|
+
const core = require('./core');
|
|
7
|
+
|
|
8
|
+
(async () => {
|
|
9
|
+
try {
|
|
10
|
+
if (core.runtimeInstalled()) {
|
|
11
|
+
return; // already present (e.g. reinstall of the same version)
|
|
12
|
+
}
|
|
13
|
+
core.assertSupported();
|
|
14
|
+
core.log('Ensemble: installing the runner + bus for this machine …');
|
|
15
|
+
await core.ensureBinaries();
|
|
16
|
+
core.log('Ensemble: runtime installed. Next: ensemble login');
|
|
17
|
+
} catch (e) {
|
|
18
|
+
core.warn('Ensemble: could not install the runtime now (' + (e && e.message ? e.message : e) + ').');
|
|
19
|
+
core.warn(' Run ensemble install when ready, then ensemble login.');
|
|
20
|
+
// exit 0 — never fail `npm install`.
|
|
21
|
+
}
|
|
22
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/ensemble",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Connect this machine to your Ensemble control plane — runs the local agent runner and wires the ensemble-bus session bus so your coding sessions talk (per-org, isolated).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ensemble": "bin/ensemble.js"
|
|
7
7
|
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node lib/postinstall.js"
|
|
10
|
+
},
|
|
8
11
|
"engines": {
|
|
9
12
|
"node": ">=18"
|
|
10
13
|
},
|