hammerkit 1.27.0-next.3 → 1.27.0-next.4

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.
@@ -5,6 +5,7 @@ const commander_1 = require("commander");
5
5
  const log_1 = require("../log");
6
6
  const server_config_1 = require("./server-config");
7
7
  const server_1 = require("./server");
8
+ const abort_on_signals_1 = require("../utils/abort-on-signals");
8
9
  // Runs this machine as a hammerkit server until interrupted. Like `remote`, it
9
10
  // only needs its own config file, not a build file.
10
11
  function registerServerCommand(program, environment) {
@@ -13,31 +14,46 @@ function registerServerCommand(program, environment) {
13
14
  .description('serve this machine to remote runs (experimental)')
14
15
  .addOption(new commander_1.Option('-c, --config <file>', 'server config file').env('HAMMERKIT_SERVER_CONFIG'))
15
16
  .action(async (options) => {
17
+ // SIGHUP reads the entitlements again, so who may do what changes without a
18
+ // restart; the rest of the config needs one. The server takes it over from the
19
+ // abort of the CLI, which stops other commands on it, before it starts: a SIGHUP
20
+ // while it reads its config and starts waits for it. Windows sends SIGHUP when the
21
+ // console closes, which stops the server there too.
22
+ let server;
23
+ let reloadWhenStarted = false;
24
+ const reload = async (running) => {
25
+ try {
26
+ const result = running.reload(await (0, server_config_1.readServerConfig)(environment, options.config));
27
+ if (result.restart.length > 0) {
28
+ environment.stdout.write(`changed but not reloaded, restart for: ${result.restart.join(', ')}\n`);
29
+ }
30
+ }
31
+ catch (e) {
32
+ environment.stdout.write(`entitlements were not reloaded: ${(0, log_1.getErrorMessage)(e)}\n`);
33
+ }
34
+ };
35
+ const releaseReload = process.platform !== 'win32'
36
+ ? (0, abort_on_signals_1.takeOverSignal)(process, 'SIGHUP', () => {
37
+ if (server) {
38
+ void reload(server);
39
+ }
40
+ else {
41
+ reloadWhenStarted = true;
42
+ }
43
+ })
44
+ : () => undefined;
16
45
  try {
17
46
  if (!options.config) {
18
47
  throw new Error('pass the server config with --config or HAMMERKIT_SERVER_CONFIG');
19
48
  }
20
49
  const config = await (0, server_config_1.readServerConfig)(environment, options.config);
21
- const server = await (0, server_1.startServer)(config, environment, {
50
+ const running = await (0, server_1.startServer)(config, environment, {
22
51
  log: (line) => environment.stdout.write(`${line}\n`),
23
52
  });
24
- environment.stdout.write(`hammerkit server listening on ${server.url}\n`);
25
- // SIGHUP reads the entitlements again, so who may do what changes without
26
- // a restart; the rest of the config needs one
27
- const reload = async () => {
28
- try {
29
- const result = server.reload(await (0, server_config_1.readServerConfig)(environment, options.config));
30
- if (result.restart.length > 0) {
31
- environment.stdout.write(`changed but not reloaded, restart for: ${result.restart.join(', ')}\n`);
32
- }
33
- }
34
- catch (e) {
35
- environment.stdout.write(`entitlements were not reloaded: ${(0, log_1.getErrorMessage)(e)}\n`);
36
- }
37
- };
38
- const onReload = () => void reload();
39
- if (process.platform !== 'win32') {
40
- process.on('SIGHUP', onReload);
53
+ server = running;
54
+ environment.stdout.write(`hammerkit server listening on ${running.url}\n`);
55
+ if (reloadWhenStarted) {
56
+ void reload(running);
41
57
  }
42
58
  await new Promise((resolve) => {
43
59
  if (environment.abortCtrl.signal.aborted) {
@@ -45,8 +61,8 @@ function registerServerCommand(program, environment) {
45
61
  }
46
62
  environment.abortCtrl.signal.addEventListener('abort', () => resolve(), { once: true });
47
63
  });
48
- process.off('SIGHUP', onReload);
49
- await server.close();
64
+ releaseReload();
65
+ await running.close();
50
66
  environment.stdout.write('hammerkit server stopped\n');
51
67
  }
52
68
  catch (e) {
@@ -55,6 +71,9 @@ function registerServerCommand(program, environment) {
55
71
  }
56
72
  program.error(`Server was not successful: ${(0, log_1.getErrorMessage)(e)}`, { exitCode: 1 });
57
73
  }
74
+ finally {
75
+ releaseReload();
76
+ }
58
77
  });
59
78
  }
60
79
  exports.registerServerCommand = registerServerCommand;
@@ -1,17 +1,57 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.abortOnSignals = exports.ABORT_SIGNALS = void 0;
3
+ exports.takeOverSignal = exports.abortOnSignals = exports.ABORT_SIGNALS = void 0;
4
4
  // Ctrl-C (SIGINT), a CI job being cancelled or a supervisor stopping hammerkit
5
5
  // (SIGTERM) and a closed terminal (SIGHUP) all stop the run the same way: abort,
6
6
  // so services are removed and local tasks' process groups are killed. Without a
7
7
  // handler Node exits on SIGTERM/SIGHUP at once and leaves both behind.
8
8
  exports.ABORT_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
9
- function abortOnSignals(source, abortCtrl) {
10
- for (const signal of exports.ABORT_SIGNALS) {
11
- source.on(signal, () => {
12
- abortCtrl.abort();
13
- });
9
+ // signals a running command gives a meaning of its own (the server reloads its
10
+ // entitlements on SIGHUP), per source and counted by how many hold them: they don't
11
+ // abort the run
12
+ const takenOver = new WeakMap();
13
+ function heldOn(source) {
14
+ let held = takenOver.get(source);
15
+ if (!held) {
16
+ held = new Map();
17
+ takenOver.set(source, held);
14
18
  }
19
+ return held;
20
+ }
21
+ /**
22
+ * Stops the run on the abort signals, all but those a command took over with
23
+ * `takeOverSignal`. Returns what removes the listeners again.
24
+ */
25
+ function abortOnSignals(source, abortCtrl) {
26
+ const listeners = exports.ABORT_SIGNALS.map((signal) => {
27
+ const listener = () => {
28
+ if (!takenOver.get(source)?.get(signal)) {
29
+ abortCtrl.abort();
30
+ }
31
+ };
32
+ source.on(signal, listener);
33
+ return { signal, listener };
34
+ });
35
+ return () => listeners.forEach(({ signal, listener }) => source.off(signal, listener));
15
36
  }
16
37
  exports.abortOnSignals = abortOnSignals;
38
+ /**
39
+ * Calls `handler` on `signal` instead of stopping the run, until the returned function
40
+ * gives the signal back.
41
+ */
42
+ function takeOverSignal(source, signal, handler) {
43
+ const held = heldOn(source);
44
+ held.set(signal, (held.get(signal) ?? 0) + 1);
45
+ source.on(signal, handler);
46
+ let released = false;
47
+ return () => {
48
+ if (released) {
49
+ return;
50
+ }
51
+ released = true;
52
+ source.off(signal, handler);
53
+ held.set(signal, (held.get(signal) ?? 1) - 1);
54
+ };
55
+ }
56
+ exports.takeOverSignal = takeOverSignal;
17
57
  //# sourceMappingURL=abort-on-signals.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hammerkit",
3
3
  "description": "Containerized build tool with incremental caching that runs the same build file locally, in Docker and on Kubernetes.",
4
- "version": "1.27.0-next.3",
4
+ "version": "1.27.0-next.4",
5
5
  "keywords": [
6
6
  "build",
7
7
  "build-tool",