@bevel-software/platform-shared 0.14.0 → 0.19.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/auth/types.d.ts +12 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/git/pr.types.d.ts +75 -11
- package/dist/git/pr.types.d.ts.map +1 -1
- package/dist/git/types.d.ts +75 -2
- package/dist/git/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/workflow/events.d.ts +39 -4
- package/dist/workflow/events.d.ts.map +1 -1
- package/dist/workflow/events.js.map +1 -1
- package/dist/workflow/interface.d.ts +137 -5
- package/dist/workflow/interface.d.ts.map +1 -1
- package/dist/workflow/types.d.ts +93 -0
- package/dist/workflow/types.d.ts.map +1 -1
- package/dist/workspace/access-verbs.d.ts +83 -0
- package/dist/workspace/access-verbs.d.ts.map +1 -0
- package/dist/workspace/access-verbs.js +110 -0
- package/dist/workspace/access-verbs.js.map +1 -0
- package/dist/workspace/agent-preamble.d.ts +33 -0
- package/dist/workspace/agent-preamble.d.ts.map +1 -0
- package/dist/workspace/agent-preamble.js +45 -0
- package/dist/workspace/agent-preamble.js.map +1 -0
- package/dist/workspace/entry-exists.d.ts +27 -0
- package/dist/workspace/entry-exists.d.ts.map +1 -0
- package/dist/workspace/entry-exists.js +33 -0
- package/dist/workspace/entry-exists.js.map +1 -0
- package/dist/workspace/filename.d.ts +15 -0
- package/dist/workspace/filename.d.ts.map +1 -1
- package/dist/workspace/filename.js +37 -3
- package/dist/workspace/filename.js.map +1 -1
- package/dist/workspace/frontmatter-carriers.d.ts +44 -0
- package/dist/workspace/frontmatter-carriers.d.ts.map +1 -0
- package/dist/workspace/frontmatter-carriers.js +52 -0
- package/dist/workspace/frontmatter-carriers.js.map +1 -0
- package/dist/workspace/frontmatter.d.ts +23 -4
- package/dist/workspace/frontmatter.d.ts.map +1 -1
- package/dist/workspace/frontmatter.js +59 -9
- package/dist/workspace/frontmatter.js.map +1 -1
- package/dist/workspace/kb-layout.d.ts +365 -19
- package/dist/workspace/kb-layout.d.ts.map +1 -1
- package/dist/workspace/kb-layout.js +619 -20
- package/dist/workspace/kb-layout.js.map +1 -1
- package/dist/workspace/placeholder.d.ts +21 -0
- package/dist/workspace/placeholder.d.ts.map +1 -0
- package/dist/workspace/placeholder.js +28 -0
- package/dist/workspace/placeholder.js.map +1 -0
- package/dist/workspace/platform-files.d.ts +105 -0
- package/dist/workspace/platform-files.d.ts.map +1 -0
- package/dist/workspace/platform-files.js +147 -0
- package/dist/workspace/platform-files.js.map +1 -0
- package/dist/workspace/types.d.ts +8 -0
- package/dist/workspace/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/auth/types.ts +12 -0
- package/src/git/pr.types.ts +77 -11
- package/src/git/types.ts +81 -3
- package/src/index.ts +6 -0
- package/src/workflow/events.ts +40 -3
- package/src/workflow/interface.ts +162 -6
- package/src/workflow/types.ts +70 -0
- package/src/workspace/access-verbs.ts +124 -0
- package/src/workspace/agent-preamble.ts +47 -0
- package/src/workspace/entry-exists.ts +36 -0
- package/src/workspace/filename.ts +38 -3
- package/src/workspace/frontmatter-carriers.ts +57 -0
- package/src/workspace/frontmatter.ts +57 -8
- package/src/workspace/kb-layout.ts +660 -24
- package/src/workspace/placeholder.ts +29 -0
- package/src/workspace/platform-files.ts +188 -0
- package/src/workspace/types.ts +8 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE sentence a rename, a move or a copy answers with when something is
|
|
3
|
+
* already at the destination.
|
|
4
|
+
*
|
|
5
|
+
* A rename is not a way to replace content: renaming a `.docx` onto an
|
|
6
|
+
* existing `.md` used to hand the markdown file the Word bytes and break the
|
|
7
|
+
* page. Every surface — the sidebar rename box, a drag onto a folder, the
|
|
8
|
+
* agent's `move_file` and `copy_file` — refuses with this one sentence, so a
|
|
9
|
+
* user who meets it in the sidebar and an agent that meets it in a tool
|
|
10
|
+
* result are told the same thing.
|
|
11
|
+
*
|
|
12
|
+
* Kept here, beside `filename.ts`, for the same reason that file is: the
|
|
13
|
+
* backend throws it and the frontend renders it, and neither should spell it
|
|
14
|
+
* its own way.
|
|
15
|
+
*/
|
|
16
|
+
/** What is already sitting at the destination. */
|
|
17
|
+
export type ExistingEntryKind = 'file' | 'folder';
|
|
18
|
+
/**
|
|
19
|
+
* `A file named <name> already exists in <folder>.` — `name` and `folder` are
|
|
20
|
+
* read off `destinationPath`, which may be workspace-relative or
|
|
21
|
+
* repo-relative; only its last two segments are used, so the reader sees the
|
|
22
|
+
* name they typed and the folder they were aiming at rather than a full path.
|
|
23
|
+
* A destination directly at the top of the tree has no folder segment to
|
|
24
|
+
* name, and says `the top level` instead.
|
|
25
|
+
*/
|
|
26
|
+
export declare function entryExistsMessage(kind: ExistingEntryKind, destinationPath: string): string;
|
|
27
|
+
//# sourceMappingURL=entry-exists.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-exists.d.ts","sourceRoot":"","sources":["../../src/workspace/entry-exists.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,kDAAkD;AAClD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAElD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAQ3F"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE sentence a rename, a move or a copy answers with when something is
|
|
3
|
+
* already at the destination.
|
|
4
|
+
*
|
|
5
|
+
* A rename is not a way to replace content: renaming a `.docx` onto an
|
|
6
|
+
* existing `.md` used to hand the markdown file the Word bytes and break the
|
|
7
|
+
* page. Every surface — the sidebar rename box, a drag onto a folder, the
|
|
8
|
+
* agent's `move_file` and `copy_file` — refuses with this one sentence, so a
|
|
9
|
+
* user who meets it in the sidebar and an agent that meets it in a tool
|
|
10
|
+
* result are told the same thing.
|
|
11
|
+
*
|
|
12
|
+
* Kept here, beside `filename.ts`, for the same reason that file is: the
|
|
13
|
+
* backend throws it and the frontend renders it, and neither should spell it
|
|
14
|
+
* its own way.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* `A file named <name> already exists in <folder>.` — `name` and `folder` are
|
|
18
|
+
* read off `destinationPath`, which may be workspace-relative or
|
|
19
|
+
* repo-relative; only its last two segments are used, so the reader sees the
|
|
20
|
+
* name they typed and the folder they were aiming at rather than a full path.
|
|
21
|
+
* A destination directly at the top of the tree has no folder segment to
|
|
22
|
+
* name, and says `the top level` instead.
|
|
23
|
+
*/
|
|
24
|
+
export function entryExistsMessage(kind, destinationPath) {
|
|
25
|
+
const segments = destinationPath
|
|
26
|
+
.replace(/\\/g, '/')
|
|
27
|
+
.split('/')
|
|
28
|
+
.filter((segment) => segment.length > 0 && segment !== '.');
|
|
29
|
+
const name = segments[segments.length - 1] ?? destinationPath;
|
|
30
|
+
const folder = segments.length > 1 ? segments[segments.length - 2] : 'the top level';
|
|
31
|
+
return `A ${kind} named ${name} already exists in ${folder}.`;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=entry-exists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-exists.js","sourceRoot":"","sources":["../../src/workspace/entry-exists.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAuB,EAAE,eAAuB;IACjF,MAAM,QAAQ,GAAG,eAAe;SAC7B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,eAAe,CAAC;IAC9D,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;IACrF,OAAO,KAAK,IAAI,UAAU,IAAI,sBAAsB,MAAM,GAAG,CAAC;AAChE,CAAC"}
|
|
@@ -25,6 +25,21 @@ export declare function validateFilename(name: string): string | null;
|
|
|
25
25
|
* `\` would round-trip differently on Windows vs the Linux container.
|
|
26
26
|
*/
|
|
27
27
|
export declare function validateRelativePath(relativePath: string): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* ONE identity per file, so everything that coordinates on a path agrees about
|
|
30
|
+
* what it is coordinating on: an in-process queue, a database lock row, and
|
|
31
|
+
* the bytes on disk. {@link validateRelativePath} accepts a leading `./` and
|
|
32
|
+
* repeated slashes as spellings of the same path, and two callers spelling one
|
|
33
|
+
* file differently would otherwise take two different locks and write over
|
|
34
|
+
* each other. `.` and `..` segments are refused outright by the validator, so
|
|
35
|
+
* there is nothing to resolve here beyond the separators.
|
|
36
|
+
*
|
|
37
|
+
* Case is deliberately left alone. The deployment target is Linux, where
|
|
38
|
+
* `Foo.md` and `foo.md` are two different files; folding case to suit a
|
|
39
|
+
* case-insensitive development machine would merge two real files in
|
|
40
|
+
* production, which is a worse failure than the race it would close.
|
|
41
|
+
*/
|
|
42
|
+
export declare function canonicalRelativePath(relativePath: string): string;
|
|
28
43
|
/**
|
|
29
44
|
* Throwing wrapper for the backend service layer — keeps call sites a single
|
|
30
45
|
* line and produces a clear `Error` the route handler can surface as a 400.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/workspace/filename.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/workspace/filename.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2B5D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBxE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAelE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAGlE"}
|
|
@@ -21,9 +21,13 @@ const WINDOWS_RESERVED_NAMES = new Set([
|
|
|
21
21
|
]);
|
|
22
22
|
/** Characters Windows forbids in any filename component. `/` is the path
|
|
23
23
|
* separator on Unix, included for the same reason. `\` is also a path
|
|
24
|
-
* separator on Windows. Control
|
|
24
|
+
* separator on Windows. Control characters are rejected with them: NUL and
|
|
25
|
+
* 0x01–0x1F because Windows refuses them, and DEL (0x7F) — which every
|
|
26
|
+
* filesystem would write — as a matter of hygiene: a name carrying a
|
|
27
|
+
* character nobody can see or type is not a name people can share, and the
|
|
28
|
+
* knowledge-base root names are validated by this same rule. */
|
|
25
29
|
// eslint-disable-next-line no-control-regex
|
|
26
|
-
const FORBIDDEN_CHARS = /[<>:"/\\|?*\x00-\x1F]/;
|
|
30
|
+
const FORBIDDEN_CHARS = /[<>:"/\\|?*\x00-\x1F\x7F]/;
|
|
27
31
|
/** Per-component byte limit: NTFS = 255 UTF-16 units, ext4 = 255 bytes,
|
|
28
32
|
* APFS = 255 UTF-8 bytes. Use UTF-8 bytes — the strictest of the three. */
|
|
29
33
|
const MAX_NAME_BYTES = 255;
|
|
@@ -43,7 +47,7 @@ export function validateFilename(name) {
|
|
|
43
47
|
if (name === '.' || name === '..')
|
|
44
48
|
return 'Name cannot be "." or ".."';
|
|
45
49
|
if (FORBIDDEN_CHARS.test(name)) {
|
|
46
|
-
return 'Name cannot contain any of
|
|
50
|
+
return 'Name cannot contain control characters or any of: < > : " / \\ | ? *';
|
|
47
51
|
}
|
|
48
52
|
// Windows trims trailing dots and spaces silently — a name ending in either
|
|
49
53
|
// becomes a different filename on disk, breaking sync between OSes.
|
|
@@ -86,6 +90,36 @@ export function validateRelativePath(relativePath) {
|
|
|
86
90
|
}
|
|
87
91
|
return null;
|
|
88
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* ONE identity per file, so everything that coordinates on a path agrees about
|
|
95
|
+
* what it is coordinating on: an in-process queue, a database lock row, and
|
|
96
|
+
* the bytes on disk. {@link validateRelativePath} accepts a leading `./` and
|
|
97
|
+
* repeated slashes as spellings of the same path, and two callers spelling one
|
|
98
|
+
* file differently would otherwise take two different locks and write over
|
|
99
|
+
* each other. `.` and `..` segments are refused outright by the validator, so
|
|
100
|
+
* there is nothing to resolve here beyond the separators.
|
|
101
|
+
*
|
|
102
|
+
* Case is deliberately left alone. The deployment target is Linux, where
|
|
103
|
+
* `Foo.md` and `foo.md` are two different files; folding case to suit a
|
|
104
|
+
* case-insensitive development machine would merge two real files in
|
|
105
|
+
* production, which is a worse failure than the race it would close.
|
|
106
|
+
*/
|
|
107
|
+
export function canonicalRelativePath(relativePath) {
|
|
108
|
+
// Never LAUNDER a path. Dropping empty segments would turn the absolute
|
|
109
|
+
// `/etc/passwd` into the perfectly ordinary `etc/passwd`, and an absolute
|
|
110
|
+
// path is exactly what `path.resolve` lets win over the workspace directory
|
|
111
|
+
// — which is why the workspace-boundary check refuses it today. A path this
|
|
112
|
+
// cannot canonicalise is returned UNCHANGED, so every gate downstream sees
|
|
113
|
+
// what the caller actually sent and goes on refusing it.
|
|
114
|
+
if (relativePath.startsWith('/') || validateRelativePath(relativePath) !== null) {
|
|
115
|
+
return relativePath;
|
|
116
|
+
}
|
|
117
|
+
return relativePath
|
|
118
|
+
.replace(/^\.\//, '')
|
|
119
|
+
.split('/')
|
|
120
|
+
.filter((segment) => segment.length > 0)
|
|
121
|
+
.join('/');
|
|
122
|
+
}
|
|
89
123
|
/**
|
|
90
124
|
* Throwing wrapper for the backend service layer — keeps call sites a single
|
|
91
125
|
* line and produces a clear `Error` the route handler can surface as a 400.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/workspace/filename.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;+EAC+E;AAC/E,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK;IAC1B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAC/E,CAAC,CAAC;AAEH
|
|
1
|
+
{"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/workspace/filename.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files can carry their OWN frontmatter — and therefore their own
|
|
3
|
+
* per-file access rules.
|
|
4
|
+
*
|
|
5
|
+
* A file-level grant, revoke or restriction splices a `---` YAML block into
|
|
6
|
+
* the file itself. That is right for a Markdown note and destructive for
|
|
7
|
+
* anything else: spliced into a PDF, a presentation or an image it corrupts
|
|
8
|
+
* the bytes (the preview goes blank), and the grant never even resolves.
|
|
9
|
+
* Such a file takes its folder's rules instead.
|
|
10
|
+
*
|
|
11
|
+
* THE ONE predicate for that question: the access routes refuse a file-level
|
|
12
|
+
* mutation on a file it rejects, and the Manage access dialog shows the folder
|
|
13
|
+
* pointer for the same files.
|
|
14
|
+
*
|
|
15
|
+
* The carriers are exactly the files the access resolver reads its own
|
|
16
|
+
* frontmatter from — the backend's core access-frontmatter extension set:
|
|
17
|
+
* Markdown notes (`.md`) and `.tool` definitions (whole-document YAML whose
|
|
18
|
+
* access verbs sit beside the definition). Both are served by the file-reader
|
|
19
|
+
* registry's text-editable text reader. The registry test pins both facts, so
|
|
20
|
+
* this list cannot drift from the resolver or the registry silently. (Neither
|
|
21
|
+
* can live here: the registry's document readers carry backend-only extraction
|
|
22
|
+
* dependencies, and overlays register further extensions at boot — the backend
|
|
23
|
+
* passes its full registered set as `extensions`.)
|
|
24
|
+
*
|
|
25
|
+
* Matching is case-SENSITIVE, as the resolver's is: `Note.MD` carries no
|
|
26
|
+
* enforced rule, so a grant written there would never resolve. An extensionless
|
|
27
|
+
* file is NOT a carrier either: its content may be anything, and a path alone
|
|
28
|
+
* cannot tell a note from a binary.
|
|
29
|
+
*/
|
|
30
|
+
export declare const FRONTMATTER_CARRIER_EXTENSIONS: readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* True when the file at `path` can carry its own frontmatter (and so its own
|
|
33
|
+
* access rules). `extensions` defaults to the core set; the backend passes the
|
|
34
|
+
* resolver's registered set so an overlay's kinds count too.
|
|
35
|
+
*/
|
|
36
|
+
export declare function canCarryFrontmatter(path: string, extensions?: readonly string[]): boolean;
|
|
37
|
+
/** The `kind` a refused file-level access mutation answers with. */
|
|
38
|
+
export declare const FOLDER_GOVERNS_ACCESS_KIND = "folder-governs-access";
|
|
39
|
+
/**
|
|
40
|
+
* The one sentence both the 422 and the dialog say, naming the folder whose
|
|
41
|
+
* rules govern the file.
|
|
42
|
+
*/
|
|
43
|
+
export declare function folderGovernsAccessMessage(folder: string): string;
|
|
44
|
+
//# sourceMappingURL=frontmatter-carriers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-carriers.d.ts","sourceRoot":"","sources":["../../src/workspace/frontmatter-carriers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,8BAA8B,EAAE,SAAS,MAAM,EAAqB,CAAC;AAElF;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,SAAS,MAAM,EAAmC,GAC7D,OAAO,CAMT;AAED,oEAAoE;AACpE,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files can carry their OWN frontmatter — and therefore their own
|
|
3
|
+
* per-file access rules.
|
|
4
|
+
*
|
|
5
|
+
* A file-level grant, revoke or restriction splices a `---` YAML block into
|
|
6
|
+
* the file itself. That is right for a Markdown note and destructive for
|
|
7
|
+
* anything else: spliced into a PDF, a presentation or an image it corrupts
|
|
8
|
+
* the bytes (the preview goes blank), and the grant never even resolves.
|
|
9
|
+
* Such a file takes its folder's rules instead.
|
|
10
|
+
*
|
|
11
|
+
* THE ONE predicate for that question: the access routes refuse a file-level
|
|
12
|
+
* mutation on a file it rejects, and the Manage access dialog shows the folder
|
|
13
|
+
* pointer for the same files.
|
|
14
|
+
*
|
|
15
|
+
* The carriers are exactly the files the access resolver reads its own
|
|
16
|
+
* frontmatter from — the backend's core access-frontmatter extension set:
|
|
17
|
+
* Markdown notes (`.md`) and `.tool` definitions (whole-document YAML whose
|
|
18
|
+
* access verbs sit beside the definition). Both are served by the file-reader
|
|
19
|
+
* registry's text-editable text reader. The registry test pins both facts, so
|
|
20
|
+
* this list cannot drift from the resolver or the registry silently. (Neither
|
|
21
|
+
* can live here: the registry's document readers carry backend-only extraction
|
|
22
|
+
* dependencies, and overlays register further extensions at boot — the backend
|
|
23
|
+
* passes its full registered set as `extensions`.)
|
|
24
|
+
*
|
|
25
|
+
* Matching is case-SENSITIVE, as the resolver's is: `Note.MD` carries no
|
|
26
|
+
* enforced rule, so a grant written there would never resolve. An extensionless
|
|
27
|
+
* file is NOT a carrier either: its content may be anything, and a path alone
|
|
28
|
+
* cannot tell a note from a binary.
|
|
29
|
+
*/
|
|
30
|
+
export const FRONTMATTER_CARRIER_EXTENSIONS = ['.md', '.tool'];
|
|
31
|
+
/**
|
|
32
|
+
* True when the file at `path` can carry its own frontmatter (and so its own
|
|
33
|
+
* access rules). `extensions` defaults to the core set; the backend passes the
|
|
34
|
+
* resolver's registered set so an overlay's kinds count too.
|
|
35
|
+
*/
|
|
36
|
+
export function canCarryFrontmatter(path, extensions = FRONTMATTER_CARRIER_EXTENSIONS) {
|
|
37
|
+
const name = path.slice(path.lastIndexOf('/') + 1);
|
|
38
|
+
// The resolver's own rule is a plain suffix match, so a file named exactly
|
|
39
|
+
// `.md` is read for its frontmatter there — and must be a carrier here too,
|
|
40
|
+
// or a rule the resolver honours could not be managed.
|
|
41
|
+
return extensions.some((ext) => name.endsWith(ext));
|
|
42
|
+
}
|
|
43
|
+
/** The `kind` a refused file-level access mutation answers with. */
|
|
44
|
+
export const FOLDER_GOVERNS_ACCESS_KIND = 'folder-governs-access';
|
|
45
|
+
/**
|
|
46
|
+
* The one sentence both the 422 and the dialog say, naming the folder whose
|
|
47
|
+
* rules govern the file.
|
|
48
|
+
*/
|
|
49
|
+
export function folderGovernsAccessMessage(folder) {
|
|
50
|
+
return `This file's access comes from its folder. Manage access on ${folder} instead.`;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=frontmatter-carriers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-carriers.js","sourceRoot":"","sources":["../../src/workspace/frontmatter-carriers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAElF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,aAAgC,8BAA8B;IAE9D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,2EAA2E;IAC3E,4EAA4E;IAC5E,uDAAuD;IACvD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,MAAM,0BAA0B,GAAG,uBAAuB,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAc;IACvD,OAAO,8DAA8D,MAAM,WAAW,CAAC;AACzF,CAAC"}
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE fence rule: a line is a frontmatter fence when, whitespace aside, it is
|
|
3
|
+
* exactly `---`. Forgiving on purpose — an opening fence with a trailing space
|
|
4
|
+
* or an indented fence is still a fence.
|
|
5
|
+
*
|
|
6
|
+
* Every reader in the platform asks this one function. The access model's
|
|
7
|
+
* line scan always judged fences this way while the splitter below demanded
|
|
8
|
+
* `---` at column 0, so a file written with a near-miss fence had access
|
|
9
|
+
* rules that applied while the catalog, the tool manuals and the frontmatter
|
|
10
|
+
* panel saw no frontmatter at all. One rule, asked everywhere, is what keeps
|
|
11
|
+
* a file from meaning two things.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isFrontmatterFence(line: string | undefined): boolean;
|
|
1
14
|
/**
|
|
2
15
|
* The ONE `---` frontmatter splitter, shared by backend and frontend so no file
|
|
3
|
-
* type grows its own
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
16
|
+
* type grows its own reader. Splits a leading fenced YAML block from the body;
|
|
17
|
+
* null when the first line is not a fence or no later line closes it. Fences
|
|
18
|
+
* are judged by {@link isFrontmatterFence}. Parsing the YAML inside is the
|
|
19
|
+
* caller's concern (the access model keeps its own line scan, on the same
|
|
20
|
+
* fence rule, because a splice must put bytes back exactly as it found them).
|
|
21
|
+
*
|
|
22
|
+
* `frontmatter` is the raw text between the fence lines and `body` the raw
|
|
23
|
+
* text after the closing fence's line break, both byte for byte — line
|
|
24
|
+
* endings included — so a caller that rebuilds the file changes nothing it
|
|
25
|
+
* did not mean to.
|
|
7
26
|
*/
|
|
8
27
|
export declare function extractFrontmatter(text: string): {
|
|
9
28
|
frontmatter: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/workspace/frontmatter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/workspace/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4B7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAwBpF"}
|
|
@@ -1,15 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE fence rule: a line is a frontmatter fence when, whitespace aside, it is
|
|
3
|
+
* exactly `---`. Forgiving on purpose — an opening fence with a trailing space
|
|
4
|
+
* or an indented fence is still a fence.
|
|
5
|
+
*
|
|
6
|
+
* Every reader in the platform asks this one function. The access model's
|
|
7
|
+
* line scan always judged fences this way while the splitter below demanded
|
|
8
|
+
* `---` at column 0, so a file written with a near-miss fence had access
|
|
9
|
+
* rules that applied while the catalog, the tool manuals and the frontmatter
|
|
10
|
+
* panel saw no frontmatter at all. One rule, asked everywhere, is what keeps
|
|
11
|
+
* a file from meaning two things.
|
|
12
|
+
*/
|
|
13
|
+
export function isFrontmatterFence(line) {
|
|
14
|
+
return line?.trim() === '---';
|
|
15
|
+
}
|
|
1
16
|
/**
|
|
2
17
|
* The ONE `---` frontmatter splitter, shared by backend and frontend so no file
|
|
3
|
-
* type grows its own
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
18
|
+
* type grows its own reader. Splits a leading fenced YAML block from the body;
|
|
19
|
+
* null when the first line is not a fence or no later line closes it. Fences
|
|
20
|
+
* are judged by {@link isFrontmatterFence}. Parsing the YAML inside is the
|
|
21
|
+
* caller's concern (the access model keeps its own line scan, on the same
|
|
22
|
+
* fence rule, because a splice must put bytes back exactly as it found them).
|
|
23
|
+
*
|
|
24
|
+
* `frontmatter` is the raw text between the fence lines and `body` the raw
|
|
25
|
+
* text after the closing fence's line break, both byte for byte — line
|
|
26
|
+
* endings included — so a caller that rebuilds the file changes nothing it
|
|
27
|
+
* did not mean to.
|
|
7
28
|
*/
|
|
8
29
|
export function extractFrontmatter(text) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
30
|
+
// Walk the lines by offset rather than splitting, so both halves can be
|
|
31
|
+
// sliced out of the original text with their own line endings intact.
|
|
32
|
+
let start = 0;
|
|
33
|
+
let lineIndex = 0;
|
|
34
|
+
let openEnd = -1; // offset just past the opening fence's line break
|
|
35
|
+
while (start <= text.length) {
|
|
36
|
+
const nl = text.indexOf('\n', start);
|
|
37
|
+
const end = nl === -1 ? text.length : nl;
|
|
38
|
+
const line = text.slice(start, end).replace(/\r$/, '');
|
|
39
|
+
const next = nl === -1 ? text.length + 1 : nl + 1;
|
|
40
|
+
if (lineIndex === 0) {
|
|
41
|
+
// A lone `---` with nothing after it opens nothing.
|
|
42
|
+
if (!isFrontmatterFence(line) || nl === -1)
|
|
43
|
+
return null;
|
|
44
|
+
openEnd = next;
|
|
45
|
+
}
|
|
46
|
+
else if (isFrontmatterFence(line)) {
|
|
47
|
+
// The frontmatter ends before the line break that precedes this fence.
|
|
48
|
+
const fmEnd = start - (start >= 2 && text[start - 2] === '\r' ? 2 : 1);
|
|
49
|
+
return {
|
|
50
|
+
frontmatter: fmEnd > openEnd ? text.slice(openEnd, fmEnd) : '',
|
|
51
|
+
body: next > text.length ? '' : text.slice(next),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (nl === -1)
|
|
55
|
+
return null; // never closed
|
|
56
|
+
start = next;
|
|
57
|
+
lineIndex += 1;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
13
60
|
}
|
|
14
61
|
/**
|
|
15
62
|
* Set a top-level scalar key in a file's `---` frontmatter, LINE-based: replace an
|
|
@@ -37,7 +84,10 @@ export function setFrontmatterField(text, key, value) {
|
|
|
37
84
|
// No frontmatter — prepend a fresh block, keeping the original body intact.
|
|
38
85
|
return `---${eol}${line}${eol}---${eol}${text}`;
|
|
39
86
|
}
|
|
40
|
-
|
|
87
|
+
// An empty block has no lines, not one empty line: splitting '' would give
|
|
88
|
+
// [''], and the inserted key would be followed by a blank line before the
|
|
89
|
+
// closing fence.
|
|
90
|
+
const fmLines = fm.frontmatter === '' ? [] : fm.frontmatter.split(/\r?\n/);
|
|
41
91
|
const idx = fmLines.findIndex((l) => keyRe.test(l));
|
|
42
92
|
if (idx >= 0)
|
|
43
93
|
fmLines[idx] = line;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/workspace/frontmatter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/workspace/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAwB;IACzD,OAAO,IAAI,EAAE,IAAI,EAAE,KAAK,KAAK,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,wEAAwE;IACxE,sEAAsE;IACtE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,kDAAkD;IACpE,OAAO,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAClD,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,oDAAoD;YACpD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YACxD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,uEAAuE;YACvE,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACvE,OAAO;gBACL,WAAW,EAAE,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;gBAC9D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;aACjD,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,eAAe;QAC3C,KAAK,GAAG,IAAI,CAAC;QACb,SAAS,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,GAAW,EAAE,KAAa;IAC1E,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC;IAChC,8EAA8E;IAC9E,0EAA0E;IAC1E,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACrF,8EAA8E;IAC9E,2EAA2E;IAC3E,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,4EAA4E;QAC5E,OAAO,MAAM,GAAG,GAAG,IAAI,GAAG,GAAG,MAAM,GAAG,GAAG,IAAI,EAAE,CAAC;IAClD,CAAC;IACD,2EAA2E;IAC3E,0EAA0E;IAC1E,iBAAiB;IACjB,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3E,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;;QAC7B,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;AAClE,CAAC"}
|