javascript-solid-server 0.0.159 → 0.0.160

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.
@@ -358,7 +358,10 @@
358
358
  "Bash(rm LICENSE.full)",
359
359
  "Bash(awk -F: '{print $1,$2}')",
360
360
  "Bash(awk -F: '{print $1}')",
361
- "WebFetch(domain:www.gitfork.app)"
361
+ "WebFetch(domain:www.gitfork.app)",
362
+ "Bash(JSS_SINGLE_USER_PASSWORD=hunter2 timeout 3 node bin/jss.js start --port 4581 --root /tmp/jss-103 --single-user --single-user-name alice --idp)",
363
+ "Bash(JSS_SINGLE_USER_PASSWORD=hunter2 timeout 2 node bin/jss.js start --port 4581 --root /tmp/jss-103 --single-user-name alice --idp)",
364
+ "Bash(JSS_SINGLE_USER_PASSWORD=hunter2 timeout 3 node bin/jss.js start --port 4581 --root /tmp/jss-103-sanity --single-user-name alice --single-user --idp)"
362
365
  ]
363
366
  }
364
367
  }
package/bin/jss.js CHANGED
@@ -32,6 +32,49 @@ program
32
32
  .description('JavaScript Solid Server - A minimal, fast, JSON-LD native Solid server')
33
33
  .version(pkg.version);
34
34
 
35
+ /**
36
+ * Convert a camelCase option name back to its kebab-case CLI form for
37
+ * error messages (`singleUserName` → `single-user-name`).
38
+ */
39
+ function camelToKebab (name) {
40
+ return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
41
+ }
42
+
43
+ /**
44
+ * Reject any option value that looks like another flag (#103).
45
+ *
46
+ * Commander happily consumes the next argv as a value, so
47
+ * `jss start --single-user-name --idp`
48
+ * silently sets `singleUserName="--idp"` and the IdP flag is lost.
49
+ * This validator runs as a `preAction` hook for every subcommand, so
50
+ * any option with a missing value gets a clear error instead of a
51
+ * confusing downstream failure ("issuer has no registration endpoint",
52
+ * "Single-user: --idp" in the banner, etc.).
53
+ */
54
+ program.hook('preAction', (_thisCommand, actionCommand) => {
55
+ const opts = actionCommand.opts();
56
+ for (const [key, value] of Object.entries(opts)) {
57
+ const flag = camelToKebab(key);
58
+ if (typeof value === 'string' && value.startsWith('--')) {
59
+ console.error(
60
+ `Error: --${flag} value "${value}" looks like a flag, not a value.\n` +
61
+ `Hint: did you forget to provide a value? e.g. --${flag} someValue`
62
+ );
63
+ process.exit(1);
64
+ }
65
+ // Numeric options (parseInt-coerced like --port) silently produce
66
+ // NaN when given a flag like `--idp`. Catch that too — same root
67
+ // cause, different surface.
68
+ if (typeof value === 'number' && Number.isNaN(value)) {
69
+ console.error(
70
+ `Error: --${flag} got a non-numeric value (parsed as NaN).\n` +
71
+ `Hint: did you forget to provide a number? e.g. --${flag} 8080`
72
+ );
73
+ process.exit(1);
74
+ }
75
+ }
76
+ });
77
+
35
78
  /**
36
79
  * Start command
37
80
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.159",
3
+ "version": "0.0.160",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Regression tests for #103 — `bin/jss.js` must reject option values
3
+ * that look like flags (e.g. `--single-user-name --idp`) instead of
4
+ * silently using `--idp` as the username and breaking IdP setup.
5
+ *
6
+ * These spawn the CLI as a subprocess and assert exit-code + stderr.
7
+ */
8
+
9
+ import { describe, it } from 'node:test';
10
+ import assert from 'node:assert';
11
+ import { spawnSync } from 'node:child_process';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const BIN = path.join(__dirname, '..', 'bin', 'jss.js');
18
+
19
+ // Timeout cap so the suite can never hang if the validator regresses
20
+ // and `start` actually tries to bind a port instead of exiting early.
21
+ const RUN_TIMEOUT_MS = 10_000;
22
+
23
+ function runCli(args) {
24
+ const r = spawnSync(process.execPath, [BIN, ...args], {
25
+ encoding: 'utf8',
26
+ timeout: RUN_TIMEOUT_MS,
27
+ killSignal: 'SIGKILL'
28
+ });
29
+ // spawnSync sets `signal` to the kill signal when timeout fires. Treat
30
+ // that as a hard test failure rather than letting downstream
31
+ // assertions on stderr accidentally pass.
32
+ assert.strictEqual(
33
+ r.signal, null,
34
+ `CLI did not exit within ${RUN_TIMEOUT_MS}ms — likely the preAction ` +
35
+ `validator regressed and \`start\` is actually trying to listen. ` +
36
+ `args: ${JSON.stringify(args)}; partial stderr: ${r.stderr}`
37
+ );
38
+ return r;
39
+ }
40
+
41
+ describe('bin/jss.js — flag-like option values (#103)', () => {
42
+ it('rejects `--single-user-name --idp` with a clear error', () => {
43
+ const r = runCli(['start', '--single-user-name', '--idp']);
44
+ assert.notStrictEqual(r.status, 0, 'exit code should be non-zero');
45
+ assert.match(r.stderr, /--single-user-name value "--idp" looks like a flag/);
46
+ assert.match(r.stderr, /Hint: did you forget to provide a value\?/);
47
+ });
48
+
49
+ it('rejects another option swallowing a flag (covers --idp-issuer too)', () => {
50
+ // Commander's behaviour: it greedily consumes the next argv as the
51
+ // value, which is the whole reason the bug exists. We use a flag
52
+ // commander doesn't know about so commander doesn't reroute it
53
+ // through its own argument-count error. The dummy name is
54
+ // collision-proof — if anyone ever adds a real `--bogus-...` flag
55
+ // matching this pattern, the duplication is the bigger problem.
56
+ const FAKE_FLAG = '--__jss103_unlikely_cli_option__';
57
+ const r = runCli(['start', '--idp-issuer', FAKE_FLAG]);
58
+ assert.notStrictEqual(r.status, 0);
59
+ assert.match(
60
+ r.stderr,
61
+ new RegExp(`--idp-issuer value "${FAKE_FLAG}" looks like a flag`)
62
+ );
63
+ });
64
+
65
+ it('rejects `--port --idp` (numeric option → NaN) with helpful error', () => {
66
+ const r = runCli(['start', '--port', '--idp']);
67
+ assert.notStrictEqual(r.status, 0);
68
+ assert.match(r.stderr, /--port got a non-numeric value/);
69
+ assert.match(r.stderr, /Hint: did you forget to provide a number\?/);
70
+ });
71
+
72
+ it('accepts a real value and reaches normal config processing', () => {
73
+ // --print-config exits 0 cleanly after dumping config; this proves
74
+ // the validator doesn't false-positive on legitimate values.
75
+ // Use os.tmpdir() rather than a hard-coded /tmp/... so the test is
76
+ // portable across platforms (and matches the rest of the suite).
77
+ const tmpRoot = path.join(os.tmpdir(), 'jss-103-sanity-doesnotneedtoexist');
78
+ const r = runCli(['start',
79
+ '--port', '4582',
80
+ '--root', tmpRoot,
81
+ '--single-user-name', 'alice',
82
+ '--print-config'
83
+ ]);
84
+ assert.strictEqual(r.status, 0,
85
+ `expected clean exit, got ${r.status}; stderr: ${r.stderr}`);
86
+ assert.match(r.stdout, /Configuration:/);
87
+ });
88
+ });