@scenar/cli 0.1.19 → 0.1.20
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 +17 -5
- package/src/__tests__/deploy-command.test.ts +45 -0
- package/src/__tests__/deploy-flow.test.ts +125 -0
- package/src/__tests__/deploy-upload.test.ts +39 -0
- package/src/__tests__/embed-snippet.test.ts +56 -0
- package/src/__tests__/pack-bundle-contract.test.ts +135 -0
- package/src/__tests__/pack-command.test.ts +51 -0
- package/src/__tests__/pack-generate-embed-entry.test.ts +90 -0
- package/src/__tests__/pack-manifest.test.ts +138 -0
- package/src/__tests__/viewport.test.ts +33 -0
- package/src/commands/deploy.d.ts +3 -0
- package/src/commands/deploy.d.ts.map +1 -0
- package/src/commands/deploy.js +166 -0
- package/src/commands/deploy.js.map +1 -0
- package/src/commands/deploy.ts +204 -0
- package/src/commands/pack.d.ts +3 -0
- package/src/commands/pack.d.ts.map +1 -0
- package/src/commands/pack.js +156 -0
- package/src/commands/pack.js.map +1 -0
- package/src/commands/pack.ts +189 -0
- package/src/deploy/client.d.ts +20 -0
- package/src/deploy/client.d.ts.map +1 -0
- package/src/deploy/client.js +22 -0
- package/src/deploy/client.js.map +1 -0
- package/src/deploy/client.ts +28 -0
- package/src/deploy/deploy-flow.d.ts +78 -0
- package/src/deploy/deploy-flow.d.ts.map +1 -0
- package/src/deploy/deploy-flow.js +53 -0
- package/src/deploy/deploy-flow.js.map +1 -0
- package/src/deploy/deploy-flow.ts +115 -0
- package/src/deploy/embed-snippet.d.ts +27 -0
- package/src/deploy/embed-snippet.d.ts.map +1 -0
- package/src/deploy/embed-snippet.js +40 -0
- package/src/deploy/embed-snippet.js.map +1 -0
- package/src/deploy/embed-snippet.ts +52 -0
- package/src/deploy/upload.d.ts +10 -0
- package/src/deploy/upload.d.ts.map +1 -0
- package/src/deploy/upload.js +23 -0
- package/src/deploy/upload.js.map +1 -0
- package/src/deploy/upload.ts +29 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.js +5 -1
- package/src/index.js.map +1 -1
- package/src/index.ts +5 -1
- package/src/pack/build.d.ts +17 -0
- package/src/pack/build.d.ts.map +1 -0
- package/src/pack/build.js +42 -0
- package/src/pack/build.js.map +1 -0
- package/src/pack/build.ts +59 -0
- package/src/pack/bundle-contract.d.ts +61 -0
- package/src/pack/bundle-contract.d.ts.map +1 -0
- package/src/pack/bundle-contract.js +116 -0
- package/src/pack/bundle-contract.js.map +1 -0
- package/src/pack/bundle-contract.ts +128 -0
- package/src/pack/generate-embed-entry.d.ts +42 -0
- package/src/pack/generate-embed-entry.d.ts.map +1 -0
- package/src/pack/generate-embed-entry.js +125 -0
- package/src/pack/generate-embed-entry.js.map +1 -0
- package/src/pack/generate-embed-entry.ts +162 -0
- package/src/pack/pack-manifest.d.ts +45 -0
- package/src/pack/pack-manifest.d.ts.map +1 -0
- package/src/pack/pack-manifest.js +95 -0
- package/src/pack/pack-manifest.js.map +1 -0
- package/src/pack/pack-manifest.ts +146 -0
- package/src/pack/viewport.d.ts +30 -0
- package/src/pack/viewport.d.ts.map +1 -0
- package/src/pack/viewport.js +19 -0
- package/src/pack/viewport.js.map +1 -0
- package/src/pack/viewport.ts +40 -0
- package/src/util/load-yaml.d.ts +1 -1
- package/src/util/load-yaml.js +1 -1
- package/src/util/load-yaml.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { PackManifest } from "../pack/pack-manifest.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One presigned upload target as the backend returns it. Mirrors the relevant
|
|
5
|
+
* fields of ai.scenar.deploy.v1.FileUploadTarget.
|
|
6
|
+
*/
|
|
7
|
+
export interface UploadTarget {
|
|
8
|
+
readonly relativePath: string;
|
|
9
|
+
readonly presignedPutUrl: string;
|
|
10
|
+
/**
|
|
11
|
+
* Headers that MUST be replayed verbatim on the PUT for the signature to
|
|
12
|
+
* validate (Content-Type + x-amz-checksum-sha256). The backend deliberately
|
|
13
|
+
* omits host/content-length, which the HTTP layer owns.
|
|
14
|
+
*/
|
|
15
|
+
readonly requiredHeaders: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A file declared to the upload session — the init shape for DeclaredFile. */
|
|
19
|
+
export interface DeclaredFileInit {
|
|
20
|
+
readonly relativePath: string;
|
|
21
|
+
/** Lowercase-hex SHA-256 (the proto + presign both bind this exact form). */
|
|
22
|
+
readonly sha256: string;
|
|
23
|
+
/** int64 on the wire — carried as bigint to avoid precision loss. */
|
|
24
|
+
readonly sizeBytes: bigint;
|
|
25
|
+
readonly contentType: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Identity for the ensure-scenario (apply) step. */
|
|
29
|
+
export interface ScenarioApplyInput {
|
|
30
|
+
readonly org: string;
|
|
31
|
+
readonly slug: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The side-effecting operations the flow depends on, injected so the ordering
|
|
37
|
+
* and payloads can be unit-tested against fakes without a live backend.
|
|
38
|
+
*/
|
|
39
|
+
export interface DeployFlowDeps {
|
|
40
|
+
/** Idempotently ensure the parent scenario exists; returns its metadata.id. */
|
|
41
|
+
applyScenario(input: ScenarioApplyInput): Promise<string>;
|
|
42
|
+
/** Open the upload session; returns the deploy id + per-file targets. */
|
|
43
|
+
createSession(
|
|
44
|
+
scenarioId: string,
|
|
45
|
+
files: DeclaredFileInit[],
|
|
46
|
+
): Promise<{ deployId: string; uploadTargets: UploadTarget[] }>;
|
|
47
|
+
/** Read a bundle file's bytes by its relative path. */
|
|
48
|
+
readBundleFile(relativePath: string): Promise<Uint8Array>;
|
|
49
|
+
/** PUT one file's bytes to its presigned target, replaying required headers. */
|
|
50
|
+
uploadFile(target: UploadTarget, bytes: Uint8Array): Promise<void>;
|
|
51
|
+
/** Finalize the session; returns the published deploy's embed_url. */
|
|
52
|
+
completeSession(deployId: string): Promise<string>;
|
|
53
|
+
/** Progress sink (stderr in the command; captured in tests). */
|
|
54
|
+
log(message: string): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Map a pack manifest to the deploy session's declared-file inventory. */
|
|
58
|
+
export function toDeclaredFiles(manifest: PackManifest): DeclaredFileInit[] {
|
|
59
|
+
return manifest.files.map((file) => ({
|
|
60
|
+
relativePath: file.path,
|
|
61
|
+
sha256: file.sha256,
|
|
62
|
+
sizeBytes: BigInt(file.sizeBytes),
|
|
63
|
+
contentType: file.contentType,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Drive the deploy: ensure the parent scenario, open the upload session, PUT
|
|
69
|
+
* every file to its presigned target (in the order the backend returned them,
|
|
70
|
+
* replaying required headers verbatim), then complete and return the embed URL.
|
|
71
|
+
*
|
|
72
|
+
* The ensure-then-deploy ordering is load-bearing: createDeployUploadSession
|
|
73
|
+
* authorizes against and inherits its org from an existing scenario, so the
|
|
74
|
+
* scenario must exist first.
|
|
75
|
+
*/
|
|
76
|
+
export async function runDeployFlow(
|
|
77
|
+
deps: DeployFlowDeps,
|
|
78
|
+
opts: { manifest: PackManifest; org: string; slug: string; name: string },
|
|
79
|
+
): Promise<{ deployId: string; embedUrl: string }> {
|
|
80
|
+
deps.log(`Ensuring scenario ${opts.org}/${opts.slug}...`);
|
|
81
|
+
const scenarioId = await deps.applyScenario({ org: opts.org, slug: opts.slug, name: opts.name });
|
|
82
|
+
|
|
83
|
+
const files = toDeclaredFiles(opts.manifest);
|
|
84
|
+
deps.log(`Creating deploy session (${files.length} files)...`);
|
|
85
|
+
const { deployId, uploadTargets } = await deps.createSession(scenarioId, files);
|
|
86
|
+
|
|
87
|
+
for (const target of uploadTargets) {
|
|
88
|
+
const bytes = await deps.readBundleFile(target.relativePath);
|
|
89
|
+
deps.log(`Uploading ${target.relativePath} (${bytes.byteLength} bytes)...`);
|
|
90
|
+
await deps.uploadFile(target, bytes);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
deps.log("Completing deploy...");
|
|
94
|
+
const embedUrl = await deps.completeSession(deployId);
|
|
95
|
+
return { deployId, embedUrl };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Derive a locally-viewable URL from a deploy's embed_url. The backend always
|
|
100
|
+
* emits https://, but the local edge (wrangler dev) serves over http on
|
|
101
|
+
* *.localhost; for those hosts we downgrade the scheme so the link is clickable.
|
|
102
|
+
*/
|
|
103
|
+
export function localViewUrl(embedUrl: string): string {
|
|
104
|
+
try {
|
|
105
|
+
const url = new URL(embedUrl);
|
|
106
|
+
const isLocal = url.hostname === "localhost" || url.hostname.endsWith(".localhost");
|
|
107
|
+
if (isLocal && url.protocol === "https:") {
|
|
108
|
+
url.protocol = "http:";
|
|
109
|
+
return url.toString();
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Not a parseable URL — return as-is.
|
|
113
|
+
}
|
|
114
|
+
return embedUrl;
|
|
115
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Viewport } from "../pack/viewport.js";
|
|
2
|
+
/** Inputs for {@link buildEmbedSnippet}. */
|
|
3
|
+
export interface EmbedSnippetInput {
|
|
4
|
+
/** The deploy's public embed URL (goes verbatim into the iframe `src`). */
|
|
5
|
+
readonly embedUrl: string;
|
|
6
|
+
/** Canonical viewport — pins the responsive box's aspect ratio + max width. */
|
|
7
|
+
readonly viewport: Viewport;
|
|
8
|
+
/** Optional human title for the iframe (a11y). Defaults to a generic label. */
|
|
9
|
+
readonly title?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build the responsive, framework-free `<iframe>` embed snippet — the default,
|
|
13
|
+
* no-JS embed of DD-002. The wrapper pins the intrinsic aspect ratio
|
|
14
|
+
* (`width / height` from the baked `ViewportConfig`, DD-004) with CSS
|
|
15
|
+
* `aspect-ratio` so the embed scales fluidly to its container without
|
|
16
|
+
* letterboxing or reflow, capped at the canonical width so it never upscales
|
|
17
|
+
* past crispness.
|
|
18
|
+
*
|
|
19
|
+
* `allow="autoplay; fullscreen"` is the canonical host half of the embed
|
|
20
|
+
* permission pair (DD-003) and matches the edge's `Permissions-Policy`; reuse it
|
|
21
|
+
* verbatim. Attribute values are escaped, since the snippet is copy-pasted into
|
|
22
|
+
* third-party HTML.
|
|
23
|
+
*
|
|
24
|
+
* Pure function — returns the snippet string; the caller decides where to print.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildEmbedSnippet(input: EmbedSnippetInput): string;
|
|
27
|
+
//# sourceMappingURL=embed-snippet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed-snippet.d.ts","sourceRoot":"","sources":["../../../src/deploy/embed-snippet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAelE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the responsive, framework-free `<iframe>` embed snippet — the default,
|
|
3
|
+
* no-JS embed of DD-002. The wrapper pins the intrinsic aspect ratio
|
|
4
|
+
* (`width / height` from the baked `ViewportConfig`, DD-004) with CSS
|
|
5
|
+
* `aspect-ratio` so the embed scales fluidly to its container without
|
|
6
|
+
* letterboxing or reflow, capped at the canonical width so it never upscales
|
|
7
|
+
* past crispness.
|
|
8
|
+
*
|
|
9
|
+
* `allow="autoplay; fullscreen"` is the canonical host half of the embed
|
|
10
|
+
* permission pair (DD-003) and matches the edge's `Permissions-Policy`; reuse it
|
|
11
|
+
* verbatim. Attribute values are escaped, since the snippet is copy-pasted into
|
|
12
|
+
* third-party HTML.
|
|
13
|
+
*
|
|
14
|
+
* Pure function — returns the snippet string; the caller decides where to print.
|
|
15
|
+
*/
|
|
16
|
+
export function buildEmbedSnippet(input) {
|
|
17
|
+
const { embedUrl, viewport } = input;
|
|
18
|
+
const title = input.title ?? "Scenar embed";
|
|
19
|
+
return [
|
|
20
|
+
`<div style="position:relative;width:100%;max-width:${viewport.width}px;aspect-ratio:${viewport.width}/${viewport.height}">`,
|
|
21
|
+
` <iframe`,
|
|
22
|
+
` src="${escapeAttr(embedUrl)}"`,
|
|
23
|
+
` title="${escapeAttr(title)}"`,
|
|
24
|
+
` loading="lazy"`,
|
|
25
|
+
` style="position:absolute;inset:0;width:100%;height:100%;border:0"`,
|
|
26
|
+
` allow="autoplay; fullscreen"`,
|
|
27
|
+
` allowfullscreen`,
|
|
28
|
+
` ></iframe>`,
|
|
29
|
+
`</div>`,
|
|
30
|
+
].join("\n");
|
|
31
|
+
}
|
|
32
|
+
/** Escape a string for safe interpolation into a double-quoted HTML attribute. */
|
|
33
|
+
function escapeAttr(value) {
|
|
34
|
+
return value
|
|
35
|
+
.replace(/&/g, "&")
|
|
36
|
+
.replace(/"/g, """)
|
|
37
|
+
.replace(/</g, "<")
|
|
38
|
+
.replace(/>/g, ">");
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=embed-snippet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed-snippet.js","sourceRoot":"","sources":["../../../src/deploy/embed-snippet.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAwB;IACxD,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,cAAc,CAAC;IAC5C,OAAO;QACL,sDAAsD,QAAQ,CAAC,KAAK,mBAAmB,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,IAAI;QAC5H,WAAW;QACX,YAAY,UAAU,CAAC,QAAQ,CAAC,GAAG;QACnC,cAAc,UAAU,CAAC,KAAK,CAAC,GAAG;QAClC,oBAAoB;QACpB,uEAAuE;QACvE,kCAAkC;QAClC,qBAAqB;QACrB,cAAc;QACd,QAAQ;KACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,kFAAkF;AAClF,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Viewport } from "../pack/viewport.js";
|
|
2
|
+
|
|
3
|
+
/** Inputs for {@link buildEmbedSnippet}. */
|
|
4
|
+
export interface EmbedSnippetInput {
|
|
5
|
+
/** The deploy's public embed URL (goes verbatim into the iframe `src`). */
|
|
6
|
+
readonly embedUrl: string;
|
|
7
|
+
/** Canonical viewport — pins the responsive box's aspect ratio + max width. */
|
|
8
|
+
readonly viewport: Viewport;
|
|
9
|
+
/** Optional human title for the iframe (a11y). Defaults to a generic label. */
|
|
10
|
+
readonly title?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build the responsive, framework-free `<iframe>` embed snippet — the default,
|
|
15
|
+
* no-JS embed of DD-002. The wrapper pins the intrinsic aspect ratio
|
|
16
|
+
* (`width / height` from the baked `ViewportConfig`, DD-004) with CSS
|
|
17
|
+
* `aspect-ratio` so the embed scales fluidly to its container without
|
|
18
|
+
* letterboxing or reflow, capped at the canonical width so it never upscales
|
|
19
|
+
* past crispness.
|
|
20
|
+
*
|
|
21
|
+
* `allow="autoplay; fullscreen"` is the canonical host half of the embed
|
|
22
|
+
* permission pair (DD-003) and matches the edge's `Permissions-Policy`; reuse it
|
|
23
|
+
* verbatim. Attribute values are escaped, since the snippet is copy-pasted into
|
|
24
|
+
* third-party HTML.
|
|
25
|
+
*
|
|
26
|
+
* Pure function — returns the snippet string; the caller decides where to print.
|
|
27
|
+
*/
|
|
28
|
+
export function buildEmbedSnippet(input: EmbedSnippetInput): string {
|
|
29
|
+
const { embedUrl, viewport } = input;
|
|
30
|
+
const title = input.title ?? "Scenar embed";
|
|
31
|
+
return [
|
|
32
|
+
`<div style="position:relative;width:100%;max-width:${viewport.width}px;aspect-ratio:${viewport.width}/${viewport.height}">`,
|
|
33
|
+
` <iframe`,
|
|
34
|
+
` src="${escapeAttr(embedUrl)}"`,
|
|
35
|
+
` title="${escapeAttr(title)}"`,
|
|
36
|
+
` loading="lazy"`,
|
|
37
|
+
` style="position:absolute;inset:0;width:100%;height:100%;border:0"`,
|
|
38
|
+
` allow="autoplay; fullscreen"`,
|
|
39
|
+
` allowfullscreen`,
|
|
40
|
+
` ></iframe>`,
|
|
41
|
+
`</div>`,
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Escape a string for safe interpolation into a double-quoted HTML attribute. */
|
|
46
|
+
function escapeAttr(value: string): string {
|
|
47
|
+
return value
|
|
48
|
+
.replace(/&/g, "&")
|
|
49
|
+
.replace(/"/g, """)
|
|
50
|
+
.replace(/</g, "<")
|
|
51
|
+
.replace(/>/g, ">");
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload one file's bytes to its presigned PUT target.
|
|
3
|
+
*
|
|
4
|
+
* The presign binds the content type and SHA-256 into the signature, so every
|
|
5
|
+
* header the backend returned in `required_headers` is replayed verbatim. The
|
|
6
|
+
* backend intentionally omits host and content-length (the HTTP layer owns
|
|
7
|
+
* them) — fetch sets content-length from the body, matching the declared size.
|
|
8
|
+
*/
|
|
9
|
+
export declare function putFile(presignedPutUrl: string, requiredHeaders: Record<string, string>, bytes: Uint8Array): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/deploy/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAC3B,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload one file's bytes to its presigned PUT target.
|
|
3
|
+
*
|
|
4
|
+
* The presign binds the content type and SHA-256 into the signature, so every
|
|
5
|
+
* header the backend returned in `required_headers` is replayed verbatim. The
|
|
6
|
+
* backend intentionally omits host and content-length (the HTTP layer owns
|
|
7
|
+
* them) — fetch sets content-length from the body, matching the declared size.
|
|
8
|
+
*/
|
|
9
|
+
export async function putFile(presignedPutUrl, requiredHeaders, bytes) {
|
|
10
|
+
const response = await fetch(presignedPutUrl, {
|
|
11
|
+
method: "PUT",
|
|
12
|
+
headers: requiredHeaders,
|
|
13
|
+
// The DOM BodyInit type is overly narrow for Uint8Array under this TS/lib
|
|
14
|
+
// combination; raw bytes are a valid fetch body at runtime.
|
|
15
|
+
body: bytes,
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const detail = await response.text().catch(() => "");
|
|
19
|
+
throw new Error(`upload failed (${response.status} ${response.statusText})` +
|
|
20
|
+
(detail ? `: ${detail.slice(0, 300)}` : ""));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/deploy/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,eAAuB,EACvB,eAAuC,EACvC,KAAiB;IAEjB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,eAAe,EAAE;QAC5C,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,eAAe;QACxB,0EAA0E;QAC1E,4DAA4D;QAC5D,IAAI,EAAE,KAA4B;KACnC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACrD,MAAM,IAAI,KAAK,CACb,kBAAkB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG;YACzD,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC9C,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload one file's bytes to its presigned PUT target.
|
|
3
|
+
*
|
|
4
|
+
* The presign binds the content type and SHA-256 into the signature, so every
|
|
5
|
+
* header the backend returned in `required_headers` is replayed verbatim. The
|
|
6
|
+
* backend intentionally omits host and content-length (the HTTP layer owns
|
|
7
|
+
* them) — fetch sets content-length from the body, matching the declared size.
|
|
8
|
+
*/
|
|
9
|
+
export async function putFile(
|
|
10
|
+
presignedPutUrl: string,
|
|
11
|
+
requiredHeaders: Record<string, string>,
|
|
12
|
+
bytes: Uint8Array,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const response = await fetch(presignedPutUrl, {
|
|
15
|
+
method: "PUT",
|
|
16
|
+
headers: requiredHeaders,
|
|
17
|
+
// The DOM BodyInit type is overly narrow for Uint8Array under this TS/lib
|
|
18
|
+
// combination; raw bytes are a valid fetch body at runtime.
|
|
19
|
+
body: bytes as unknown as BodyInit,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
const detail = await response.text().catch(() => "");
|
|
24
|
+
throw new Error(
|
|
25
|
+
`upload failed (${response.status} ${response.statusText})` +
|
|
26
|
+
(detail ? `: ${detail.slice(0, 300)}` : ""),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,aAAa,IAAI,OAAO,CAgBvC;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAGxC"}
|
package/src/index.js
CHANGED
|
@@ -3,16 +3,20 @@ import { registerValidateCommand } from "./commands/validate.js";
|
|
|
3
3
|
import { registerNarrateCommand } from "./commands/narrate.js";
|
|
4
4
|
import { registerPreviewCommand } from "./commands/preview.js";
|
|
5
5
|
import { registerRenderCommand } from "./commands/render.js";
|
|
6
|
+
import { registerPackCommand } from "./commands/pack.js";
|
|
7
|
+
import { registerDeployCommand } from "./commands/deploy.js";
|
|
6
8
|
export function createProgram() {
|
|
7
9
|
const program = new Command();
|
|
8
10
|
program
|
|
9
11
|
.name("scenar")
|
|
10
|
-
.description("Scenar CLI — validate scenarios, generate narration, render videos, and
|
|
12
|
+
.description("Scenar CLI — validate scenarios, generate narration, render videos, pack embeds, and deploy.")
|
|
11
13
|
.version("0.0.1");
|
|
12
14
|
registerValidateCommand(program);
|
|
13
15
|
registerNarrateCommand(program);
|
|
14
16
|
registerRenderCommand(program);
|
|
15
17
|
registerPreviewCommand(program);
|
|
18
|
+
registerPackCommand(program);
|
|
19
|
+
registerDeployCommand(program);
|
|
16
20
|
return program;
|
|
17
21
|
}
|
|
18
22
|
export function run(argv) {
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,QAAQ,CAAC;SACd,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,QAAQ,CAAC;SACd,WAAW,CAAC,8FAA8F,CAAC;SAC3G,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjC,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAE/B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,IAAc;IAChC,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC"}
|
package/src/index.ts
CHANGED
|
@@ -3,19 +3,23 @@ import { registerValidateCommand } from "./commands/validate.js";
|
|
|
3
3
|
import { registerNarrateCommand } from "./commands/narrate.js";
|
|
4
4
|
import { registerPreviewCommand } from "./commands/preview.js";
|
|
5
5
|
import { registerRenderCommand } from "./commands/render.js";
|
|
6
|
+
import { registerPackCommand } from "./commands/pack.js";
|
|
7
|
+
import { registerDeployCommand } from "./commands/deploy.js";
|
|
6
8
|
|
|
7
9
|
export function createProgram(): Command {
|
|
8
10
|
const program = new Command();
|
|
9
11
|
|
|
10
12
|
program
|
|
11
13
|
.name("scenar")
|
|
12
|
-
.description("Scenar CLI — validate scenarios, generate narration, render videos, and
|
|
14
|
+
.description("Scenar CLI — validate scenarios, generate narration, render videos, pack embeds, and deploy.")
|
|
13
15
|
.version("0.0.1");
|
|
14
16
|
|
|
15
17
|
registerValidateCommand(program);
|
|
16
18
|
registerNarrateCommand(program);
|
|
17
19
|
registerRenderCommand(program);
|
|
18
20
|
registerPreviewCommand(program);
|
|
21
|
+
registerPackCommand(program);
|
|
22
|
+
registerDeployCommand(program);
|
|
19
23
|
|
|
20
24
|
return program;
|
|
21
25
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite build invocation for `scenar pack`.
|
|
3
|
+
*
|
|
4
|
+
* Vite is loaded dynamically (like the Remotion bundler in `render`) so the CLI
|
|
5
|
+
* stays lean for users who only validate/narrate. The build runs in "app mode"
|
|
6
|
+
* from a generated index.html, producing a self-contained static bundle.
|
|
7
|
+
*/
|
|
8
|
+
export interface ViteBuildInput {
|
|
9
|
+
/** Build root — the temp dir holding index.html + the generated entry. */
|
|
10
|
+
root: string;
|
|
11
|
+
/** Absolute output directory for the static bundle. */
|
|
12
|
+
outDir: string;
|
|
13
|
+
/** Absolute path to the generated index.html (the Rollup input). */
|
|
14
|
+
entryHtmlPath: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function runViteBuild(input: ViteBuildInput): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/pack/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAyCvE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite build invocation for `scenar pack`.
|
|
3
|
+
*
|
|
4
|
+
* Vite is loaded dynamically (like the Remotion bundler in `render`) so the CLI
|
|
5
|
+
* stays lean for users who only validate/narrate. The build runs in "app mode"
|
|
6
|
+
* from a generated index.html, producing a self-contained static bundle.
|
|
7
|
+
*/
|
|
8
|
+
export async function runViteBuild(input) {
|
|
9
|
+
const vite = await import("vite").catch(() => {
|
|
10
|
+
throw new Error("Could not load vite.\n" +
|
|
11
|
+
"scenar pack bundles the embed with Vite. Install it:\n" +
|
|
12
|
+
" pnpm add -D vite @vitejs/plugin-react");
|
|
13
|
+
});
|
|
14
|
+
const reactPluginMod = await import("@vitejs/plugin-react").catch(() => {
|
|
15
|
+
throw new Error("Could not load @vitejs/plugin-react.\n" +
|
|
16
|
+
"Install it: pnpm add -D vite @vitejs/plugin-react");
|
|
17
|
+
});
|
|
18
|
+
const react = (reactPluginMod.default ?? reactPluginMod);
|
|
19
|
+
await vite.build({
|
|
20
|
+
root: input.root,
|
|
21
|
+
base: "./", // relative asset URLs: the bundle is served from a per-deploy origin root.
|
|
22
|
+
configFile: false, // never pick up a vite.config.* from the consumer project.
|
|
23
|
+
logLevel: "warn",
|
|
24
|
+
plugins: [react()],
|
|
25
|
+
// A single React copy — the scenario, @scenar/react, and react-dom must agree.
|
|
26
|
+
resolve: { dedupe: ["react", "react-dom"] },
|
|
27
|
+
build: {
|
|
28
|
+
outDir: input.outDir,
|
|
29
|
+
emptyOutDir: true,
|
|
30
|
+
cssCodeSplit: false, // one stylesheet, served under style-src 'self'.
|
|
31
|
+
sourcemap: false, // .map is not in the deploy allowlist.
|
|
32
|
+
// Keep Vite's default assetsInlineLimit (4 KiB): small assets inline as data: URIs
|
|
33
|
+
// (allowed by the edge CSP's img-src/font-src 'self' data:), while larger images and
|
|
34
|
+
// fonts emit as hashed files — now first-class members of the deploy allowlist.
|
|
35
|
+
manifest: false, // pack writes its own deploy-facing pack-manifest.json.
|
|
36
|
+
// No inline modulepreload polyfill script — it would violate script-src 'self'.
|
|
37
|
+
modulePreload: { polyfill: false },
|
|
38
|
+
rollupOptions: { input: input.entryHtmlPath },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/pack/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAqB;IACtD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QAC3C,MAAM,IAAI,KAAK,CACb,wBAAwB;YACtB,wDAAwD;YACxD,yCAAyC,CAC5C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QACrE,MAAM,IAAI,KAAK,CACb,wCAAwC;YACtC,mDAAmD,CACtD,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,CAAC,cAAc,CAAC,OAAO,IAAI,cAAc,CAE3C,CAAC;IAEb,MAAO,IAAI,CAAC,KAA+D,CAAC;QAC1E,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,IAAI,EAAE,2EAA2E;QACvF,UAAU,EAAE,KAAK,EAAE,2DAA2D;QAC9E,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC;QAClB,+EAA+E;QAC/E,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE;QAC3C,KAAK,EAAE;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,KAAK,EAAE,iDAAiD;YACtE,SAAS,EAAE,KAAK,EAAE,uCAAuC;YACzD,mFAAmF;YACnF,qFAAqF;YACrF,gFAAgF;YAChF,QAAQ,EAAE,KAAK,EAAE,wDAAwD;YACzE,gFAAgF;YAChF,aAAa,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;YAClC,aAAa,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,EAAE;SAC9C;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite build invocation for `scenar pack`.
|
|
3
|
+
*
|
|
4
|
+
* Vite is loaded dynamically (like the Remotion bundler in `render`) so the CLI
|
|
5
|
+
* stays lean for users who only validate/narrate. The build runs in "app mode"
|
|
6
|
+
* from a generated index.html, producing a self-contained static bundle.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface ViteBuildInput {
|
|
10
|
+
/** Build root — the temp dir holding index.html + the generated entry. */
|
|
11
|
+
root: string;
|
|
12
|
+
/** Absolute output directory for the static bundle. */
|
|
13
|
+
outDir: string;
|
|
14
|
+
/** Absolute path to the generated index.html (the Rollup input). */
|
|
15
|
+
entryHtmlPath: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function runViteBuild(input: ViteBuildInput): Promise<void> {
|
|
19
|
+
const vite = await import("vite").catch(() => {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"Could not load vite.\n" +
|
|
22
|
+
"scenar pack bundles the embed with Vite. Install it:\n" +
|
|
23
|
+
" pnpm add -D vite @vitejs/plugin-react",
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const reactPluginMod = await import("@vitejs/plugin-react").catch(() => {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Could not load @vitejs/plugin-react.\n" +
|
|
30
|
+
"Install it: pnpm add -D vite @vitejs/plugin-react",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
const react = (reactPluginMod.default ?? reactPluginMod) as (
|
|
34
|
+
...args: unknown[]
|
|
35
|
+
) => unknown;
|
|
36
|
+
|
|
37
|
+
await (vite.build as (config: Record<string, unknown>) => Promise<unknown>)({
|
|
38
|
+
root: input.root,
|
|
39
|
+
base: "./", // relative asset URLs: the bundle is served from a per-deploy origin root.
|
|
40
|
+
configFile: false, // never pick up a vite.config.* from the consumer project.
|
|
41
|
+
logLevel: "warn",
|
|
42
|
+
plugins: [react()],
|
|
43
|
+
// A single React copy — the scenario, @scenar/react, and react-dom must agree.
|
|
44
|
+
resolve: { dedupe: ["react", "react-dom"] },
|
|
45
|
+
build: {
|
|
46
|
+
outDir: input.outDir,
|
|
47
|
+
emptyOutDir: true,
|
|
48
|
+
cssCodeSplit: false, // one stylesheet, served under style-src 'self'.
|
|
49
|
+
sourcemap: false, // .map is not in the deploy allowlist.
|
|
50
|
+
// Keep Vite's default assetsInlineLimit (4 KiB): small assets inline as data: URIs
|
|
51
|
+
// (allowed by the edge CSP's img-src/font-src 'self' data:), while larger images and
|
|
52
|
+
// fonts emit as hashed files — now first-class members of the deploy allowlist.
|
|
53
|
+
manifest: false, // pack writes its own deploy-facing pack-manifest.json.
|
|
54
|
+
// No inline modulepreload polyfill script — it would violate script-src 'self'.
|
|
55
|
+
modulePreload: { polyfill: false },
|
|
56
|
+
rollupOptions: { input: input.entryHtmlPath },
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundle contract — the exact rules the Scenar Cloud backend enforces when
|
|
3
|
+
* it accepts a deploy. `scenar pack` validates its own output against these
|
|
4
|
+
* rules so a malformed bundle fails locally with a clear message rather than
|
|
5
|
+
* mid-upload, and a conformance test asserts pack output satisfies them so pack
|
|
6
|
+
* and the server can never drift.
|
|
7
|
+
*
|
|
8
|
+
* Source of truth (kept in lockstep):
|
|
9
|
+
* - DeployContentTypes.java — extension allowlist + canonical content type
|
|
10
|
+
* - DeployManifestValidator.java — path pattern, depth, dupes, content-type check
|
|
11
|
+
* - CompleteDeployUploadSessionHandler.java — sha256/size/content-type shape
|
|
12
|
+
* - ScenarioJsonValidator.java — scenario.json rules
|
|
13
|
+
*
|
|
14
|
+
* The backend verifies each file's declared content type equals the canonical
|
|
15
|
+
* type for its extension (the same declared-then-verified contract as sha256 and
|
|
16
|
+
* size), so `CONTENT_TYPE_BY_EXTENSION` below must match the server's table
|
|
17
|
+
* exactly. svg is intentionally absent: it is active content (it can run script
|
|
18
|
+
* on direct same-origin navigation), so scenarios inline SVG (as a React
|
|
19
|
+
* component or a data URI) rather than emitting it as a served file.
|
|
20
|
+
*/
|
|
21
|
+
/** Conventional content type per allowed extension. Matches the backend exactly. */
|
|
22
|
+
export declare const CONTENT_TYPE_BY_EXTENSION: {
|
|
23
|
+
readonly html: "text/html";
|
|
24
|
+
readonly js: "text/javascript";
|
|
25
|
+
readonly css: "text/css";
|
|
26
|
+
readonly json: "application/json";
|
|
27
|
+
readonly mp3: "audio/mpeg";
|
|
28
|
+
readonly png: "image/png";
|
|
29
|
+
readonly jpg: "image/jpeg";
|
|
30
|
+
readonly jpeg: "image/jpeg";
|
|
31
|
+
readonly gif: "image/gif";
|
|
32
|
+
readonly webp: "image/webp";
|
|
33
|
+
readonly avif: "image/avif";
|
|
34
|
+
readonly woff2: "font/woff2";
|
|
35
|
+
readonly woff: "font/woff";
|
|
36
|
+
};
|
|
37
|
+
/** Final-extension allowlist (lowercase, no dot). Derived from the content-type table. */
|
|
38
|
+
export declare const ALLOWED_EXTENSIONS: (keyof typeof CONTENT_TYPE_BY_EXTENSION)[];
|
|
39
|
+
/** A bundle-relative path: clean POSIX segments, no leading/trailing/double slash. */
|
|
40
|
+
export declare const RELATIVE_PATH_PATTERN: RegExp;
|
|
41
|
+
/** Maximum number of path segments (directory depth) the backend allows. */
|
|
42
|
+
export declare const MAX_PATH_DEPTH = 12;
|
|
43
|
+
/** Lowercase hex SHA-256 (exactly 64 chars) — the form the presign binds. */
|
|
44
|
+
export declare const SHA256_HEX_PATTERN: RegExp;
|
|
45
|
+
/** A `type/subtype` content type — the minimal shape the backend requires. */
|
|
46
|
+
export declare const CONTENT_TYPE_PATTERN: RegExp;
|
|
47
|
+
/** The file every bundle must contain at its root. */
|
|
48
|
+
export declare const REQUIRED_FILE = "scenario.json";
|
|
49
|
+
/** Returns the lowercase final extension of a path (no dot), or "" if none. */
|
|
50
|
+
export declare function finalExtension(path: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Validate one bundle-relative file path against the backend rules. Returns an
|
|
53
|
+
* error message, or null if the path is acceptable.
|
|
54
|
+
*/
|
|
55
|
+
export declare function validateRelativePath(path: string): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Validate the parsed scenario.json content. Returns an error message, or null
|
|
58
|
+
* if it satisfies the backend's ScenarioJsonValidator rules.
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateScenarioJson(raw: string): string | null;
|
|
61
|
+
//# sourceMappingURL=bundle-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-contract.d.ts","sourceRoot":"","sources":["../../../src/pack/bundle-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,oFAAoF;AACpF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;CAiB5B,CAAC;AAEX,0FAA0F;AAC1F,eAAO,MAAM,kBAAkB,EAE1B,CAAC,MAAM,OAAO,yBAAyB,CAAC,EAAE,CAAC;AAEhD,sFAAsF;AACtF,eAAO,MAAM,qBAAqB,QAA0C,CAAC;AAE7E,4EAA4E;AAC5E,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,QAAmB,CAAC;AAEnD,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,QAAuB,CAAC;AAEzD,sDAAsD;AACtD,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAQ7C,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBhE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB/D"}
|