fb-slides 0.7.0-rc.1 → 0.7.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/CHANGELOG.md CHANGED
@@ -7,7 +7,7 @@ this — which makes it worth being able to see what arrived.
7
7
  Entries are written as the work lands, under **Unreleased**; the release commit
8
8
  that stamps the version renames that heading to the version and its date.
9
9
 
10
- ## Unreleased
10
+ ## 0.7.0 — 2026-09-07
11
11
 
12
12
  - **A `source ↗` button on demo slides.** `editor: true` in `slides.config.js`
13
13
  starts `code serve-web` — the web server built into VS Code — alongside the
@@ -20,8 +20,10 @@ that stamps the version renames that heading to the version and its date.
20
20
  The slide's address and the one in `servers:` need not match to the character:
21
21
  the exact URL is tried first, then the origin, so a demo embedded at `#/` or
22
22
  `/tools` still finds its folder.
23
- The editor's port — the deck's plus 100 — is checked before it starts, because
24
- `serve-web` has no `--strictPort` and two decks are enough to collide. A built
23
+ The editor listens on 7100 — clear of 3000, 4200, 5173 and the rest of the band
24
+ the demos want, since the editor must never land on the app it exists to open —
25
+ and walks up from there if something holds it, because `serve-web` has no
26
+ `--strictPort` and would otherwise half-bind a busy port. A built
25
27
  deck never carries the button: the link is a localhost address and a path on
26
28
  your disk. `--no-editor` turns it off for one run. When the editor cannot
27
29
  start — no `code` in `PATH`, port taken — the deck comes up as usual and says
@@ -17,13 +17,13 @@ export default {
17
17
  };
18
18
  ```
19
19
 
20
- That is the whole configuration. `dev` starts it alongside the deck, on the
21
- deck's port plus 100, and every demo slide grows the link.
20
+ That is the whole configuration. `dev` starts it alongside the deck, on port
21
+ 7100, and every demo slide grows the link.
22
22
 
23
23
  ```
24
24
  $ npm run dev
25
25
 
26
- ↑ code serve-web → http://localhost:4100/
26
+ ↑ code serve-web → http://localhost:7100/
27
27
 
28
28
  My Talk
29
29
  → http://localhost:4000/
@@ -32,10 +32,11 @@ $ npm run dev
32
32
  Both defaults move if they have to:
33
33
 
34
34
  ```js
35
- editor: { port: 5000, command: 'code-insiders' },
35
+ editor: { port: 7300, command: 'code-insiders' },
36
36
  ```
37
37
 
38
- `command` is a name in PATH. `code-insiders` and `cursor` answer `serve-web`
38
+ `command` is a name in PATH, or a path to the binary when the shell you start
39
+ the deck from does not carry it. `code-insiders` and `cursor` answer `serve-web`
39
40
  too; whatever you name has to be a VS Code CLI, because that subcommand is what
40
41
  this uses.
41
42
 
@@ -49,28 +50,30 @@ Start `dev` once at your desk after turning the key on. That is the whole
49
50
  precaution, and it is why `editor:` is off in a fresh deck rather than on.
50
51
 
51
52
  If `code` is not in PATH at all, the deck says so and carries on without the
