@omega.js/desktop 0.51.0 → 0.52.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 +1 -1
- package/dist/commands/runner.js +134 -37
- package/dist/defaults/.github/workflows/build.yml +2 -0
- package/dist/runner/job-started.js +1 -1
- package/dist/test/suites/build/runner.test.js +610 -3
- package/dist/vendor/config/index.js +5 -1
- package/dist/vendor/config/repo.js +24 -0
- package/dist/vendor/devkit/brand-version.js +162 -1
- package/dist/vendor/devkit/deploy.js +64 -4
- package/dist/vendor/devkit/git-remote.js +78 -1
- package/dist/vendor/devkit/local.js +51 -3
- package/dist/vendor/devkit/lockfile.js +127 -0
- package/dist/vendor/devkit/pack-local.js +4 -7
- package/dist/vendor/devkit/preludes/origin-heal.js +35 -49
- package/dist/vendor/devkit/target-secrets.js +34 -45
- package/docs/index.md +1 -1
- package/docs/releasing.md +4 -2
- package/docs/runner.md +12 -10
- package/docs/shared/brands.md +1 -1
- package/docs/shared/config.md +7 -0
- package/docs/shared/deploys.md +30 -7
- package/docs/shared/local-dev.md +5 -3
- package/docs/signing.md +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -152,7 +152,7 @@ Each subsystem has its own API reference under [`docs/`](docs/):
|
|
|
152
152
|
- [installer-options](docs/installer-options.md) — installer/distribution config: NSIS one-click defaults, ia32 inclusion, app.category mapping, `{YEAR}` copyright token, snap publishing (default-on with cred-gated auto-skip), MAS roadmap
|
|
153
153
|
- [signing](docs/signing.md) — macOS + Windows code signing reference, cert files, env vars
|
|
154
154
|
- [releasing](docs/releasing.md) — end-to-end release walkthrough (`.env` → GitHub Release)
|
|
155
|
-
- [runner](docs/runner.md) — Windows EV-token signing runner — `npx omega runner
|
|
155
|
+
- [runner](docs/runner.md) — Windows EV-token signing runner — `npx omega runner start`, which onboards new GH orgs, `npx omega runner monitor` for a live signing event tail
|
|
156
156
|
- [test-framework](docs/test-framework.md) — writing tests, running them, layers
|
|
157
157
|
- [test-boot-layer](docs/test-boot-layer.md) — boot test layer (spawns the consumer's actual built bundle for end-to-end smoke tests)
|
|
158
158
|
- [build-system](docs/build-system.md) — gulp, esbuild, electron-builder pipeline
|
package/dist/commands/runner.js
CHANGED
|
@@ -14,18 +14,20 @@
|
|
|
14
14
|
// the defaults — and the orgs it serves. How a value that
|
|
15
15
|
// is already there gets changed.
|
|
16
16
|
// register-org <org> Register the runner against one specific GH org.
|
|
17
|
-
// start
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// start The one command: installs a bare box, refreshes a stale
|
|
18
|
+
// actions/runner download in place, registers any admin
|
|
19
|
+
// org the box does not serve yet, then brings EVERY
|
|
20
|
+
// registered org online, detached, in Startup-shortcut
|
|
21
|
+
// order. An org already alive is named and skipped; the
|
|
22
|
+
// terminal is never taken over.
|
|
20
23
|
// restart stop, wait for every listener to go, then start.
|
|
21
24
|
// stop Kill every Runner.Listener.exe under RUNNER_HOME.
|
|
22
25
|
// status Registered orgs, Startup shortcuts, live listeners.
|
|
23
26
|
// uninstall Remove everything — legacy services and tasks, and the
|
|
24
27
|
// electron-manager era's em-runner install, included.
|
|
25
|
-
// self-update Force an immediate self-update of @omega.js/desktop (npm i -g @omega.js/desktop@latest).
|
|
26
28
|
// monitor Tail the JSONL signing event log, pretty-printed.
|
|
27
29
|
//
|
|
28
|
-
// Every subcommand except `
|
|
30
|
+
// Every subcommand except `monitor` refuses on non-Windows
|
|
29
31
|
// (set OMEGA_RUNNER_FORCE=1 to override — testing only).
|
|
30
32
|
|
|
31
33
|
const path = require('path');
|
|
@@ -35,7 +37,6 @@ const jetpack = require('fs-jetpack');
|
|
|
35
37
|
|
|
36
38
|
const Manager = new (require('../build.js'));
|
|
37
39
|
const logger = Manager.logger('runner');
|
|
38
|
-
const { safeInstall } = require('../utils/safe-install');
|
|
39
40
|
|
|
40
41
|
const RUNNER_LABELS = ['self-hosted', 'windows', 'ev-token'];
|
|
41
42
|
|
|
@@ -103,8 +104,8 @@ function orgRunnerDir(org, home) {
|
|
|
103
104
|
}
|
|
104
105
|
const ACTIONS_RUNNER_VERSION = '2.319.1'; // pinned; bump intentionally
|
|
105
106
|
|
|
106
|
-
// The subcommands, and the
|
|
107
|
-
//
|
|
107
|
+
// The subcommands, and the one of them that runs on any platform: monitor only
|
|
108
|
+
// reads a log file. Every other one is the box's.
|
|
108
109
|
const SUBCOMMANDS = {
|
|
109
110
|
'install': install,
|
|
110
111
|
'config': configureRunner,
|
|
@@ -114,17 +115,16 @@ const SUBCOMMANDS = {
|
|
|
114
115
|
'stop': stopServices,
|
|
115
116
|
'status': statusServices,
|
|
116
117
|
'uninstall': uninstall,
|
|
117
|
-
'self-update': selfUpdate,
|
|
118
118
|
'monitor': monitor,
|
|
119
119
|
};
|
|
120
|
-
const ANY_PLATFORM_SUBCOMMANDS = new Set(['
|
|
120
|
+
const ANY_PLATFORM_SUBCOMMANDS = new Set(['monitor']);
|
|
121
121
|
|
|
122
122
|
// The ones that CHANGE the machine: they register, deregister, download, delete,
|
|
123
|
-
// kill listeners, rewrite the box's own configuration
|
|
124
|
-
//
|
|
123
|
+
// kill listeners, or rewrite the box's own configuration. A test process may
|
|
124
|
+
// only point these at a scratch home
|
|
125
125
|
// ([#337](https://github.com/Omega-JS-Stack/omega/issues/337) — the suite once
|
|
126
126
|
// ran a real install against the box). `status` and `monitor` only read.
|
|
127
|
-
const MUTATING_SUBCOMMANDS = new Set(['install', 'config', 'register-org', 'start', 'restart', 'stop', 'uninstall'
|
|
127
|
+
const MUTATING_SUBCOMMANDS = new Set(['install', 'config', 'register-org', 'start', 'restart', 'stop', 'uninstall']);
|
|
128
128
|
|
|
129
129
|
module.exports = async function (options) {
|
|
130
130
|
options = options || {};
|
|
@@ -261,7 +261,7 @@ async function install(options) {
|
|
|
261
261
|
// admin orgs on a tick ran as NT AUTHORITY\NETWORK SERVICE in Session 0, so
|
|
262
262
|
// the runners it spawned could not see the user's cert store and the Startup
|
|
263
263
|
// shortcuts it wrote landed in the wrong profile. Adding a new org is a
|
|
264
|
-
// `
|
|
264
|
+
// `npx omega runner start` away; uninstall() still tears down any leftover
|
|
265
265
|
// watcher service from an older install.
|
|
266
266
|
|
|
267
267
|
// 5. Save install metadata.
|
|
@@ -304,7 +304,7 @@ async function install(options) {
|
|
|
304
304
|
logger.log('');
|
|
305
305
|
logger.log(`Bringing every registered runner (${succeeded.length}) online in the background. 'npx omega runner status' shows them, 'npx omega runner monitor' tails the log.`);
|
|
306
306
|
logger.log('');
|
|
307
|
-
await startServices({ ...options, _home: home });
|
|
307
|
+
await startServices({ ...options, _home: home, _reconcileDone: true });
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|
|
@@ -416,7 +416,7 @@ async function registerOrg(options) {
|
|
|
416
416
|
const runnerDir = orgRunnerDir(org, home);
|
|
417
417
|
const templateDir = options._templateDir || path.join(home, '_template');
|
|
418
418
|
if (!jetpack.exists(path.join(templateDir, 'config.cmd'))) {
|
|
419
|
-
throw new Error(`actions/runner template not found at ${templateDir}. Run 'npx omega runner
|
|
419
|
+
throw new Error(`actions/runner template not found at ${templateDir}. Run 'npx omega runner start' first.`);
|
|
420
420
|
}
|
|
421
421
|
if (jetpack.exists(runnerDir)) {
|
|
422
422
|
logger.log(` Removing stale actions-runner-${org.toLowerCase()}/ before re-clone…`);
|
|
@@ -528,7 +528,8 @@ async function registerOrg(options) {
|
|
|
528
528
|
// What `start` settles before it touches a listener: the box's required keys —
|
|
529
529
|
// asked in a terminal and refused without one, exactly like install — and then
|
|
530
530
|
// the orgs the .env names against the orgs this install actually registered.
|
|
531
|
-
// start
|
|
531
|
+
// start only ever ADDS a named admin org (reconcileRunnerInstall); reshaping the
|
|
532
|
+
// box to the list is install's, so the mismatch names it.
|
|
532
533
|
async function startPreflight(options) {
|
|
533
534
|
const { home, env = process.env, logger: log = logger, prompt, interactive } = options || {};
|
|
534
535
|
await ensureRunnerConfig({ home, env, logger: log, prompt, interactive });
|
|
@@ -557,7 +558,7 @@ async function startPreflight(options) {
|
|
|
557
558
|
const named = parseRunnerOrgs(env.OMEGA_RUNNER_ORGS);
|
|
558
559
|
const registered = readConfig(home).registeredOrgs || [];
|
|
559
560
|
if (named.length > 0 && !sameRunnerOrgs(named, registered)) {
|
|
560
|
-
log.warn(`OMEGA_RUNNER_ORGS names ${named.join(', ')} but this install registered ${registered.join(', ') || '(none)'}.
|
|
561
|
+
log.warn(`OMEGA_RUNNER_ORGS names ${named.join(', ')} but this install registered ${registered.join(', ') || '(none)'}. \`start\` registers a named admin org it does not serve yet and keeps the rest; \`npx omega runner install\` reshapes the box to the list.`);
|
|
561
562
|
}
|
|
562
563
|
}
|
|
563
564
|
|
|
@@ -574,6 +575,92 @@ function runnerStartupTargets(home) {
|
|
|
574
575
|
});
|
|
575
576
|
}
|
|
576
577
|
|
|
578
|
+
// What `start` settles between the preflight and the spawns
|
|
579
|
+
// ([#937](https://github.com/Omega-JS-Stack/omega/issues/937)), so a box never
|
|
580
|
+
// needs a second verb to catch up:
|
|
581
|
+
// 1. a stale actions/runner download is refreshed IN PLACE: the template is
|
|
582
|
+
// re-downloaded and laid over each org dir whose listener is down. The
|
|
583
|
+
// registration (`.runner`, `.credentials`, `.env`) is never in the
|
|
584
|
+
// template, so it survives the copy. A live listener holds its binaries
|
|
585
|
+
// open, so that org is skipped and named with `restart`, and the new
|
|
586
|
+
// version is recorded only once every org dir took it. No teardown. A
|
|
587
|
+
// download or a copy that fails is one warn and a skipped refresh, never
|
|
588
|
+
// a failed start: the next start tries again.
|
|
589
|
+
// 2. an admin org the box does not serve yet (OMEGA_RUNNER_ORGS honoured) is
|
|
590
|
+
// registered, Startup shortcut and all. The only step that needs GitHub,
|
|
591
|
+
// so discovery that fails is one line and the box still comes online.
|
|
592
|
+
// GH_TOKEN is never missing here: it is a required key, and the preflight
|
|
593
|
+
// refuses a box without one. Orgs registered outside the filter stay;
|
|
594
|
+
// reshaping the box to the list is install's.
|
|
595
|
+
//
|
|
596
|
+
// `_download`, `_discoverOrgs` and `_registerOrg` are the tests' seams, the way
|
|
597
|
+
// `_spawn` is. Registration is not home-scoped (it acts on GitHub with the real
|
|
598
|
+
// token, whatever scratch home a case made), so a test run that has not
|
|
599
|
+
// injected both GitHub seams skips the org check instead of reaching it.
|
|
600
|
+
async function reconcileRunnerInstall(options) {
|
|
601
|
+
const home = options._home;
|
|
602
|
+
const env = options._env;
|
|
603
|
+
const log = options._logger;
|
|
604
|
+
const listen = options._listeners;
|
|
605
|
+
const download = options._download || downloadActionsRunner;
|
|
606
|
+
const templateDir = path.join(home, '_template');
|
|
607
|
+
|
|
608
|
+
const installed = readConfig(home).actionsRunnerVersion;
|
|
609
|
+
if (installed !== ACTIONS_RUNNER_VERSION) {
|
|
610
|
+
log.log(`actions/runner ${installed || '(unrecorded)'} is installed and ${ACTIONS_RUNNER_VERSION} is pinned: refreshing in place.`);
|
|
611
|
+
let downloaded = true;
|
|
612
|
+
try {
|
|
613
|
+
await download(templateDir, home);
|
|
614
|
+
} catch (e) {
|
|
615
|
+
log.warn(`✗ actions/runner ${ACTIONS_RUNNER_VERSION} download failed (${e.message}): refresh skipped until the next start.`);
|
|
616
|
+
downloaded = false;
|
|
617
|
+
}
|
|
618
|
+
if (downloaded) {
|
|
619
|
+
let skipped = 0;
|
|
620
|
+
for (const { org, dir } of listOrgRunnerDirs(home)) {
|
|
621
|
+
if (listen(dir).length > 0) {
|
|
622
|
+
log.warn(`· ${org} is running, so its actions/runner files are locked and were not refreshed. Run \`npx omega runner restart\` to refresh it.`);
|
|
623
|
+
skipped++;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
try {
|
|
627
|
+
jetpack.copy(templateDir, dir, { overwrite: true });
|
|
628
|
+
log.log(`✓ ${org} refreshed to actions/runner ${ACTIONS_RUNNER_VERSION}`);
|
|
629
|
+
} catch (e) {
|
|
630
|
+
log.warn(`✗ ${org} was not refreshed (${e.message}): the next start tries again.`);
|
|
631
|
+
skipped++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (skipped === 0) saveConfig({ actionsRunnerVersion: ACTIONS_RUNNER_VERSION }, home);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (isTestRun() && !(options._discoverOrgs && options._registerOrg)) {
|
|
639
|
+
log.log('Org check skipped: a test run never asks GitHub unless both _discoverOrgs and _registerOrg are injected.');
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const discover = options._discoverOrgs || discoverAdminOrgs;
|
|
643
|
+
const register = options._registerOrg || registerOrg;
|
|
644
|
+
let adminOrgs;
|
|
645
|
+
try {
|
|
646
|
+
adminOrgs = await discover();
|
|
647
|
+
} catch (e) {
|
|
648
|
+
log.warn(`Org check skipped: could not list your admin orgs (${e.message}).`);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const { orgs } = selectRunnerOrgs(adminOrgs, env.OMEGA_RUNNER_ORGS);
|
|
652
|
+
const served = new Set((readConfig(home).registeredOrgs || []).map((org) => org.toLowerCase()));
|
|
653
|
+
for (const org of orgs.filter((o) => !served.has(o.toLowerCase()))) {
|
|
654
|
+
log.log(`Registering ${org}: an admin org this box does not serve yet.`);
|
|
655
|
+
try {
|
|
656
|
+
await register({ ...options, _: ['runner', 'register-org', org], _templateDir: templateDir });
|
|
657
|
+
} catch (e) {
|
|
658
|
+
log.warn(`✗ register ${org}: ${e.message}`);
|
|
659
|
+
process.exitCode = 1;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
577
664
|
// Every registered org, brought online detached, in Startup-shortcut order.
|
|
578
665
|
//
|
|
579
666
|
// A box registers one runner per org, and `start` used to bring up exactly ONE
|
|
@@ -589,7 +676,15 @@ function runnerStartupTargets(home) {
|
|
|
589
676
|
// `_spawn` and `_listeners` are injected by the tests, the same way `_home` and
|
|
590
677
|
// `_logger` are: neither cmd.exe nor the process scan exists off Windows.
|
|
591
678
|
// `_preflightDone` is `restart`'s: it settles the box config BEFORE it kills
|
|
592
|
-
// anything, so the walk must not run a second time here.
|
|
679
|
+
// anything, so the walk must not run a second time here. The reconcile is NOT
|
|
680
|
+
// gated on it: after restart's stop is exactly when nothing is locked.
|
|
681
|
+
// `_reconcileDone` is install's alone: it just downloaded and registered, so the
|
|
682
|
+
// reconcile is skipped there.
|
|
683
|
+
//
|
|
684
|
+
// A bare box (no template to clone from) is handed to install, `_install` in
|
|
685
|
+
// the tests, which ends by calling this function again with the template in
|
|
686
|
+
// place. A test run without `_install` refuses: the real install downloads,
|
|
687
|
+
// discovers and registers against GitHub, and no scratch home scopes that.
|
|
593
688
|
async function startServices(options) {
|
|
594
689
|
options = options || {};
|
|
595
690
|
ensureWindows();
|
|
@@ -597,8 +692,23 @@ async function startServices(options) {
|
|
|
597
692
|
const log = options._logger || logger;
|
|
598
693
|
const spawnIt = options._spawn || spawnRunnerDetached;
|
|
599
694
|
const listen = options._listeners || listRunnerListenerProcessesUnder;
|
|
695
|
+
if (!jetpack.exists(path.join(home, '_template', 'config.cmd'))) {
|
|
696
|
+
if (isTestRun() && !options._install) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Refusing to install onto the bare runner home ${home}: this is a test run `
|
|
699
|
+
+ '(OMEGA_TEST_RUNNER / OMEGA_TEST_MODE) and the call did not inject `_install`. The real install '
|
|
700
|
+
+ 'downloads actions/runner and registers every admin org against GitHub with GH_TOKEN.',
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
log.log(`No runner install under ${home}: running the full install.`);
|
|
704
|
+
return (options._install || install)(options);
|
|
705
|
+
}
|
|
706
|
+
const env = options._env || process.env;
|
|
600
707
|
if (!options._preflightDone) {
|
|
601
|
-
await startPreflight({ home, env
|
|
708
|
+
await startPreflight({ home, env, logger: log, prompt: options._prompt, interactive: options._interactive });
|
|
709
|
+
}
|
|
710
|
+
if (!options._reconcileDone) {
|
|
711
|
+
await reconcileRunnerInstall({ ...options, _home: home, _env: env, _logger: log, _listeners: listen });
|
|
602
712
|
}
|
|
603
713
|
const targets = runnerStartupTargets(home);
|
|
604
714
|
if (targets.length === 0) {
|
|
@@ -762,7 +872,7 @@ async function statusServices(options) {
|
|
|
762
872
|
logger.log('');
|
|
763
873
|
const report = orgRunnerReport({ home });
|
|
764
874
|
if (report.length === 0) {
|
|
765
|
-
logger.warn('No orgs registered. Run `npx omega runner
|
|
875
|
+
logger.warn('No orgs registered. Run `npx omega runner start`.');
|
|
766
876
|
} else {
|
|
767
877
|
logger.log(`Per-org runner state (${report.length}):`);
|
|
768
878
|
for (const { state, lines } of report) {
|
|
@@ -1161,19 +1271,6 @@ function identifyHandleHolders(targetPath) {
|
|
|
1161
1271
|
}
|
|
1162
1272
|
}
|
|
1163
1273
|
|
|
1164
|
-
// ─── self-update ────────────────────────────────────────────────────────────────
|
|
1165
|
-
async function selfUpdate() {
|
|
1166
|
-
const { execute } = require('node-powertools');
|
|
1167
|
-
logger.log('Updating @omega.js/desktop to latest…');
|
|
1168
|
-
try {
|
|
1169
|
-
const out = await safeInstall('npm i -g @omega.js/desktop@latest');
|
|
1170
|
-
logger.log(out);
|
|
1171
|
-
logger.log('✓ @omega.js/desktop updated.');
|
|
1172
|
-
} catch (e) {
|
|
1173
|
-
logger.warn(`Self-update failed: ${e.message}`);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
1274
|
// ─── helpers ────────────────────────────────────────────────────────────────────
|
|
1178
1275
|
|
|
1179
1276
|
const WATCHER_SERVICE_NAME = 'omega-runner-watcher';
|
|
@@ -1571,8 +1668,8 @@ function listLegacyRunnerStartupShortcuts(startupDir) {
|
|
|
1571
1668
|
// wrapper that blocks for the lifetime of the listener with cwd = runnerDir;
|
|
1572
1669
|
// a long-lived cmd.exe holding cwd inside RUNNER_HOME blocks every later
|
|
1573
1670
|
// uninstall with EPERM. Bypassing run.cmd loses its self-update relaunch
|
|
1574
|
-
// path, but `
|
|
1575
|
-
//
|
|
1671
|
+
// path, but `npx omega runner start` refreshing the runner binary in place
|
|
1672
|
+
// is the update mechanism in @omega.js/desktop anyway.
|
|
1576
1673
|
//
|
|
1577
1674
|
// Detached + windowsHide + ignored stdio = no console window flashes during
|
|
1578
1675
|
// install and the listener survives the install command's exit. UAC-
|
|
@@ -1733,7 +1830,7 @@ async function monitor(options) {
|
|
|
1733
1830
|
// exactly which orgs the monitor will pick up signing events from.
|
|
1734
1831
|
const report = orgRunnerReport({ home });
|
|
1735
1832
|
if (report.length === 0) {
|
|
1736
|
-
logger.log('(no orgs registered yet
|
|
1833
|
+
logger.log('(no orgs registered yet: run `npx omega runner start` first)');
|
|
1737
1834
|
} else {
|
|
1738
1835
|
logger.log(`Monitoring signing requests across ${report.length} org(s):`);
|
|
1739
1836
|
for (const { state, lines } of report) {
|
|
@@ -239,6 +239,8 @@ jobs:
|
|
|
239
239
|
uses: actions/upload-artifact@v7
|
|
240
240
|
with:
|
|
241
241
|
name: windows-unsigned
|
|
242
|
+
# An intermediate windows-sign consumes in this same run; the releases repo is the durable home.
|
|
243
|
+
retention-days: 1
|
|
242
244
|
# Capture installer + auto-updater feed + blockmap so windows-sign can re-upload them all.
|
|
243
245
|
path: |
|
|
244
246
|
release/*.exe
|
|
@@ -72,7 +72,7 @@ function checkJob(env, lists) {
|
|
|
72
72
|
return { allowed: false, line: `Refusing this job: GITHUB_EVENT_NAME is "${event}", and this runner serves ${DISPATCH_EVENTS.join(' and ')} only.` };
|
|
73
73
|
}
|
|
74
74
|
if (repos.length === 0) {
|
|
75
|
-
return { allowed: false, line: `Refusing this job: ${reposFile} lists no repository, so this runner allows none. Run \`npx omega runner
|
|
75
|
+
return { allowed: false, line: `Refusing this job: ${reposFile} lists no repository, so this runner allows none. Run \`npx omega runner start\` on the box, or add one \`owner/name\` per line.` };
|
|
76
76
|
}
|
|
77
77
|
if (!repos.some((entry) => repoMatches(repository, entry))) {
|
|
78
78
|
return { allowed: false, line: `Refusing this job: GITHUB_REPOSITORY is "${repository}", which is not listed in ${reposFile}.` };
|