@deskwork/core 0.9.5
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/dist/body-state.d.ts +27 -0
- package/dist/body-state.d.ts.map +1 -0
- package/dist/body-state.js +62 -0
- package/dist/body-state.js.map +1 -0
- package/dist/calendar-mutations.d.ts +124 -0
- package/dist/calendar-mutations.d.ts.map +1 -0
- package/dist/calendar-mutations.js +305 -0
- package/dist/calendar-mutations.js.map +1 -0
- package/dist/calendar.d.ts +54 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +430 -0
- package/dist/calendar.js.map +1 -0
- package/dist/cli.d.ts +38 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +72 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +91 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +216 -0
- package/dist/config.js.map +1 -0
- package/dist/content-index.d.ts +74 -0
- package/dist/content-index.d.ts.map +1 -0
- package/dist/content-index.js +205 -0
- package/dist/content-index.js.map +1 -0
- package/dist/content-tree-fs-walk.d.ts +54 -0
- package/dist/content-tree-fs-walk.d.ts.map +1 -0
- package/dist/content-tree-fs-walk.js +112 -0
- package/dist/content-tree-fs-walk.js.map +1 -0
- package/dist/content-tree-helpers.d.ts +52 -0
- package/dist/content-tree-helpers.d.ts.map +1 -0
- package/dist/content-tree-helpers.js +116 -0
- package/dist/content-tree-helpers.js.map +1 -0
- package/dist/content-tree-types.d.ts +175 -0
- package/dist/content-tree-types.d.ts.map +1 -0
- package/dist/content-tree-types.js +10 -0
- package/dist/content-tree-types.js.map +1 -0
- package/dist/content-tree.d.ts +93 -0
- package/dist/content-tree.d.ts.map +1 -0
- package/dist/content-tree.js +385 -0
- package/dist/content-tree.js.map +1 -0
- package/dist/doctor/index.d.ts +11 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +10 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/project-rules.d.ts +59 -0
- package/dist/doctor/project-rules.d.ts.map +1 -0
- package/dist/doctor/project-rules.js +143 -0
- package/dist/doctor/project-rules.js.map +1 -0
- package/dist/doctor/rules/calendar-uuid-missing.d.ts +19 -0
- package/dist/doctor/rules/calendar-uuid-missing.d.ts.map +1 -0
- package/dist/doctor/rules/calendar-uuid-missing.js +176 -0
- package/dist/doctor/rules/calendar-uuid-missing.js.map +1 -0
- package/dist/doctor/rules/duplicate-id.d.ts +27 -0
- package/dist/doctor/rules/duplicate-id.d.ts.map +1 -0
- package/dist/doctor/rules/duplicate-id.js +157 -0
- package/dist/doctor/rules/duplicate-id.js.map +1 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.d.ts +40 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.d.ts.map +1 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.js +232 -0
- package/dist/doctor/rules/legacy-top-level-id-migration.js.map +1 -0
- package/dist/doctor/rules/missing-frontmatter-id.d.ts +45 -0
- package/dist/doctor/rules/missing-frontmatter-id.d.ts.map +1 -0
- package/dist/doctor/rules/missing-frontmatter-id.js +283 -0
- package/dist/doctor/rules/missing-frontmatter-id.js.map +1 -0
- package/dist/doctor/rules/orphan-frontmatter-id.d.ts +18 -0
- package/dist/doctor/rules/orphan-frontmatter-id.d.ts.map +1 -0
- package/dist/doctor/rules/orphan-frontmatter-id.js +154 -0
- package/dist/doctor/rules/orphan-frontmatter-id.js.map +1 -0
- package/dist/doctor/rules/schema-rejected.d.ts +20 -0
- package/dist/doctor/rules/schema-rejected.d.ts.map +1 -0
- package/dist/doctor/rules/schema-rejected.js +44 -0
- package/dist/doctor/rules/schema-rejected.js.map +1 -0
- package/dist/doctor/rules/slug-collision.d.ts +18 -0
- package/dist/doctor/rules/slug-collision.d.ts.map +1 -0
- package/dist/doctor/rules/slug-collision.js +65 -0
- package/dist/doctor/rules/slug-collision.js.map +1 -0
- package/dist/doctor/rules/workflow-stale.d.ts +20 -0
- package/dist/doctor/rules/workflow-stale.d.ts.map +1 -0
- package/dist/doctor/rules/workflow-stale.js +136 -0
- package/dist/doctor/rules/workflow-stale.js.map +1 -0
- package/dist/doctor/runner.d.ts +75 -0
- package/dist/doctor/runner.d.ts.map +1 -0
- package/dist/doctor/runner.js +289 -0
- package/dist/doctor/runner.js.map +1 -0
- package/dist/doctor/schema-patch.d.ts +21 -0
- package/dist/doctor/schema-patch.d.ts.map +1 -0
- package/dist/doctor/schema-patch.js +92 -0
- package/dist/doctor/schema-patch.js.map +1 -0
- package/dist/doctor/types.d.ts +185 -0
- package/dist/doctor/types.d.ts.map +1 -0
- package/dist/doctor/types.js +13 -0
- package/dist/doctor/types.js.map +1 -0
- package/dist/frontmatter.d.ts +103 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +306 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest-derive.d.ts +79 -0
- package/dist/ingest-derive.d.ts.map +1 -0
- package/dist/ingest-derive.js +299 -0
- package/dist/ingest-derive.js.map +1 -0
- package/dist/ingest-paths.d.ts +37 -0
- package/dist/ingest-paths.d.ts.map +1 -0
- package/dist/ingest-paths.js +176 -0
- package/dist/ingest-paths.js.map +1 -0
- package/dist/ingest.d.ts +162 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/ingest.js +269 -0
- package/dist/ingest.js.map +1 -0
- package/dist/journal.d.ts +49 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +113 -0
- package/dist/journal.js.map +1 -0
- package/dist/outline-split.d.ts +38 -0
- package/dist/outline-split.d.ts.map +1 -0
- package/dist/outline-split.js +84 -0
- package/dist/outline-split.js.map +1 -0
- package/dist/overrides.d.ts +83 -0
- package/dist/overrides.d.ts.map +1 -0
- package/dist/overrides.js +88 -0
- package/dist/overrides.js.map +1 -0
- package/dist/paths.d.ts +183 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +266 -0
- package/dist/paths.js.map +1 -0
- package/dist/remark-image-figure.mjs +77 -0
- package/dist/remark-strip-first-h1.mjs +26 -0
- package/dist/remark-strip-outline.mjs +44 -0
- package/dist/rename-slug.d.ts +49 -0
- package/dist/rename-slug.d.ts.map +1 -0
- package/dist/rename-slug.js +161 -0
- package/dist/rename-slug.js.map +1 -0
- package/dist/review/handlers.d.ts +55 -0
- package/dist/review/handlers.d.ts.map +1 -0
- package/dist/review/handlers.js +307 -0
- package/dist/review/handlers.js.map +1 -0
- package/dist/review/index.d.ts +14 -0
- package/dist/review/index.d.ts.map +1 -0
- package/dist/review/index.js +13 -0
- package/dist/review/index.js.map +1 -0
- package/dist/review/journal-mappers.d.ts +35 -0
- package/dist/review/journal-mappers.d.ts.map +1 -0
- package/dist/review/journal-mappers.js +48 -0
- package/dist/review/journal-mappers.js.map +1 -0
- package/dist/review/pipeline.d.ts +79 -0
- package/dist/review/pipeline.d.ts.map +1 -0
- package/dist/review/pipeline.js +234 -0
- package/dist/review/pipeline.js.map +1 -0
- package/dist/review/render.d.ts +27 -0
- package/dist/review/render.d.ts.map +1 -0
- package/dist/review/render.js +42 -0
- package/dist/review/render.js.map +1 -0
- package/dist/review/report.d.ts +50 -0
- package/dist/review/report.d.ts.map +1 -0
- package/dist/review/report.js +164 -0
- package/dist/review/report.js.map +1 -0
- package/dist/review/result.d.ts +12 -0
- package/dist/review/result.d.ts.map +1 -0
- package/dist/review/result.js +12 -0
- package/dist/review/result.js.map +1 -0
- package/dist/review/start-handlers.d.ts +62 -0
- package/dist/review/start-handlers.d.ts.map +1 -0
- package/dist/review/start-handlers.js +223 -0
- package/dist/review/start-handlers.js.map +1 -0
- package/dist/review/types.d.ts +169 -0
- package/dist/review/types.d.ts.map +1 -0
- package/dist/review/types.js +26 -0
- package/dist/review/types.js.map +1 -0
- package/dist/review/workflow-paths.d.ts +68 -0
- package/dist/review/workflow-paths.d.ts.map +1 -0
- package/dist/review/workflow-paths.js +112 -0
- package/dist/review/workflow-paths.js.map +1 -0
- package/dist/scaffold.d.ts +67 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +122 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/scrapbook.d.ts +229 -0
- package/dist/scrapbook.d.ts.map +1 -0
- package/dist/scrapbook.js +500 -0
- package/dist/scrapbook.js.map +1 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +120 -0
- package/dist/types.js.map +1 -0
- package/package.json +160 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-tree filesystem walk — pure recursive directory scan.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `content-tree.ts` (Phase 19c) to keep that file under
|
|
5
|
+
* the project's 500-line guideline. The walk is read-only and produces
|
|
6
|
+
* one `FsWalkEntry` per directory beneath `<contentDir>` — the input
|
|
7
|
+
* to the tree assembly's fs-primary inversion.
|
|
8
|
+
*
|
|
9
|
+
* No knowledge of the calendar or content index lives here.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { resolveContentDir } from "./paths.js";
|
|
14
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
15
|
+
/** Match the index/README basenames the studio recognizes as a node marker. */
|
|
16
|
+
export const INDEX_BASENAMES = new Set([
|
|
17
|
+
'index.md', 'index.mdx', 'index.markdown',
|
|
18
|
+
]);
|
|
19
|
+
const README_BASENAMES = new Set([
|
|
20
|
+
'readme.md', 'readme.mdx', 'readme.markdown',
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Index file basenames the host's default template (`<path>/index.md`)
|
|
24
|
+
* recognises. Used to decide `hasOwnIndex` for tracked entries when the
|
|
25
|
+
* content index doesn't bind them to a specific file (pre-doctor state).
|
|
26
|
+
*/
|
|
27
|
+
export const TEMPLATE_INDEX_BASENAMES = [
|
|
28
|
+
'index.md',
|
|
29
|
+
'index.mdx',
|
|
30
|
+
'index.markdown',
|
|
31
|
+
];
|
|
32
|
+
function readTitleFromMarkdown(absPath) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(absPath, 'utf-8');
|
|
35
|
+
const parsed = parseFrontmatter(raw);
|
|
36
|
+
const t = parsed.data.title;
|
|
37
|
+
if (typeof t === 'string' && t.trim().length > 0)
|
|
38
|
+
return t.trim();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Unreadable / unparseable — fall through.
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Default filesystem walk — recursively scan a site's contentDir for
|
|
47
|
+
* directories. Returns one `FsWalkEntry` per directory beneath
|
|
48
|
+
* contentDir (not contentDir itself). Skips dotfiles and the
|
|
49
|
+
* conventional non-content names (`scrapbook`, `node_modules`, etc.).
|
|
50
|
+
*
|
|
51
|
+
* Per-directory the walk records whether an `index.md` / `README.md`
|
|
52
|
+
* is present and (when present) reads the frontmatter `title`.
|
|
53
|
+
*/
|
|
54
|
+
export function defaultFsWalk(projectRoot, config, site) {
|
|
55
|
+
const root = resolveContentDir(projectRoot, config, site);
|
|
56
|
+
if (!existsSync(root))
|
|
57
|
+
return [];
|
|
58
|
+
const out = [];
|
|
59
|
+
const SKIP = new Set(['scrapbook', 'node_modules', 'dist', '.git']);
|
|
60
|
+
const visit = (dirAbs, pathSoFar) => {
|
|
61
|
+
let names;
|
|
62
|
+
try {
|
|
63
|
+
names = readdirSync(dirAbs);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let hasIndex = false;
|
|
69
|
+
let hasReadme = false;
|
|
70
|
+
let titleSource = null;
|
|
71
|
+
for (const name of names) {
|
|
72
|
+
const lower = name.toLowerCase();
|
|
73
|
+
if (INDEX_BASENAMES.has(lower)) {
|
|
74
|
+
hasIndex = true;
|
|
75
|
+
if (titleSource === null)
|
|
76
|
+
titleSource = join(dirAbs, name);
|
|
77
|
+
}
|
|
78
|
+
else if (README_BASENAMES.has(lower)) {
|
|
79
|
+
hasReadme = true;
|
|
80
|
+
// Prefer index.md as the title source when both exist; only
|
|
81
|
+
// fall back to README when there is no index.
|
|
82
|
+
if (titleSource === null && !hasIndex)
|
|
83
|
+
titleSource = join(dirAbs, name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (pathSoFar !== '') {
|
|
87
|
+
const title = titleSource ? readTitleFromMarkdown(titleSource) : null;
|
|
88
|
+
out.push({ slug: pathSoFar, hasIndex, hasReadme, title });
|
|
89
|
+
}
|
|
90
|
+
for (const name of names) {
|
|
91
|
+
if (name.startsWith('.'))
|
|
92
|
+
continue;
|
|
93
|
+
if (SKIP.has(name.toLowerCase()))
|
|
94
|
+
continue;
|
|
95
|
+
const childAbs = join(dirAbs, name);
|
|
96
|
+
let childStat;
|
|
97
|
+
try {
|
|
98
|
+
childStat = statSync(childAbs);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!childStat.isDirectory())
|
|
104
|
+
continue;
|
|
105
|
+
const childPath = pathSoFar === '' ? name : `${pathSoFar}/${name}`;
|
|
106
|
+
visit(childAbs, childPath);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
visit(root, '');
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=content-tree-fs-walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-tree-fs-walk.js","sourceRoot":"","sources":["../src/content-tree-fs-walk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers for `content-tree.ts`. Path-shape utilities, id-binding
|
|
3
|
+
* lookups, and the `hasOwnIndex` decision. Extracted to keep the main
|
|
4
|
+
* tree-assembly module under the project's 500-line guideline.
|
|
5
|
+
*
|
|
6
|
+
* No public API here — the tree builder re-exports what callers need.
|
|
7
|
+
*/
|
|
8
|
+
import type { CalendarEntry } from './types.ts';
|
|
9
|
+
import type { ContentIndex } from './content-index.ts';
|
|
10
|
+
/** Last segment of a `/`-separated path. */
|
|
11
|
+
export declare function leafOfPath(path: string): string;
|
|
12
|
+
/** Every prefix path of a `/`-separated path, excluding the path itself. */
|
|
13
|
+
export declare function ancestorsOf(path: string): string[];
|
|
14
|
+
/** First segment of a `/`-separated path. */
|
|
15
|
+
export declare function rootSegment(path: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Decide `hasOwnIndex` for a tracked entry.
|
|
18
|
+
*
|
|
19
|
+
* Order:
|
|
20
|
+
* 1. The content index binds the entry to a real file → `true`.
|
|
21
|
+
* 2. The fs walk found an index/README at this path → `true`.
|
|
22
|
+
* 3. A template-path file actually exists on disk → `true` (covers
|
|
23
|
+
* the pre-bind case where the file is at `<path>/index.md` but
|
|
24
|
+
* the walk didn't surface this directory because we synthesized
|
|
25
|
+
* it from the calendar — should be rare post-19c).
|
|
26
|
+
* 4. Calendar-only entry (no fs node, no id binding): default to
|
|
27
|
+
* `true` because the host renderer's template implies a file at
|
|
28
|
+
* `<path>/index.md`. Pre-19c behavior for ghost entries.
|
|
29
|
+
*/
|
|
30
|
+
export declare function entryHasOwnIndex(contentDir: string, entryPath: string, fsHasIndex: boolean, fsHasReadme: boolean, boundFile: string | undefined, hasFsDir: boolean): boolean;
|
|
31
|
+
/** Return the most-recent ISO mtime across two values, or null when both are null. */
|
|
32
|
+
export declare function pickLatestMtime(a: string | null, b: string | null): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* If the relative path ends with a recognized index file basename
|
|
35
|
+
* (`index.md`, `index.mdx`, `index.markdown`), return the parent
|
|
36
|
+
* directory path. Otherwise return the path unchanged. Used to map
|
|
37
|
+
* `projects/the-outbound/index.md` (the file) → `projects/the-outbound`
|
|
38
|
+
* (the tree node).
|
|
39
|
+
*
|
|
40
|
+
* Files at flat paths (e.g. `my-flat-post.md`) keep their full path
|
|
41
|
+
* minus the extension as the node key. This matches today's
|
|
42
|
+
* audiocontrol shape where the slug is the basename.
|
|
43
|
+
*/
|
|
44
|
+
export declare function stripIndexBasename(relPath: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Find the fs path that the content index binds to this entry's id, or
|
|
47
|
+
* null when the entry has no id or its id isn't in the index.
|
|
48
|
+
*/
|
|
49
|
+
export declare function findIdBoundPath(entry: CalendarEntry, index: ContentIndex): string | null;
|
|
50
|
+
/** Absolute path to the file the index binds to this entry, or undefined. */
|
|
51
|
+
export declare function idBoundFile(entry: CalendarEntry, index: ContentIndex): string | undefined;
|
|
52
|
+
//# sourceMappingURL=content-tree-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-tree-helpers.d.ts","sourceRoot":"","sources":["../src/content-tree-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAMvD,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOlD;AAED,6CAA6C;AAC7C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,OAAO,EACnB,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,OAAO,GAChB,OAAO,CAYT;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAC7B,CAAC,EAAE,MAAM,GAAG,IAAI,EAChB,CAAC,EAAE,MAAM,GAAG,IAAI,GACf,MAAM,GAAG,IAAI,CAIf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAa1D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,YAAY,GAClB,MAAM,GAAG,IAAI,CAQf;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CACzB,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,YAAY,GAClB,MAAM,GAAG,SAAS,CAGpB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers for `content-tree.ts`. Path-shape utilities, id-binding
|
|
3
|
+
* lookups, and the `hasOwnIndex` decision. Extracted to keep the main
|
|
4
|
+
* tree-assembly module under the project's 500-line guideline.
|
|
5
|
+
*
|
|
6
|
+
* No public API here — the tree builder re-exports what callers need.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { INDEX_BASENAMES, TEMPLATE_INDEX_BASENAMES, } from "./content-tree-fs-walk.js";
|
|
11
|
+
/** Last segment of a `/`-separated path. */
|
|
12
|
+
export function leafOfPath(path) {
|
|
13
|
+
const idx = path.lastIndexOf('/');
|
|
14
|
+
return idx < 0 ? path : path.slice(idx + 1);
|
|
15
|
+
}
|
|
16
|
+
/** Every prefix path of a `/`-separated path, excluding the path itself. */
|
|
17
|
+
export function ancestorsOf(path) {
|
|
18
|
+
const segments = path.split('/');
|
|
19
|
+
const out = [];
|
|
20
|
+
for (let i = 1; i < segments.length; i++) {
|
|
21
|
+
out.push(segments.slice(0, i).join('/'));
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** First segment of a `/`-separated path. */
|
|
26
|
+
export function rootSegment(path) {
|
|
27
|
+
const idx = path.indexOf('/');
|
|
28
|
+
return idx < 0 ? path : path.slice(0, idx);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Decide `hasOwnIndex` for a tracked entry.
|
|
32
|
+
*
|
|
33
|
+
* Order:
|
|
34
|
+
* 1. The content index binds the entry to a real file → `true`.
|
|
35
|
+
* 2. The fs walk found an index/README at this path → `true`.
|
|
36
|
+
* 3. A template-path file actually exists on disk → `true` (covers
|
|
37
|
+
* the pre-bind case where the file is at `<path>/index.md` but
|
|
38
|
+
* the walk didn't surface this directory because we synthesized
|
|
39
|
+
* it from the calendar — should be rare post-19c).
|
|
40
|
+
* 4. Calendar-only entry (no fs node, no id binding): default to
|
|
41
|
+
* `true` because the host renderer's template implies a file at
|
|
42
|
+
* `<path>/index.md`. Pre-19c behavior for ghost entries.
|
|
43
|
+
*/
|
|
44
|
+
export function entryHasOwnIndex(contentDir, entryPath, fsHasIndex, fsHasReadme, boundFile, hasFsDir) {
|
|
45
|
+
if (boundFile !== undefined)
|
|
46
|
+
return true;
|
|
47
|
+
if (fsHasIndex)
|
|
48
|
+
return true;
|
|
49
|
+
if (fsHasReadme)
|
|
50
|
+
return true;
|
|
51
|
+
for (const basename of TEMPLATE_INDEX_BASENAMES) {
|
|
52
|
+
if (existsSync(join(contentDir, entryPath, basename)))
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
// No fs evidence at all — calendar-only ghost. Assume the host
|
|
56
|
+
// template (`<path>/index.md`) so detail-panel paths remain
|
|
57
|
+
// discoverable for entries that haven't been scaffolded yet.
|
|
58
|
+
if (!hasFsDir)
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/** Return the most-recent ISO mtime across two values, or null when both are null. */
|
|
63
|
+
export function pickLatestMtime(a, b) {
|
|
64
|
+
if (a === null)
|
|
65
|
+
return b;
|
|
66
|
+
if (b === null)
|
|
67
|
+
return a;
|
|
68
|
+
return a > b ? a : b;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* If the relative path ends with a recognized index file basename
|
|
72
|
+
* (`index.md`, `index.mdx`, `index.markdown`), return the parent
|
|
73
|
+
* directory path. Otherwise return the path unchanged. Used to map
|
|
74
|
+
* `projects/the-outbound/index.md` (the file) → `projects/the-outbound`
|
|
75
|
+
* (the tree node).
|
|
76
|
+
*
|
|
77
|
+
* Files at flat paths (e.g. `my-flat-post.md`) keep their full path
|
|
78
|
+
* minus the extension as the node key. This matches today's
|
|
79
|
+
* audiocontrol shape where the slug is the basename.
|
|
80
|
+
*/
|
|
81
|
+
export function stripIndexBasename(relPath) {
|
|
82
|
+
const segments = relPath.split('/');
|
|
83
|
+
const last = segments[segments.length - 1].toLowerCase();
|
|
84
|
+
if (INDEX_BASENAMES.has(last)) {
|
|
85
|
+
return segments.slice(0, -1).join('/');
|
|
86
|
+
}
|
|
87
|
+
const dotIdx = last.lastIndexOf('.');
|
|
88
|
+
if (dotIdx > 0) {
|
|
89
|
+
const stripped = segments.slice(0, -1);
|
|
90
|
+
stripped.push(last.slice(0, dotIdx));
|
|
91
|
+
return stripped.join('/');
|
|
92
|
+
}
|
|
93
|
+
return relPath;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Find the fs path that the content index binds to this entry's id, or
|
|
97
|
+
* null when the entry has no id or its id isn't in the index.
|
|
98
|
+
*/
|
|
99
|
+
export function findIdBoundPath(entry, index) {
|
|
100
|
+
if (typeof entry.id !== 'string' || entry.id === '')
|
|
101
|
+
return null;
|
|
102
|
+
// Reverse-lookup byPath for value === entry.id. Keeps the index
|
|
103
|
+
// single-source-of-truth without us needing a third map.
|
|
104
|
+
for (const [path, id] of index.byPath.entries()) {
|
|
105
|
+
if (id === entry.id)
|
|
106
|
+
return stripIndexBasename(path);
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/** Absolute path to the file the index binds to this entry, or undefined. */
|
|
111
|
+
export function idBoundFile(entry, index) {
|
|
112
|
+
if (typeof entry.id !== 'string' || entry.id === '')
|
|
113
|
+
return undefined;
|
|
114
|
+
return index.byId.get(entry.id);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=content-tree-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-tree-helpers.js","sourceRoot":"","sources":["../src/content-tree-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the bird's-eye content tree (Phase 16d / 19c).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `content-tree.ts` to keep the assembly module under
|
|
5
|
+
* the project's 500-line guideline. The tree builder re-exports every
|
|
6
|
+
* symbol from this module so callers continue importing from
|
|
7
|
+
* `@deskwork/core/content-tree`.
|
|
8
|
+
*/
|
|
9
|
+
import type { CalendarEntry, Stage } from './types.ts';
|
|
10
|
+
import type { ContentIndex } from './content-index.ts';
|
|
11
|
+
import type { FsWalkEntry } from './content-tree-fs-walk.ts';
|
|
12
|
+
/** A node in the content tree. */
|
|
13
|
+
export interface ContentNode {
|
|
14
|
+
/** Site slug — passed through from the input. */
|
|
15
|
+
site: string;
|
|
16
|
+
/**
|
|
17
|
+
* Fs-relative path under contentDir (e.g. `projects/the-outbound`,
|
|
18
|
+
* `essays/whats-in-a-name`). The structural key for the tree —
|
|
19
|
+
* parent/child wiring, URL construction, and node lookup all go
|
|
20
|
+
* through this field.
|
|
21
|
+
*
|
|
22
|
+
* Renamed from `slug` in Phase 19c (#33). For audiocontrol-shaped
|
|
23
|
+
* flat blogs, this is identical to today's slug (`my-flat-post`);
|
|
24
|
+
* for hierarchical content collections, this captures the actual
|
|
25
|
+
* fs hierarchy independent of any host-derived public URL.
|
|
26
|
+
*/
|
|
27
|
+
path: string;
|
|
28
|
+
/**
|
|
29
|
+
* Host-owned public URL slug — populated only when a calendar entry
|
|
30
|
+
* is overlaid on this node. The studio uses this for the "public
|
|
31
|
+
* URL: /blog/<slug>" hover hint. Not used internally by deskwork
|
|
32
|
+
* for routing or path resolution. `undefined` for organizational
|
|
33
|
+
* nodes and for fs nodes that have no calendar entry.
|
|
34
|
+
*/
|
|
35
|
+
slug?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Absolute path of the markdown file backing this node, when one
|
|
38
|
+
* is known. Populated from the content index's id-binding (Issue #70)
|
|
39
|
+
* — the actual on-disk file, not a slug-template ghost. The studio's
|
|
40
|
+
* "file path" hint reads this to display the real path; renderers
|
|
41
|
+
* that previously reconstructed `<path>/index.md` from `path` were
|
|
42
|
+
* showing a path that didn't necessarily exist.
|
|
43
|
+
*
|
|
44
|
+
* `undefined` for ghost calendar entries (no fs binding yet) and for
|
|
45
|
+
* organizational nodes without a tracked calendar entry. Callers
|
|
46
|
+
* needing a fallback should check `hasOwnIndex` and fall back to
|
|
47
|
+
* a path constructed from `path`.
|
|
48
|
+
*/
|
|
49
|
+
filePath?: string;
|
|
50
|
+
/** Display title. Resolution order: calendar entry title → README/index frontmatter title → leaf path segment. */
|
|
51
|
+
title: string;
|
|
52
|
+
/**
|
|
53
|
+
* Lifecycle stage when the node corresponds to a tracked calendar
|
|
54
|
+
* entry. `null` for organizational nodes (filesystem-only — no
|
|
55
|
+
* calendar entry).
|
|
56
|
+
*/
|
|
57
|
+
lane: Stage | null;
|
|
58
|
+
/**
|
|
59
|
+
* Underlying calendar entry, when present. `null` for organizational
|
|
60
|
+
* filesystem nodes. Distinguishes "tracked / has lane" from
|
|
61
|
+
* "structural only / no lane".
|
|
62
|
+
*/
|
|
63
|
+
entry: CalendarEntry | null;
|
|
64
|
+
/**
|
|
65
|
+
* True when the node has its own `index.md` / `README.md` on disk.
|
|
66
|
+
* Used by the UI to pick branch / leaf icons and (for organizational
|
|
67
|
+
* nodes) to decide whether the detail panel can show a README excerpt.
|
|
68
|
+
*/
|
|
69
|
+
hasOwnIndex: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* True when the node was discovered by the filesystem walk
|
|
72
|
+
* (independent of whether it has a calendar entry). Pure-calendar
|
|
73
|
+
* nodes (entry exists, no fs directory) report false here. Used by
|
|
74
|
+
* the studio detail panel to decide whether organizational README
|
|
75
|
+
* content is fetchable.
|
|
76
|
+
*/
|
|
77
|
+
hasFsDir: boolean;
|
|
78
|
+
/** Items at `<contentDir>/<path>/scrapbook/` (public + secret). */
|
|
79
|
+
scrapbookCount: number;
|
|
80
|
+
/**
|
|
81
|
+
* Most recent mtime across the node's scrapbook items (ISO8601), or
|
|
82
|
+
* `null` when the scrapbook is empty / absent.
|
|
83
|
+
*/
|
|
84
|
+
scrapbookMostRecentMtime: string | null;
|
|
85
|
+
/** Direct children — already sorted in path order. */
|
|
86
|
+
children: ContentNode[];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Project-level tree summary returned by `buildContentTree`. Operators
|
|
90
|
+
* use this both for the per-project drilldown and for the top-level
|
|
91
|
+
* site cards (counts + lanes derive from the same shape).
|
|
92
|
+
*/
|
|
93
|
+
export interface ContentProject {
|
|
94
|
+
site: string;
|
|
95
|
+
/**
|
|
96
|
+
* First path segment — the project root (e.g. `projects` for
|
|
97
|
+
* writingcontrol's hierarchical layout, or `my-flat-post` for an
|
|
98
|
+
* audiocontrol-shaped flat blog). Pre-19c this was "first slug
|
|
99
|
+
* segment"; post-19c the semantics shift to fs-driven. For flat
|
|
100
|
+
* layouts the value is unchanged; for hierarchical layouts it now
|
|
101
|
+
* captures the operator's organizational top-level (e.g. `projects`,
|
|
102
|
+
* `essays`) rather than a per-entry slug.
|
|
103
|
+
*/
|
|
104
|
+
rootSlug: string;
|
|
105
|
+
/**
|
|
106
|
+
* Display name for the project. When a tracked entry exists at the
|
|
107
|
+
* root path, its `title` is used; otherwise the README frontmatter
|
|
108
|
+
* title or the rootSlug verbatim.
|
|
109
|
+
*/
|
|
110
|
+
title: string;
|
|
111
|
+
/** Total tracked entries beneath this project (recursive). */
|
|
112
|
+
trackedCount: number;
|
|
113
|
+
/** Total tree nodes including organizational nodes (recursive). */
|
|
114
|
+
totalNodes: number;
|
|
115
|
+
/** Maximum depth (1 = single root node, 2 = root + leaves, …). */
|
|
116
|
+
maxDepth: number;
|
|
117
|
+
/** Sum of scrapbookCount across every node beneath this project. */
|
|
118
|
+
scrapbookCount: number;
|
|
119
|
+
/**
|
|
120
|
+
* Predominant lane across tracked nodes — the most-frequent stage,
|
|
121
|
+
* tie-broken by the lane order (`STAGES`). `null` when the project
|
|
122
|
+
* has no tracked entries.
|
|
123
|
+
*/
|
|
124
|
+
predominantLane: Stage | null;
|
|
125
|
+
/** The project root node (its descendants live in `.children`). */
|
|
126
|
+
root: ContentNode;
|
|
127
|
+
}
|
|
128
|
+
/** Build-time options for `buildContentTree`. Test-friendly injection points. */
|
|
129
|
+
export interface BuildOptions {
|
|
130
|
+
/**
|
|
131
|
+
* Override scrapbook lookups — useful for tests that don't want to
|
|
132
|
+
* depend on the real filesystem layout. Defaults to `listScrapbook`
|
|
133
|
+
* from `@deskwork/core/scrapbook`.
|
|
134
|
+
*/
|
|
135
|
+
scrapbookLookup?: (site: string, path: string) => {
|
|
136
|
+
items: {
|
|
137
|
+
mtime: string;
|
|
138
|
+
}[];
|
|
139
|
+
secretItems: {
|
|
140
|
+
mtime: string;
|
|
141
|
+
}[];
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Override the filesystem walk — used by tests to inject a synthetic
|
|
145
|
+
* directory shape without writing to disk. Defaults to
|
|
146
|
+
* `defaultFsWalk` (recursive walk under `resolveContentDir`).
|
|
147
|
+
*/
|
|
148
|
+
fsWalk?: (site: string) => readonly FsWalkEntry[];
|
|
149
|
+
/**
|
|
150
|
+
* Override the content index — used by tests + the studio's
|
|
151
|
+
* per-request memoization. Defaults to `buildContentIndex` per call.
|
|
152
|
+
* The index drives the entry-id → fs-path overlay; without it,
|
|
153
|
+
* calendar entries fall through to the legacy slug-fallback path.
|
|
154
|
+
*/
|
|
155
|
+
contentIndex?: ContentIndex;
|
|
156
|
+
/**
|
|
157
|
+
* Optional logger for the legacy slug-fallback warning. Tests inject
|
|
158
|
+
* a spy here; production code lets it default to `console.warn`.
|
|
159
|
+
* Receives one warning per calendar entry whose id wasn't found in
|
|
160
|
+
* the content index but whose slug matched an fs node.
|
|
161
|
+
*/
|
|
162
|
+
warn?: (message: string) => void;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* One row of the depth-first flatten — used by the studio's tree
|
|
166
|
+
* renderer. `depth` starts at 0 for the project root; `isLast` is true
|
|
167
|
+
* when the node is the last child of its parent (the UI uses it to
|
|
168
|
+
* truncate tree connector lines).
|
|
169
|
+
*/
|
|
170
|
+
export interface FlatNode {
|
|
171
|
+
node: ContentNode;
|
|
172
|
+
depth: number;
|
|
173
|
+
isLast: boolean;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=content-tree-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-tree-types.d.ts","sourceRoot":"","sources":["../src/content-tree-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;OAUG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kHAAkH;IAClH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IACnB;;;;OAIG;IACH,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,sDAAsD;IACtD,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;OAQG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,eAAe,EAAE,KAAK,GAAG,IAAI,CAAC;IAC9B,mEAAmE;IACnE,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,KACT;QAAE,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,WAAW,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,WAAW,EAAE,CAAC;IAClD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the bird's-eye content tree (Phase 16d / 19c).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `content-tree.ts` to keep the assembly module under
|
|
5
|
+
* the project's 500-line guideline. The tree builder re-exports every
|
|
6
|
+
* symbol from this module so callers continue importing from
|
|
7
|
+
* `@deskwork/core/content-tree`.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=content-tree-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-tree-types.js","sourceRoot":"","sources":["../src/content-tree-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bird's-eye content-tree builder (Phase 16d, fs-inverted in v0.6.0 / #24,
|
|
3
|
+
* fs-keyed in Phase 19c / #33).
|
|
4
|
+
*
|
|
5
|
+
* Derives a tree-of-nodes representation from the host project's
|
|
6
|
+
* filesystem, with the editorial calendar overlaid as a state layer.
|
|
7
|
+
*
|
|
8
|
+
* - The **filesystem walk** is the primary structure source. Every
|
|
9
|
+
* directory under `<contentDir>/` becomes a node, keyed by its
|
|
10
|
+
* fs-relative path (e.g. `projects/the-outbound`).
|
|
11
|
+
* - The **calendar** is the state overlay. A calendar entry whose
|
|
12
|
+
* `id` matches an fs node's frontmatter `id:` (via the content
|
|
13
|
+
* index) sets the node's lane (its lifecycle stage), display
|
|
14
|
+
* title, AND the `slug` display attribute (the entry's
|
|
15
|
+
* host-rendering-engine slug, used by the studio for the
|
|
16
|
+
* "public URL: /blog/<slug>" hover hint).
|
|
17
|
+
*
|
|
18
|
+
* Read-only — never mutates the calendar or the filesystem. Callers
|
|
19
|
+
* cache the result for the lifetime of a single request unless the
|
|
20
|
+
* caller knows the underlying data has changed.
|
|
21
|
+
*
|
|
22
|
+
* Inversion rationale (#33): pre-19c the tree keyed nodes by **slug**.
|
|
23
|
+
* This worked for audiocontrol's flat layout where slug == fs path,
|
|
24
|
+
* but broke for writingcontrol where slug `the-outbound` (the
|
|
25
|
+
* Astro-derived public URL) is unrelated to the file's path
|
|
26
|
+
* (`projects/the-outbound/index.md`). The ghost-root bug came from
|
|
27
|
+
* union-by-slug producing a calendar-only tree at slug
|
|
28
|
+
* `the-outbound` plus a separate untracked tree under `projects/`
|
|
29
|
+
* that never merged.
|
|
30
|
+
*
|
|
31
|
+
* Now: tree placement is filesystem-driven. Each fs node carries the
|
|
32
|
+
* relative path (e.g. `projects/the-outbound`). Calendar entries
|
|
33
|
+
* overlay state onto fs nodes by matching the entry's `id` against
|
|
34
|
+
* the file's frontmatter `id:` via `buildContentIndex`. Slug stops
|
|
35
|
+
* being load-bearing structurally and becomes a display attribute.
|
|
36
|
+
*
|
|
37
|
+
* Legacy slug-fallback (intentional, for pre-doctor entries): when a
|
|
38
|
+
* calendar entry's id isn't found in the content index (its file
|
|
39
|
+
* hasn't been bound to frontmatter yet), the assembly looks for an
|
|
40
|
+
* fs node whose path equals the entry's slug. If found → overlay
|
|
41
|
+
* with a one-time warning hinting at `deskwork doctor`. If not found
|
|
42
|
+
* → place the entry as a ghost node (preserving today's behavior
|
|
43
|
+
* for entries with neither id-binding nor a path-shaped slug).
|
|
44
|
+
* This is NOT a "fallback for missing functionality" in the project
|
|
45
|
+
* rule's sense — it's a deliberate transitional path that the doctor
|
|
46
|
+
* command resolves operator-side.
|
|
47
|
+
*/
|
|
48
|
+
import type { CalendarEntry } from './types.ts';
|
|
49
|
+
import type { DeskworkConfig } from './config.ts';
|
|
50
|
+
import type { BuildOptions, ContentNode, ContentProject, FlatNode } from './content-tree-types.ts';
|
|
51
|
+
export { defaultFsWalk } from './content-tree-fs-walk.ts';
|
|
52
|
+
export type { FsWalkEntry } from './content-tree-fs-walk.ts';
|
|
53
|
+
export type { BuildOptions, ContentNode, ContentProject, FlatNode, } from './content-tree-types.ts';
|
|
54
|
+
/**
|
|
55
|
+
* Test-only: clear the legacy slug-fallback warning de-dup set so tests
|
|
56
|
+
* that exercise warning behavior can do so independently. Not part of the
|
|
57
|
+
* public API — exposed only because process-level state is awkward to
|
|
58
|
+
* reset from inside tests without a hook.
|
|
59
|
+
*/
|
|
60
|
+
export declare function __resetLegacyFallbackWarnings(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Build the content-tree projects for one site. Pure data — no HTML,
|
|
63
|
+
* no path-style decisions.
|
|
64
|
+
*
|
|
65
|
+
* Tree assembly (Phase 19c):
|
|
66
|
+
* 1. Walk the fs (`fsWalk`) to enumerate every directory under
|
|
67
|
+
* contentDir. Each fs entry contributes a candidate node keyed by
|
|
68
|
+
* its fs-relative path.
|
|
69
|
+
* 2. Build the content index (`buildContentIndex`) to map
|
|
70
|
+
* `relPath → entryId` based on frontmatter `id:`.
|
|
71
|
+
* 3. For each fs node with a markdown index/README, look up the
|
|
72
|
+
* bound entry id via `index.byPath`, then resolve the calendar
|
|
73
|
+
* entry by id. If found → overlay state (lane, title from entry,
|
|
74
|
+
* slug as a display attribute).
|
|
75
|
+
* 4. For calendar entries whose id is NOT in the index (their file
|
|
76
|
+
* isn't bound to frontmatter yet — pre-doctor state), do
|
|
77
|
+
* legacy slug fallback: look for an fs node whose path equals
|
|
78
|
+
* the entry's slug. If found → overlay with a one-time warning.
|
|
79
|
+
* If not found → place as a ghost node (today's behavior,
|
|
80
|
+
* preserved for backward compat).
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildContentTree(site: string, entries: readonly CalendarEntry[], config: DeskworkConfig, projectRoot: string, options?: BuildOptions): ContentProject[];
|
|
83
|
+
/**
|
|
84
|
+
* Helper: find the node with the given path under a project tree, or
|
|
85
|
+
* return null. Used by the studio's node-detail panel.
|
|
86
|
+
*/
|
|
87
|
+
export declare function findNode(project: ContentProject, path: string): ContentNode | null;
|
|
88
|
+
/**
|
|
89
|
+
* Flatten the tree into a depth-first ordered list. See `FlatNode` in
|
|
90
|
+
* `content-tree-types.ts` for the row shape.
|
|
91
|
+
*/
|
|
92
|
+
export declare function flattenForRender(root: ContentNode): FlatNode[];
|
|
93
|
+
//# sourceMappingURL=content-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-tree.d.ts","sourceRoot":"","sources":["../src/content-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAS,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAclD,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACT,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,YAAY,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,GACT,MAAM,yBAAyB,CAAC;AAkBjC;;;;;GAKG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,YAAiB,GACzB,cAAc,EAAE,CAsOlB;AAgDD;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,GACX,WAAW,GAAG,IAAI,CAUpB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,EAAE,CAW9D"}
|