saastore-port 0.1.1
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/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/analyze_repo.js +203 -0
- package/dist/analyze_repo.js.map +1 -0
- package/dist/apply_auth_rewire.js +64 -0
- package/dist/apply_auth_rewire.js.map +1 -0
- package/dist/boot_test_local.js +202 -0
- package/dist/boot_test_local.js.map +1 -0
- package/dist/generate_manifest.js +109 -0
- package/dist/generate_manifest.js.map +1 -0
- package/dist/package_and_push.js +151 -0
- package/dist/package_and_push.js.map +1 -0
- package/dist/propose_auth_rewire.js +113 -0
- package/dist/propose_auth_rewire.js.map +1 -0
- package/dist/server.js +276 -0
- package/dist/server.js.map +1 -0
- package/dist/validate_compliance.js +203 -0
- package/dist/validate_compliance.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generate_manifest — emit a saastore.yaml manifest the seller commits in
|
|
3
|
+
* their repo. Takes the output of analyze_repo + a few seller-supplied
|
|
4
|
+
* fields (port, memory, external_apis caps) and returns the YAML string.
|
|
5
|
+
*
|
|
6
|
+
* Schema mirrors backend/app/services/manifest.py::SaastoreManifest. The
|
|
7
|
+
* MCP tool layer SHOULD round-trip the result through the backend
|
|
8
|
+
* /api/projects/admin/.../releases POST endpoint to validate before
|
|
9
|
+
* shipping, since the backend is the source of truth.
|
|
10
|
+
*/
|
|
11
|
+
import { stringify as yamlStringify } from "yaml";
|
|
12
|
+
// ----- Helpers -------------------------------------------------------------
|
|
13
|
+
const ADAPTER_HINTS = {
|
|
14
|
+
nextauth: "saastore-nextauth",
|
|
15
|
+
"auth.js": "saastore-nextauth",
|
|
16
|
+
passport: "saastore-passport",
|
|
17
|
+
"django-auth": "saastore-django",
|
|
18
|
+
fastapi: "saastore-fastapi",
|
|
19
|
+
flask: "saastore-flask",
|
|
20
|
+
};
|
|
21
|
+
const FRAMEWORK_HINTS = {
|
|
22
|
+
"next.js": "next.js",
|
|
23
|
+
remix: "remix",
|
|
24
|
+
nuxt: "nuxt",
|
|
25
|
+
fastapi: "fastapi",
|
|
26
|
+
django: "django",
|
|
27
|
+
flask: "flask",
|
|
28
|
+
express: "express",
|
|
29
|
+
};
|
|
30
|
+
function inferAdapter(analysis) {
|
|
31
|
+
if (analysis.auth_library && ADAPTER_HINTS[analysis.auth_library]) {
|
|
32
|
+
return ADAPTER_HINTS[analysis.auth_library];
|
|
33
|
+
}
|
|
34
|
+
if (analysis.backend_framework && ADAPTER_HINTS[analysis.backend_framework]) {
|
|
35
|
+
return ADAPTER_HINTS[analysis.backend_framework];
|
|
36
|
+
}
|
|
37
|
+
// Last-resort fallback — seller will replace.
|
|
38
|
+
return "saastore-generic";
|
|
39
|
+
}
|
|
40
|
+
function inferFramework(analysis) {
|
|
41
|
+
// Prefer frontend if present (Next.js owns its own server),
|
|
42
|
+
// otherwise the detected backend.
|
|
43
|
+
return ((analysis.framework && FRAMEWORK_HINTS[analysis.framework]) ||
|
|
44
|
+
(analysis.backend_framework && FRAMEWORK_HINTS[analysis.backend_framework]) ||
|
|
45
|
+
"unknown");
|
|
46
|
+
}
|
|
47
|
+
function defaultPort(framework) {
|
|
48
|
+
if (framework === "next.js" || framework === "remix" || framework === "nuxt")
|
|
49
|
+
return 3000;
|
|
50
|
+
if (framework === "django" || framework === "fastapi" || framework === "flask")
|
|
51
|
+
return 8000;
|
|
52
|
+
return 8080;
|
|
53
|
+
}
|
|
54
|
+
// ----- Main entry ----------------------------------------------------------
|
|
55
|
+
export function generateManifest(analysis, input) {
|
|
56
|
+
if (!input.app_name) {
|
|
57
|
+
throw new Error("generate_manifest: app_name is required");
|
|
58
|
+
}
|
|
59
|
+
const framework = input.framework_override ?? inferFramework(analysis);
|
|
60
|
+
const adapter = input.adapter_override ?? inferAdapter(analysis);
|
|
61
|
+
const port = input.port ?? defaultPort(framework);
|
|
62
|
+
const caps = input.external_api_caps_usd ?? {};
|
|
63
|
+
const externalApis = analysis.external_apis
|
|
64
|
+
.filter((name) => name !== "sentry") // observability isn't a paid LLM cap
|
|
65
|
+
.map((apiName) => {
|
|
66
|
+
const envName = apiName.toUpperCase().replace(/-/g, "_") + "_API_KEY";
|
|
67
|
+
return {
|
|
68
|
+
name: envName,
|
|
69
|
+
type: "platform",
|
|
70
|
+
monthly_cap_usd: caps[apiName] ?? 5,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
const manifest = {
|
|
74
|
+
version: 1,
|
|
75
|
+
app: {
|
|
76
|
+
name: input.app_name,
|
|
77
|
+
framework,
|
|
78
|
+
port,
|
|
79
|
+
},
|
|
80
|
+
auth: {
|
|
81
|
+
adapter,
|
|
82
|
+
removed_routes: input.removed_routes ?? [],
|
|
83
|
+
},
|
|
84
|
+
database: {
|
|
85
|
+
postgres: analysis.database === "postgres",
|
|
86
|
+
schema_owner: input.schema_owner ?? "app",
|
|
87
|
+
...(input.migrate_command ? { migrate_command: input.migrate_command } : {}),
|
|
88
|
+
},
|
|
89
|
+
storage: {
|
|
90
|
+
s3_bucket: input.s3_bucket ?? false,
|
|
91
|
+
},
|
|
92
|
+
external_apis: externalApis,
|
|
93
|
+
resources: {
|
|
94
|
+
memory_mb: input.memory_mb ?? 512,
|
|
95
|
+
cpus: 1,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const header = [
|
|
99
|
+
"# saastore.yaml — generated by saastore-port",
|
|
100
|
+
"# Safe to edit. The verification pipeline re-validates this on every push.",
|
|
101
|
+
"# Schema: docs/superpowers/specs/2026-05-22-hosted-apps-poc-design.md section 6.3",
|
|
102
|
+
"",
|
|
103
|
+
].join("\n");
|
|
104
|
+
return {
|
|
105
|
+
yaml: header + yamlStringify(manifest),
|
|
106
|
+
manifest,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=generate_manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate_manifest.js","sourceRoot":"","sources":["../src/generate_manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package_image + push_image — thin wrappers around `docker build` /
|
|
3
|
+
* `docker push` so the seller's agent can drive the final two steps of
|
|
4
|
+
* the port pipeline.
|
|
5
|
+
*
|
|
6
|
+
* package_image:
|
|
7
|
+
* - Builds the seller's Dockerfile
|
|
8
|
+
* - Stamps the manifest into an OCI label (org.saastore.manifest) so
|
|
9
|
+
* the verification pipeline can read the contract without a separate
|
|
10
|
+
* file download
|
|
11
|
+
* - Returns the local tag + image digest (sha256:..) when available
|
|
12
|
+
*
|
|
13
|
+
* push_image:
|
|
14
|
+
* - Tags + pushes to the saastore registry
|
|
15
|
+
* - Accepts a saastore-issued OAuth token (not the seller's docker login)
|
|
16
|
+
* - Returns the pushed reference + digest
|
|
17
|
+
*
|
|
18
|
+
* Both wrap child_process.spawn — Docker subprocesses are kept silent
|
|
19
|
+
* unless they fail, in which case the last 500 chars of stderr are
|
|
20
|
+
* surfaced in the result.
|
|
21
|
+
*/
|
|
22
|
+
import { spawn } from "node:child_process";
|
|
23
|
+
import { writeFile, mkdtemp, rm } from "node:fs/promises";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { stringify as yamlStringify } from "yaml";
|
|
27
|
+
function run(cmd, args, opts = {}) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const proc = spawn(cmd, args, { cwd: opts.cwd, shell: false });
|
|
30
|
+
let stdout = "";
|
|
31
|
+
let stderr = "";
|
|
32
|
+
proc.stdout?.on("data", (d) => (stdout += d.toString()));
|
|
33
|
+
proc.stderr?.on("data", (d) => (stderr += d.toString()));
|
|
34
|
+
let timer;
|
|
35
|
+
if (opts.timeout_ms) {
|
|
36
|
+
timer = setTimeout(() => proc.kill("SIGKILL"), opts.timeout_ms);
|
|
37
|
+
}
|
|
38
|
+
proc.on("close", (code) => {
|
|
39
|
+
if (timer)
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
resolve({ code: code ?? -1, stdout, stderr });
|
|
42
|
+
});
|
|
43
|
+
if (opts.stdin) {
|
|
44
|
+
proc.stdin?.write(opts.stdin);
|
|
45
|
+
proc.stdin?.end();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function inspectDigest(dockerBin, ref) {
|
|
50
|
+
const res = await run(dockerBin, ["inspect", "--format", "{{ .Id }}", ref], {
|
|
51
|
+
timeout_ms: 15_000,
|
|
52
|
+
});
|
|
53
|
+
if (res.code !== 0)
|
|
54
|
+
return null;
|
|
55
|
+
const id = res.stdout.trim();
|
|
56
|
+
return id.startsWith("sha256:") ? id : null;
|
|
57
|
+
}
|
|
58
|
+
// ----- package_image -------------------------------------------------------
|
|
59
|
+
export async function packageImage(opts) {
|
|
60
|
+
const dockerBin = opts.docker_bin ?? "docker";
|
|
61
|
+
const context = opts.context ?? process.cwd();
|
|
62
|
+
const dockerfile = opts.dockerfile ?? "Dockerfile";
|
|
63
|
+
// Write the manifest into a workspace file we can COPY/LABEL from a
|
|
64
|
+
// small wrapper Dockerfile. The wrapper FROMs the seller's image and
|
|
65
|
+
// adds a single LABEL — keeps the seller's build process untouched.
|
|
66
|
+
const workspace = await mkdtemp(join(tmpdir(), "saastore-package-"));
|
|
67
|
+
try {
|
|
68
|
+
const manifestYaml = yamlStringify(opts.manifest);
|
|
69
|
+
await writeFile(join(workspace, "saastore.yaml"), manifestYaml, "utf8");
|
|
70
|
+
// First: build the seller's image to a base tag.
|
|
71
|
+
const baseTag = `${opts.tag}-base`;
|
|
72
|
+
const build = await run(dockerBin, ["build", "-f", dockerfile, "-t", baseTag, "."], { cwd: context, timeout_ms: 10 * 60_000 });
|
|
73
|
+
if (build.code !== 0) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
tag: opts.tag,
|
|
77
|
+
digest: null,
|
|
78
|
+
error: `docker build failed: ${build.stderr.slice(-500)}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Second: wrap with manifest LABEL via a small derived Dockerfile.
|
|
82
|
+
// Escape any control characters in the JSON-encoded manifest so docker
|
|
83
|
+
// LABEL parses it as a single value.
|
|
84
|
+
const manifestJsonEscaped = JSON.stringify(opts.manifest).replace(/"/g, '\\"');
|
|
85
|
+
const wrapDockerfile = [
|
|
86
|
+
`FROM ${baseTag}`,
|
|
87
|
+
`LABEL org.saastore.manifest="${manifestJsonEscaped}"`,
|
|
88
|
+
"",
|
|
89
|
+
].join("\n");
|
|
90
|
+
await writeFile(join(workspace, "Dockerfile.wrap"), wrapDockerfile, "utf8");
|
|
91
|
+
const wrap = await run(dockerBin, ["build", "-f", "Dockerfile.wrap", "-t", opts.tag, "."], { cwd: workspace, timeout_ms: 60_000 });
|
|
92
|
+
if (wrap.code !== 0) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
tag: opts.tag,
|
|
96
|
+
digest: null,
|
|
97
|
+
error: `manifest label build failed: ${wrap.stderr.slice(-500)}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const digest = await inspectDigest(dockerBin, opts.tag);
|
|
101
|
+
return { ok: true, tag: opts.tag, digest, error: null };
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await rm(workspace, { recursive: true, force: true }).catch(() => { });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ----- push_image ----------------------------------------------------------
|
|
108
|
+
export async function pushImage(opts) {
|
|
109
|
+
const dockerBin = opts.docker_bin ?? "docker";
|
|
110
|
+
const username = opts.username ?? "x";
|
|
111
|
+
// Resolve the registry host from the remote (e.g. registry.fly.io/foo -> registry.fly.io)
|
|
112
|
+
const registryHost = opts.remote.split("/")[0];
|
|
113
|
+
// 1. docker login. Use --password-stdin to avoid leaking the token via argv
|
|
114
|
+
// (and to handle tokens containing comma / shell-meta chars).
|
|
115
|
+
const login = await run(dockerBin, ["login", registryHost, "--username", username, "--password-stdin"], { timeout_ms: 30_000, stdin: opts.token });
|
|
116
|
+
if (login.code !== 0) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
ref: opts.remote,
|
|
120
|
+
digest: null,
|
|
121
|
+
error: `docker login failed: ${login.stderr.slice(-500)}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// 2. Re-tag locally so docker push sends to the right remote
|
|
125
|
+
const retag = await run(dockerBin, ["tag", opts.tag, opts.remote], {
|
|
126
|
+
timeout_ms: 15_000,
|
|
127
|
+
});
|
|
128
|
+
if (retag.code !== 0) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
ref: opts.remote,
|
|
132
|
+
digest: null,
|
|
133
|
+
error: `docker tag failed: ${retag.stderr.slice(-500)}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// 3. Push.
|
|
137
|
+
const push = await run(dockerBin, ["push", opts.remote], {
|
|
138
|
+
timeout_ms: 10 * 60_000,
|
|
139
|
+
});
|
|
140
|
+
if (push.code !== 0) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
ref: opts.remote,
|
|
144
|
+
digest: null,
|
|
145
|
+
error: `docker push failed: ${push.stderr.slice(-500)}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const digest = await inspectDigest(dockerBin, opts.remote);
|
|
149
|
+
return { ok: true, ref: opts.remote, digest, error: null };
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=package_and_push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package_and_push.js","sourceRoot":"","sources":["../src/package_and_push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* propose_auth_rewire — emit a structured set of changes the seller's agent
|
|
3
|
+
* can review + apply (via apply_auth_rewire — future tool) to wire the
|
|
4
|
+
* saastore SDK adapter into their app and remove the now-dead auth UI.
|
|
5
|
+
*
|
|
6
|
+
* The PoC ships per-framework "rewire kits" — template files + a list of
|
|
7
|
+
* removals — rather than AST-aware diffs. The seller's agent has the
|
|
8
|
+
* authoritative source-code context to fill in any seller-specific glue;
|
|
9
|
+
* we provide the canonical scaffolding that mirrors the SDK adapter's
|
|
10
|
+
* expected wire format.
|
|
11
|
+
*
|
|
12
|
+
* Supported frameworks today: fastapi. Add nextjs, express, django, etc.
|
|
13
|
+
* as we work through the concierge first-seller pipeline.
|
|
14
|
+
*/
|
|
15
|
+
// ----- Per-framework kits --------------------------------------------------
|
|
16
|
+
function fastapiKit() {
|
|
17
|
+
return {
|
|
18
|
+
package: "saastore-fastapi",
|
|
19
|
+
install: "pip install saastore-fastapi",
|
|
20
|
+
fileContents: `"""saastore identity helper.
|
|
21
|
+
|
|
22
|
+
Drop-in replacement for whatever you used to get the current user.
|
|
23
|
+
Wire any route that needs the buyer's identity as:
|
|
24
|
+
|
|
25
|
+
from fastapi import Depends
|
|
26
|
+
from app.saastore_identity import current_user, SaastoreUser
|
|
27
|
+
|
|
28
|
+
@app.get("/dashboard")
|
|
29
|
+
def dashboard(user: SaastoreUser = Depends(current_user)):
|
|
30
|
+
return {"email": user.email}
|
|
31
|
+
|
|
32
|
+
The SDK reads SAASTORE_HMAC_SECRET from env (saastore sets this at
|
|
33
|
+
container provision time) and verifies the signed identity headers.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from saastore_fastapi import SaastoreUser, saastore_user
|
|
37
|
+
|
|
38
|
+
# Re-export so seller code imports from one project-local module.
|
|
39
|
+
__all__ = ["SaastoreUser", "current_user"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def current_user(
|
|
43
|
+
user: SaastoreUser = saastore_user, # type: ignore[assignment]
|
|
44
|
+
) -> SaastoreUser:
|
|
45
|
+
"""FastAPI dependency returning the saastore-verified buyer identity."""
|
|
46
|
+
return user
|
|
47
|
+
`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// ----- Main entry ----------------------------------------------------------
|
|
51
|
+
export function proposeAuthRewire(analysis) {
|
|
52
|
+
const warnings = [];
|
|
53
|
+
const changes = [];
|
|
54
|
+
const framework = analysis.backend_framework ?? analysis.framework ?? "unknown";
|
|
55
|
+
if (framework === "fastapi") {
|
|
56
|
+
const kit = fastapiKit();
|
|
57
|
+
changes.push({
|
|
58
|
+
path: "app/saastore_identity.py",
|
|
59
|
+
intent: "create",
|
|
60
|
+
reason: "Project-local helper that re-exports the saastore-fastapi dependency. Imports anywhere in the seller's code that previously used their own get_current_user dependency should be replaced with this module.",
|
|
61
|
+
contents: kit.fileContents,
|
|
62
|
+
});
|
|
63
|
+
changes.push({
|
|
64
|
+
path: "requirements.txt or pyproject.toml",
|
|
65
|
+
intent: "modify",
|
|
66
|
+
reason: `Add the SDK dependency. After install, ${kit.install}.`,
|
|
67
|
+
hints: [
|
|
68
|
+
`Add 'saastore-fastapi==0.1.0' to your dependency list.`,
|
|
69
|
+
"Pin to a specific version — the wire contract may evolve before V1.",
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
changes.push({
|
|
73
|
+
path: "<any file with auth route handlers>",
|
|
74
|
+
intent: "delete",
|
|
75
|
+
reason: "Saastore handles authentication. Routes like /login, /signup, /logout, /forgot-password must be removed; the seller's app never sees them. Run `validate_compliance` after editing to confirm none remain.",
|
|
76
|
+
hints: [
|
|
77
|
+
"Search for @app.post('/login'), @app.get('/auth/...'), router.include_router(auth.router), etc.",
|
|
78
|
+
"If your login endpoint set cookies/sessions, you can delete the session middleware too — saastore signs identity per-request.",
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
changes.push({
|
|
82
|
+
path: "<your dependency call sites>",
|
|
83
|
+
intent: "modify",
|
|
84
|
+
reason: `Replace your existing 'Depends(get_current_user)' (or equivalent) with 'Depends(current_user)' imported from app.saastore_identity. The user object is now a SaastoreUser dataclass — adapt downstream code as needed.`,
|
|
85
|
+
hints: [
|
|
86
|
+
"Grep for 'Depends(' in your routers; the auth dependency is usually the only one that needs to change.",
|
|
87
|
+
"If your routes accessed user.id or user.email — those fields exist on SaastoreUser as user.user_id and user.email respectively.",
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
framework: "fastapi",
|
|
92
|
+
adapter_npm_or_pypi_package: kit.package,
|
|
93
|
+
install_command: kit.install,
|
|
94
|
+
changes,
|
|
95
|
+
warnings,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Unsupported framework — emit a generic stub + warning so the seller's
|
|
99
|
+
// agent at least knows what's needed even without a kit.
|
|
100
|
+
warnings.push(`framework '${framework}' has no rewire kit yet. The PoC ships kits as the concierge first-seller pipeline surfaces them. Manual port:\n` +
|
|
101
|
+
` 1. install a saastore SDK adapter for your framework (see spec section 5.3 for the matrix)\n` +
|
|
102
|
+
` 2. configure it to trust saastore as an external identity source\n` +
|
|
103
|
+
` 3. remove all auth UI / route handlers (saastore handles login)\n` +
|
|
104
|
+
` 4. run validate_compliance to confirm.`);
|
|
105
|
+
return {
|
|
106
|
+
framework,
|
|
107
|
+
adapter_npm_or_pypi_package: "(none yet)",
|
|
108
|
+
install_command: "(see saastore docs for your framework)",
|
|
109
|
+
changes: [],
|
|
110
|
+
warnings,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=propose_auth_rewire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propose_auth_rewire.js","sourceRoot":"","sources":["../src/propose_auth_rewire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA4BH,8EAA8E;AAE9E,SAAS,UAAU;IACjB,OAAO;QACL,OAAO,EAAE,kBAAkB;QAC3B,OAAO,EAAE,8BAA8B;QACvC,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BjB;KACE,CAAA;AACH,CAAC;AAED,8EAA8E;AAE9E,MAAM,UAAU,iBAAiB,CAAC,QAAsB;IACtD,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,MAAM,OAAO,GAAqB,EAAE,CAAA;IAEpC,MAAM,SAAS,GACb,QAAQ,CAAC,iBAAiB,IAAI,QAAQ,CAAC,SAAS,IAAI,SAAS,CAAA;IAE/D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;QACxB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,0BAA0B;YAChC,MAAM,EAAE,QAAQ;YAChB,MAAM,EACJ,6MAA6M;YAC/M,QAAQ,EAAE,GAAG,CAAC,YAAY;SAC3B,CAAC,CAAA;QACF,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,oCAAoC;YAC1C,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,0CAA0C,GAAG,CAAC,OAAO,GAAG;YAChE,KAAK,EAAE;gBACL,wDAAwD;gBACxD,qEAAqE;aACtE;SACF,CAAC,CAAA;QACF,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,qCAAqC;YAC3C,MAAM,EAAE,QAAQ;YAChB,MAAM,EACJ,4MAA4M;YAC9M,KAAK,EAAE;gBACL,iGAAiG;gBACjG,+HAA+H;aAChI;SACF,CAAC,CAAA;QACF,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,8BAA8B;YACpC,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,wNAAwN;YAChO,KAAK,EAAE;gBACL,wGAAwG;gBACxG,iIAAiI;aAClI;SACF,CAAC,CAAA;QACF,OAAO;YACL,SAAS,EAAE,SAAS;YACpB,2BAA2B,EAAE,GAAG,CAAC,OAAO;YACxC,eAAe,EAAE,GAAG,CAAC,OAAO;YAC5B,OAAO;YACP,QAAQ;SACT,CAAA;IACH,CAAC;IAED,wEAAwE;IACxE,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CACX,cAAc,SAAS,kHAAkH;QACvI,+FAA+F;QAC/F,qEAAqE;QACrE,oEAAoE;QACpE,yCAAyC,CAC5C,CAAA;IACD,OAAO;QACL,SAAS;QACT,2BAA2B,EAAE,YAAY;QACzC,eAAe,EAAE,wCAAwC;QACzD,OAAO,EAAE,EAAE;QACX,QAAQ;KACT,CAAA;AACH,CAAC"}
|