@serverkgg/bridge 0.0.1 → 0.0.2
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/package.json +10 -3
- package/src/cli/main.ts +11 -1
- package/src/cli/schema.ts +62 -0
- package/src/cli/validate.ts +119 -36
- package/src/context/bridgeContext.ts +11 -1
- package/src/core/bridgeAction.ts +13 -0
- package/src/core/bridgeFailure.ts +16 -0
- package/src/core/bridgeKind.ts +2 -0
- package/src/core/bridgeNetError.ts +10 -0
- package/src/core/index.ts +2 -0
- package/src/guides/blocks.ts +167 -0
- package/src/guides/compile.ts +193 -0
- package/src/guides/compiled.ts +28 -0
- package/src/guides/index.ts +6 -0
- package/src/guides/markdown.test.ts +374 -0
- package/src/guides/markdown.ts +769 -0
- package/src/guides/variables.ts +52 -0
- package/src/guides/version.ts +1 -0
- package/src/main.ts +2 -1
- package/src/manifest/assets.ts +4 -4
- package/src/manifest/guide.ts +23 -18
- package/src/manifest/index.ts +3 -2
- package/src/manifest/jsonSchema.ts +16 -0
- package/src/manifest/localized.ts +7 -0
- package/src/manifest/manifest.test.ts +366 -0
- package/src/manifest/manifest.ts +354 -120
- package/src/manifest/primitives.ts +63 -0
- package/src/manifest/source.ts +5 -4
- package/src/modules/bridge.ts +19 -3
- package/src/modules/bridgeDriver.ts +4 -0
- package/src/modules/bridgeModule.ts +22 -24
- package/src/panel/bridgeAccess.ts +3 -0
- package/src/panel/bridgePanel.ts +6 -0
- package/src/panel/index.ts +1 -0
- package/src/protocol/bridgeCatalog.ts +56 -0
- package/src/protocol/bridgeEgress.ts +53 -0
- package/src/protocol/bridgeFrame.ts +17 -1
- package/src/protocol/bridgeLine.ts +41 -6
- package/src/protocol/bridgeManifest.ts +15 -1
- package/src/protocol/bridgeVersion.ts +1 -1
- package/src/protocol/index.ts +3 -0
- package/src/runtime/bridgeContext.ts +29 -9
- package/src/runtime/bridgeDriver.ts +1 -0
- package/src/runtime/bridgeEgress.ts +360 -0
- package/src/runtime/bridgeExec.ts +15 -5
- package/src/runtime/bridgeFiles.ts +58 -21
- package/src/runtime/bridgeLog.ts +26 -3
- package/src/runtime/bridgeNet.ts +75 -22
- package/src/runtime/bridgeRegistry.ts +51 -6
- package/src/runtime/bridgeServer.ts +48 -1
- package/src/runtime/bridgeSupervisor.ts +6 -6
- package/src/runtime/index.ts +0 -17
- package/src/runtime/serve.ts +44 -3
- package/src/terminal/bridgeTerminalArg.ts +11 -0
- package/src/terminal/bridgeTerminalCommand.ts +11 -0
- package/src/terminal/bridgeTerminalLevel.ts +6 -0
- package/src/terminal/bridgeTerminalRule.ts +8 -0
- package/src/terminal/index.ts +4 -0
- package/src/manifest/checks.ts +0 -50
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serverkgg/bridge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Serverk game bridge — driver runtime, wire protocol, manifest schema, and maintainer CLI",
|
|
5
5
|
"homepage": "https://serverk.gg",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/serverkgg/serverk.git",
|
|
10
|
+
"directory": "modules/bridge"
|
|
11
|
+
},
|
|
7
12
|
"type": "module",
|
|
8
13
|
"bin": {
|
|
9
|
-
"serverk-bridge": "
|
|
14
|
+
"serverk-bridge": "src/cli/main.ts"
|
|
10
15
|
},
|
|
11
16
|
"files": [
|
|
12
17
|
"src"
|
|
@@ -15,10 +20,12 @@
|
|
|
15
20
|
".": "./src/main.ts",
|
|
16
21
|
"./protocol": "./src/protocol/index.ts",
|
|
17
22
|
"./manifest": "./src/manifest/index.ts",
|
|
23
|
+
"./guides": "./src/guides/index.ts",
|
|
18
24
|
"./package.json": "./package.json"
|
|
19
25
|
},
|
|
20
26
|
"scripts": {
|
|
21
|
-
"tsc": "tsc"
|
|
27
|
+
"tsc": "tsc",
|
|
28
|
+
"test": "bun test"
|
|
22
29
|
},
|
|
23
30
|
"dependencies": {
|
|
24
31
|
"zod": "4.4.3"
|
package/src/cli/main.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { errorText } from "../runtime/bridgeLog";
|
|
2
4
|
import { runCompile } from "./compile";
|
|
5
|
+
import { runSchema } from "./schema";
|
|
3
6
|
import { runValidate } from "./validate";
|
|
4
7
|
|
|
5
8
|
const USAGE = `serverk-bridge <command>
|
|
6
9
|
|
|
7
10
|
commands:
|
|
8
11
|
validate validate serverk.yml, assets, and driver wiring
|
|
12
|
+
schema write the serverk.yml JSON Schema for editor tooling
|
|
9
13
|
compile compile the bridge binary for amd64 and arm64
|
|
10
14
|
|
|
11
15
|
run "serverk-bridge <command> --help" for options`;
|
|
@@ -20,6 +24,12 @@ try {
|
|
|
20
24
|
break;
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
case "schema": {
|
|
28
|
+
await runSchema(args);
|
|
29
|
+
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
case "compile": {
|
|
24
34
|
await runCompile(args);
|
|
25
35
|
|
|
@@ -39,6 +49,6 @@ try {
|
|
|
39
49
|
}
|
|
40
50
|
}
|
|
41
51
|
} catch (error) {
|
|
42
|
-
console.error(
|
|
52
|
+
console.error(errorText(error));
|
|
43
53
|
process.exit(1);
|
|
44
54
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { renderJsonSchema, SCHEMA_FILE } from "../manifest";
|
|
5
|
+
|
|
6
|
+
const HELP = `serverk-bridge schema [options]
|
|
7
|
+
|
|
8
|
+
options:
|
|
9
|
+
--out <path> file to write (default: ${SCHEMA_FILE})
|
|
10
|
+
--check fail when the file on disk is out of date instead of writing
|
|
11
|
+
--help show this help`;
|
|
12
|
+
|
|
13
|
+
export const runSchema = async (args: string[]) => {
|
|
14
|
+
const { values } = parseArgs({
|
|
15
|
+
args,
|
|
16
|
+
options: {
|
|
17
|
+
out: {
|
|
18
|
+
type: "string",
|
|
19
|
+
default: SCHEMA_FILE,
|
|
20
|
+
},
|
|
21
|
+
check: {
|
|
22
|
+
type: "boolean",
|
|
23
|
+
default: false,
|
|
24
|
+
},
|
|
25
|
+
help: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
default: false,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (values.help) {
|
|
33
|
+
console.log(HELP);
|
|
34
|
+
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const target = resolve(values.out);
|
|
39
|
+
const rendered = renderJsonSchema();
|
|
40
|
+
|
|
41
|
+
if (values.check) {
|
|
42
|
+
let current: string;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
current = readFileSync(target, "utf8");
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(`${values.out} is missing — run "serverk-bridge schema" and commit it`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (current !== rendered) {
|
|
51
|
+
throw new Error(`${values.out} is out of date — run "serverk-bridge schema" and commit it`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`${values.out} is up to date`);
|
|
55
|
+
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
writeFileSync(target, rendered);
|
|
60
|
+
|
|
61
|
+
console.log(`wrote ${values.out}`);
|
|
62
|
+
};
|
package/src/cli/validate.ts
CHANGED
|
@@ -3,7 +3,16 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import {
|
|
6
|
+
import { type GuidePack, readGuidePack, readGuidePackage } from "../guides";
|
|
7
|
+
import {
|
|
8
|
+
declaredFiles,
|
|
9
|
+
type GuidePackage,
|
|
10
|
+
MANIFEST_FILE,
|
|
11
|
+
type Manifest,
|
|
12
|
+
type PackageSource,
|
|
13
|
+
readAssets,
|
|
14
|
+
readPackageDocument,
|
|
15
|
+
} from "../manifest";
|
|
7
16
|
import type { BridgeDriver } from "../modules";
|
|
8
17
|
import { validateDriver } from "../runtime/bridgeDriver";
|
|
9
18
|
import { directorySource } from "./directorySource";
|
|
@@ -11,53 +20,81 @@ import { directorySource } from "./directorySource";
|
|
|
11
20
|
const HELP = `serverk-bridge validate [options]
|
|
12
21
|
|
|
13
22
|
options:
|
|
14
|
-
--dir <path>
|
|
15
|
-
--driver <path> driver module (default: <dir>/src/driver.ts)
|
|
23
|
+
--dir <path> package directory (default: current directory)
|
|
24
|
+
--driver <path> driver module (default: <dir>/src/driver.ts), game packages only
|
|
16
25
|
--help show this help`;
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
dir: {
|
|
23
|
-
type: "string",
|
|
24
|
-
default: ".",
|
|
25
|
-
},
|
|
26
|
-
driver: {
|
|
27
|
-
type: "string",
|
|
28
|
-
},
|
|
29
|
-
help: {
|
|
30
|
-
type: "boolean",
|
|
31
|
-
default: false,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
});
|
|
27
|
+
const invalidManifest = (error: unknown): never => {
|
|
28
|
+
if (error instanceof z.ZodError) {
|
|
29
|
+
throw new Error(`${MANIFEST_FILE} is invalid\n${z.prettifyError(error)}`);
|
|
30
|
+
}
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
throw error;
|
|
33
|
+
};
|
|
38
34
|
|
|
35
|
+
const DYNAMIC_BLOCKS: ReadonlySet<string> = new Set([
|
|
36
|
+
"field",
|
|
37
|
+
"command",
|
|
38
|
+
"open",
|
|
39
|
+
"conditional",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const countDynamic = (pack: GuidePack) => {
|
|
43
|
+
return pack.drafts.reduce((total, draft) => {
|
|
44
|
+
const blocks = [
|
|
45
|
+
...draft.body.ar,
|
|
46
|
+
...draft.body.en,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
return total + blocks.filter((block) => DYNAMIC_BLOCKS.has(block.type)).length;
|
|
50
|
+
}, 0);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const reportGuides = (pack: GuidePack | null) => {
|
|
54
|
+
if (!pack) {
|
|
39
55
|
return;
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
console.log(
|
|
59
|
+
` guides: ${pack.drafts.length} guide(s), ${countDynamic(pack)} dynamic block(s), ${pack.images.length} image(s)`,
|
|
60
|
+
);
|
|
44
61
|
|
|
45
|
-
|
|
62
|
+
if (pack.commands.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
66
|
+
console.log(` commands: ${pack.commands.length} runnable command(s) — review each one`);
|
|
67
|
+
|
|
68
|
+
for (const command of [
|
|
69
|
+
...new Set(pack.commands),
|
|
70
|
+
]) {
|
|
71
|
+
console.log(` ${command}`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const validateGuidePackage = (source: PackageSource, document: GuidePackage) => {
|
|
76
|
+
const guideFiles = document.guides.flatMap((guide) => [
|
|
77
|
+
guide.bodyPath.ar,
|
|
78
|
+
guide.bodyPath.en,
|
|
79
|
+
]);
|
|
53
80
|
|
|
54
|
-
|
|
81
|
+
for (const path of guideFiles) {
|
|
82
|
+
if (source.read(path) === null) {
|
|
83
|
+
throw new Error(`declared file "${path}" is missing`);
|
|
84
|
+
}
|
|
55
85
|
}
|
|
56
86
|
|
|
87
|
+
const pack = readGuidePackage(source, document);
|
|
88
|
+
|
|
89
|
+
console.log(`${document.metadata.id} is valid`);
|
|
90
|
+
reportGuides(pack);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const validateGamePackage = async (source: PackageSource, manifest: Manifest, dir: string, driverArg?: string) => {
|
|
57
94
|
const assets = readAssets(source, manifest);
|
|
58
95
|
const guideFiles = manifest.guides.flatMap((guide) => [
|
|
59
|
-
guide.
|
|
60
|
-
guide.
|
|
96
|
+
guide.bodyPath.ar,
|
|
97
|
+
guide.bodyPath.en,
|
|
61
98
|
]);
|
|
62
99
|
|
|
63
100
|
for (const path of declaredFiles(manifest, guideFiles)) {
|
|
@@ -66,7 +103,8 @@ export const runValidate = async (args: string[]) => {
|
|
|
66
103
|
}
|
|
67
104
|
}
|
|
68
105
|
|
|
69
|
-
const
|
|
106
|
+
const pack = readGuidePack(source, manifest);
|
|
107
|
+
const driverPath = driverArg ? resolve(driverArg) : join(dir, "src/driver.ts");
|
|
70
108
|
|
|
71
109
|
if (!existsSync(driverPath)) {
|
|
72
110
|
throw new Error(`no driver module at ${driverPath} — export your BridgeDriver from src/driver.ts`);
|
|
@@ -83,12 +121,57 @@ export const runValidate = async (args: string[]) => {
|
|
|
83
121
|
|
|
84
122
|
console.log(`${manifest.metadata.id} is valid`);
|
|
85
123
|
console.log(
|
|
86
|
-
` manifest: ${manifest.
|
|
124
|
+
` manifest: ${manifest.container.ports.length} port(s), ${manifest.secrets.length} secret(s), ${manifest.guides.length} guide(s)`,
|
|
87
125
|
);
|
|
88
126
|
console.log(
|
|
89
127
|
` assets: logo ${assets.logo.dimensions.width}x${assets.logo.dimensions.height}, banner ${assets.banner.dimensions.width}x${assets.banner.dimensions.height}`,
|
|
90
128
|
);
|
|
129
|
+
reportGuides(pack);
|
|
91
130
|
console.log(
|
|
92
131
|
` driver: ${Object.keys(driver.modules ?? {}).length} panel module(s), ${driver.panel?.tabs.length ?? 0} tab(s)`,
|
|
93
132
|
);
|
|
94
133
|
};
|
|
134
|
+
|
|
135
|
+
export const runValidate = async (args: string[]) => {
|
|
136
|
+
const { values } = parseArgs({
|
|
137
|
+
args,
|
|
138
|
+
options: {
|
|
139
|
+
dir: {
|
|
140
|
+
type: "string",
|
|
141
|
+
default: ".",
|
|
142
|
+
},
|
|
143
|
+
driver: {
|
|
144
|
+
type: "string",
|
|
145
|
+
},
|
|
146
|
+
help: {
|
|
147
|
+
type: "boolean",
|
|
148
|
+
default: false,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (values.help) {
|
|
154
|
+
console.log(HELP);
|
|
155
|
+
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const dir = resolve(values.dir);
|
|
160
|
+
const source = directorySource(dir);
|
|
161
|
+
|
|
162
|
+
let document: Manifest | GuidePackage;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
document = readPackageDocument(source);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return invalidManifest(error);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (document.kind === "GuidePackage") {
|
|
171
|
+
validateGuidePackage(source, document);
|
|
172
|
+
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
await validateGamePackage(source, document, dir, values.driver);
|
|
177
|
+
};
|
|
@@ -18,6 +18,7 @@ export interface BridgeFileEntry {
|
|
|
18
18
|
export interface BridgeDownloadOptions {
|
|
19
19
|
digest?: string;
|
|
20
20
|
sizeBytes?: number;
|
|
21
|
+
cache?: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export interface BridgeFiles {
|
|
@@ -52,6 +53,9 @@ export interface BridgeCodecs {
|
|
|
52
53
|
export interface BridgeRequestOptions {
|
|
53
54
|
headers?: Record<string, string>;
|
|
54
55
|
timeoutMs?: number;
|
|
56
|
+
cache?: boolean;
|
|
57
|
+
cacheSeconds?: number;
|
|
58
|
+
refresh?: boolean;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
export interface BridgeNet {
|
|
@@ -103,6 +107,12 @@ export interface BridgeLogs {
|
|
|
103
107
|
watch(pattern: RegExp, timeoutMs: number): Promise<RegExpMatchArray | null>;
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
export interface BridgeLog {
|
|
111
|
+
(message: string, fields?: BridgeValues): void;
|
|
112
|
+
warn(message: string, fields?: BridgeValues): void;
|
|
113
|
+
error(message: string, fields?: BridgeValues): void;
|
|
114
|
+
}
|
|
115
|
+
|
|
106
116
|
export interface BridgeContext {
|
|
107
117
|
readonly server: BridgeServer;
|
|
108
118
|
variable(key: string): string | null;
|
|
@@ -116,5 +126,5 @@ export interface BridgeContext {
|
|
|
116
126
|
exec(command: string[], options?: BridgeExecOptions): Promise<BridgeExecResult>;
|
|
117
127
|
logs: BridgeLogs;
|
|
118
128
|
emit(event: string, payload?: BridgeValues): void;
|
|
119
|
-
log
|
|
129
|
+
log: BridgeLog;
|
|
120
130
|
}
|
package/src/core/bridgeAction.ts
CHANGED
|
@@ -9,5 +9,18 @@ export enum BridgeAction {
|
|
|
9
9
|
Remove = "remove",
|
|
10
10
|
Sample = "sample",
|
|
11
11
|
Search = "search",
|
|
12
|
+
Toggle = "toggle",
|
|
12
13
|
Write = "write",
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
export const BRIDGE_READ_ACTIONS: readonly string[] = [
|
|
17
|
+
BridgeAction.Installed,
|
|
18
|
+
BridgeAction.List,
|
|
19
|
+
BridgeAction.Read,
|
|
20
|
+
BridgeAction.Sample,
|
|
21
|
+
BridgeAction.Search,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const isReadAction = (action: string): boolean => {
|
|
25
|
+
return BRIDGE_READ_ACTIONS.includes(action);
|
|
26
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export enum BridgeFailureCode {
|
|
2
|
+
CatalogRateLimited = "catalog-rate-limited",
|
|
3
|
+
CatalogRestricted = "catalog-restricted",
|
|
4
|
+
NoCatalogVersionAvailable = "no-catalog-version-available",
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class BridgeFailureError extends Error {
|
|
8
|
+
readonly code: BridgeFailureCode;
|
|
9
|
+
|
|
10
|
+
constructor(code: BridgeFailureCode, message: string) {
|
|
11
|
+
super(message);
|
|
12
|
+
|
|
13
|
+
this.name = "BridgeFailureError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/core/bridgeKind.ts
CHANGED
package/src/core/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from "./bridgeAction";
|
|
2
|
+
export * from "./bridgeFailure";
|
|
2
3
|
export * from "./bridgeId";
|
|
3
4
|
export * from "./bridgeKind";
|
|
5
|
+
export * from "./bridgeNetError";
|
|
4
6
|
export * from "./bridgePort";
|
|
5
7
|
export * from "./bridgeRuntime";
|
|
6
8
|
export * from "./bridgeSecretError";
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zYoutubeId } from "../manifest/guide";
|
|
3
|
+
import { zGuideVariableRef } from "./variables";
|
|
4
|
+
|
|
5
|
+
export enum GuideCalloutTone {
|
|
6
|
+
Note = "note",
|
|
7
|
+
Warning = "warning",
|
|
8
|
+
Danger = "danger",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export enum GuideOpenTab {
|
|
12
|
+
Overview = "overview",
|
|
13
|
+
Console = "console",
|
|
14
|
+
Files = "files",
|
|
15
|
+
Backups = "backups",
|
|
16
|
+
Panel = "panel",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const zGuideFilePath = z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1)
|
|
22
|
+
.max(256)
|
|
23
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9./_-]*$/)
|
|
24
|
+
.refine((value) => !value.includes(".."), {
|
|
25
|
+
error: "file path must not traverse upwards",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const zGuidePanelTabId = z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.max(32)
|
|
32
|
+
.regex(/^[a-z][a-z0-9-]*$/);
|
|
33
|
+
|
|
34
|
+
export const GUIDE_COMMAND_MAX_LENGTH = 512;
|
|
35
|
+
|
|
36
|
+
export const zGuideCommand = z
|
|
37
|
+
.string()
|
|
38
|
+
.min(1)
|
|
39
|
+
.max(GUIDE_COMMAND_MAX_LENGTH)
|
|
40
|
+
.regex(/^[^\p{Cc}]+$/u, {
|
|
41
|
+
error: "a command must be a single line without control characters",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const guideOpenTargetSchema = z.discriminatedUnion("tab", [
|
|
45
|
+
z.object({
|
|
46
|
+
tab: z.literal(GuideOpenTab.Overview),
|
|
47
|
+
}),
|
|
48
|
+
z.object({
|
|
49
|
+
tab: z.literal(GuideOpenTab.Console),
|
|
50
|
+
}),
|
|
51
|
+
z.object({
|
|
52
|
+
tab: z.literal(GuideOpenTab.Backups),
|
|
53
|
+
}),
|
|
54
|
+
z.object({
|
|
55
|
+
tab: z.literal(GuideOpenTab.Files),
|
|
56
|
+
path: zGuideFilePath.nullable(),
|
|
57
|
+
}),
|
|
58
|
+
z.object({
|
|
59
|
+
tab: z.literal(GuideOpenTab.Panel),
|
|
60
|
+
tabId: zGuidePanelTabId,
|
|
61
|
+
}),
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
export type GuideOpenTarget = z.infer<typeof guideOpenTargetSchema>;
|
|
65
|
+
|
|
66
|
+
export const guideInlineSchema = z.discriminatedUnion("type", [
|
|
67
|
+
z.object({
|
|
68
|
+
type: z.literal("text"),
|
|
69
|
+
text: z.string().min(1).max(2048),
|
|
70
|
+
}),
|
|
71
|
+
z.object({
|
|
72
|
+
type: z.literal("bold"),
|
|
73
|
+
text: z.string().min(1).max(512),
|
|
74
|
+
}),
|
|
75
|
+
z.object({
|
|
76
|
+
type: z.literal("italic"),
|
|
77
|
+
text: z.string().min(1).max(512),
|
|
78
|
+
}),
|
|
79
|
+
z.object({
|
|
80
|
+
type: z.literal("code"),
|
|
81
|
+
text: z.string().min(1).max(512),
|
|
82
|
+
}),
|
|
83
|
+
z.object({
|
|
84
|
+
type: z.literal("link"),
|
|
85
|
+
href: z
|
|
86
|
+
.string()
|
|
87
|
+
.max(2048)
|
|
88
|
+
.regex(/^(https:\/\/|\/)/),
|
|
89
|
+
text: z.string().min(1).max(512),
|
|
90
|
+
}),
|
|
91
|
+
z.object({
|
|
92
|
+
type: z.literal("variable"),
|
|
93
|
+
key: zGuideVariableRef,
|
|
94
|
+
}),
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
export type GuideInline = z.infer<typeof guideInlineSchema>;
|
|
98
|
+
|
|
99
|
+
const guideLeafVariants = [
|
|
100
|
+
z.object({
|
|
101
|
+
type: z.literal("heading"),
|
|
102
|
+
level: z.union([
|
|
103
|
+
z.literal(2),
|
|
104
|
+
z.literal(3),
|
|
105
|
+
]),
|
|
106
|
+
id: z.string().min(1).max(128),
|
|
107
|
+
children: z.array(guideInlineSchema).min(1),
|
|
108
|
+
}),
|
|
109
|
+
z.object({
|
|
110
|
+
type: z.literal("paragraph"),
|
|
111
|
+
children: z.array(guideInlineSchema).min(1),
|
|
112
|
+
}),
|
|
113
|
+
z.object({
|
|
114
|
+
type: z.literal("list"),
|
|
115
|
+
ordered: z.boolean(),
|
|
116
|
+
items: z.array(z.array(guideInlineSchema).min(1)).min(1).max(64),
|
|
117
|
+
}),
|
|
118
|
+
z.object({
|
|
119
|
+
type: z.literal("code"),
|
|
120
|
+
language: z.string().min(1).max(32).nullable(),
|
|
121
|
+
content: z.string().min(1).max(8192),
|
|
122
|
+
}),
|
|
123
|
+
z.object({
|
|
124
|
+
type: z.literal("callout"),
|
|
125
|
+
tone: z.enum(GuideCalloutTone),
|
|
126
|
+
children: z.array(guideInlineSchema).min(1),
|
|
127
|
+
}),
|
|
128
|
+
z.object({
|
|
129
|
+
type: z.literal("image"),
|
|
130
|
+
url: z.url(),
|
|
131
|
+
alt: z.string().max(256),
|
|
132
|
+
width: z.int().positive().nullable(),
|
|
133
|
+
height: z.int().positive().nullable(),
|
|
134
|
+
}),
|
|
135
|
+
z.object({
|
|
136
|
+
type: z.literal("youtube"),
|
|
137
|
+
videoId: zYoutubeId,
|
|
138
|
+
}),
|
|
139
|
+
z.object({
|
|
140
|
+
type: z.literal("field"),
|
|
141
|
+
key: zGuideVariableRef,
|
|
142
|
+
}),
|
|
143
|
+
z.object({
|
|
144
|
+
type: z.literal("command"),
|
|
145
|
+
command: zGuideCommand,
|
|
146
|
+
}),
|
|
147
|
+
z.object({
|
|
148
|
+
type: z.literal("open"),
|
|
149
|
+
target: guideOpenTargetSchema,
|
|
150
|
+
}),
|
|
151
|
+
] as const;
|
|
152
|
+
|
|
153
|
+
export const guideLeafBlockSchema = z.discriminatedUnion("type", guideLeafVariants);
|
|
154
|
+
|
|
155
|
+
export type GuideLeafBlock = z.infer<typeof guideLeafBlockSchema>;
|
|
156
|
+
|
|
157
|
+
export const guideBlockSchema = z.discriminatedUnion("type", [
|
|
158
|
+
...guideLeafVariants,
|
|
159
|
+
z.object({
|
|
160
|
+
type: z.literal("conditional"),
|
|
161
|
+
key: zGuideVariableRef,
|
|
162
|
+
when: z.array(guideLeafBlockSchema).min(1).max(32),
|
|
163
|
+
otherwise: z.array(guideLeafBlockSchema).max(32),
|
|
164
|
+
}),
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
export type GuideBlock = z.infer<typeof guideBlockSchema>;
|