52
- button:
53
+ button — see [When no slide has one](#when-no-slide-has-one) below.
53
54
 
54
- ```
55
- ⚠ code is not in PATH — the demo slides get no source button.
56
- In VS Code: Shell Command: Install 'code' command in PATH.
57
- ```
55
+ ## Why 7100
58
56
 
59
- ## When the port is taken
57
+ Because the demos own everything below it. 3000 is Next's default, 4200 is
58
+ `ng serve`'s, 5173 and 5174 are Vite's, 6006 is Storybook's, 8080 is everyone's.
59
+ The editor landing on the very app it exists to open is the one collision that
60
+ must not happen — and since `dev` starts the demo servers first, the editor is
61
+ the one that would lose.
60
62
 
61
- The default is the deck's port plus 100, and **two decks are enough to collide**:
62
- one on 4000 puts its editor on 4100, which is a perfectly ordinary port for the
63
- second deck to be on.
63
+ It is not derived from the deck's port for the same reason: a deck on 4100 would
64
+ put its editor on 4200, which is `ng serve` waiting to happen.
64
65
 
65
66
  `code serve-web` does not fail on a busy port the way the deck server does — a
66
67
  process holding the IPv6 side leaves the IPv4 side free, both bind, and
67
68
  `localhost` in the browser then lands on whichever the resolver picked. That is
68
69
  the failure you find out about on stage. So the port is checked before the
69
- editor is started, and a taken one is a message rather than a wrong tab:
70
+ editor starts, and a taken one moves the editor along: 7101, 7102, up to ten
71
+ tries. Nobody types this address — the page is handed it — so a different number
72
+ costs nothing. Only ten taken ports in a row is a message:
70
73
 
71
74
  ```
72
- ⚠ port 4100 is in use — no source button.
73
- Free it, or give the editor another one: `editor: { port: n }`.
75
+ ⚠ source ↗ off: ports 7100–7109 are all in use.
76
+ Give the editor one of its own: `editor: { port: n }`.
74
77
  ```
75
78
 
76
79
  ## Where a slide's code is found
package/lib/config.mjs CHANGED
@@ -21,16 +21,24 @@ const AUTO_STATIC = ['assets', 'demo', 'public', 'images', 'img'];
21
21
  // Never served, never published — heavy, private, or generated.
22
22
  export const ALWAYS_EXCLUDED = ['node_modules', '.git', '.angular', '.next', '.cache', '.DS_Store'];
23
23
 
24
+ // The editor's port. A fixed number rather than one derived from the deck's,
25
+ // because the band under 6000 belongs to the demos: 3000 is Next's, 4200 is
26
+ // `ng serve`'s, 5173 and 5174 are Vite's, 8080 is everyone's. An editor that
27
+ // lands on the app it exists to open is the one collision that must not happen
28
+ // — and `dev` starts the demo servers first, so it would be the editor that
29
+ // loses. Two decks at once are handled by walking up from here instead.
30
+ const EDITOR_PORT = 7100;
31
+
24
32
  // `editor: true` turns the source button on with the defaults; `{ port, command }`
25
- // changes them; leaving the key out is no button at all. The port follows the
26
- // deck's so two talks open at once do not fight over it, and the command is a
27
- // name in PATH — `code-insiders` and `cursor` answer `serve-web` too.
28
- const editorSpec = (value, deckPort) => {
33
+ // changes them; leaving the key out is no button at all. The command is a name in
34
+ // PATH — `code-insiders` and `cursor` answer `serve-web` too — or a path, for a
35
+ // shell whose PATH does not carry the editor.
36
+ const editorSpec = (value) => {
29
37
  if (!value) return null;
30
38
  const user = value === true ? {} : value;
31
39
  return {
32
40
  command: user.command ?? 'code',
33
- port: Number(user.port ?? deckPort + 100),
41
+ port: Number(user.port ?? EDITOR_PORT),
34
42
  };
35
43
  };
36
44
 
@@ -109,7 +117,7 @@ export const loadConfig = async (root = process.cwd(), overrides = {}) => {
109
117
  // `--no-editor` is its own flag rather than a part of `--no-servers`: that one
110
118
  // is for skipping a demo's dev server, and a demo that is not running is
111
119
  // exactly when you want to be able to open its code.
112
- config.editor = overrides.editor === false ? null : editorSpec(user.editor, config.port);
120
+ config.editor = overrides.editor === false ? null : editorSpec(user.editor);
113
121
  // The folder behind each demo embedded by URL, taken from `servers:` as the
114
122
  // project wrote it rather than from the list `dev` will start: `--no-servers`
115
123
  // empties that one, and a demo that is not running is exactly when its code is
package/lib/dev.mjs CHANGED
@@ -66,9 +66,7 @@ const startSideServer = (spec, root, children) => {
66
66
  // `serve-web` has no --strictPort, and it does not fail on a taken port the way
67
67
  // the deck server does: another process holding the IPv6 side leaves the IPv4
68
68
  // side free, both bind, and `localhost` in the browser then lands on whichever
69
- // the resolver picked. Two fb-slides decks are enough to arrange that — one on
70
- // 4000 puts its editor on 4100, which is a perfectly ordinary port for the
71
- // second deck. So ask first, and say which port to use instead.
69
+ // the resolver picked — the failure you find out about on stage. So ask first.
72
70
  const portInUse = (port) =>
73
71
  Promise.all(
74
72
  ['127.0.0.1', '::1'].map(
@@ -86,6 +84,17 @@ const portInUse = (port) =>
86
84
  ),
87
85
  ).then((answers) => answers.some(Boolean));
88
86
 
87
+ // A second deck, or anything else already sitting there, moves the editor along
88
+ // rather than turning it off: nobody types this address — the page is handed it.
89
+ const EDITOR_PORT_TRIES = 10;
90
+
91
+ const freePort = async (start) => {
92
+ for (let port = start; port < start + EDITOR_PORT_TRIES; port += 1) {
93
+ if (!(await portInUse(port))) return port;
94
+ }
95
+ return null;
96
+ };
97
+
89
98
  const hasCommand = (command) =>
90
99
  new Promise((done) => {
91
100
  const probe = spawn(command, ['--version'], { stdio: 'ignore' });
@@ -108,16 +117,17 @@ const startEditor = async (spec, children) => {
108
117
  };
109
118
  }
110
119
 
111
- if (await portInUse(spec.port)) {
120
+ const port = await freePort(spec.port);
121
+ if (port === null) {
112
122
  return {
113
123
  note: [
114
- `⚠ source ↗ off: port ${spec.port} is in use.`,
115
- `Free it, or give the editor another one: \`editor: { port: n }\`.`,
124
+ `⚠ source ↗ off: ports ${spec.port}–${spec.port + EDITOR_PORT_TRIES - 1} are all in use.`,
125
+ `Give the editor one of its own: \`editor: { port: n }\`.`,
116
126
  ],
117
127
  };
118
128
  }
119
129
 
120
- const url = `http://localhost:${spec.port}/`;
130
+ const url = `http://localhost:${port}/`;
121
131
  // First run downloads the server half of VS Code into ~/.vscode/cli, which is
122
132
  // a hundred megabytes and a minute — hence `stdio: 'inherit'`, so it happens
123
133
  // in front of whoever started the deck rather than silently before a talk.
@@ -127,7 +137,7 @@ const startEditor = async (spec, children) => {
127
137
  [
128
138
  'serve-web',
129
139
  '--port',
130
- String(spec.port),
140
+ String(port),
131
141
  // No token in the URL: the slide has to be able to link straight to a
132
142
  // folder, and this listens on localhost only.
133
143
  '--without-connection-token',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fb-slides",
3
- "version": "0.7.0-rc.1",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Markdown-driven reveal.js decks: live demo embeds, annotation, mermaid, and a zero-config dev server",
6
6
  "keywords": [
@@ -44,9 +44,10 @@ export default {
44
44
  // and nothing is embedded. Off by default because the very first run downloads
45
45
  // VS Code's server half (~100 MB) and you do not want to discover that on
46
46
  // stage: uncomment, run `dev` once at your desk, and it is cached from then on.
47
- // `{ port: 4100, command: 'code' }` to change either — the default port is the
48
- // deck's plus 100. `--no-editor` turns it off for one run, and a
49
- // built deck never has it: the link is a localhost address and a path on disk.
47
+ // `{ port: 7300, command: 'code' }` to change either — the default is 7100,
48
+ // which is clear of the ports the demos want. `--no-editor` turns it off for
49
+ // one run, and a built deck never has it: the link is a localhost address and
50
+ // a path on disk.
50
51
  editor: true,
51
52
 
52
53
  // The corner signature. Uncomment to show it.