@troylar/flightdeck 1.20.0 → 1.21.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 +15 -0
- package/bin/flightdeck.js +14 -4
- package/dist/flightdeck.html +477 -93
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- Generated by build.py from src/releases.json. Edit that file, not this one. -->
|
|
4
4
|
|
|
5
|
+
## 1.21.0 · Oct 20, 2026
|
|
6
|
+
|
|
7
|
+
Ready for teammates on Flightdeck 2.0.
|
|
8
|
+
|
|
9
|
+
### Improved
|
|
10
|
+
|
|
11
|
+
- **Files made with Flightdeck 2.0 say so** (Versions and runs). Versions and runs made with Flightdeck 2.0 say "Needs Flightdeck 2.0". A test run from Flightdeck 2.0 says so too. This copy still shows them by name and never changes them. *Where:* Process pages, Runs and the run itself
|
|
12
|
+
- **See what a newer Flightdeck wrote, and who is on an older one** (Folder health). Folder health lists each file a newer Flightdeck wrote, by kind, with the version it needs. It also names older copies of Flightdeck that answered connection checks or edited drafts here in the last 7 days, and says to move every computer to this version before anyone uses Flightdeck 2.0 features. *Where:* Folder health
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Decisions from a newer Flightdeck aren't read as older ones** (Follow-ups and corrections). A decision from a newer Flightdeck is no longer read as if it were an older one. It's listed as needing a newer Flightdeck, and the follow-up or correction stays as it was.
|
|
17
|
+
- **A "format:" line no longer passes for a requirement** (Checklist editor). A checklist with a "format:" or "features:" line can't be published until the line is removed, and the editor says why. Nothing in a checklist needs one.
|
|
18
|
+
- **Plans from a newer Flightdeck are named, never started on the wrong version** (Plans). A planned run saved by Flightdeck 2.0 for one exact version isn't shown as a plan here, so it can't start on the latest by mistake. Folder health lists it as needing Flightdeck 2.0.
|
|
19
|
+
|
|
5
20
|
## 1.20.0 · Oct 20, 2026
|
|
6
21
|
|
|
7
22
|
Let another app ask for a run.
|
package/bin/flightdeck.js
CHANGED
|
@@ -219,6 +219,14 @@ function merged(item) {
|
|
|
219
219
|
const old = region(item.now), fresh = region(item.text);
|
|
220
220
|
return old.marked ? norm(item.now).slice(0, old.start) + fresh.text + norm(item.now).slice(old.end) : item.text;
|
|
221
221
|
}
|
|
222
|
+
/* A record written by a newer Flightdeck (flightdeck-installed/2 on, or a must-understand feature): never read for meaning and
|
|
223
|
+
never rewritten. The same verdict as PF.contract.cleanInstalled (test/cli.test.js checks they agree). */
|
|
224
|
+
function installedNewer(r) {
|
|
225
|
+
if (!r || typeof r !== 'object' || Array.isArray(r)) return '';
|
|
226
|
+
const m = typeof r.format === 'string' ? /^([a-z][a-z0-9-]{1,59})\/([1-9]\d{0,3})$/.exec(r.format) : null;
|
|
227
|
+
if ((m && m[1] === 'flightdeck-installed' && m[2] !== '1') || (m && m[1] === 'flightdeck-installed' && (Array.isArray(r.features) ? r.features.length > 0 : r.features != null))) return 'was written by a newer Flightdeck' + (typeof r.version === 'string' && /^\d+\.\d+\.\d+$/.test(r.version) ? ' (' + r.version + ')' : '') + '.';
|
|
228
|
+
return '';
|
|
229
|
+
}
|
|
222
230
|
/* The folder's record, checked field by field. Anyone can edit it, so a record that isn't exactly what this command writes is
|
|
223
231
|
ignored (and said so), and only entries for files this version installs, with a well-formed hash, are read. */
|
|
224
232
|
function readRecord(t, keys) {
|
|
@@ -226,6 +234,8 @@ function readRecord(t, keys) {
|
|
|
226
234
|
if (text === null) return none;
|
|
227
235
|
let r = null;
|
|
228
236
|
try { r = JSON.parse(text); } catch (e) { /* reported below */ }
|
|
237
|
+
const newer = installedNewer(r);
|
|
238
|
+
if (newer) fail(path.join(t.dir, ...t.recordAt) + ' ' + newer + ' Nothing was changed. Use the newer Flightdeck package.');
|
|
229
239
|
const ok = r && typeof r === 'object' && !Array.isArray(r) && r.format === 'flightdeck-installed/1' && typeof r.version === 'string' && /^(\d+\.\d+\.\d+|dev)$/.test(r.version)
|
|
230
240
|
&& typeof r.at === 'string' && (r.by === 'cli' || r.by === 'app') && r.files && typeof r.files === 'object' && !Array.isArray(r.files)
|
|
231
241
|
&& Object.keys(r.files).every((k) => typeof r.files[k] === 'string' && /^[0-9a-f]{64}$/.test(r.files[k]));
|
|
@@ -699,8 +709,8 @@ function folderProblems(PF, d) {
|
|
|
699
709
|
(d.unreadable || []).forEach((u) => out.push({ path: u.path, kind: u.kind || 'unreadable', why: fileWhy(u.why) }));
|
|
700
710
|
(d.nofile || []).forEach((n) => out.push({ path: n.path, kind: 'nofile', why: 'The run folder has no run.json.' }));
|
|
701
711
|
(d.malformed || []).forEach((m) => out.push({ path: m.path, kind: m.kind || 'malformed', why: fileWhy(m.why) }));
|
|
702
|
-
(d.fuUnread || []).forEach((u) => out.push({ path: 'followups/' + u.process + '/' + u._file, kind: 'malformed', why: 'This file couldn\'t be read, or isn\'t a follow-up decision this copy can read.' }));
|
|
703
|
-
(d.corrUnread || []).forEach((u) => out.push({ path: u.dir + '/' + u.process + '/' + u._file, kind: 'malformed', why: 'This file couldn\'t be read, or isn\'t a decision this copy can read.' }));
|
|
712
|
+
(d.fuUnread || []).forEach((u) => out.push({ path: 'followups/' + u.process + '/' + u._file, kind: u.kind === 'newer' ? 'newer' : 'malformed', why: u.kind === 'newer' ? u.why : 'This file couldn\'t be read, or isn\'t a follow-up decision this copy can read.' }));
|
|
713
|
+
(d.corrUnread || []).forEach((u) => out.push({ path: u.dir + '/' + u.process + '/' + u._file, kind: u.kind === 'newer' ? 'newer' : 'malformed', why: u.kind === 'newer' ? u.why : 'This file couldn\'t be read, or isn\'t a decision this copy can read.' }));
|
|
704
714
|
(d.processes || []).forEach((proc) => (proc.pubs || []).forEach((p) => { if (!p.ok && !p._readFailed) { const x = A.pubProblem(p); out.push({ path: 'publications/' + proc.process + '/' + p.folder + '/', kind: x.kind, why: x.why }); } }));
|
|
705
715
|
(d.fuDecisions || []).forEach((x) => { const p = decisionDrift(x); if (p) out.push(p); });
|
|
706
716
|
const pw = peopleWhy(d.people);
|
|
@@ -768,7 +778,7 @@ async function readData(eng, adapter, req) {
|
|
|
768
778
|
const r = one.run, key = m[1] + '/' + m[2];
|
|
769
779
|
if (one.nofile) L.note('runs/' + key + '/run.json', 'nofile', 'The run folder has no run.json.');
|
|
770
780
|
take(one.bad.map((b) => ({ path: b.path, kind: b.kind || 'unreadable', why: fileWhy(b.why) })));
|
|
771
|
-
take(one.fuUnread.map((u) => ({ path: u.path, kind: u.kind || 'malformed', why: fileWhy(u.why) })));
|
|
781
|
+
take(one.fuUnread.map((u) => ({ path: u.path, kind: u.kind || 'malformed', why: u.kind === 'newer' ? u.why : fileWhy(u.why) })));
|
|
772
782
|
one.fuDecisions.forEach((x) => { const p = decisionDrift(x); if (p) take([p]); });
|
|
773
783
|
if (one.peopleWhy) L.note('people.json', one.peopleKind || 'unreadable', fileWhy(one.peopleWhy));
|
|
774
784
|
const pw = peopleWhy(one.people);
|
|
@@ -1465,4 +1475,4 @@ if (require.main === module) {
|
|
|
1465
1475
|
if (r && typeof r.then === 'function') r.then(done, failed); else done(r);
|
|
1466
1476
|
} catch (e) { failed(e); }
|
|
1467
1477
|
}
|
|
1468
|
-
module.exports = { _hooks, main, versionOf, cmp, skillFiles, norm, region, managed, merged, hash, baselineKey, examples, readAdapter, loadEngine, readData, requestData, writeRequest, EXIT };
|
|
1478
|
+
module.exports = { _hooks, main, installedNewer, versionOf, cmp, skillFiles, norm, region, managed, merged, hash, baselineKey, examples, readAdapter, loadEngine, readData, requestData, writeRequest, EXIT };
|