dsh-foreman-node 0.1.0 → 0.1.1

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/CHANGELOG.md CHANGED
@@ -1,7 +1,21 @@
1
- # Changelog
1
+ ## 0.1.1 — 2026-11-07
2
2
 
3
- 本包是 [`dsh-foreman`](https://www.npmjs.com/package/dsh-foreman) 的设备侧,单独发版,
4
- 但**协议与 hub 版本绑定**:两侧协议版本不一致时,握手就拒绝并给出可读原因。
3
+ ### 修复:装上去就把整个 profile 弄成「异常」
4
+
5
+ 设备包一进 profile 的 `bundles`,它的 patch 就会插入 `dsh-foreman-node` 这一行,
6
+ 而这一行**还没有任何 config**——`0.1.0` 的 schema 把 `url` / `deviceId` / `token` 都写成 `.required()`,
7
+ 于是激活直接失败:
8
+
9
+ ```
10
+ invalid config:
11
+ - $.url missing required value (at url)
12
+ ```
13
+
14
+ 整个 profile 因此显示为异常,而不是「这个设备还没配好」。
15
+
16
+ 现在这三项都有空字符串默认值,`apply` 在缺值时**干净地不激活**,只记一行说明缺哪几项、
17
+ 去哪儿填,profile 保持健康。值填齐后才开始拨号。
18
+ 只有空白的值也算缺——否则设备会一直拨一个空地址,看起来只是离线。
5
19
 
6
20
  ## 0.1.0 — 2026-11-07
7
21
 
package/lib/index.d.ts CHANGED
@@ -186,11 +186,11 @@ interface NodeRoot {
186
186
  }
187
187
  /** What the `dsh-foreman-node` entry in a profile's patch configures. */
188
188
  interface NodeConfig {
189
- /** The hub's stable address; the device dials out to it. */
189
+ /** The hub's stable address; the device dials out to it. Blank until someone fills it in. */
190
190
  url: string;
191
- /** This device's id, as registered on the hub with `Devices.add`. */
191
+ /** This device's id, as registered on the hub with `Devices.add`. Blank until someone fills it in. */
192
192
  deviceId: string;
193
- /** This device's token; sent in the hello and nowhere else. */
193
+ /** This device's token; sent in the hello and nowhere else. Blank until someone fills it in. */
194
194
  token: string;
195
195
  /** The projects this device can work on. */
196
196
  roots: NodeRoot[];
@@ -202,18 +202,34 @@ interface NodeConfig {
202
202
  /**
203
203
  * Configuration of one device, from the `dsh-foreman-node` entry in the profile's patch.
204
204
  *
205
+ * Everything is optional on purpose. A bundle patch inserts this row the moment the package is added to a profile,
206
+ * so `apply` runs before anyone has filled the values in — and a schema that demanded them would turn the whole
207
+ * profile red with "invalid config" on a fresh install. `apply` treats a blank address as "not configured yet":
208
+ * it says what is missing and stops, instead of dialling nowhere or refusing to activate.
209
+ *
205
210
  * The annotation names the schema type through the global `Schemastery` namespace rather than letting it be inferred:
206
211
  * an inferred type here names a `cosmokit` symbol the emitted declaration cannot reference portably.
207
212
  */
208
213
  declare const Config: Schemastery<NodeConfig>;
214
+ /**
215
+ * What this configuration still needs before the device can dial its hub, or undefined when it is complete.
216
+ *
217
+ * A missing value is reported by name so an operator can fill it in without reading the source; a value that is only
218
+ * whitespace counts as missing, because a device that dials a blank address retries forever and looks merely offline.
219
+ */
220
+ declare function missingConfiguration(config: NodeConfig): string | undefined;
209
221
  declare const name = "dsh-foreman-node";
210
222
  /** DSH services a job needs; all are the host's own, and none is a foreman one. */
211
223
  declare const inject: string[];
212
224
  /**
213
225
  * Start the device.
214
226
  *
227
+ * A package that has just been installed has its row in the profile patch with no values in it yet. Activating
228
+ * cleanly and saying what is still missing keeps the whole profile healthy; throwing here is what made a fresh
229
+ * install report the profile as broken.
230
+ *
215
231
  * The plugin returns once the connection is attempted, not once it succeeds: a device must boot and stay usable
216
232
  * while its hub is unreachable, and the client keeps retrying in the background.
217
233
  */
218
234
  declare function apply(ctx: Context, config: NodeConfig): Promise<void>;
219
- export { Config, type Frame, type FrameAttachment, JobSessions, NodeClient, NodeConfig, NodeRoot, REPORT_STATES, type Root, VERSION, apply, foremanReportTool, inject, name };
235
+ export { Config, type Frame, type FrameAttachment, JobSessions, NodeClient, NodeConfig, NodeRoot, REPORT_STATES, type Root, VERSION, apply, foremanReportTool, inject, missingConfiguration, name };
package/lib/index.js CHANGED
@@ -477,13 +477,18 @@ const VERSION = "0.1.0";
477
477
  /**
478
478
  * Configuration of one device, from the `dsh-foreman-node` entry in the profile's patch.
479
479
  *
480
+ * Everything is optional on purpose. A bundle patch inserts this row the moment the package is added to a profile,
481
+ * so `apply` runs before anyone has filled the values in — and a schema that demanded them would turn the whole
482
+ * profile red with "invalid config" on a fresh install. `apply` treats a blank address as "not configured yet":
483
+ * it says what is missing and stops, instead of dialling nowhere or refusing to activate.
484
+ *
480
485
  * The annotation names the schema type through the global `Schemastery` namespace rather than letting it be inferred:
481
486
  * an inferred type here names a `cosmokit` symbol the emitted declaration cannot reference portably.
482
487
  */
483
488
  const Config = s.object({
484
- url: s.string().required().description("The hub's address, such as wss://foreman.example.com. The device dials out to it."),
485
- deviceId: s.string().required().description("This device's id, as registered on the hub with `Devices.add`."),
486
- token: s.string().required().role("secret").description("This device's token; it is sent in the hello frame and nowhere else."),
489
+ url: s.string().default("").description("The hub's address, such as wss://foreman.example.com. The device dials out to it."),
490
+ deviceId: s.string().default("").description("This device's id, as registered on the hub with `Devices.add`."),
491
+ token: s.string().default("").role("secret").description("This device's token; it is sent in the hello frame and nowhere else."),
487
492
  roots: s.array(s.object({
488
493
  project: s.string().required(),
489
494
  path: s.string().required()
@@ -491,6 +496,20 @@ const Config = s.object({
491
496
  heartbeatMs: s.natural().default(15e3).description("How often to tell the hub this device is alive, in milliseconds."),
492
497
  reconnectMs: s.natural().default(1e3).description("Delay before the first reconnect attempt, in milliseconds.")
493
498
  });
499
+ /**
500
+ * What this configuration still needs before the device can dial its hub, or undefined when it is complete.
501
+ *
502
+ * A missing value is reported by name so an operator can fill it in without reading the source; a value that is only
503
+ * whitespace counts as missing, because a device that dials a blank address retries forever and looks merely offline.
504
+ */
505
+ function missingConfiguration(config) {
506
+ const missing = [];
507
+ if (config.url.trim() === "") missing.push("url");
508
+ if (config.deviceId.trim() === "") missing.push("deviceId");
509
+ if (config.token.trim() === "") missing.push("token");
510
+ if (missing.length === 0) return void 0;
511
+ return `dsh-foreman-node is not configured yet: ${missing.join(", ")} missing. Set them on the dsh-foreman-node row in the profile patch (the hub prints the token when you register the device).`;
512
+ }
494
513
  const name = "dsh-foreman-node";
495
514
  /** DSH services a job needs; all are the host's own, and none is a foreman one. */
496
515
  const inject = [
@@ -504,10 +523,19 @@ const inject = [
504
523
  /**
505
524
  * Start the device.
506
525
  *
526
+ * A package that has just been installed has its row in the profile patch with no values in it yet. Activating
527
+ * cleanly and saying what is still missing keeps the whole profile healthy; throwing here is what made a fresh
528
+ * install report the profile as broken.
529
+ *
507
530
  * The plugin returns once the connection is attempted, not once it succeeds: a device must boot and stay usable
508
531
  * while its hub is unreachable, and the client keeps retrying in the background.
509
532
  */
510
533
  async function apply(ctx, config) {
534
+ const missing = missingConfiguration(config);
535
+ if (missing !== void 0) {
536
+ ctx.logger.info("%s", missing);
537
+ return;
538
+ }
511
539
  const jobs = new JobSessions(ctx);
512
540
  const client = new NodeClient({
513
541
  url: config.url,
@@ -570,4 +598,4 @@ async function receive(ctx, jobs, client, frame) {
570
598
  });
571
599
  }
572
600
  }
573
- export { Config, JobSessions, NodeClient, REPORT_STATES, VERSION, apply, foremanReportTool, inject, name };
601
+ export { Config, JobSessions, NodeClient, REPORT_STATES, VERSION, apply, foremanReportTool, inject, missingConfiguration, name };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-foreman-node",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Device side of dsh-foreman: connects out to a foreman hub, takes a job into a local DSH session, reports back. No ledger, no policy, no database.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
package/src/index.ts CHANGED
@@ -24,11 +24,11 @@ export interface NodeRoot { project: string; path: string }
24
24
 
25
25
  /** What the `dsh-foreman-node` entry in a profile's patch configures. */
26
26
  export interface NodeConfig {
27
- /** The hub's stable address; the device dials out to it. */
27
+ /** The hub's stable address; the device dials out to it. Blank until someone fills it in. */
28
28
  url: string
29
- /** This device's id, as registered on the hub with `Devices.add`. */
29
+ /** This device's id, as registered on the hub with `Devices.add`. Blank until someone fills it in. */
30
30
  deviceId: string
31
- /** This device's token; sent in the hello and nowhere else. */
31
+ /** This device's token; sent in the hello and nowhere else. Blank until someone fills it in. */
32
32
  token: string
33
33
  /** The projects this device can work on. */
34
34
  roots: NodeRoot[]
@@ -41,19 +41,40 @@ export interface NodeConfig {
41
41
  /**
42
42
  * Configuration of one device, from the `dsh-foreman-node` entry in the profile's patch.
43
43
  *
44
+ * Everything is optional on purpose. A bundle patch inserts this row the moment the package is added to a profile,
45
+ * so `apply` runs before anyone has filled the values in — and a schema that demanded them would turn the whole
46
+ * profile red with "invalid config" on a fresh install. `apply` treats a blank address as "not configured yet":
47
+ * it says what is missing and stops, instead of dialling nowhere or refusing to activate.
48
+ *
44
49
  * The annotation names the schema type through the global `Schemastery` namespace rather than letting it be inferred:
45
50
  * an inferred type here names a `cosmokit` symbol the emitted declaration cannot reference portably.
46
51
  */
47
52
  export const Config: Schemastery<NodeConfig> = s.object({
48
- url: s.string().required().description('The hub\'s address, such as wss://foreman.example.com. The device dials out to it.'),
49
- deviceId: s.string().required().description('This device\'s id, as registered on the hub with `Devices.add`.'),
50
- token: s.string().required().role('secret').description('This device\'s token; it is sent in the hello frame and nowhere else.'),
53
+ url: s.string().default('').description('The hub\'s address, such as wss://foreman.example.com. The device dials out to it.'),
54
+ deviceId: s.string().default('').description('This device\'s id, as registered on the hub with `Devices.add`.'),
55
+ token: s.string().default('').role('secret').description('This device\'s token; it is sent in the hello frame and nowhere else.'),
51
56
  roots: s.array(s.object({ project: s.string().required(), path: s.string().required() })).default([])
52
57
  .description('Projects this device can work on: a logical name and this machine\'s own absolute path, kept verbatim.'),
53
58
  heartbeatMs: s.natural().default(15_000).description('How often to tell the hub this device is alive, in milliseconds.'),
54
59
  reconnectMs: s.natural().default(1_000).description('Delay before the first reconnect attempt, in milliseconds.'),
55
60
  })
56
61
 
62
+ /**
63
+ * What this configuration still needs before the device can dial its hub, or undefined when it is complete.
64
+ *
65
+ * A missing value is reported by name so an operator can fill it in without reading the source; a value that is only
66
+ * whitespace counts as missing, because a device that dials a blank address retries forever and looks merely offline.
67
+ */
68
+ export function missingConfiguration(config: NodeConfig): string | undefined {
69
+ const missing: string[] = []
70
+ if (config.url.trim() === '') missing.push('url')
71
+ if (config.deviceId.trim() === '') missing.push('deviceId')
72
+ if (config.token.trim() === '') missing.push('token')
73
+ if (missing.length === 0) return undefined
74
+ return `dsh-foreman-node is not configured yet: ${missing.join(', ')} ` +
75
+ `missing. Set them on the dsh-foreman-node row in the profile patch (the hub prints the token when you register the device).`
76
+ }
77
+
57
78
  export const name = 'dsh-foreman-node'
58
79
  /** DSH services a job needs; all are the host's own, and none is a foreman one. */
59
80
  export const inject = ['sessionController', 'sessionPersistence', 'workspaceRegistry', 'permissionPresets', 'tools', 'agents']
@@ -61,10 +82,19 @@ export const inject = ['sessionController', 'sessionPersistence', 'workspaceRegi
61
82
  /**
62
83
  * Start the device.
63
84
  *
85
+ * A package that has just been installed has its row in the profile patch with no values in it yet. Activating
86
+ * cleanly and saying what is still missing keeps the whole profile healthy; throwing here is what made a fresh
87
+ * install report the profile as broken.
88
+ *
64
89
  * The plugin returns once the connection is attempted, not once it succeeds: a device must boot and stay usable
65
90
  * while its hub is unreachable, and the client keeps retrying in the background.
66
91
  */
67
92
  export async function apply(ctx: Context, config: NodeConfig): Promise<void> {
93
+ const missing = missingConfiguration(config)
94
+ if (missing !== undefined) {
95
+ ctx.logger.info('%s', missing)
96
+ return
97
+ }
68
98
  const jobs = new JobSessions(ctx)
69
99
  const client = new NodeClient({
70
100
  url: config.url,