@deskwork/core 0.45.2 → 0.47.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/dist/config.d.ts +14 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -12
- package/dist/config.js.map +1 -1
- package/dist/content-index.d.ts +25 -0
- package/dist/content-index.d.ts.map +1 -1
- package/dist/content-index.js +142 -15
- package/dist/content-index.js.map +1 -1
- package/dist/doctor/index.d.ts +3 -0
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +3 -0
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/legacy-config.d.ts +98 -0
- package/dist/doctor/legacy-config.d.ts.map +1 -0
- package/dist/doctor/legacy-config.js +174 -0
- package/dist/doctor/legacy-config.js.map +1 -0
- package/dist/doctor/project-scope-gate.d.ts +12 -21
- package/dist/doctor/project-scope-gate.d.ts.map +1 -1
- package/dist/doctor/project-scope-gate.js +13 -25
- package/dist/doctor/project-scope-gate.js.map +1 -1
- package/dist/doctor/repair.d.ts +11 -0
- package/dist/doctor/repair.d.ts.map +1 -1
- package/dist/doctor/repair.js +10 -89
- package/dist/doctor/repair.js.map +1 -1
- package/dist/doctor/rules/duplicate-id.d.ts +7 -2
- package/dist/doctor/rules/duplicate-id.d.ts.map +1 -1
- package/dist/doctor/rules/duplicate-id.js +8 -5
- package/dist/doctor/rules/duplicate-id.js.map +1 -1
- package/dist/doctor/rules/duplicate-id.ts +8 -5
- package/dist/doctor/rules/legacy-top-level-id-migration.d.ts.map +1 -1
- package/dist/doctor/rules/legacy-top-level-id-migration.js +11 -8
- package/dist/doctor/rules/legacy-top-level-id-migration.js.map +1 -1
- package/dist/doctor/rules/legacy-top-level-id-migration.ts +10 -11
- package/dist/doctor/rules/sites-to-lanes-migration.d.ts +39 -0
- package/dist/doctor/rules/sites-to-lanes-migration.d.ts.map +1 -0
- package/dist/doctor/rules/sites-to-lanes-migration.js +395 -0
- package/dist/doctor/rules/sites-to-lanes-migration.js.map +1 -0
- package/dist/doctor/rules/sites-to-lanes-migration.ts +449 -0
- package/dist/doctor/rules/workflow-stale.d.ts.map +1 -1
- package/dist/doctor/rules/workflow-stale.js +5 -2
- package/dist/doctor/rules/workflow-stale.js.map +1 -1
- package/dist/doctor/rules/workflow-stale.ts +5 -1
- package/dist/doctor/runner.d.ts +15 -1
- package/dist/doctor/runner.d.ts.map +1 -1
- package/dist/doctor/runner.js +48 -41
- package/dist/doctor/runner.js.map +1 -1
- package/dist/doctor/sites-migration-backfill.d.ts +71 -0
- package/dist/doctor/sites-migration-backfill.d.ts.map +1 -0
- package/dist/doctor/sites-migration-backfill.js +164 -0
- package/dist/doctor/sites-migration-backfill.js.map +1 -0
- package/dist/doctor/validate.d.ts.map +1 -1
- package/dist/doctor/validate.js +34 -73
- package/dist/doctor/validate.js.map +1 -1
- package/dist/entry/resolve-artifact.d.ts +80 -0
- package/dist/entry/resolve-artifact.d.ts.map +1 -0
- package/dist/entry/resolve-artifact.js +102 -0
- package/dist/entry/resolve-artifact.js.map +1 -0
- package/dist/entry/shortform-path.d.ts +34 -0
- package/dist/entry/shortform-path.d.ts.map +1 -0
- package/dist/entry/shortform-path.js +50 -0
- package/dist/entry/shortform-path.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/iterate/iterate.d.ts.map +1 -1
- package/dist/iterate/iterate.js +21 -26
- package/dist/iterate/iterate.js.map +1 -1
- package/dist/lanes/bootstrap.d.ts +11 -7
- package/dist/lanes/bootstrap.d.ts.map +1 -1
- package/dist/lanes/bootstrap.js +42 -17
- package/dist/lanes/bootstrap.js.map +1 -1
- package/dist/lanes/index.d.ts +1 -0
- package/dist/lanes/index.d.ts.map +1 -1
- package/dist/lanes/index.js +6 -0
- package/dist/lanes/index.js.map +1 -1
- package/dist/lanes/loader.d.ts +33 -6
- package/dist/lanes/loader.d.ts.map +1 -1
- package/dist/lanes/loader.js +44 -11
- package/dist/lanes/loader.js.map +1 -1
- package/dist/lanes/operations/create.d.ts +11 -2
- package/dist/lanes/operations/create.d.ts.map +1 -1
- package/dist/lanes/operations/create.js +15 -4
- package/dist/lanes/operations/create.js.map +1 -1
- package/dist/lanes/operations/list.d.ts +3 -2
- package/dist/lanes/operations/list.d.ts.map +1 -1
- package/dist/lanes/operations/list.js +3 -2
- package/dist/lanes/operations/list.js.map +1 -1
- package/dist/lanes/operations/move.d.ts +29 -25
- package/dist/lanes/operations/move.d.ts.map +1 -1
- package/dist/lanes/operations/move.js +45 -242
- package/dist/lanes/operations/move.js.map +1 -1
- package/dist/lanes/operations/update.d.ts +15 -5
- package/dist/lanes/operations/update.d.ts.map +1 -1
- package/dist/lanes/operations/update.js +32 -12
- package/dist/lanes/operations/update.js.map +1 -1
- package/dist/lanes/scaffold-path.d.ts +88 -0
- package/dist/lanes/scaffold-path.d.ts.map +1 -0
- package/dist/lanes/scaffold-path.js +122 -0
- package/dist/lanes/scaffold-path.js.map +1 -0
- package/dist/lanes/types.d.ts +54 -31
- package/dist/lanes/types.d.ts.map +1 -1
- package/dist/lanes/types.js +60 -21
- package/dist/lanes/types.js.map +1 -1
- package/dist/outline-split.d.ts +2 -3
- package/dist/outline-split.d.ts.map +1 -1
- package/dist/outline-split.js +2 -3
- package/dist/outline-split.js.map +1 -1
- package/dist/paths.d.ts +14 -87
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +21 -131
- package/dist/paths.js.map +1 -1
- package/dist/remark-strip-outline.mjs +2 -3
- package/dist/rename-slug.d.ts +3 -3
- package/dist/rename-slug.d.ts.map +1 -1
- package/dist/rename-slug.js +137 -44
- package/dist/rename-slug.js.map +1 -1
- package/dist/review/handlers.d.ts +3 -3
- package/dist/review/handlers.d.ts.map +1 -1
- package/dist/review/handlers.js +18 -14
- package/dist/review/handlers.js.map +1 -1
- package/dist/review/report.d.ts +13 -2
- package/dist/review/report.d.ts.map +1 -1
- package/dist/review/report.js +48 -18
- package/dist/review/report.js.map +1 -1
- package/dist/review/start-handlers.d.ts +4 -4
- package/dist/review/start-handlers.d.ts.map +1 -1
- package/dist/review/start-handlers.js +25 -25
- package/dist/review/start-handlers.js.map +1 -1
- package/dist/review/workflow-paths.d.ts +24 -26
- package/dist/review/workflow-paths.d.ts.map +1 -1
- package/dist/review/workflow-paths.js +70 -60
- package/dist/review/workflow-paths.js.map +1 -1
- package/dist/schema/draft-annotation.d.ts +8 -8
- package/dist/schema/entry.d.ts +6 -6
- package/dist/schema/journal-events.d.ts +56 -42
- package/dist/schema/journal-events.d.ts.map +1 -1
- package/dist/schema/journal-events.js +23 -9
- package/dist/schema/journal-events.js.map +1 -1
- package/dist/sidecar/read.d.ts +8 -0
- package/dist/sidecar/read.d.ts.map +1 -1
- package/dist/sidecar/read.js +36 -9
- package/dist/sidecar/read.js.map +1 -1
- package/dist/sidecar/write.d.ts +14 -0
- package/dist/sidecar/write.d.ts.map +1 -1
- package/dist/sidecar/write.js +25 -0
- package/dist/sidecar/write.js.map +1 -1
- package/package.json +9 -9
- package/dist/body-state.d.ts +0 -27
- package/dist/body-state.d.ts.map +0 -1
- package/dist/body-state.js +0 -62
- package/dist/body-state.js.map +0 -1
- package/dist/scaffold.d.ts +0 -67
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/scaffold.js +0 -122
- package/dist/scaffold.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lane move — relocate an entry from one lane to another.
|
|
3
3
|
*
|
|
4
|
-
* Phase 6 Task 6.1 (graphical-entries)
|
|
4
|
+
* Phase 6 Task 6.1 (graphical-entries); reshaped by Phase 39
|
|
5
|
+
* (sites→lanes retirement). The move:
|
|
5
6
|
*
|
|
6
7
|
* 1. Resolves the entry's current lane via the sidecar's `lane`
|
|
7
8
|
* field. Migration-window default: an entry without a `lane`
|
|
@@ -9,11 +10,10 @@
|
|
|
9
10
|
* the doctor's lane-back-fill default). The move is refused
|
|
10
11
|
* when the source lane and target lane are the same. Per
|
|
11
12
|
* AUDIT-20260530-58, when the resolved source lane config does
|
|
12
|
-
* NOT exist on disk the function refuses with an
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* contentDir — the migration gap is surfaced.
|
|
13
|
+
* NOT exist on disk the function refuses with an entry-named
|
|
14
|
+
* error (no `lane` field → instruct operator to run
|
|
15
|
+
* `/deskwork:doctor`; explicit `lane` field → name the missing
|
|
16
|
+
* lane id).
|
|
17
17
|
*
|
|
18
18
|
* 2. Resolves the target lane's pipeline template. The target
|
|
19
19
|
* stage MUST be in the union of `linearStages ∪
|
|
@@ -21,119 +21,38 @@
|
|
|
21
21
|
* omits `targetStage`, the move defaults to the target
|
|
22
22
|
* template's FIRST `linearStages` entry.
|
|
23
23
|
*
|
|
24
|
-
* 3.
|
|
25
|
-
* `<sourceContentDir>/<artifactPath>` to
|
|
26
|
-
* `<targetContentDir>/<artifactPath>` (same relative path under
|
|
27
|
-
* the lane's contentDir). When the source file does not exist,
|
|
28
|
-
* the move is refused — the operator must repair the binding
|
|
29
|
-
* before relocating.
|
|
30
|
-
*
|
|
31
|
-
* 4. Relocates the per-entry scrapbook directory at
|
|
32
|
-
* `<sourceContentDir>/<slug>/scrapbook/` (when present) to the
|
|
33
|
-
* target lane's parallel location. A missing scrapbook
|
|
34
|
-
* directory is normal; the move proceeds.
|
|
35
|
-
*
|
|
36
|
-
* 5. Rewrites the sidecar with `lane = target`, `currentStage =
|
|
24
|
+
* 3. Rewrites the sidecar with `lane = target`, `currentStage =
|
|
37
25
|
* targetStage`. Per the PRD's open-question default,
|
|
38
26
|
* `iterationByStage` is preserved verbatim — no stage-name
|
|
39
27
|
* remapping. Old keys from the prior lane template become dead
|
|
40
28
|
* entries that cause no harm (iterate uses `?? 0`).
|
|
41
29
|
*
|
|
42
|
-
*
|
|
43
|
-
* lanes, source / target stages, and the artifact
|
|
30
|
+
* 4. Emits a `lane-move` journal event identifying source / target
|
|
31
|
+
* lanes, source / target stages, and the (unchanged) artifact
|
|
32
|
+
* path for audit.
|
|
33
|
+
*
|
|
34
|
+
* **Phase 39: the move is a METADATA change only.** A lane carries no
|
|
35
|
+
* `contentDir` — location is a property of the ENTRY
|
|
36
|
+
* (`entry.artifactPath`, resolved against the project root), never the
|
|
37
|
+
* lane. So moving an entry between lanes does NOT relocate its artifact
|
|
38
|
+
* file or its per-entry scrapbook; both stay exactly where they are.
|
|
39
|
+
* The lane "spans" whatever directories its entries happen to live in,
|
|
40
|
+
* emergent from the entries. This dissolves the former
|
|
41
|
+
* source/target-`contentDir` relocation (and its cross-device fallback
|
|
42
|
+
* + rollback): there is nothing on disk to move.
|
|
44
43
|
*
|
|
45
|
-
* The
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* move survives a contentDir that points at a separate mount.
|
|
44
|
+
* The source artifact must still EXIST on disk (when the entry carries
|
|
45
|
+
* an `artifactPath`) — moving an entry whose artifact is missing is
|
|
46
|
+
* refused so the operator repairs the binding first.
|
|
49
47
|
*/
|
|
50
|
-
import {
|
|
51
|
-
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
48
|
+
import { existsSync } from 'node:fs';
|
|
52
49
|
import { appendJournalEvent } from "../../journal/append.js";
|
|
53
50
|
import { writeSidecar } from "../../sidecar/write.js";
|
|
54
51
|
import { readSidecar } from "../../sidecar/read.js";
|
|
55
52
|
import { loadPipelineTemplate } from "../../pipelines/loader.js";
|
|
53
|
+
import { resolveStoredArtifactPath } from "../../entry/resolve-artifact.js";
|
|
56
54
|
import { loadLaneConfig } from "../loader.js";
|
|
57
55
|
const DEFAULT_LANE_ID = 'default';
|
|
58
|
-
/**
|
|
59
|
-
* Resolve `<contentDir>` to an absolute path. Lane configs may
|
|
60
|
-
* declare `contentDir` as either absolute (taken verbatim) or
|
|
61
|
-
* relative (resolved against the project root).
|
|
62
|
-
*/
|
|
63
|
-
function resolveContentDirAbs(projectRoot, contentDir) {
|
|
64
|
-
return isAbsolute(contentDir) ? contentDir : resolve(projectRoot, contentDir);
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* AUDIT-20260530-64 boundary check. Refuses any resolved filesystem
|
|
68
|
-
* path that escapes its containing lane contentDir. Used at four
|
|
69
|
-
* sites in `moveEntryToLane`: source artifact, target artifact,
|
|
70
|
-
* source scrapbook, target scrapbook. Each site joins a contentDir
|
|
71
|
-
* with an entry-controlled relative segment (`sidecar.artifactPath`
|
|
72
|
-
* or `sidecar.slug`) — without this check, a malformed sidecar with
|
|
73
|
-
* `artifactPath: "../outside.md"` or `slug: "../escape"` makes the
|
|
74
|
-
* move read + write files outside the lane content tree.
|
|
75
|
-
*
|
|
76
|
-
* The check uses `path.relative(contentDir, resolvedPath)` and
|
|
77
|
-
* refuses any relative path that starts with `..` (or is itself
|
|
78
|
-
* `..`) or is absolute. This is the canonical Node pattern for
|
|
79
|
-
* "is X inside Y" — works on both POSIX and Windows because the
|
|
80
|
-
* underlying `path.resolve` and `path.relative` are platform-aware.
|
|
81
|
-
*/
|
|
82
|
-
function assertPathInsideContentDir(args) {
|
|
83
|
-
const { resolvedPath, contentDirAbs, entrySlug, boundary, side } = args;
|
|
84
|
-
const rel = relative(contentDirAbs, resolvedPath);
|
|
85
|
-
const escapes = rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
|
|
86
|
-
if (escapes) {
|
|
87
|
-
throw new Error(`cannot move entry '${entrySlug}': ${side} ${boundary} resolves to `
|
|
88
|
-
+ `'${resolvedPath}', which escapes the ${side} lane contentDir `
|
|
89
|
-
+ `'${contentDirAbs}'. Refusing the move — repair the sidecar `
|
|
90
|
-
+ `(remove '..' segments / absolute paths from the offending field) `
|
|
91
|
-
+ `before retrying.`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Type guard for the subset of Node ErrnoException we care about
|
|
96
|
-
* (just the `code` string). Keeps the cross-device fallback path
|
|
97
|
-
* type-safe without an unchecked `as NodeJS.ErrnoException`.
|
|
98
|
-
*/
|
|
99
|
-
function isErrnoCode(err, expected) {
|
|
100
|
-
if (err === null || typeof err !== 'object')
|
|
101
|
-
return false;
|
|
102
|
-
const maybe = err.code;
|
|
103
|
-
return typeof maybe === 'string' && maybe === expected;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Move a path with renameSync, falling back to a caller-supplied
|
|
107
|
-
* cross-device strategy on EXDEV. The fallback is responsible for
|
|
108
|
-
* both creating the destination and removing the source — the
|
|
109
|
-
* helper does not split copy/delete across calls.
|
|
110
|
-
*
|
|
111
|
-
* The parent directory of `dst` is mkdir'd on every call so callers
|
|
112
|
-
* don't have to thread that detail.
|
|
113
|
-
*/
|
|
114
|
-
function tryRenameWithFallback(src, dst, exdevFallback) {
|
|
115
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
116
|
-
try {
|
|
117
|
-
renameSync(src, dst);
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
if (!isErrnoCode(err, 'EXDEV'))
|
|
121
|
-
throw err;
|
|
122
|
-
exdevFallback(src, dst);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function moveFile(src, dst) {
|
|
126
|
-
tryRenameWithFallback(src, dst, (s, d) => {
|
|
127
|
-
copyFileSync(s, d);
|
|
128
|
-
unlinkSync(s);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
function moveDir(src, dst) {
|
|
132
|
-
tryRenameWithFallback(src, dst, (s, d) => {
|
|
133
|
-
cpSync(s, d, { recursive: true });
|
|
134
|
-
rmSync(s, { recursive: true, force: true });
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
56
|
export async function moveEntryToLane(projectRoot, opts) {
|
|
138
57
|
const sidecar = await readSidecar(projectRoot, opts.uuid);
|
|
139
58
|
const sidecarHadLane = sidecar.lane !== undefined;
|
|
@@ -154,13 +73,8 @@ export async function moveEntryToLane(projectRoot, opts) {
|
|
|
154
73
|
// migration-window state); the error names the entry slug
|
|
155
74
|
// and directs the operator to `/deskwork:doctor` to back-fill
|
|
156
75
|
// lane assignments before retrying the move.
|
|
157
|
-
//
|
|
158
|
-
// Per the project no-fallback rule, the function does NOT silently
|
|
159
|
-
// substitute the project's contentDir for the missing lane — that
|
|
160
|
-
// would hide the migration gap and produce wrong-place file moves.
|
|
161
|
-
let sourceLane;
|
|
162
76
|
try {
|
|
163
|
-
|
|
77
|
+
loadLaneConfig(sourceLaneId, projectRoot);
|
|
164
78
|
}
|
|
165
79
|
catch (err) {
|
|
166
80
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -200,98 +114,16 @@ export async function moveEntryToLane(projectRoot, opts) {
|
|
|
200
114
|
+ `not in target lane "${opts.toLane}" template "${targetTemplate.id}". `
|
|
201
115
|
+ `Allowed stages: ${[...allowed].join(', ')}.`);
|
|
202
116
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// `artifactPath` blocks the canonical attack shapes upstream, but
|
|
214
|
-
// the boundary check stays as defense-in-depth — the field could be
|
|
215
|
-
// populated by a non-deskwork process, or a future code path could
|
|
216
|
-
// bypass the schema. The check uses `path.resolve` (via
|
|
217
|
-
// `assertPathInsideContentDir`) so even a normalised-looking
|
|
218
|
-
// relative path that traverses out is caught.
|
|
219
|
-
let fromArtifactAbs;
|
|
220
|
-
let toArtifactAbs;
|
|
221
|
-
if (sidecar.artifactPath !== undefined) {
|
|
222
|
-
fromArtifactAbs = resolve(sourceContentDir, sidecar.artifactPath);
|
|
223
|
-
toArtifactAbs = resolve(targetContentDir, sidecar.artifactPath);
|
|
224
|
-
assertPathInsideContentDir({
|
|
225
|
-
resolvedPath: fromArtifactAbs,
|
|
226
|
-
contentDirAbs: sourceContentDir,
|
|
227
|
-
entrySlug: sidecar.slug,
|
|
228
|
-
boundary: 'artifactPath',
|
|
229
|
-
side: 'source',
|
|
230
|
-
});
|
|
231
|
-
assertPathInsideContentDir({
|
|
232
|
-
resolvedPath: toArtifactAbs,
|
|
233
|
-
contentDirAbs: targetContentDir,
|
|
234
|
-
entrySlug: sidecar.slug,
|
|
235
|
-
boundary: 'artifactPath',
|
|
236
|
-
side: 'target',
|
|
237
|
-
});
|
|
238
|
-
if (!existsSync(fromArtifactAbs)) {
|
|
239
|
-
throw new Error(`Cannot move entry ${sidecar.slug}: source artifact does not exist at `
|
|
240
|
-
+ `${fromArtifactAbs}. Repair the binding (e.g. via "deskwork doctor") `
|
|
241
|
-
+ `before moving.`);
|
|
242
|
-
}
|
|
243
|
-
if (existsSync(toArtifactAbs)) {
|
|
244
|
-
throw new Error(`Cannot move entry ${sidecar.slug}: target artifact already exists at `
|
|
245
|
-
+ `${toArtifactAbs}. The target lane already holds a file at the same `
|
|
246
|
-
+ `relative path; resolve the collision (rename / move / remove) before `
|
|
247
|
-
+ `running lane move.`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
const sourceScrapbookDir = resolve(sourceContentDir, sidecar.slug, 'scrapbook');
|
|
251
|
-
const targetScrapbookDir = resolve(targetContentDir, sidecar.slug, 'scrapbook');
|
|
252
|
-
// Per AUDIT-20260530-64, the per-entry scrapbook path is built from
|
|
253
|
-
// `sidecar.slug` — also entry-controlled. EntrySchema's `slug` is
|
|
254
|
-
// an unconstrained `z.string().min(1)` for back-compat, so a slug
|
|
255
|
-
// like `../escape` parses cleanly. Refuse here for the same reason
|
|
256
|
-
// as the artifact branch above; check both sides.
|
|
257
|
-
assertPathInsideContentDir({
|
|
258
|
-
resolvedPath: sourceScrapbookDir,
|
|
259
|
-
contentDirAbs: sourceContentDir,
|
|
260
|
-
entrySlug: sidecar.slug,
|
|
261
|
-
boundary: 'slug-derived scrapbook',
|
|
262
|
-
side: 'source',
|
|
263
|
-
});
|
|
264
|
-
assertPathInsideContentDir({
|
|
265
|
-
resolvedPath: targetScrapbookDir,
|
|
266
|
-
contentDirAbs: targetContentDir,
|
|
267
|
-
entrySlug: sidecar.slug,
|
|
268
|
-
boundary: 'slug-derived scrapbook',
|
|
269
|
-
side: 'target',
|
|
270
|
-
});
|
|
271
|
-
// Track which filesystem operations succeeded so the catch below
|
|
272
|
-
// can reverse them on a later failure (e.g. writeSidecar throwing
|
|
273
|
-
// after the artifact + scrapbook are already in the target lane).
|
|
274
|
-
let artifactMoved = false;
|
|
275
|
-
let scrapbookMoved = false;
|
|
276
|
-
if (fromArtifactAbs !== undefined && toArtifactAbs !== undefined) {
|
|
277
|
-
moveFile(fromArtifactAbs, toArtifactAbs);
|
|
278
|
-
artifactMoved = true;
|
|
279
|
-
}
|
|
280
|
-
// Relocate the per-entry scrapbook directory when present. Lives at
|
|
281
|
-
// `<contentDir>/<slug>/scrapbook/` per the slug-template convention
|
|
282
|
-
// — see packages/core/src/scrapbook/paths.ts (_scrapbookDirSlug).
|
|
283
|
-
if (existsSync(sourceScrapbookDir)) {
|
|
284
|
-
if (existsSync(targetScrapbookDir)) {
|
|
285
|
-
// Rollback the artifact relocation so the operator's state is
|
|
286
|
-
// consistent before re-running.
|
|
287
|
-
if (artifactMoved && fromArtifactAbs !== undefined && toArtifactAbs !== undefined) {
|
|
288
|
-
moveFile(toArtifactAbs, fromArtifactAbs);
|
|
289
|
-
}
|
|
290
|
-
throw new Error(`Cannot move entry ${sidecar.slug}: target scrapbook directory already `
|
|
291
|
-
+ `exists at ${targetScrapbookDir}. Resolve the collision before moving.`);
|
|
292
|
-
}
|
|
293
|
-
moveDir(sourceScrapbookDir, targetScrapbookDir);
|
|
294
|
-
scrapbookMoved = true;
|
|
117
|
+
// Resolve the entry's artifact from its STORED `artifactPath` only
|
|
118
|
+
// (Phase 39 — location is an ENTRY property, resolved against the
|
|
119
|
+
// project root, never a lane dir). The move does NOT relocate the
|
|
120
|
+
// file; it stays put. We still require the file to EXIST so the
|
|
121
|
+
// operator repairs a broken binding before moving lanes.
|
|
122
|
+
const artifactAbs = resolveStoredArtifactPath(sidecar, projectRoot);
|
|
123
|
+
if (artifactAbs !== null && !existsSync(artifactAbs)) {
|
|
124
|
+
throw new Error(`Cannot move entry ${sidecar.slug}: source artifact does not exist at `
|
|
125
|
+
+ `${artifactAbs}. Repair the binding (e.g. via "deskwork doctor") `
|
|
126
|
+
+ `before moving.`);
|
|
295
127
|
}
|
|
296
128
|
const at = new Date().toISOString();
|
|
297
129
|
const fromStage = sidecar.currentStage;
|
|
@@ -301,48 +133,17 @@ export async function moveEntryToLane(projectRoot, opts) {
|
|
|
301
133
|
currentStage: targetStage,
|
|
302
134
|
updatedAt: at,
|
|
303
135
|
};
|
|
304
|
-
|
|
305
|
-
// AFTER the artifact + scrapbook have been moved, the entry is
|
|
306
|
-
// half-moved: filesystem says "target lane" but sidecar still says
|
|
307
|
-
// "source lane". Reverse the successful filesystem moves before
|
|
308
|
-
// re-throwing so the operator's state is consistent.
|
|
309
|
-
try {
|
|
310
|
-
await writeSidecar(projectRoot, updated);
|
|
311
|
-
}
|
|
312
|
-
catch (err) {
|
|
313
|
-
try {
|
|
314
|
-
if (scrapbookMoved) {
|
|
315
|
-
moveDir(targetScrapbookDir, sourceScrapbookDir);
|
|
316
|
-
}
|
|
317
|
-
if (artifactMoved
|
|
318
|
-
&& fromArtifactAbs !== undefined
|
|
319
|
-
&& toArtifactAbs !== undefined) {
|
|
320
|
-
moveFile(toArtifactAbs, fromArtifactAbs);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
catch (rollbackErr) {
|
|
324
|
-
const rollbackDetail = rollbackErr instanceof Error
|
|
325
|
-
? rollbackErr.message
|
|
326
|
-
: String(rollbackErr);
|
|
327
|
-
const writeDetail = err instanceof Error ? err.message : String(err);
|
|
328
|
-
throw new Error(`Failed to move entry ${sidecar.slug}: sidecar write failed `
|
|
329
|
-
+ `(${writeDetail}) AND rollback of filesystem moves failed `
|
|
330
|
-
+ `(${rollbackDetail}). Operator intervention required.`);
|
|
331
|
-
}
|
|
332
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
333
|
-
throw new Error(`Failed to move entry ${sidecar.slug}: sidecar write failed `
|
|
334
|
-
+ `(${detail}); filesystem moves rolled back.`);
|
|
335
|
-
}
|
|
136
|
+
await writeSidecar(projectRoot, updated);
|
|
336
137
|
const moveDetails = {
|
|
337
138
|
fromLane: sourceLaneId,
|
|
338
139
|
toLane: opts.toLane,
|
|
339
140
|
fromStage,
|
|
340
141
|
toStage: targetStage,
|
|
341
142
|
};
|
|
342
|
-
if (
|
|
343
|
-
moveDetails.fromArtifactPath =
|
|
344
|
-
|
|
345
|
-
|
|
143
|
+
if (artifactAbs !== null) {
|
|
144
|
+
moveDetails.fromArtifactPath = artifactAbs;
|
|
145
|
+
moveDetails.toArtifactPath = artifactAbs;
|
|
146
|
+
}
|
|
346
147
|
await appendJournalEvent(projectRoot, {
|
|
347
148
|
kind: 'lane-move',
|
|
348
149
|
at,
|
|
@@ -355,8 +156,10 @@ export async function moveEntryToLane(projectRoot, opts) {
|
|
|
355
156
|
toLane: opts.toLane,
|
|
356
157
|
fromStage,
|
|
357
158
|
toStage: targetStage,
|
|
358
|
-
...(
|
|
359
|
-
|
|
159
|
+
...(artifactAbs !== null && {
|
|
160
|
+
fromArtifactPath: artifactAbs,
|
|
161
|
+
toArtifactPath: artifactAbs,
|
|
162
|
+
}),
|
|
360
163
|
};
|
|
361
164
|
return result;
|
|
362
165
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move.js","sourceRoot":"","sources":["../../../src/lanes/operations/move.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"move.js","sourceRoot":"","sources":["../../../src/lanes/operations/move.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* lane update — mutate a subset of fields on an existing lane config.
|
|
3
3
|
*
|
|
4
4
|
* Phase 6 Task 6.1 (graphical-entries). Accepts optional patches for
|
|
5
|
-
* `name`, `pipelineTemplate`, and `
|
|
6
|
-
* cannot change (it's the filename). The `archivedAt`
|
|
7
|
-
* by `archive` / `restore` and is not mutable through
|
|
5
|
+
* `name`, `pipelineTemplate`, `scaffoldDefaults`, and `host`. The
|
|
6
|
+
* lane's `id` cannot change (it's the filename). The `archivedAt`
|
|
7
|
+
* field is owned by `archive` / `restore` and is not mutable through
|
|
8
|
+
* `update`. Per Phase 39 (sites→lanes retirement) a lane carries no
|
|
9
|
+
* `contentDir` — the former `--content-dir` patch is replaced by
|
|
10
|
+
* `scaffoldDefaults` (add-time directories, keyed by artifact kind).
|
|
8
11
|
*
|
|
9
12
|
* Cross-validation:
|
|
10
13
|
* - If `pipelineTemplate` is patched, the new template MUST resolve
|
|
@@ -19,12 +22,19 @@
|
|
|
19
22
|
*
|
|
20
23
|
* Emits a `lane-update` journal event on success.
|
|
21
24
|
*/
|
|
22
|
-
import { type LaneConfig } from '../types.ts';
|
|
25
|
+
import { type ArtifactKind, type LaneConfig } from '../types.ts';
|
|
23
26
|
export interface UpdateLaneOptions {
|
|
24
27
|
readonly id: string;
|
|
25
28
|
readonly name?: string;
|
|
26
29
|
readonly pipelineTemplate?: string;
|
|
27
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Replacement scaffold-default directories, keyed by artifact kind.
|
|
32
|
+
* Replaces the whole `scaffoldDefaults` map when supplied (a lane
|
|
33
|
+
* carries no `contentDir` — Phase 39 sites→lanes retirement).
|
|
34
|
+
*/
|
|
35
|
+
readonly scaffoldDefaults?: Partial<Record<ArtifactKind, string>>;
|
|
36
|
+
/** Replacement website host. */
|
|
37
|
+
readonly host?: string;
|
|
28
38
|
}
|
|
29
39
|
export interface UpdateLaneResult {
|
|
30
40
|
readonly lane: LaneConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/lanes/operations/update.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/lanes/operations/update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGjE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,wBAAsB,UAAU,CAC9B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAiF3B"}
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* lane update — mutate a subset of fields on an existing lane config.
|
|
3
3
|
*
|
|
4
4
|
* Phase 6 Task 6.1 (graphical-entries). Accepts optional patches for
|
|
5
|
-
* `name`, `pipelineTemplate`, and `
|
|
6
|
-
* cannot change (it's the filename). The `archivedAt`
|
|
7
|
-
* by `archive` / `restore` and is not mutable through
|
|
5
|
+
* `name`, `pipelineTemplate`, `scaffoldDefaults`, and `host`. The
|
|
6
|
+
* lane's `id` cannot change (it's the filename). The `archivedAt`
|
|
7
|
+
* field is owned by `archive` / `restore` and is not mutable through
|
|
8
|
+
* `update`. Per Phase 39 (sites→lanes retirement) a lane carries no
|
|
9
|
+
* `contentDir` — the former `--content-dir` patch is replaced by
|
|
10
|
+
* `scaffoldDefaults` (add-time directories, keyed by artifact kind).
|
|
8
11
|
*
|
|
9
12
|
* Cross-validation:
|
|
10
13
|
* - If `pipelineTemplate` is patched, the new template MUST resolve
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
*/
|
|
22
25
|
import { appendJournalEvent } from "../../journal/append.js";
|
|
23
26
|
import { loadPipelineTemplate } from "../../pipelines/loader.js";
|
|
24
|
-
import {
|
|
27
|
+
import { assertSafeScaffoldDir, loadLaneConfig } from "../loader.js";
|
|
25
28
|
import { commitLaneConfig } from "./commit.js";
|
|
26
29
|
export async function updateLane(projectRoot, opts) {
|
|
27
30
|
const existing = loadLaneConfig(opts.id, projectRoot);
|
|
@@ -31,24 +34,31 @@ export async function updateLane(projectRoot, opts) {
|
|
|
31
34
|
if (opts.pipelineTemplate !== undefined) {
|
|
32
35
|
patches['pipelineTemplate'] = opts.pipelineTemplate;
|
|
33
36
|
}
|
|
34
|
-
if (opts.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
if (opts.scaffoldDefaults !== undefined) {
|
|
38
|
+
for (const dir of Object.values(opts.scaffoldDefaults)) {
|
|
39
|
+
if (dir !== undefined)
|
|
40
|
+
assertSafeScaffoldDir(projectRoot, dir);
|
|
41
|
+
}
|
|
42
|
+
patches['scaffoldDefaults'] = opts.scaffoldDefaults;
|
|
43
|
+
}
|
|
44
|
+
if (opts.host !== undefined) {
|
|
45
|
+
patches['host'] = opts.host;
|
|
37
46
|
}
|
|
38
47
|
const changedFields = Object.keys(patches);
|
|
39
48
|
if (changedFields.length === 0) {
|
|
40
49
|
throw new Error(`Cannot update lane "${opts.id}": no patch fields supplied. `
|
|
41
|
-
+ `Pass at least one of --name, --template, --
|
|
50
|
+
+ `Pass at least one of --name, --template, --scaffold-default, --host.`);
|
|
42
51
|
}
|
|
43
52
|
// Cross-validate the patched pipeline template up front so we don't
|
|
44
53
|
// half-write a broken lane.
|
|
45
|
-
|
|
54
|
+
const patchedTemplate = patches['pipelineTemplate'];
|
|
55
|
+
if (typeof patchedTemplate === 'string') {
|
|
46
56
|
try {
|
|
47
|
-
loadPipelineTemplate(
|
|
57
|
+
loadPipelineTemplate(patchedTemplate, projectRoot);
|
|
48
58
|
}
|
|
49
59
|
catch (err) {
|
|
50
60
|
const detail = err instanceof Error ? err.message : String(err);
|
|
51
|
-
throw new Error(`Cannot update lane "${opts.id}": pipelineTemplate "${
|
|
61
|
+
throw new Error(`Cannot update lane "${opts.id}": pipelineTemplate "${patchedTemplate}" `
|
|
52
62
|
+ `does not resolve:\n${detail}`);
|
|
53
63
|
}
|
|
54
64
|
}
|
|
@@ -62,9 +72,19 @@ export async function updateLane(projectRoot, opts) {
|
|
|
62
72
|
before[field] = Reflect.get(existing, field);
|
|
63
73
|
after[field] = patches[field];
|
|
64
74
|
}
|
|
75
|
+
// Build the patched lane from typed options rather than spreading the
|
|
76
|
+
// `unknown`-valued `patches` bag, so the merge stays type-safe (no
|
|
77
|
+
// `as` cast). `commitLaneConfig` re-validates the result via Zod.
|
|
65
78
|
const updated = {
|
|
66
79
|
...existing,
|
|
67
|
-
...
|
|
80
|
+
...(opts.name !== undefined && { name: opts.name }),
|
|
81
|
+
...(opts.pipelineTemplate !== undefined && {
|
|
82
|
+
pipelineTemplate: opts.pipelineTemplate,
|
|
83
|
+
}),
|
|
84
|
+
...(opts.scaffoldDefaults !== undefined && {
|
|
85
|
+
scaffoldDefaults: opts.scaffoldDefaults,
|
|
86
|
+
}),
|
|
87
|
+
...(opts.host !== undefined && { host: opts.host }),
|
|
68
88
|
};
|
|
69
89
|
const { lane, path } = commitLaneConfig(projectRoot, opts.id, updated, 'update');
|
|
70
90
|
await appendJournalEvent(projectRoot, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../../src/lanes/operations/update.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../../src/lanes/operations/update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAsB/C,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,WAAmB,EACnB,IAAuB;IAEvB,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;IAEtD,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IACzD,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC;IACtD,CAAC;IACD,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACvD,IAAI,GAAG,KAAK,SAAS;gBAAE,qBAAqB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC;IACtD,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IAC9B,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,CAAC,EAAE,+BAA+B;cAC3D,sEAAsE,CACzE,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,4BAA4B;IAC5B,MAAM,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACpD,IAAI,OAAO,eAAe,KAAK,QAAQ,EAAE,CAAC;QACxC,IAAI,CAAC;YACH,oBAAoB,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,CAAC,EAAE,wBAAwB,eAAe,IAAI;kBACvE,sBAAsB,MAAM,EAAE,CACjC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,8DAA8D;QAC9D,wDAAwD;QACxD,sDAAsD;QACtD,kCAAkC;QAClC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC7C,KAAK,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,sEAAsE;IACtE,mEAAmE;IACnE,kEAAkE;IAClE,MAAM,OAAO,GAAe;QAC1B,GAAG,QAAQ;QACX,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QACnD,GAAG,CAAC,IAAI,CAAC,gBAAgB,KAAK,SAAS,IAAI;YACzC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;SACxC,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,gBAAgB,KAAK,SAAS,IAAI;YACzC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;SACxC,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;KACpD,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAEjF,MAAM,kBAAkB,CAAC,WAAW,EAAE;QACpC,IAAI,EAAE,aAAa;QACnB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,OAAO,EAAE;YACP,aAAa;YACb,MAAM;YACN,KAAK;SACN;KACF,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* add-time artifactPath composition (Phase 39c-2b, sub-task b).
|
|
3
|
+
*
|
|
4
|
+
* `deskwork add --lane X [--kind markdown] [--layout L]` creates a NEW
|
|
5
|
+
* entry that has no `artifactPath` yet. This module composes that path
|
|
6
|
+
* from the lane's add-time `scaffoldDefaults` (the directory), the
|
|
7
|
+
* requested layout (the on-disk file shape), and the entry's slug, then
|
|
8
|
+
* the caller stamps the result onto the new entry's sidecar — from which
|
|
9
|
+
* point it is authoritative (resolution never recomputes it).
|
|
10
|
+
*
|
|
11
|
+
* MARKDOWN ONLY (operator decision): `deskwork add` supports only
|
|
12
|
+
* markdown entries right now — deskwork has no non-markdown materializer,
|
|
13
|
+
* so a non-markdown entry can't be created. {@link composeAddArtifactPath} therefore throws loudly
|
|
14
|
+
* for any non-markdown kind rather than composing a path that nothing
|
|
15
|
+
* can fulfill. The `ArtifactKind` TYPE still carries the other kinds for
|
|
16
|
+
* graphical-entries; only the add path gates to markdown.
|
|
17
|
+
*
|
|
18
|
+
* Per the sites→lanes retirement design (§ "add-time path composition"):
|
|
19
|
+
*
|
|
20
|
+
* directory ← lane.scaffoldDefaults['markdown'] (FAILS LOUDLY if absent)
|
|
21
|
+
* layout ← --layout flag, else DEFAULT_SCAFFOLD_LAYOUT (index)
|
|
22
|
+
* relativePath ← layoutToContentRelativePath(layout, slug)
|
|
23
|
+
* artifactPath ← posixJoin(directory, relativePath)
|
|
24
|
+
*
|
|
25
|
+
* POSIX join (AUDIT-40): `artifactPath` is persisted and string-compared
|
|
26
|
+
* against the forward-slash paths the rest of the system stores, so the
|
|
27
|
+
* join uses `node:path/posix` (never `node:path.join`, which yields
|
|
28
|
+
* backslashes on Windows).
|
|
29
|
+
*
|
|
30
|
+
* No fallback: a lane that does not declare a `scaffoldDefaults['markdown']`
|
|
31
|
+
* entry is an actionable operator error, not a silent default directory
|
|
32
|
+
* (per the project no-fallbacks rule). The thrown message names the lane
|
|
33
|
+
* id and how to fix it.
|
|
34
|
+
*/
|
|
35
|
+
import type { LaneConfig, ArtifactKind } from './types.ts';
|
|
36
|
+
/**
|
|
37
|
+
* On-disk filename shape of a scaffolded markdown artifact:
|
|
38
|
+
*
|
|
39
|
+
* - `index` → `<slug>/index.md` (hub-style directory)
|
|
40
|
+
* - `readme` → `<slug>/README.md` (editorial-private directory)
|
|
41
|
+
* - `flat` → `<slug>.md` (sibling file, no own directory)
|
|
42
|
+
*/
|
|
43
|
+
export type ScaffoldLayout = 'index' | 'readme' | 'flat';
|
|
44
|
+
/**
|
|
45
|
+
* The default layout used by `deskwork add` when `--layout` is omitted.
|
|
46
|
+
* `index` reproduces the legacy `{slug}/index.md` template default
|
|
47
|
+
* byte-for-byte — chosen for zero-behavior-change at the sites→lanes
|
|
48
|
+
* cutover.
|
|
49
|
+
*
|
|
50
|
+
* Per design Decision #12 this is a single GLOBAL default. (The
|
|
51
|
+
* superseding per-kind Decision #16 was retired alongside the multi-kind
|
|
52
|
+
* machinery when `add` was gated to markdown-only — a global default is
|
|
53
|
+
* correct again now that only one kind is supported.)
|
|
54
|
+
*/
|
|
55
|
+
export declare const DEFAULT_SCAFFOLD_LAYOUT: ScaffoldLayout;
|
|
56
|
+
/**
|
|
57
|
+
* Map a {@link ScaffoldLayout} + slug to the directory-relative path for
|
|
58
|
+
* a MARKDOWN artifact (the only kind `deskwork add` scaffolds).
|
|
59
|
+
*/
|
|
60
|
+
export declare function layoutToContentRelativePath(layout: ScaffoldLayout, slug: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Compose the project-relative `artifactPath` for a NEW markdown entry
|
|
63
|
+
* being scaffolded into a lane.
|
|
64
|
+
*
|
|
65
|
+
* @param lane - The resolved lane config the entry belongs to.
|
|
66
|
+
* @param kind - The entry's artifact kind. ONLY `markdown` is supported;
|
|
67
|
+
* any other kind throws (see module docblock — file creation for
|
|
68
|
+
* non-markdown kinds is not implemented).
|
|
69
|
+
* @param slug - The entry's slug (one or more `/`-separated kebab-case
|
|
70
|
+
* segments).
|
|
71
|
+
* @param layout - The on-disk file shape. When omitted, defaults to
|
|
72
|
+
* {@link DEFAULT_SCAFFOLD_LAYOUT} (`index`).
|
|
73
|
+
* @returns The project-root-relative path to stamp onto the entry's
|
|
74
|
+
* sidecar (e.g. `src/content/blog/my-post/index.md`), joined with
|
|
75
|
+
* forward slashes (AUDIT-40).
|
|
76
|
+
* @throws When `kind` is not `markdown`; or when the lane declares no
|
|
77
|
+
* `scaffoldDefaults['markdown']` entry.
|
|
78
|
+
*/
|
|
79
|
+
export declare function composeAddArtifactPath(lane: LaneConfig, kind: ArtifactKind, slug: string, layout?: ScaffoldLayout): string;
|
|
80
|
+
/**
|
|
81
|
+
* Narrow a raw `--layout` flag value to a {@link ScaffoldLayout}.
|
|
82
|
+
* Returns `undefined` for unrecognized values so the CLI caller can
|
|
83
|
+
* raise its own argument-shaped error (with the legal list and exit 2).
|
|
84
|
+
*/
|
|
85
|
+
export declare function parseScaffoldLayout(value: string): ScaffoldLayout | undefined;
|
|
86
|
+
/** The legal `--layout` values, for error messages. */
|
|
87
|
+
export declare const SCAFFOLD_LAYOUTS: readonly ScaffoldLayout[];
|
|
88
|
+
//# sourceMappingURL=scaffold-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold-path.d.ts","sourceRoot":"","sources":["../../src/lanes/scaffold-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,EAAE,cAAwB,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,GACX,MAAM,CASR;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,cAAc,GACtB,MAAM,CA4BR;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAK7E;AAED,uDAAuD;AACvD,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAIrD,CAAC"}
|