@brydio/manifest 0.1.0-alpha.6 → 0.1.0-alpha.61
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/README.md +1 -1
- package/package.json +6 -2
- package/src/anonymous.d.ts +42 -0
- package/src/anonymous.js +86 -0
- package/src/base.d.ts +17 -1
- package/src/base.js +13 -1
- package/src/bundle.d.ts +22 -2
- package/src/bundle.js +93 -11
- package/src/confirm-email.d.ts +40 -0
- package/src/confirm-email.js +78 -0
- package/src/define.d.ts +1 -1
- package/src/define.js +1 -1
- package/src/field-types.d.ts +41 -5
- package/src/field-types.js +60 -10
- package/src/frame-checks.d.ts +13 -0
- package/src/frame-checks.js +35 -0
- package/src/grants.d.ts +14 -2
- package/src/grants.js +34 -2
- package/src/index.d.ts +13 -5
- package/src/index.js +15 -5
- package/src/look.d.ts +196 -0
- package/src/look.js +744 -0
- package/src/migrations.d.ts +1 -1
- package/src/migrations.js +29 -0
- package/src/open-schema.d.ts +128 -0
- package/src/open-schema.js +444 -0
- package/src/readers.d.ts +54 -0
- package/src/readers.js +96 -0
- package/src/requirements.d.ts +122 -0
- package/src/requirements.js +692 -0
- package/src/schema.d.ts +576 -9
- package/src/schema.js +484 -31
- package/src/screen-kinds.d.ts +18 -0
- package/src/screen-kinds.js +30 -0
- package/src/secrets.d.ts +14 -0
- package/src/secrets.js +38 -5
- package/src/validate.d.ts +12 -3
- package/src/validate.js +7 -5
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two kinds of screen (`tasks/app-publishing` PB27). A copy of Brydio's
|
|
3
|
+
* `apps/api/src/apps/manifest/screen-kinds.ts`.
|
|
4
|
+
*
|
|
5
|
+
* A **tree screen** is the app's elements, drawn by Brydio (PB21 to PB24). A
|
|
6
|
+
* **frame screen** is the app's own HTML and CSS, drawn in a sandboxed frame
|
|
7
|
+
* on an origin that holds only app code. The manifest says which, per screen:
|
|
8
|
+
* `{ "kind": "frame", "entry": "screens/settings.html" }`. Left out, a screen
|
|
9
|
+
* is a tree screen, so every manifest from before keeps its meaning.
|
|
10
|
+
*/
|
|
11
|
+
export const SCREEN_KINDS = ['tree', 'frame'];
|
|
12
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
/** Which kind one of the manifest's screens is; `tree` when it says nothing. */
|
|
14
|
+
export function screenKindOf(manifest, screen) {
|
|
15
|
+
const screens = isRecord(manifest) && isRecord(manifest.screens) ? manifest.screens : {};
|
|
16
|
+
const declared = Object.prototype.hasOwnProperty.call(screens, screen) ? screens[screen] : undefined;
|
|
17
|
+
return isRecord(declared) && declared.kind === 'frame' ? 'frame' : 'tree';
|
|
18
|
+
}
|
|
19
|
+
/** The manifest's screens of one kind, by name. */
|
|
20
|
+
export function screensOfKind(manifest, kind) {
|
|
21
|
+
const screens = isRecord(manifest) && isRecord(manifest.screens) ? manifest.screens : {};
|
|
22
|
+
return Object.keys(screens).filter(screen => screenKindOf(manifest, screen) === kind);
|
|
23
|
+
}
|
|
24
|
+
/** Every frame screen's page, by its path inside the bundle. */
|
|
25
|
+
export function framePagesOf(manifest) {
|
|
26
|
+
const screens = isRecord(manifest) && isRecord(manifest.screens) ? manifest.screens : {};
|
|
27
|
+
return new Set(screensOfKind(manifest, 'frame')
|
|
28
|
+
.map(screen => screens[screen].entry)
|
|
29
|
+
.filter((entry) => typeof entry === 'string'));
|
|
30
|
+
}
|
package/src/secrets.d.ts
CHANGED
|
@@ -30,3 +30,17 @@ export interface SecretFound {
|
|
|
30
30
|
export declare function secretsInJson(document: unknown, at: string): SecretFound[];
|
|
31
31
|
/** Every secret declared in a bundle's files, as the server finds them. */
|
|
32
32
|
export declare function findSecrets(files: ReadonlyMap<string, Uint8Array>, root?: string): SecretFound[];
|
|
33
|
+
/**
|
|
34
|
+
* The server's sentence for `developer_key_in_bundle` (ADR-A22), word for
|
|
35
|
+
* word: `DEVELOPER_KEY_IN_BUNDLE` in Brydio's `app-publish.service.ts`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DEVELOPER_KEY_MESSAGE = "That package contains a Brydio developer API key. A key belongs on your own server, never in an app: revoke it in Settings \u203A Developer, and call Brydio\u2019s model from a handler with the model grant instead.";
|
|
38
|
+
/** Whether a text holds a Brydio developer API key. */
|
|
39
|
+
export declare const holdsDeveloperKey: (text: string) => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The first file holding a developer API key, by path, as Brydio's publish
|
|
42
|
+
* route finds it (`developerKeyIn`). Unlike the declaration scan, every file
|
|
43
|
+
* is read, scripts included: the key's shape is exact enough that a match is
|
|
44
|
+
* a key, not a guess, and no part of an app may hold one (ADR-A24).
|
|
45
|
+
*/
|
|
46
|
+
export declare function developerKeyIn(files: ReadonlyMap<string, Uint8Array>): string | null;
|
package/src/secrets.js
CHANGED
|
@@ -35,21 +35,21 @@ const isRealValue = (value) => {
|
|
|
35
35
|
return false;
|
|
36
36
|
return true;
|
|
37
37
|
};
|
|
38
|
-
function walk(node, at, found) {
|
|
38
|
+
function walk(node, at, found, publicValue = () => false) {
|
|
39
39
|
if (Array.isArray(node)) {
|
|
40
|
-
node.forEach((one, index) => walk(one, `${at}[${index}]`, found));
|
|
40
|
+
node.forEach((one, index) => walk(one, `${at}[${index}]`, found, publicValue));
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
if (!node || typeof node !== 'object')
|
|
44
44
|
return;
|
|
45
45
|
for (const [key, value] of Object.entries(node)) {
|
|
46
46
|
const path = at ? `${at}.${key}` : key;
|
|
47
|
-
if (VALUE_KEYS.has(key) && isRealValue(value)) {
|
|
47
|
+
if (VALUE_KEYS.has(key) && isRealValue(value) && !publicValue(path)) {
|
|
48
48
|
// Never quoted back: a report that repeated the secret would copy it somewhere else.
|
|
49
49
|
found.push({ code: 'secret_in_bundle', message: SECRET_MESSAGE, path });
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
|
-
walk(value, path, found);
|
|
52
|
+
walk(value, path, found, publicValue);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
@@ -59,9 +59,20 @@ function walk(node, at, found) {
|
|
|
59
59
|
*/
|
|
60
60
|
export function secretsInJson(document, at) {
|
|
61
61
|
const found = [];
|
|
62
|
-
walk(document, at, found);
|
|
62
|
+
walk(document, at, found, path => DECLARATIONS.some(declared => declared.test(path)));
|
|
63
63
|
return found;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Names in an app.json that read like credentials but are declarations: a
|
|
67
|
+
* placement's identity, a collection's field definition (an open-schema
|
|
68
|
+
* companion must declare `key`), and a tool's input field. Their values are
|
|
69
|
+
* types and names, never secrets.
|
|
70
|
+
*/
|
|
71
|
+
const DECLARATIONS = [
|
|
72
|
+
/(?:^|\.)placements\[\d+\]\.key$/,
|
|
73
|
+
/(?:^|\.)data\.[^.[\]]+\.schema\.[^.[\]]+$/,
|
|
74
|
+
/(?:^|\.)tools\.custom\[\d+\]\.input\.[^.[\]]+$/,
|
|
75
|
+
];
|
|
65
76
|
/** Every secret declared in a bundle's files, as the server finds them. */
|
|
66
77
|
export function findSecrets(files, root = '') {
|
|
67
78
|
const found = [];
|
|
@@ -79,3 +90,25 @@ export function findSecrets(files, root = '') {
|
|
|
79
90
|
}
|
|
80
91
|
return found;
|
|
81
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* The server's sentence for `developer_key_in_bundle` (ADR-A22), word for
|
|
95
|
+
* word: `DEVELOPER_KEY_IN_BUNDLE` in Brydio's `app-publish.service.ts`.
|
|
96
|
+
*/
|
|
97
|
+
export const DEVELOPER_KEY_MESSAGE = 'That package contains a Brydio developer API key. A key belongs on your own server, never in an app: revoke it in Settings › Developer, and call Brydio’s model from a handler with the model grant instead.';
|
|
98
|
+
/** A developer API key's exact shape, anywhere in a text. */
|
|
99
|
+
const DEVELOPER_KEY = /bry_live_[0-9a-f]{12}_[A-Za-z0-9_-]{43}/;
|
|
100
|
+
/** Whether a text holds a Brydio developer API key. */
|
|
101
|
+
export const holdsDeveloperKey = (text) => DEVELOPER_KEY.test(text);
|
|
102
|
+
/**
|
|
103
|
+
* The first file holding a developer API key, by path, as Brydio's publish
|
|
104
|
+
* route finds it (`developerKeyIn`). Unlike the declaration scan, every file
|
|
105
|
+
* is read, scripts included: the key's shape is exact enough that a match is
|
|
106
|
+
* a key, not a guess, and no part of an app may hold one (ADR-A24).
|
|
107
|
+
*/
|
|
108
|
+
export function developerKeyIn(files) {
|
|
109
|
+
for (const [path, bytes] of files) {
|
|
110
|
+
if (holdsDeveloperKey(new TextDecoder('latin1').decode(bytes)))
|
|
111
|
+
return path;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
package/src/validate.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LookProblemCode } from './look.js';
|
|
1
2
|
import { type AppManifestWithData, type DataProblemCode } from './schema.js';
|
|
2
3
|
/**
|
|
3
4
|
* Reading a manifest and saying what is wrong with it, the way Brydio would.
|
|
@@ -9,7 +10,9 @@ import { type AppManifestWithData, type DataProblemCode } from './schema.js';
|
|
|
9
10
|
* `manifest_invalid` with the path to it, so an app's builder can find the
|
|
10
11
|
* line.
|
|
11
12
|
*/
|
|
12
|
-
export type ManifestProblemCode = DataProblemCode |
|
|
13
|
+
export type ManifestProblemCode = DataProblemCode | LookProblemCode
|
|
14
|
+
/** A frame screen without an .html page, or an .html entry that isn't a frame screen (PB27). */
|
|
15
|
+
| 'screen_kind_entry' | 'manifest_not_json' | 'manifest_invalid';
|
|
13
16
|
export interface ManifestProblem {
|
|
14
17
|
code: ManifestProblemCode;
|
|
15
18
|
message: string;
|
|
@@ -22,6 +25,12 @@ export interface ManifestValidation {
|
|
|
22
25
|
manifest?: AppManifestWithData;
|
|
23
26
|
problems: ManifestProblem[];
|
|
24
27
|
}
|
|
25
|
-
export declare function validateManifest(source: unknown
|
|
28
|
+
export declare function validateManifest(source: unknown,
|
|
29
|
+
/** `retired`: a manifest being written now, which may not use a retired kind such as `project-tab`. */
|
|
30
|
+
options?: {
|
|
31
|
+
retired?: boolean;
|
|
32
|
+
}): ManifestValidation;
|
|
26
33
|
/** Parses the text of `.brydio/app.json` and validates it. */
|
|
27
|
-
export declare function validateManifestText(text: string
|
|
34
|
+
export declare function validateManifestText(text: string, options?: {
|
|
35
|
+
retired?: boolean;
|
|
36
|
+
}): ManifestValidation;
|
package/src/validate.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { appManifestSchema } from "./schema.js";
|
|
2
|
-
export function validateManifest(source
|
|
3
|
-
|
|
1
|
+
import { appManifestSchema, newAppManifestSchema } from "./schema.js";
|
|
2
|
+
export function validateManifest(source,
|
|
3
|
+
/** `retired`: a manifest being written now, which may not use a retired kind such as `project-tab`. */
|
|
4
|
+
options = {}) {
|
|
5
|
+
const parsed = (options.retired ? newAppManifestSchema : appManifestSchema).safeParse(source);
|
|
4
6
|
if (parsed.success)
|
|
5
7
|
return { ok: true, manifest: parsed.data, problems: [] };
|
|
6
8
|
return {
|
|
@@ -14,7 +16,7 @@ export function validateManifest(source) {
|
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
/** Parses the text of `.brydio/app.json` and validates it. */
|
|
17
|
-
export function validateManifestText(text) {
|
|
19
|
+
export function validateManifestText(text, options = {}) {
|
|
18
20
|
let json;
|
|
19
21
|
try {
|
|
20
22
|
json = JSON.parse(text);
|
|
@@ -25,5 +27,5 @@ export function validateManifestText(text) {
|
|
|
25
27
|
problems: [{ code: 'manifest_not_json', message: `The manifest is not valid JSON: ${error instanceof Error ? error.message : String(error)}` }],
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
|
-
return validateManifest(json);
|
|
30
|
+
return validateManifest(json, options);
|
|
29
31
|
}
|