@deskwork/studio 0.14.1 → 0.16.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/components/scrapbook-item.d.ts +20 -0
- package/dist/components/scrapbook-item.d.ts.map +1 -1
- package/dist/components/scrapbook-item.js +21 -2
- package/dist/components/scrapbook-item.js.map +1 -1
- package/dist/pages/dashboard/affordances.d.ts.map +1 -1
- package/dist/pages/dashboard/affordances.js +13 -1
- package/dist/pages/dashboard/affordances.js.map +1 -1
- package/dist/pages/dashboard/affordances.ts +13 -1
- package/dist/pages/dashboard/press-queue.d.ts +16 -0
- package/dist/pages/dashboard/press-queue.d.ts.map +1 -0
- package/dist/pages/dashboard/press-queue.js +91 -0
- package/dist/pages/dashboard/press-queue.js.map +1 -0
- package/dist/pages/dashboard/press-queue.ts +112 -0
- package/dist/pages/dashboard.d.ts.map +1 -1
- package/dist/pages/dashboard.js +2 -0
- package/dist/pages/dashboard.js.map +1 -1
- package/dist/pages/dashboard.ts +2 -0
- package/dist/pages/entry-review/decision-strip.d.ts.map +1 -1
- package/dist/pages/entry-review/decision-strip.js +10 -2
- package/dist/pages/entry-review/decision-strip.js.map +1 -1
- package/dist/pages/entry-review/index.d.ts.map +1 -1
- package/dist/pages/entry-review/index.js +7 -4
- package/dist/pages/entry-review/index.js.map +1 -1
- package/dist/pages/help.js +11 -11
- package/dist/pages/help.js.map +1 -1
- package/dist/pages/help.ts +11 -11
- package/dist/pages/review-scrapbook-drawer.d.ts.map +1 -1
- package/dist/pages/review-scrapbook-drawer.js +10 -1
- package/dist/pages/review-scrapbook-drawer.js.map +1 -1
- package/dist/pages/review-scrapbook-drawer.ts +11 -1
- package/dist/pages/scrapbook/dispatch.d.ts +50 -0
- package/dist/pages/scrapbook/dispatch.d.ts.map +1 -0
- package/dist/pages/scrapbook/dispatch.js +104 -0
- package/dist/pages/scrapbook/dispatch.js.map +1 -0
- package/dist/pages/scrapbook/image-readers.d.ts +24 -0
- package/dist/pages/scrapbook/image-readers.d.ts.map +1 -0
- package/dist/pages/scrapbook/image-readers.js +135 -0
- package/dist/pages/scrapbook/image-readers.js.map +1 -0
- package/dist/pages/scrapbook/index.d.ts +21 -0
- package/dist/pages/scrapbook/index.d.ts.map +1 -0
- package/dist/pages/scrapbook/index.js +96 -0
- package/dist/pages/scrapbook/index.js.map +1 -0
- package/dist/pages/scrapbook/render.d.ts +68 -0
- package/dist/pages/scrapbook/render.d.ts.map +1 -0
- package/dist/pages/scrapbook/render.js +315 -0
- package/dist/pages/scrapbook/render.js.map +1 -0
- package/dist/pages/scrapbook/text-helpers.d.ts +40 -0
- package/dist/pages/scrapbook/text-helpers.d.ts.map +1 -0
- package/dist/pages/scrapbook/text-helpers.js +92 -0
- package/dist/pages/scrapbook/text-helpers.js.map +1 -0
- package/dist/pages/scrapbook/types.d.ts +26 -0
- package/dist/pages/scrapbook/types.d.ts.map +1 -0
- package/dist/pages/scrapbook/types.js +12 -0
- package/dist/pages/scrapbook/types.js.map +1 -0
- package/dist/pages/scrapbook.d.ts +8 -8
- package/dist/pages/scrapbook.d.ts.map +1 -1
- package/dist/pages/scrapbook.js +8 -600
- package/dist/pages/scrapbook.js.map +1 -1
- package/dist/pages/scrapbook.ts +11 -660
- package/dist/routes/api.d.ts.map +1 -1
- package/dist/routes/api.js +102 -140
- package/dist/routes/api.js.map +1 -1
- package/dist/routes/entry-annotation-body.d.ts +29 -0
- package/dist/routes/entry-annotation-body.d.ts.map +1 -1
- package/dist/routes/entry-annotation-body.js +80 -0
- package/dist/routes/entry-annotation-body.js.map +1 -1
- package/dist/routes/scrapbook-file.d.ts +17 -9
- package/dist/routes/scrapbook-file.d.ts.map +1 -1
- package/dist/routes/scrapbook-file.js +48 -15
- package/dist/routes/scrapbook-file.js.map +1 -1
- package/dist/routes/scrapbook-mutation-dispatch.d.ts +29 -0
- package/dist/routes/scrapbook-mutation-dispatch.d.ts.map +1 -0
- package/dist/routes/scrapbook-mutation-dispatch.js +63 -0
- package/dist/routes/scrapbook-mutation-dispatch.js.map +1 -0
- package/dist/routes/scrapbook-mutation-envelope.d.ts +93 -0
- package/dist/routes/scrapbook-mutation-envelope.d.ts.map +1 -0
- package/dist/routes/scrapbook-mutation-envelope.js +147 -0
- package/dist/routes/scrapbook-mutation-envelope.js.map +1 -0
- package/dist/routes/scrapbook-mutations.d.ts +25 -7
- package/dist/routes/scrapbook-mutations.d.ts.map +1 -1
- package/dist/routes/scrapbook-mutations.js +67 -92
- package/dist/routes/scrapbook-mutations.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -2
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Read-only binary endpoint for scrapbook files.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Two addressing modes:
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* `GET /api/dev/scrapbook-file?site=<slug>&path=<scrapbook path>&name=<filename>[&secret=1]`
|
|
7
|
+
* — slug-shape addressing. `path` is the directory under `contentDir`
|
|
8
|
+
* whose `scrapbook/` subdir holds the file. Validated through
|
|
9
|
+
* `assertSlug` (kebab-case-only); rejects paths with dots / uppercase
|
|
10
|
+
* / non-slug characters.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `GET /api/dev/scrapbook-file?site=<slug>&entryId=<uuid>&name=<filename>[&secret=1]`
|
|
13
|
+
* — entry-id addressing. The route reads the entry's sidecar to find
|
|
14
|
+
* its on-disk artifactPath, derives the scrapbook dir from the
|
|
15
|
+
* artifact's parent directory, and serves the file. No slug-shape
|
|
16
|
+
* validation — works for projects whose feature-doc layout doesn't
|
|
17
|
+
* match the kebab-case slug template (e.g. `docs/<version>/<status>/<feature>/`).
|
|
18
|
+
*
|
|
19
|
+
* Both modes return the raw bytes of a single scrapbook file with a
|
|
20
|
+
* sensible Content-Type header. Filename + path-traversal guards apply
|
|
21
|
+
* in both modes (via `assertFilename` + the containment check in
|
|
22
|
+
* `scrapbookFilePathAtDir`).
|
|
15
23
|
*/
|
|
16
24
|
import { extname } from 'node:path';
|
|
17
|
-
import { readScrapbookFile } from '@deskwork/core/scrapbook';
|
|
25
|
+
import { readScrapbookFile, readScrapbookFileForEntry, } from '@deskwork/core/scrapbook';
|
|
26
|
+
import { readSidecar } from '@deskwork/core/sidecar';
|
|
18
27
|
const MIME_TYPES = {
|
|
19
28
|
'.png': 'image/png',
|
|
20
29
|
'.jpg': 'image/jpeg',
|
|
@@ -38,19 +47,43 @@ function contentTypeFor(filename) {
|
|
|
38
47
|
export async function serveScrapbookFile(c, ctx) {
|
|
39
48
|
const site = c.req.query('site');
|
|
40
49
|
const path = c.req.query('path');
|
|
50
|
+
const entryId = c.req.query('entryId');
|
|
41
51
|
const name = c.req.query('name');
|
|
42
52
|
const secret = c.req.query('secret') === '1';
|
|
43
|
-
if (!site || !
|
|
44
|
-
return c.json({ error: 'site
|
|
53
|
+
if (!site || !name) {
|
|
54
|
+
return c.json({ error: 'site and name query params are required' }, 400);
|
|
55
|
+
}
|
|
56
|
+
if (!path && !entryId) {
|
|
57
|
+
return c.json({ error: 'either path or entryId query param is required' }, 400);
|
|
45
58
|
}
|
|
46
59
|
if (!(site in ctx.config.sites)) {
|
|
47
60
|
return c.json({ error: `unknown site: ${site}` }, 404);
|
|
48
61
|
}
|
|
62
|
+
// Reject malformed entryId before it reaches the filesystem. `readSidecar`
|
|
63
|
+
// composes the path as `<projectRoot>/.deskwork/entries/<entryId>.json` —
|
|
64
|
+
// `node:path`'s join collapses `..` segments, so an unvalidated entryId
|
|
65
|
+
// can probe arbitrary on-disk locations even though no data is leaked
|
|
66
|
+
// (ENOENT becomes 404). Match the UUID schema enforced on entry creation.
|
|
67
|
+
if (entryId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(entryId)) {
|
|
68
|
+
return c.json({ error: 'invalid entryId' }, 400);
|
|
69
|
+
}
|
|
49
70
|
let result;
|
|
50
71
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
if (entryId) {
|
|
73
|
+
// Entry-id mode: resolve the scrapbook dir via the entry's sidecar.
|
|
74
|
+
// Bypasses slug-shape validation so projects with non-kebab-case
|
|
75
|
+
// content layouts (dots, uppercase, etc.) can still serve assets.
|
|
76
|
+
const entry = await readSidecar(ctx.projectRoot, entryId);
|
|
77
|
+
result = readScrapbookFileForEntry(ctx.projectRoot, ctx.config, site, { id: entry.uuid, slug: entry.slug }, name, { secret });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Slug-shape mode: backwards-compatible with existing scrapbook-viewer
|
|
81
|
+
// callers. `path!` is non-null here because the early-return covered
|
|
82
|
+
// the both-missing case.
|
|
83
|
+
result = readScrapbookFile(ctx.projectRoot, ctx.config, site, path, name, {
|
|
84
|
+
secret,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
54
87
|
}
|
|
55
88
|
catch (err) {
|
|
56
89
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrapbook-file.js","sourceRoot":"","sources":["../../src/routes/scrapbook-file.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scrapbook-file.js","sourceRoot":"","sources":["../../src/routes/scrapbook-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-mode dispatch helpers for the studio scrapbook mutation routes.
|
|
3
|
+
*
|
|
4
|
+
* Post-#192: both modes (`entry` and `slug`) route through the entry-
|
|
5
|
+
* aware `*AtDir` family after `resolveScrapbookDir` produces an absolute
|
|
6
|
+
* scrapbook directory. The slug-mode resolution still goes through
|
|
7
|
+
* `scrapbookDirForEntry` — passing `{ slug }` without an id triggers
|
|
8
|
+
* the internal slug-template fallback inside the resolver — but the
|
|
9
|
+
* public CRUD surface is unified on the `*AtDir` helpers. The route
|
|
10
|
+
* handler stays thin: read JSON, parse envelope, validate route-
|
|
11
|
+
* specific args, dispatch.
|
|
12
|
+
*
|
|
13
|
+
* Extracted from `scrapbook-mutations.ts` (#191) to keep the route file
|
|
14
|
+
* under the project's 300–500 line cap.
|
|
15
|
+
*/
|
|
16
|
+
import { type ScrapbookItem } from '@deskwork/core/scrapbook';
|
|
17
|
+
import type { StudioContext } from './api.ts';
|
|
18
|
+
import { type ParsedEnvelope } from './scrapbook-mutation-envelope.ts';
|
|
19
|
+
export declare function saveDispatch(ctx: StudioContext, env: ParsedEnvelope, filename: string, bodyText: string): Promise<ScrapbookItem>;
|
|
20
|
+
export declare function renameInPlace(ctx: StudioContext, env: ParsedEnvelope, oldName: string, newName: string): Promise<ScrapbookItem>;
|
|
21
|
+
export interface CrossSectionPaths {
|
|
22
|
+
srcAbs: string;
|
|
23
|
+
dstAbs: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function resolveCrossSectionPaths(ctx: StudioContext, env: ParsedEnvelope, oldName: string, newName: string, toSecret: boolean): Promise<CrossSectionPaths>;
|
|
26
|
+
export declare function deleteDispatch(ctx: StudioContext, env: ParsedEnvelope, filename: string): Promise<void>;
|
|
27
|
+
export declare function createDispatch(ctx: StudioContext, env: ParsedEnvelope, filename: string, bodyText: string): Promise<ScrapbookItem>;
|
|
28
|
+
export declare function uploadDispatch(ctx: StudioContext, env: ParsedEnvelope, filename: string, buf: Buffer): Promise<ScrapbookItem>;
|
|
29
|
+
//# sourceMappingURL=scrapbook-mutation-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrapbook-mutation-dispatch.d.ts","sourceRoot":"","sources":["../../src/routes/scrapbook-mutation-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAE1C,wBAAsB,YAAY,CAChC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAmBxB;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAKxB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,iBAAiB,CAAC,CAM5B;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAKxB;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,aAAa,CAAC,CAKxB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-mode dispatch helpers for the studio scrapbook mutation routes.
|
|
3
|
+
*
|
|
4
|
+
* Post-#192: both modes (`entry` and `slug`) route through the entry-
|
|
5
|
+
* aware `*AtDir` family after `resolveScrapbookDir` produces an absolute
|
|
6
|
+
* scrapbook directory. The slug-mode resolution still goes through
|
|
7
|
+
* `scrapbookDirForEntry` — passing `{ slug }` without an id triggers
|
|
8
|
+
* the internal slug-template fallback inside the resolver — but the
|
|
9
|
+
* public CRUD surface is unified on the `*AtDir` helpers. The route
|
|
10
|
+
* handler stays thin: read JSON, parse envelope, validate route-
|
|
11
|
+
* specific args, dispatch.
|
|
12
|
+
*
|
|
13
|
+
* Extracted from `scrapbook-mutations.ts` (#191) to keep the route file
|
|
14
|
+
* under the project's 300–500 line cap.
|
|
15
|
+
*/
|
|
16
|
+
import { createScrapbookMarkdownAtDir, deleteScrapbookFileAtDir, renameScrapbookFileAtDir, saveScrapbookFileAtDir, scrapbookFilePathAtDir, writeScrapbookUploadAtDir, } from '@deskwork/core/scrapbook';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { resolveScrapbookDir, } from "./scrapbook-mutation-envelope.js";
|
|
19
|
+
export async function saveDispatch(ctx, env, filename, bodyText) {
|
|
20
|
+
const dir = await resolveScrapbookDir(ctx, env);
|
|
21
|
+
const abs = scrapbookFilePathAtDir(dir, filename, { secret: env.secret });
|
|
22
|
+
if (!existsSync(abs)) {
|
|
23
|
+
if (filename.endsWith('.md')) {
|
|
24
|
+
return createScrapbookMarkdownAtDir(dir, filename, bodyText, {
|
|
25
|
+
secret: env.secret,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return writeScrapbookUploadAtDir(dir, filename, Buffer.from(bodyText, 'utf-8'), { secret: env.secret });
|
|
29
|
+
}
|
|
30
|
+
return saveScrapbookFileAtDir(dir, filename, bodyText, {
|
|
31
|
+
secret: env.secret,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function renameInPlace(ctx, env, oldName, newName) {
|
|
35
|
+
const dir = await resolveScrapbookDir(ctx, env);
|
|
36
|
+
return renameScrapbookFileAtDir(dir, oldName, newName, {
|
|
37
|
+
secret: env.secret,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export async function resolveCrossSectionPaths(ctx, env, oldName, newName, toSecret) {
|
|
41
|
+
const dir = await resolveScrapbookDir(ctx, env);
|
|
42
|
+
return {
|
|
43
|
+
srcAbs: scrapbookFilePathAtDir(dir, oldName, { secret: env.secret }),
|
|
44
|
+
dstAbs: scrapbookFilePathAtDir(dir, newName, { secret: toSecret }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function deleteDispatch(ctx, env, filename) {
|
|
48
|
+
const dir = await resolveScrapbookDir(ctx, env);
|
|
49
|
+
deleteScrapbookFileAtDir(dir, filename, { secret: env.secret });
|
|
50
|
+
}
|
|
51
|
+
export async function createDispatch(ctx, env, filename, bodyText) {
|
|
52
|
+
const dir = await resolveScrapbookDir(ctx, env);
|
|
53
|
+
return createScrapbookMarkdownAtDir(dir, filename, bodyText, {
|
|
54
|
+
secret: env.secret,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export async function uploadDispatch(ctx, env, filename, buf) {
|
|
58
|
+
const dir = await resolveScrapbookDir(ctx, env);
|
|
59
|
+
return writeScrapbookUploadAtDir(dir, filename, buf, {
|
|
60
|
+
secret: env.secret,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=scrapbook-mutation-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrapbook-mutation-dispatch.js","sourceRoot":"","sources":["../../src/routes/scrapbook-mutation-dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation-envelope parsing for the studio scrapbook routes.
|
|
3
|
+
*
|
|
4
|
+
* Lives next to `scrapbook-mutations.ts`; extracted (#191) to keep the
|
|
5
|
+
* route file under the project's 300–500 line cap. The single
|
|
6
|
+
* responsibility is:
|
|
7
|
+
*
|
|
8
|
+
* 1. Validate the request envelope (`site`, `entryId | slug`, `secret?`).
|
|
9
|
+
* 2. Validate `entryId` against the UUID regex BEFORE filesystem access
|
|
10
|
+
* (the scrapbook-file route's same pattern, landed in v0.15.0
|
|
11
|
+
* commit `14ffbe7`).
|
|
12
|
+
* 3. Resolve the absolute scrapbook directory for whichever addressing
|
|
13
|
+
* mode the request used. Both modes route through
|
|
14
|
+
* `scrapbookDirForEntry`: entry-id mode reads the sidecar to get
|
|
15
|
+
* the bound `{ uuid, slug }`; slug mode synthesizes `{ slug }`
|
|
16
|
+
* (no id) and lets the entry-aware resolver fall back to its
|
|
17
|
+
* internal slug-template path. Post-#192 this is a single code
|
|
18
|
+
* path with no public slug-template entry point.
|
|
19
|
+
*
|
|
20
|
+
* The envelope is a discriminated union (`mode: 'entry' | 'slug'`); call
|
|
21
|
+
* sites narrow without `as` casts.
|
|
22
|
+
*/
|
|
23
|
+
import type { StudioContext } from './api.ts';
|
|
24
|
+
/**
|
|
25
|
+
* Mirrors the UUID regex enforced on entry creation and used by the
|
|
26
|
+
* scrapbook-file route. Rejects malformed `entryId` before it reaches
|
|
27
|
+
* the filesystem — `readSidecar` composes its path as
|
|
28
|
+
* `<projectRoot>/.deskwork/entries/<entryId>.json`, and `node:path`'s
|
|
29
|
+
* join collapses `..` segments, so an unvalidated `entryId` could probe
|
|
30
|
+
* arbitrary on-disk locations.
|
|
31
|
+
*/
|
|
32
|
+
export declare const UUID_RE: RegExp;
|
|
33
|
+
/**
|
|
34
|
+
* Successfully-parsed mutation envelope.
|
|
35
|
+
*
|
|
36
|
+
* Exactly one of `mode === 'entry'` or `mode === 'slug'` is set per
|
|
37
|
+
* request — the discriminant tells the dispatch which addressing mode to
|
|
38
|
+
* use without `as` casts at the call site.
|
|
39
|
+
*/
|
|
40
|
+
export type ParsedEnvelope = {
|
|
41
|
+
mode: 'entry';
|
|
42
|
+
site: string;
|
|
43
|
+
entryId: string;
|
|
44
|
+
secret: boolean;
|
|
45
|
+
} | {
|
|
46
|
+
mode: 'slug';
|
|
47
|
+
site: string;
|
|
48
|
+
slug: string;
|
|
49
|
+
secret: boolean;
|
|
50
|
+
};
|
|
51
|
+
export interface EnvelopeError {
|
|
52
|
+
error: string;
|
|
53
|
+
status: 400 | 404;
|
|
54
|
+
}
|
|
55
|
+
export declare function isEnvelopeError(v: ParsedEnvelope | EnvelopeError): v is EnvelopeError;
|
|
56
|
+
/**
|
|
57
|
+
* Validate the common `{ site, entryId? | slug?, secret? }` envelope for
|
|
58
|
+
* JSON-body routes. Returns a typed object or an `EnvelopeError` that
|
|
59
|
+
* the caller propagates directly. The site existence check is here so
|
|
60
|
+
* every mutation 404s on unknown sites the same way the read endpoint
|
|
61
|
+
* does.
|
|
62
|
+
*
|
|
63
|
+
* `entryId` is preferred when both are present. UUID validation runs
|
|
64
|
+
* BEFORE filesystem access for the same reason as the scrapbook-file
|
|
65
|
+
* route — see the UUID_RE comment above.
|
|
66
|
+
*/
|
|
67
|
+
export declare function checkJsonEnvelope(ctx: StudioContext, body: Record<string, unknown>): ParsedEnvelope | EnvelopeError;
|
|
68
|
+
/**
|
|
69
|
+
* Same shape as `checkJsonEnvelope` but reads from a `FormData` instance.
|
|
70
|
+
* Used by the upload route, which speaks multipart for binary file
|
|
71
|
+
* payloads. The semantics match — `entryId` preferred, `slug` is the
|
|
72
|
+
* deprecation-window fallback, UUID validation up-front.
|
|
73
|
+
*
|
|
74
|
+
* Note: the multipart `secret` field arrives as the literal string
|
|
75
|
+
* `"true"` (form fields are always strings); we normalize it here so
|
|
76
|
+
* the rest of the pipeline doesn't have to special-case form-vs-json.
|
|
77
|
+
*/
|
|
78
|
+
export declare function checkFormEnvelope(ctx: StudioContext, form: FormData): ParsedEnvelope | EnvelopeError;
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the absolute scrapbook directory for a parsed envelope. Both
|
|
81
|
+
* modes go through `scrapbookDirForEntry`:
|
|
82
|
+
* - entry-id mode reads the sidecar and passes `{ id, slug }` so the
|
|
83
|
+
* resolver hits the index lookup (refactor-proof binding).
|
|
84
|
+
* - slug mode passes `{ slug }` only; the resolver still goes through
|
|
85
|
+
* `findEntryFile`, which falls back to its private slug-template
|
|
86
|
+
* path when no id binding exists. Post-#192 this is the only code
|
|
87
|
+
* path; the public `scrapbookDir(slug)` was retired.
|
|
88
|
+
*
|
|
89
|
+
* Throws on lookup / resolution failure; the caller maps the error
|
|
90
|
+
* message onto the right HTTP status via `statusForError`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function resolveScrapbookDir(ctx: StudioContext, env: ParsedEnvelope): Promise<string>;
|
|
93
|
+
//# sourceMappingURL=scrapbook-mutation-envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrapbook-mutation-envelope.d.ts","sourceRoot":"","sources":["../../src/routes/scrapbook-mutation-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAM9C;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,QAC+C,CAAC;AAMpE;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEN,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;CACnB;AAED,wBAAgB,eAAe,CAC7B,CAAC,EAAE,cAAc,GAAG,aAAa,GAChC,CAAC,IAAI,aAAa,CAEpB;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,cAAc,GAAG,aAAa,CAmChC;AAMD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,QAAQ,GACb,cAAc,GAAG,aAAa,CAwBhC;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,MAAM,CAAC,CAgBjB"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation-envelope parsing for the studio scrapbook routes.
|
|
3
|
+
*
|
|
4
|
+
* Lives next to `scrapbook-mutations.ts`; extracted (#191) to keep the
|
|
5
|
+
* route file under the project's 300–500 line cap. The single
|
|
6
|
+
* responsibility is:
|
|
7
|
+
*
|
|
8
|
+
* 1. Validate the request envelope (`site`, `entryId | slug`, `secret?`).
|
|
9
|
+
* 2. Validate `entryId` against the UUID regex BEFORE filesystem access
|
|
10
|
+
* (the scrapbook-file route's same pattern, landed in v0.15.0
|
|
11
|
+
* commit `14ffbe7`).
|
|
12
|
+
* 3. Resolve the absolute scrapbook directory for whichever addressing
|
|
13
|
+
* mode the request used. Both modes route through
|
|
14
|
+
* `scrapbookDirForEntry`: entry-id mode reads the sidecar to get
|
|
15
|
+
* the bound `{ uuid, slug }`; slug mode synthesizes `{ slug }`
|
|
16
|
+
* (no id) and lets the entry-aware resolver fall back to its
|
|
17
|
+
* internal slug-template path. Post-#192 this is a single code
|
|
18
|
+
* path with no public slug-template entry point.
|
|
19
|
+
*
|
|
20
|
+
* The envelope is a discriminated union (`mode: 'entry' | 'slug'`); call
|
|
21
|
+
* sites narrow without `as` casts.
|
|
22
|
+
*/
|
|
23
|
+
import { scrapbookDirForEntry } from '@deskwork/core/scrapbook';
|
|
24
|
+
import { readSidecar } from '@deskwork/core/sidecar';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// UUID validation
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Mirrors the UUID regex enforced on entry creation and used by the
|
|
30
|
+
* scrapbook-file route. Rejects malformed `entryId` before it reaches
|
|
31
|
+
* the filesystem — `readSidecar` composes its path as
|
|
32
|
+
* `<projectRoot>/.deskwork/entries/<entryId>.json`, and `node:path`'s
|
|
33
|
+
* join collapses `..` segments, so an unvalidated `entryId` could probe
|
|
34
|
+
* arbitrary on-disk locations.
|
|
35
|
+
*/
|
|
36
|
+
export const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
37
|
+
export function isEnvelopeError(v) {
|
|
38
|
+
return 'error' in v;
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// JSON envelope check
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Validate the common `{ site, entryId? | slug?, secret? }` envelope for
|
|
45
|
+
* JSON-body routes. Returns a typed object or an `EnvelopeError` that
|
|
46
|
+
* the caller propagates directly. The site existence check is here so
|
|
47
|
+
* every mutation 404s on unknown sites the same way the read endpoint
|
|
48
|
+
* does.
|
|
49
|
+
*
|
|
50
|
+
* `entryId` is preferred when both are present. UUID validation runs
|
|
51
|
+
* BEFORE filesystem access for the same reason as the scrapbook-file
|
|
52
|
+
* route — see the UUID_RE comment above.
|
|
53
|
+
*/
|
|
54
|
+
export function checkJsonEnvelope(ctx, body) {
|
|
55
|
+
const site = body.site;
|
|
56
|
+
if (typeof site !== 'string' || site.length === 0) {
|
|
57
|
+
return { error: 'site is required', status: 400 };
|
|
58
|
+
}
|
|
59
|
+
if (!(site in ctx.config.sites)) {
|
|
60
|
+
return { error: `unknown site: ${site}`, status: 404 };
|
|
61
|
+
}
|
|
62
|
+
const secretRaw = body.secret;
|
|
63
|
+
if (secretRaw !== undefined && typeof secretRaw !== 'boolean') {
|
|
64
|
+
return { error: 'secret must be a boolean when provided', status: 400 };
|
|
65
|
+
}
|
|
66
|
+
const secret = secretRaw === true;
|
|
67
|
+
const entryId = body.entryId;
|
|
68
|
+
if (entryId !== undefined) {
|
|
69
|
+
if (typeof entryId !== 'string' || entryId.length === 0) {
|
|
70
|
+
return {
|
|
71
|
+
error: 'entryId must be a non-empty string when provided',
|
|
72
|
+
status: 400,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (!UUID_RE.test(entryId)) {
|
|
76
|
+
return { error: 'invalid entryId', status: 400 };
|
|
77
|
+
}
|
|
78
|
+
return { mode: 'entry', site, entryId, secret };
|
|
79
|
+
}
|
|
80
|
+
// Back-compat slug-template fallback. To be removed in #192 once all
|
|
81
|
+
// callers send entryId.
|
|
82
|
+
const slug = body.slug;
|
|
83
|
+
if (typeof slug !== 'string' || slug.length === 0) {
|
|
84
|
+
return { error: 'entryId or slug is required', status: 400 };
|
|
85
|
+
}
|
|
86
|
+
return { mode: 'slug', site, slug, secret };
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Multipart envelope check
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* Same shape as `checkJsonEnvelope` but reads from a `FormData` instance.
|
|
93
|
+
* Used by the upload route, which speaks multipart for binary file
|
|
94
|
+
* payloads. The semantics match — `entryId` preferred, `slug` is the
|
|
95
|
+
* deprecation-window fallback, UUID validation up-front.
|
|
96
|
+
*
|
|
97
|
+
* Note: the multipart `secret` field arrives as the literal string
|
|
98
|
+
* `"true"` (form fields are always strings); we normalize it here so
|
|
99
|
+
* the rest of the pipeline doesn't have to special-case form-vs-json.
|
|
100
|
+
*/
|
|
101
|
+
export function checkFormEnvelope(ctx, form) {
|
|
102
|
+
const site = form.get('site');
|
|
103
|
+
if (typeof site !== 'string' || site.length === 0) {
|
|
104
|
+
return { error: 'site is required', status: 400 };
|
|
105
|
+
}
|
|
106
|
+
if (!(site in ctx.config.sites)) {
|
|
107
|
+
return { error: `unknown site: ${site}`, status: 404 };
|
|
108
|
+
}
|
|
109
|
+
const secretField = form.get('secret');
|
|
110
|
+
const secret = typeof secretField === 'string' && secretField === 'true';
|
|
111
|
+
const entryIdField = form.get('entryId');
|
|
112
|
+
if (typeof entryIdField === 'string' && entryIdField.length > 0) {
|
|
113
|
+
if (!UUID_RE.test(entryIdField)) {
|
|
114
|
+
return { error: 'invalid entryId', status: 400 };
|
|
115
|
+
}
|
|
116
|
+
return { mode: 'entry', site, entryId: entryIdField, secret };
|
|
117
|
+
}
|
|
118
|
+
const slug = form.get('slug');
|
|
119
|
+
if (typeof slug !== 'string' || slug.length === 0) {
|
|
120
|
+
return { error: 'entryId or slug is required', status: 400 };
|
|
121
|
+
}
|
|
122
|
+
return { mode: 'slug', site, slug, secret };
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Directory resolution
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the absolute scrapbook directory for a parsed envelope. Both
|
|
129
|
+
* modes go through `scrapbookDirForEntry`:
|
|
130
|
+
* - entry-id mode reads the sidecar and passes `{ id, slug }` so the
|
|
131
|
+
* resolver hits the index lookup (refactor-proof binding).
|
|
132
|
+
* - slug mode passes `{ slug }` only; the resolver still goes through
|
|
133
|
+
* `findEntryFile`, which falls back to its private slug-template
|
|
134
|
+
* path when no id binding exists. Post-#192 this is the only code
|
|
135
|
+
* path; the public `scrapbookDir(slug)` was retired.
|
|
136
|
+
*
|
|
137
|
+
* Throws on lookup / resolution failure; the caller maps the error
|
|
138
|
+
* message onto the right HTTP status via `statusForError`.
|
|
139
|
+
*/
|
|
140
|
+
export async function resolveScrapbookDir(ctx, env) {
|
|
141
|
+
if (env.mode === 'entry') {
|
|
142
|
+
const entry = await readSidecar(ctx.projectRoot, env.entryId);
|
|
143
|
+
return scrapbookDirForEntry(ctx.projectRoot, ctx.config, env.site, { id: entry.uuid, slug: entry.slug });
|
|
144
|
+
}
|
|
145
|
+
return scrapbookDirForEntry(ctx.projectRoot, ctx.config, env.site, { slug: env.slug });
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=scrapbook-mutation-envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrapbook-mutation-envelope.js","sourceRoot":"","sources":["../../src/routes/scrapbook-mutation-envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,15 +10,33 @@
|
|
|
10
10
|
* Phase 16d retrofitted only the read path. v0.4.1 closes the gap
|
|
11
11
|
* (issue #21).
|
|
12
12
|
*
|
|
13
|
+
* Two addressing modes (#191, unified post-#192):
|
|
14
|
+
* - **Entry-id mode** (preferred): `{ site, entryId, ... }` — looks up
|
|
15
|
+
* the entry's sidecar, derives the scrapbook dir from the artifact's
|
|
16
|
+
* parent directory via `scrapbookDirForEntry`, mutates there. Same
|
|
17
|
+
* code path the read endpoints use; refactor-proof for projects whose
|
|
18
|
+
* feature-doc layout doesn't match the kebab-case slug template.
|
|
19
|
+
* - **Slug mode** (back-compat fallback): `{ site, slug, ... }` —
|
|
20
|
+
* accepted for back-compat with older clients. Post-#192, slug mode
|
|
21
|
+
* also routes through `scrapbookDirForEntry`: passing `{ slug }`
|
|
22
|
+
* without an id triggers the resolver's internal slug-template
|
|
23
|
+
* fallback, so the public mutation surface is unified on the
|
|
24
|
+
* entry-aware code path. The legacy `scrapbookDir(slug)` and the
|
|
25
|
+
* slug-template CRUD primitives are no longer exported from
|
|
26
|
+
* `@deskwork/core/scrapbook`.
|
|
27
|
+
*
|
|
28
|
+
* Companion modules (#191 split, to keep this file under the project's
|
|
29
|
+
* 300–500 line cap):
|
|
30
|
+
* - `scrapbook-mutation-envelope.ts` — JSON / form envelope parsing,
|
|
31
|
+
* UUID validation, dir resolution.
|
|
32
|
+
* - `scrapbook-mutation-dispatch.ts` — per-mode dispatch helpers
|
|
33
|
+
* (entry-aware vs. slug-template), one per route verb.
|
|
34
|
+
*
|
|
13
35
|
* Design notes:
|
|
14
36
|
* - Path resolution + traversal protection runs through
|
|
15
|
-
* `@deskwork/core/scrapbook` helpers
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - The client speaks `{ site, slug, filename, body }` for save/create,
|
|
19
|
-
* `{ site, slug, oldName, newName }` for rename, `{ site, slug,
|
|
20
|
-
* filename }` for delete, multipart `{ site, slug, file }` for
|
|
21
|
-
* upload. Endpoints accept those exact field names.
|
|
37
|
+
* `@deskwork/core/scrapbook` helpers. Those throw on `..` sequences,
|
|
38
|
+
* absolute paths, and any filename that escapes the scrapbook dir;
|
|
39
|
+
* we surface those as 400.
|
|
22
40
|
* - We never log file contents on save / upload (privacy). Logging
|
|
23
41
|
* filename + slug is fine but kept silent here — the studio's
|
|
24
42
|
* console is the operator's, not a server log.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrapbook-mutations.d.ts","sourceRoot":"","sources":["../../src/routes/scrapbook-mutations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scrapbook-mutations.d.ts","sourceRoot":"","sources":["../../src/routes/scrapbook-mutations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgE9C,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyMvE"}
|