@decohere/scaffold 0.0.1-dev.18 → 0.0.1-dev.81
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/commonjs/src/add-api.action.js +39 -0
- package/commonjs/src/add-api.command.js +41 -0
- package/commonjs/src/add.command.js +24 -0
- package/commonjs/src/add.module.js +26 -0
- package/commonjs/src/cli.js +9 -0
- package/commonjs/src/create.command.js +24 -0
- package/commonjs/src/create.module.js +26 -0
- package/commonjs/src/domain/prompt.js +64 -0
- package/commonjs/src/index.js +38 -0
- package/commonjs/src/models/compose.model.js +150 -0
- package/commonjs/src/models/config-yaml.model.js +88 -0
- package/commonjs/src/models/const.js +32 -0
- package/commonjs/src/models/dev-container.model.js +116 -0
- package/commonjs/src/models/docker-image.model.js +259 -0
- package/commonjs/src/models/env-file.model.js +50 -0
- package/commonjs/src/models/gitignore.model.js +43 -0
- package/commonjs/src/models/launch-json.model.js +74 -0
- package/commonjs/src/models/package-json.model.js +149 -0
- package/commonjs/src/models/project.model.js +134 -0
- package/commonjs/src/models/source-file.js +6 -0
- package/commonjs/src/models/tsconfig.model.js +34 -0
- package/commonjs/src/models/vitest-config.model.js +35 -0
- package/commonjs/src/models/vscode-settings.model.js +31 -0
- package/commonjs/src/program.js +57 -0
- package/commonjs/src/resolve-target-dir.js +19 -0
- package/commonjs/src/services/scaffold.service.js +62 -0
- package/commonjs/src/version.js +57 -0
- package/commonjs/src/webapi/api-name.js +95 -0
- package/commonjs/src/webapi/cli-options.js +101 -0
- package/commonjs/src/webapi/const.js +50 -0
- package/commonjs/src/webapi/create-api.js +237 -0
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +166 -0
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/commonjs/src/webapi/devcontainer/host-scripts.js +52 -0
- package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
- package/commonjs/src/webapi/devcontainer/templates/initialize.sh +169 -0
- package/commonjs/src/webapi/devcontainer/templates/post-create.sh +35 -0
- package/commonjs/src/webapi/devcontainer/templates/post-start.sh +65 -0
- package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
- package/commonjs/src/webapi/features/auth.feature.js +441 -0
- package/commonjs/src/webapi/features/auth0-cli.js +435 -0
- package/commonjs/src/webapi/features/biome-format.js +49 -0
- package/commonjs/src/webapi/features/database.feature.js +433 -0
- package/commonjs/src/webapi/features/finalize.feature.js +95 -0
- package/commonjs/src/webapi/features/postman.feature.js +324 -0
- package/commonjs/src/webapi/models/api-package-json.model.js +60 -0
- package/commonjs/src/webapi/models/auth-config.model.js +26 -0
- package/commonjs/src/webapi/models/controller-source-file.model.js +193 -0
- package/commonjs/src/webapi/models/github-ci.model.js +193 -0
- package/commonjs/src/webapi/models/main-source-file.model.js +167 -0
- package/commonjs/src/webapi/models/production-dockerfile.model.js +157 -0
- package/commonjs/src/webapi/models/sample-files.model.js +60 -0
- package/commonjs/src/webapi/models/webapi-image.model.js +27 -0
- package/commonjs/src/webapi/postman/hello-openapi.js +175 -0
- package/commonjs/src/webapi/postman/local-view.js +184 -0
- package/commonjs/src/webapi/postman/openapi-to-postman.js +202 -0
- package/commonjs/src/webapi/workspace-readme.js +110 -0
- package/commonjs/src/webapi.action.js +30 -0
- package/commonjs/src/webapi.command.js +45 -0
- package/es6/src/add-api.action.js +29 -0
- package/es6/src/add-api.command.js +31 -0
- package/es6/src/add.command.js +14 -0
- package/es6/src/add.module.js +16 -0
- package/es6/src/cli.js +5 -0
- package/es6/src/create.command.js +14 -0
- package/es6/src/create.module.js +16 -0
- package/es6/src/domain/prompt.js +43 -0
- package/es6/src/index.js +8 -0
- package/es6/src/models/compose.model.js +144 -0
- package/es6/src/models/config-yaml.model.js +76 -0
- package/es6/src/models/const.js +22 -0
- package/es6/src/models/dev-container.model.js +106 -0
- package/es6/src/models/docker-image.model.js +217 -0
- package/es6/src/models/env-file.model.js +46 -0
- package/es6/src/models/gitignore.model.js +33 -0
- package/es6/src/models/launch-json.model.js +64 -0
- package/es6/src/models/package-json.model.js +137 -0
- package/es6/src/models/project.model.js +119 -0
- package/es6/src/models/source-file.js +3 -0
- package/es6/src/models/tsconfig.model.js +24 -0
- package/es6/src/models/vitest-config.model.js +25 -0
- package/es6/src/models/vscode-settings.model.js +21 -0
- package/es6/src/program.js +39 -0
- package/es6/src/resolve-target-dir.js +9 -0
- package/es6/src/services/scaffold.service.js +52 -0
- package/es6/src/version.js +36 -0
- package/es6/src/webapi/api-name.js +59 -0
- package/es6/src/webapi/cli-options.js +36 -0
- package/es6/src/webapi/const.js +20 -0
- package/es6/src/webapi/create-api.js +217 -0
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +156 -0
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/es6/src/webapi/devcontainer/host-scripts.js +31 -0
- package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
- package/es6/src/webapi/devcontainer/templates/initialize.sh +169 -0
- package/es6/src/webapi/devcontainer/templates/post-create.sh +35 -0
- package/es6/src/webapi/devcontainer/templates/post-start.sh +65 -0
- package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
- package/es6/src/webapi/features/auth.feature.js +390 -0
- package/es6/src/webapi/features/auth0-cli.js +375 -0
- package/es6/src/webapi/features/biome-format.js +42 -0
- package/es6/src/webapi/features/database.feature.js +423 -0
- package/es6/src/webapi/features/finalize.feature.js +85 -0
- package/es6/src/webapi/features/postman.feature.js +258 -0
- package/es6/src/webapi/models/api-package-json.model.js +55 -0
- package/es6/src/webapi/models/auth-config.model.js +22 -0
- package/es6/src/webapi/models/controller-source-file.model.js +184 -0
- package/es6/src/webapi/models/github-ci.model.js +186 -0
- package/es6/src/webapi/models/main-source-file.model.js +163 -0
- package/es6/src/webapi/models/production-dockerfile.model.js +148 -0
- package/es6/src/webapi/models/sample-files.model.js +42 -0
- package/es6/src/webapi/models/webapi-image.model.js +20 -0
- package/es6/src/webapi/postman/hello-openapi.js +168 -0
- package/es6/src/webapi/postman/local-view.js +169 -0
- package/es6/src/webapi/postman/openapi-to-postman.js +179 -0
- package/es6/src/webapi/workspace-readme.js +89 -0
- package/es6/src/webapi.action.js +20 -0
- package/es6/src/webapi.command.js +35 -0
- package/package.json +8 -8
- package/types/src/add-api.action.d.ts +13 -0
- package/types/src/add-api.command.d.ts +7 -0
- package/types/src/add.command.d.ts +6 -0
- package/types/src/add.module.d.ts +14 -0
- package/types/src/cli.d.ts +2 -0
- package/types/src/create.command.d.ts +6 -0
- package/types/src/create.module.d.ts +14 -0
- package/types/src/domain/prompt.d.ts +4 -0
- package/types/src/models/compose.model.d.ts +64 -0
- package/types/src/models/config-yaml.model.d.ts +27 -0
- package/types/src/models/const.d.ts +20 -0
- package/types/src/models/dev-container.model.d.ts +37 -0
- package/types/src/models/docker-image.model.d.ts +84 -0
- package/types/src/models/env-file.model.d.ts +16 -0
- package/types/src/models/gitignore.model.d.ts +7 -0
- package/types/src/models/launch-json.model.d.ts +7 -0
- package/types/src/models/package-json.model.d.ts +35 -0
- package/types/src/models/project.model.d.ts +60 -0
- package/{src/models/source-file.ts → types/src/models/source-file.d.ts} +4 -6
- package/types/src/models/tsconfig.model.d.ts +5 -0
- package/types/src/models/vitest-config.model.d.ts +5 -0
- package/types/src/models/vscode-settings.model.d.ts +7 -0
- package/types/src/program.d.ts +3 -0
- package/{src/resolve-target-dir.ts → types/src/resolve-target-dir.d.ts} +1 -4
- package/types/src/services/scaffold.service.d.ts +22 -0
- package/types/src/version.d.ts +6 -0
- package/types/src/webapi/api-name.d.ts +14 -0
- package/types/src/webapi/cli-options.d.ts +6 -0
- package/{src/webapi/const.ts → types/src/webapi/const.d.ts} +7 -21
- package/types/src/webapi/create-api.d.ts +15 -0
- package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +32 -0
- package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/types/src/webapi/devcontainer/host-scripts.d.ts +7 -0
- package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
- package/types/src/webapi/devcontainer/templates/initialize.sh +169 -0
- package/types/src/webapi/devcontainer/templates/post-create.sh +35 -0
- package/types/src/webapi/devcontainer/templates/post-start.sh +65 -0
- package/types/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
- package/types/src/webapi/features/auth.feature.d.ts +10 -0
- package/types/src/webapi/features/auth0-cli.d.ts +80 -0
- package/types/src/webapi/features/biome-format.d.ts +5 -0
- package/types/src/webapi/features/database.feature.d.ts +7 -0
- package/types/src/webapi/features/finalize.feature.d.ts +2 -0
- package/types/src/webapi/features/postman.feature.d.ts +16 -0
- package/types/src/webapi/models/api-package-json.model.d.ts +18 -0
- package/types/src/webapi/models/auth-config.model.d.ts +26 -0
- package/types/src/webapi/models/controller-source-file.model.d.ts +46 -0
- package/types/src/webapi/models/github-ci.model.d.ts +9 -0
- package/types/src/webapi/models/main-source-file.model.d.ts +54 -0
- package/types/src/webapi/models/production-dockerfile.model.d.ts +23 -0
- package/types/src/webapi/models/sample-files.model.d.ts +7 -0
- package/types/src/webapi/models/webapi-image.model.d.ts +12 -0
- package/types/src/webapi/postman/hello-openapi.d.ts +19 -0
- package/types/src/webapi/postman/local-view.d.ts +17 -0
- package/types/src/webapi/postman/openapi-to-postman.d.ts +62 -0
- package/types/src/webapi/workspace-readme.d.ts +18 -0
- package/types/src/webapi.action.d.ts +14 -0
- package/types/src/webapi.command.d.ts +11 -0
- package/src/add-api.action.ts +0 -25
- package/src/add-api.command.ts +0 -31
- package/src/add.command.ts +0 -20
- package/src/add.module.ts +0 -11
- package/src/cli.ts +0 -4
- package/src/create.command.ts +0 -20
- package/src/create.module.ts +0 -11
- package/src/domain/prompt.ts +0 -50
- package/src/models/compose.model.ts +0 -205
- package/src/models/config-yaml.model.ts +0 -83
- package/src/models/const.ts +0 -20
- package/src/models/dev-container.model.ts +0 -136
- package/src/models/docker-image.model.ts +0 -343
- package/src/models/env-file.model.ts +0 -45
- package/src/models/gitignore.model.ts +0 -35
- package/src/models/launch-json.model.ts +0 -60
- package/src/models/package-json.model.ts +0 -169
- package/src/models/project.model.ts +0 -141
- package/src/models/tsconfig.model.ts +0 -29
- package/src/models/vitest-config.model.ts +0 -26
- package/src/models/vscode-settings.model.ts +0 -24
- package/src/program.ts +0 -63
- package/src/services/scaffold.service.ts +0 -89
- package/src/version.ts +0 -56
- package/src/webapi/api-name.ts +0 -73
- package/src/webapi/cli-options.ts +0 -56
- package/src/webapi/create-api.ts +0 -275
- package/src/webapi/devcontainer/host-scripts.ts +0 -35
- package/src/webapi/features/auth.feature.ts +0 -454
- package/src/webapi/features/auth0-cli.ts +0 -421
- package/src/webapi/features/biome-format.ts +0 -34
- package/src/webapi/features/database.feature.ts +0 -522
- package/src/webapi/features/finalize.feature.ts +0 -112
- package/src/webapi/features/postman.feature.ts +0 -287
- package/src/webapi/models/api-package-json.model.ts +0 -60
- package/src/webapi/models/auth-config.model.ts +0 -36
- package/src/webapi/models/controller-source-file.model.ts +0 -232
- package/src/webapi/models/github-ci.model.ts +0 -188
- package/src/webapi/models/main-source-file.model.ts +0 -208
- package/src/webapi/models/production-dockerfile.model.ts +0 -162
- package/src/webapi/models/sample-files.model.ts +0 -45
- package/src/webapi/models/webapi-image.model.ts +0 -21
- package/src/webapi/postman/hello-openapi.ts +0 -92
- package/src/webapi/postman/local-view.ts +0 -195
- package/src/webapi/postman/openapi-to-postman.ts +0 -208
- package/src/webapi/workspace-readme.ts +0 -192
- package/src/webapi.action.ts +0 -22
- package/src/webapi.command.ts +0 -41
- package/tests/auth-postman.wiring.spec.ts +0 -263
- package/tests/auth0-cli.spec.ts +0 -232
- package/tests/biome-format.spec.ts +0 -49
- package/tests/cli.integration.spec.ts +0 -658
- package/tests/compose.model.spec.ts +0 -51
- package/tests/container-image.model.spec.ts +0 -162
- package/tests/dev-container.model.spec.ts +0 -29
- package/tests/devcontainer-base-image.spec.ts +0 -32
- package/tests/hello-di-inject.scaffold.spec.ts +0 -18
- package/tests/hello-route.contract.spec.ts +0 -94
- package/tests/package-json.order.spec.ts +0 -59
- package/tests/postman-from-openapi.spec.ts +0 -197
- package/tests/seed-devcontainer-templates.ts +0 -40
- package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +0 -96
- /package/{src/index.ts → types/src/index.d.ts} +0 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "DatabaseFeature", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return DatabaseFeature;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _nodecrypto = require("node:crypto");
|
|
12
|
+
const _nodefs = require("node:fs");
|
|
13
|
+
const _nodepath = require("node:path");
|
|
14
|
+
const _prompt = require("../../domain/prompt.js");
|
|
15
|
+
const _const = require("../../models/const.js");
|
|
16
|
+
const _dockerimagemodel = require("../../models/docker-image.model.js");
|
|
17
|
+
const _version = require("../../version.js");
|
|
18
|
+
const _apiname = require("../api-name.js");
|
|
19
|
+
/** Bind path relative to `.devcontainer/docker-compose.yml` (dies with the working tree). */ const POSTGRES_DATA_BIND = "./postgres-data:/var/lib/postgresql/data";
|
|
20
|
+
const POSTGRES_DATA_GITIGNORE = ".devcontainer/postgres-data";
|
|
21
|
+
const DB_RESET_SCRIPT = "rm -rf .devcontainer/postgres-data && docker compose -f .devcontainer/docker-compose.yml down && docker compose -f .devcontainer/docker-compose.yml up -d postgres";
|
|
22
|
+
/** Read a single KEY= from an existing target `.env` (if any). */ function readExistingEnvValue(workspace, key) {
|
|
23
|
+
try {
|
|
24
|
+
const raw = (0, _nodefs.readFileSync)((0, _nodepath.join)(workspace.targetDir, ".env"), "utf8");
|
|
25
|
+
const line = raw.split("\n").find((l)=>l.startsWith(`${key}=`));
|
|
26
|
+
const value = line?.slice(key.length + 1).trim();
|
|
27
|
+
return value || undefined;
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function randomPassword() {
|
|
33
|
+
return (0, _nodecrypto.randomBytes)(16).toString("hex");
|
|
34
|
+
}
|
|
35
|
+
/** Safe Postgres identifier fragment from the project name. */ function toSqlIdentFragment(name) {
|
|
36
|
+
const cleaned = name.replace(/[^A-Za-z0-9_]/g, "_").replace(/^([^A-Za-z_])/, "p_$1");
|
|
37
|
+
return cleaned.length > 0 ? cleaned.slice(0, 40) : "app";
|
|
38
|
+
}
|
|
39
|
+
function passwordOrExisting(workspace, key, fallbackExistingKeys = []) {
|
|
40
|
+
const existing = readExistingEnvValue(workspace, key);
|
|
41
|
+
if (existing) return existing;
|
|
42
|
+
for (const alt of fallbackExistingKeys){
|
|
43
|
+
const v = readExistingEnvValue(workspace, alt);
|
|
44
|
+
if (v) return v;
|
|
45
|
+
}
|
|
46
|
+
return randomPassword();
|
|
47
|
+
}
|
|
48
|
+
// Production: runtime app role only — no bootstrap, no admin/migrate secrets required.
|
|
49
|
+
function renderBaseDataSource(apiName) {
|
|
50
|
+
const p = (0, _apiname.apiEnvPrefix)(apiName);
|
|
51
|
+
return [
|
|
52
|
+
` - name: ${apiName}`,
|
|
53
|
+
` provider: postgres`,
|
|
54
|
+
` host: \${${p}_DB_HOST}`,
|
|
55
|
+
` port: \${${p}_DB_PORT}`,
|
|
56
|
+
` database: \${${p}_DB_NAME}`,
|
|
57
|
+
` username: \${${p}_DB_USER}`,
|
|
58
|
+
` password: \${${p}_DB_PASSWORD}`,
|
|
59
|
+
` synchronize: false`,
|
|
60
|
+
` seed: false`,
|
|
61
|
+
` bootstrap: false`
|
|
62
|
+
].join("\n");
|
|
63
|
+
}
|
|
64
|
+
// Dev overlay (merged by list index onto config.yaml). Enable bootstrap + migrate role.
|
|
65
|
+
function renderDevDataSource(apiName) {
|
|
66
|
+
const p = (0, _apiname.apiEnvPrefix)(apiName);
|
|
67
|
+
return [
|
|
68
|
+
` - name: ${apiName}`,
|
|
69
|
+
` synchronize: true`,
|
|
70
|
+
` seed: true`,
|
|
71
|
+
` bootstrap: true`,
|
|
72
|
+
` adminUser: \${DB_ADMIN_USER}`,
|
|
73
|
+
` adminPassword: \${DB_ADMIN_PASSWORD}`,
|
|
74
|
+
` migrateUsername: \${${p}_DB_MIGRATE_USER}`,
|
|
75
|
+
` migratePassword: \${${p}_DB_MIGRATE_PASSWORD}`
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
78
|
+
function renderDatabaseSection(apiNames, items) {
|
|
79
|
+
return `database:\n dataSources:\n${apiNames.map(items).join("\n")}\n`;
|
|
80
|
+
}
|
|
81
|
+
/** Create-or-sync bootstrap role; runs on every Postgres container start (not only empty volume). */ const POSTGRES_ENSURE_BOOTSTRAP_SH = `#!/bin/bash
|
|
82
|
+
set -euo pipefail
|
|
83
|
+
# Superuser aligns decohere_bootstrap with DB_ADMIN_* from env_file on every start.
|
|
84
|
+
# Initdb.d alone is insufficient: passwords in .env can change while the volume persists.
|
|
85
|
+
: "\${POSTGRES_USER:?POSTGRES_USER is required}"
|
|
86
|
+
: "\${DB_ADMIN_USER:?DB_ADMIN_USER is required}"
|
|
87
|
+
: "\${DB_ADMIN_PASSWORD:?DB_ADMIN_PASSWORD is required}"
|
|
88
|
+
|
|
89
|
+
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres \\
|
|
90
|
+
\t--set=bootstrap_user="$DB_ADMIN_USER" \\
|
|
91
|
+
\t--set=bootstrap_password="$DB_ADMIN_PASSWORD" <<'EOSQL'
|
|
92
|
+
SELECT format(
|
|
93
|
+
\t'CREATE ROLE %I LOGIN PASSWORD %L CREATEDB CREATEROLE NOSUPERUSER NOBYPASSRLS',
|
|
94
|
+
\t:'bootstrap_user',
|
|
95
|
+
\t:'bootstrap_password'
|
|
96
|
+
)
|
|
97
|
+
WHERE NOT EXISTS (
|
|
98
|
+
\tSELECT 1 FROM pg_catalog.pg_roles WHERE rolname = :'bootstrap_user'
|
|
99
|
+
)\\gexec
|
|
100
|
+
|
|
101
|
+
SELECT format(
|
|
102
|
+
\t'ALTER ROLE %I WITH LOGIN PASSWORD %L CREATEDB CREATEROLE NOSUPERUSER NOBYPASSRLS',
|
|
103
|
+
\t:'bootstrap_user',
|
|
104
|
+
\t:'bootstrap_password'
|
|
105
|
+
)\\gexec
|
|
106
|
+
|
|
107
|
+
GRANT CONNECT ON DATABASE postgres TO :"bootstrap_user";
|
|
108
|
+
EOSQL
|
|
109
|
+
`;
|
|
110
|
+
const POSTGRES_ENTRYPOINT_SH = `#!/bin/bash
|
|
111
|
+
set -euo pipefail
|
|
112
|
+
(
|
|
113
|
+
\tfor _ in $(seq 1 60); do
|
|
114
|
+
\t\tif pg_isready -U "\${POSTGRES_USER:-postgres}" >/dev/null 2>&1; then
|
|
115
|
+
\t\t\t/usr/local/bin/decohere-ensure-bootstrap.sh
|
|
116
|
+
\t\t\texit 0
|
|
117
|
+
\t\tfi
|
|
118
|
+
\t\tsleep 0.5
|
|
119
|
+
\tdone
|
|
120
|
+
\techo "decohere: timed out waiting for Postgres before ensuring bootstrap role" >&2
|
|
121
|
+
\texit 1
|
|
122
|
+
) &
|
|
123
|
+
exec docker-entrypoint.sh "$@"
|
|
124
|
+
`;
|
|
125
|
+
const POSTGRES_IMAGE_DOCKERFILE = `FROM postgres:16
|
|
126
|
+
COPY ensure-bootstrap-role.sh /usr/local/bin/decohere-ensure-bootstrap.sh
|
|
127
|
+
COPY entrypoint.sh /usr/local/bin/decohere-postgres-entrypoint.sh
|
|
128
|
+
RUN chmod +x /usr/local/bin/decohere-ensure-bootstrap.sh /usr/local/bin/decohere-postgres-entrypoint.sh
|
|
129
|
+
ENTRYPOINT ["/usr/local/bin/decohere-postgres-entrypoint.sh"]
|
|
130
|
+
CMD ["postgres"]
|
|
131
|
+
`;
|
|
132
|
+
const SAMPLE_ENTITY_TS = `import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|
133
|
+
import type { Entity as IEntity } from "@decohere/codex";
|
|
134
|
+
|
|
135
|
+
@Entity()
|
|
136
|
+
export class SampleEntity implements IEntity {
|
|
137
|
+
\t@PrimaryGeneratedColumn()
|
|
138
|
+
\tpublic id!: number;
|
|
139
|
+
|
|
140
|
+
\t@Column()
|
|
141
|
+
\tpublic name!: string;
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
const SAMPLE_REPOSITORY_TS = `import { injectable } from "@decohere/crucible/decorators";
|
|
145
|
+
import { injectUnitOfWork } from "@decohere/codex";
|
|
146
|
+
import { TypeOrmRepository, TypeOrmUnitOfWork } from "@decohere/codex-typeorm";
|
|
147
|
+
import { SampleEntity } from "../entities/sample.entity.js";
|
|
148
|
+
|
|
149
|
+
@injectable()
|
|
150
|
+
export class SampleEntityRepository extends TypeOrmRepository<SampleEntity> {
|
|
151
|
+
\tpublic constructor(@injectUnitOfWork() unitOfWork: TypeOrmUnitOfWork) {
|
|
152
|
+
\t\tsuper(unitOfWork, SampleEntity);
|
|
153
|
+
\t}
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
/** TypeORM CLI DataSource — connects as the migrate role; never enables synchronize. */ function renderTypeOrmDataSource(apiName) {
|
|
157
|
+
const p = (0, _apiname.apiEnvPrefix)(apiName);
|
|
158
|
+
return `import { dirname, join } from "node:path";
|
|
159
|
+
import { fileURLToPath } from "node:url";
|
|
160
|
+
import { DataSource } from "typeorm";
|
|
161
|
+
|
|
162
|
+
const root = dirname(fileURLToPath(import.meta.url));
|
|
163
|
+
const required = (key: string): string => {
|
|
164
|
+
\tconst value = process.env[key];
|
|
165
|
+
\tif (value == null || value === "") {
|
|
166
|
+
\t\tthrow new Error(\`Missing env \${key} (load .env via --env-file or export it)\`);
|
|
167
|
+
\t}
|
|
168
|
+
\treturn value;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* CLI-only DataSource for \`pnpm db:migrate\` / \`pnpm db:migrate:generate\`.
|
|
173
|
+
* Uses the migrate role (DDL). Runtime API pods use the app role with synchronize: false.
|
|
174
|
+
*/
|
|
175
|
+
export default new DataSource({
|
|
176
|
+
\ttype: "postgres",
|
|
177
|
+
\thost: required("${p}_DB_HOST"),
|
|
178
|
+
\tport: Number(required("${p}_DB_PORT")),
|
|
179
|
+
\tdatabase: required("${p}_DB_NAME"),
|
|
180
|
+
\tusername: required("${p}_DB_MIGRATE_USER"),
|
|
181
|
+
\tpassword: required("${p}_DB_MIGRATE_PASSWORD"),
|
|
182
|
+
\tsynchronize: false,
|
|
183
|
+
\tentities: [join(root, "entities", "*.{ts,js}")],
|
|
184
|
+
\tmigrations: [join(root, "migrations", "*.{ts,js}")],
|
|
185
|
+
});
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
const DATABASE_DOCS_MD = `# Database (Postgres / TypeORM)
|
|
189
|
+
|
|
190
|
+
## Roles
|
|
191
|
+
|
|
192
|
+
| Role | Env | Purpose |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| App | \`{PREFIX}_DB_USER\` | Runtime API (DML only in production) |
|
|
195
|
+
| Migrate | \`{PREFIX}_DB_MIGRATE_USER\` | Schema changes / TypeORM migrations (CI/deploy) |
|
|
196
|
+
| Bootstrap | \`DB_ADMIN_USER\` | Create DB + roles when \`bootstrap: true\` (dev) |
|
|
197
|
+
|
|
198
|
+
\`{PREFIX}\` is the API slug in SCREAMING_SNAKE (\`api\` → \`API\`, \`billing\` → \`BILLING\`).
|
|
199
|
+
|
|
200
|
+
## Development (fast iteration)
|
|
201
|
+
|
|
202
|
+
\`config/config.dev.yaml\` sets \`synchronize: true\` and \`seed: true\` so the API can create tables on startup. You can keep using sync while prototyping.
|
|
203
|
+
|
|
204
|
+
Alternatively, turn sync off in the overlay and use migrations the same way as production:
|
|
205
|
+
|
|
206
|
+
\`\`\`bash
|
|
207
|
+
pnpm db:up
|
|
208
|
+
pnpm db:migrate
|
|
209
|
+
pnpm db:migrate:generate -- ./projects/api/src/migrations/AddSomething
|
|
210
|
+
\`\`\`
|
|
211
|
+
|
|
212
|
+
Do **not** mix sync and migrations for the same schema change (pick one path per change).
|
|
213
|
+
|
|
214
|
+
## Production (migrations only)
|
|
215
|
+
|
|
216
|
+
\`config/config.yaml\` keeps \`synchronize: false\` / \`seed: false\` / \`bootstrap: false\`. Deploy steps:
|
|
217
|
+
|
|
218
|
+
1. Ensure the app database and roles already exist (provisioner / ops — not the API pod).
|
|
219
|
+
2. Run migrations with the migrate role: \`pnpm db:migrate\` (or \`pnpm db:migrate:<api>\` for multi-API).
|
|
220
|
+
3. Start the API with the app role only — no migrate/admin secrets on the pod.
|
|
221
|
+
|
|
222
|
+
## Scripts
|
|
223
|
+
|
|
224
|
+
| Script | What it does |
|
|
225
|
+
|---|---|
|
|
226
|
+
| \`db:up\` / \`db:down\` / \`db:reset\` | Local Compose Postgres |
|
|
227
|
+
| \`db:migrate\` | Run pending migrations (\`projects/api\` by default) |
|
|
228
|
+
| \`db:migrate:generate\` | Generate a migration from entity drift (pass path after \`--\`) |
|
|
229
|
+
| \`db:migrate:<api>\` | Same for a named API under \`projects/<api>\` |
|
|
230
|
+
|
|
231
|
+
CLI entry: \`projects/<api>/src/typeorm.data-source.ts\` (migrate credentials, \`synchronize: false\`).
|
|
232
|
+
|
|
233
|
+
## Browse the database
|
|
234
|
+
|
|
235
|
+
Prefer the Dev Container **SQLTools** extension (Postgres driver) or any desktop client against \`127.0.0.1:5433\` (see \`.env\` for credentials).
|
|
236
|
+
|
|
237
|
+
Adminer is **not** scaffolded. If you want a browser UI in Compose, add this under \`services:\` in \`.devcontainer/docker-compose.yml\` (profile \`tools\` so it stays off by default):
|
|
238
|
+
|
|
239
|
+
\`\`\`yaml
|
|
240
|
+
adminer:
|
|
241
|
+
image: adminer
|
|
242
|
+
profiles:
|
|
243
|
+
- tools
|
|
244
|
+
ports:
|
|
245
|
+
- "127.0.0.1:8081:8080"
|
|
246
|
+
networks:
|
|
247
|
+
- <compose-network-key> # same key as postgres / devcontainer
|
|
248
|
+
\`\`\`
|
|
249
|
+
|
|
250
|
+
Then: \`docker compose -f .devcontainer/docker-compose.yml --profile tools up -d adminer\`
|
|
251
|
+
`;
|
|
252
|
+
function typeormCli(args) {
|
|
253
|
+
return `node --import tsx --env-file=.env ./node_modules/typeorm/cli.js ${args}`;
|
|
254
|
+
}
|
|
255
|
+
function migrateRunScript(apiName) {
|
|
256
|
+
return typeormCli(`migration:run -d ./projects/${apiName}/src/typeorm.data-source.ts`);
|
|
257
|
+
}
|
|
258
|
+
function migrateGenerateScript(apiName) {
|
|
259
|
+
return typeormCli(`migration:generate --esm -d ./projects/${apiName}/src/typeorm.data-source.ts`);
|
|
260
|
+
}
|
|
261
|
+
async function getOptions(_workspace, cliOptions) {
|
|
262
|
+
if (cliOptions.databaseProvider != null) return {};
|
|
263
|
+
const choices = [
|
|
264
|
+
{
|
|
265
|
+
label: "none"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
label: "postgres/typeorm"
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
const selected = await (0, _prompt.pick)("\n Database: ", choices, (c)=>c.label);
|
|
272
|
+
return {
|
|
273
|
+
databaseProvider: selected?.label ?? "none"
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function scaffold(workspace, options) {
|
|
277
|
+
if (options.databaseProvider !== "postgres/typeorm") return;
|
|
278
|
+
const name = workspace.name;
|
|
279
|
+
const ident = toSqlIdentFragment(name);
|
|
280
|
+
const apiProjects = workspace.getProjects();
|
|
281
|
+
const apiNames = apiProjects.map((project)=>(0, _apiname.apiNameFromProjectDir)(project.dir));
|
|
282
|
+
// Container init superuser — never reused as an API runtime role.
|
|
283
|
+
const postgresSuperuser = "postgres";
|
|
284
|
+
const postgresPassword = passwordOrExisting(workspace, "POSTGRES_PASSWORD");
|
|
285
|
+
const bootstrapUser = "decohere_bootstrap";
|
|
286
|
+
const bootstrapPassword = passwordOrExisting(workspace, "DB_ADMIN_PASSWORD", [
|
|
287
|
+
"POSTGRES_PASSWORD"
|
|
288
|
+
]);
|
|
289
|
+
const devContainer = workspace.getModel(_const.WELL_KNOWN_FILES.DEVCONTAINER_JSON);
|
|
290
|
+
const compose = workspace.getModel(_const.WELL_KNOWN_FILES.DOCKER_COMPOSE);
|
|
291
|
+
const networkKey = name;
|
|
292
|
+
if (compose) {
|
|
293
|
+
// One shared instance. Each API owns a database on it (created at bootstrap).
|
|
294
|
+
// Point `{PREFIX}_DB_HOST` / `{PREFIX}_DB_PORT` at another server to split later.
|
|
295
|
+
// Custom image: sync bootstrap role password on every start (initdb.d is once-only).
|
|
296
|
+
// Default data is a bind mount under the project so `rm -rf` the tree removes it.
|
|
297
|
+
compose.addHeaderComment("Postgres data defaults to ./postgres-data under this folder (bind mount; dies with the working tree).", `For data that outlives the checkout, replace the postgres volumes entry with a named volume`, `e.g. "${name}-postgres-data:/var/lib/postgresql/data" and declare it under top-level volumes:.`).addService("postgres", {
|
|
298
|
+
build: "./postgres",
|
|
299
|
+
container_name: `samples-${name}-postgres`,
|
|
300
|
+
restart: "unless-stopped",
|
|
301
|
+
ports: [
|
|
302
|
+
"127.0.0.1:5433:5432"
|
|
303
|
+
],
|
|
304
|
+
volumes: [
|
|
305
|
+
POSTGRES_DATA_BIND
|
|
306
|
+
],
|
|
307
|
+
networks: [
|
|
308
|
+
{
|
|
309
|
+
[networkKey]: {
|
|
310
|
+
aliases: [
|
|
311
|
+
"postgres"
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
healthcheck: {
|
|
317
|
+
// Ensure role/password match .env before reporting healthy.
|
|
318
|
+
test: [
|
|
319
|
+
"CMD-SHELL",
|
|
320
|
+
`pg_isready -U ${postgresSuperuser} && /usr/local/bin/decohere-ensure-bootstrap.sh`
|
|
321
|
+
],
|
|
322
|
+
interval: "2s",
|
|
323
|
+
timeout: "5s",
|
|
324
|
+
retries: 15
|
|
325
|
+
}
|
|
326
|
+
}).addEnvFile("postgres", "../.env");
|
|
327
|
+
workspace.getModel(_const.WELL_KNOWN_FILES.GITIGNORE)?.add(POSTGRES_DATA_GITIGNORE);
|
|
328
|
+
workspace.addFile({
|
|
329
|
+
files: ()=>[
|
|
330
|
+
{
|
|
331
|
+
filePath: ".devcontainer/postgres/Dockerfile",
|
|
332
|
+
content: POSTGRES_IMAGE_DOCKERFILE
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
filePath: ".devcontainer/postgres/entrypoint.sh",
|
|
336
|
+
content: POSTGRES_ENTRYPOINT_SH
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
filePath: ".devcontainer/postgres/ensure-bootstrap-role.sh",
|
|
340
|
+
content: POSTGRES_ENSURE_BOOTSTRAP_SH
|
|
341
|
+
}
|
|
342
|
+
]
|
|
343
|
+
});
|
|
344
|
+
compose.addEnvFile("devcontainer", "../.env");
|
|
345
|
+
}
|
|
346
|
+
devContainer?.addExtension("mtxr.sqltools", "mtxr.sqltools-driver-pg");
|
|
347
|
+
workspace.getModel(_const.WELL_KNOWN_FILES.DOCKERFILE)?.addLayer((0, _dockerimagemodel.withPostgresqlClient)());
|
|
348
|
+
const pkg = workspace.getModel(_const.WELL_KNOWN_FILES.PACKAGE_JSON)?.addDependency("@decohere/codex", _version.CREATE_VERSION).addDependency("typeorm", "^0.3.28").addDependency("@decohere/codex-typeorm", _version.CREATE_VERSION).addDependency("tsx", "^4.20.5").addScript("db:up", "docker compose -f .devcontainer/docker-compose.yml up -d postgres").addScript("db:down", "docker compose -f .devcontainer/docker-compose.yml down").addScript("db:reset", DB_RESET_SCRIPT);
|
|
349
|
+
const primaryApi = apiNames[0];
|
|
350
|
+
if (pkg != null) {
|
|
351
|
+
for (const apiName of apiNames){
|
|
352
|
+
pkg.addScript(`db:migrate:${apiName}`, migrateRunScript(apiName)).addScript(`db:migrate:generate:${apiName}`, migrateGenerateScript(apiName));
|
|
353
|
+
}
|
|
354
|
+
// Default aliases target the first API of a new workspace only — never overwrite on `webapi add`.
|
|
355
|
+
if (!options.add && primaryApi != null) {
|
|
356
|
+
pkg.addScript("db:migrate", migrateRunScript(primaryApi)).addScript("db:migrate:generate", migrateGenerateScript(primaryApi));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
workspace.addFile({
|
|
360
|
+
files: ()=>[
|
|
361
|
+
{
|
|
362
|
+
filePath: "docs/database.md",
|
|
363
|
+
content: DATABASE_DOCS_MD
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
});
|
|
367
|
+
writeDatabaseConfig(workspace, apiNames);
|
|
368
|
+
for (const project of apiProjects){
|
|
369
|
+
wireApiDatabase(project);
|
|
370
|
+
}
|
|
371
|
+
const env = workspace.getModel(_const.WELL_KNOWN_FILES.ENV_FILE);
|
|
372
|
+
env?.addEntry("POSTGRES_DB", ident).addEntry("POSTGRES_USER", postgresSuperuser).addEntry("POSTGRES_PASSWORD", postgresPassword).addEntry("DB_ADMIN_USER", bootstrapUser).addEntry("DB_ADMIN_PASSWORD", bootstrapPassword);
|
|
373
|
+
for (const apiName of apiNames){
|
|
374
|
+
writeApiDatabaseEnv(workspace, ident, apiName);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function writeDatabaseConfig(workspace, apiNames) {
|
|
378
|
+
const config = workspace.getModel(_const.WELL_KNOWN_FILES.CONFIG_YAML);
|
|
379
|
+
const devConfig = workspace.getModel(_const.WELL_KNOWN_FILES.CONFIG_DEV_YAML);
|
|
380
|
+
const pending = apiNames.filter((apiName)=>!config?.hasDataSource(apiName));
|
|
381
|
+
if (pending.length === 0) return;
|
|
382
|
+
if (config?.hasDataSources()) {
|
|
383
|
+
for (const apiName of pending){
|
|
384
|
+
config.appendDataSource(renderBaseDataSource(apiName));
|
|
385
|
+
devConfig?.appendDataSource(renderDevDataSource(apiName));
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
config?.addSection(renderDatabaseSection(pending, renderBaseDataSource));
|
|
390
|
+
devConfig?.addSection(renderDatabaseSection(pending, renderDevDataSource));
|
|
391
|
+
}
|
|
392
|
+
function writeApiDatabaseEnv(workspace, ident, apiName) {
|
|
393
|
+
const prefix = (0, _apiname.apiEnvPrefix)(apiName);
|
|
394
|
+
const sqlApi = toSqlIdentFragment(apiName);
|
|
395
|
+
workspace.getModel(_const.WELL_KNOWN_FILES.ENV_FILE)?.addEntry(`${prefix}_DB_HOST`, "postgres").addEntry(`${prefix}_DB_PORT`, "5432").addEntry(`${prefix}_DB_NAME`, `${ident}_${sqlApi}_dev`).addEntry(`${prefix}_DB_USER`, `${ident}_${sqlApi}_app`).addEntry(`${prefix}_DB_PASSWORD`, passwordOrExisting(workspace, `${prefix}_DB_PASSWORD`)).addEntry(`${prefix}_DB_MIGRATE_USER`, `${ident}_${sqlApi}_migrate`).addEntry(`${prefix}_DB_MIGRATE_PASSWORD`, passwordOrExisting(workspace, `${prefix}_DB_MIGRATE_PASSWORD`));
|
|
396
|
+
}
|
|
397
|
+
function wireApiDatabase(project) {
|
|
398
|
+
const apiName = (0, _apiname.apiNameFromProjectDir)(project.dir);
|
|
399
|
+
project.getModel(_const.WELL_KNOWN_FILES.MAIN_TS)?.setDataSourceName(apiName).addSideEffectImport("@decohere/codex-typeorm").addEntity({
|
|
400
|
+
className: "SampleEntity",
|
|
401
|
+
importPath: "./entities/sample.entity.js"
|
|
402
|
+
}).addRepository({
|
|
403
|
+
className: "SampleEntityRepository",
|
|
404
|
+
importPath: "./repositories/sample.repository.js"
|
|
405
|
+
});
|
|
406
|
+
project.getModel(_const.WELL_KNOWN_FILES.API_PACKAGE_JSON)?.addDependency("@decohere/codex", _version.CREATE_VERSION).addDependency("@decohere/codex-typeorm", _version.CREATE_VERSION);
|
|
407
|
+
project.addFile({
|
|
408
|
+
files: ()=>[
|
|
409
|
+
{
|
|
410
|
+
filePath: "src/entities/sample.entity.ts",
|
|
411
|
+
content: SAMPLE_ENTITY_TS
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
filePath: "src/repositories/sample.repository.ts",
|
|
415
|
+
content: SAMPLE_REPOSITORY_TS
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
filePath: "src/typeorm.data-source.ts",
|
|
419
|
+
content: renderTypeOrmDataSource(apiName)
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
filePath: "src/migrations/.gitkeep",
|
|
423
|
+
content: ""
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const DatabaseFeature = {
|
|
429
|
+
getOptions,
|
|
430
|
+
scaffold
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL3NjYWZmb2xkL3NyYy93ZWJhcGkvZmVhdHVyZXMvZGF0YWJhc2UuZmVhdHVyZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyByYW5kb21CeXRlcyB9IGZyb20gXCJub2RlOmNyeXB0b1wiO1xuaW1wb3J0IHsgcmVhZEZpbGVTeW5jIH0gZnJvbSBcIm5vZGU6ZnNcIjtcbmltcG9ydCB7IGpvaW4gfSBmcm9tIFwibm9kZTpwYXRoXCI7XG5pbXBvcnQgeyBwaWNrIH0gZnJvbSBcIi4uLy4uL2RvbWFpbi9wcm9tcHQuanNcIjtcbmltcG9ydCB0eXBlIHsgRG9ja2VyQ29tcG9zZSB9IGZyb20gXCIuLi8uLi9tb2RlbHMvY29tcG9zZS5tb2RlbC5qc1wiO1xuaW1wb3J0IHR5cGUgeyBDb25maWdZYW1sIH0gZnJvbSBcIi4uLy4uL21vZGVscy9jb25maWcteWFtbC5tb2RlbC5qc1wiO1xuaW1wb3J0IHsgV0VMTF9LTk9XTl9GSUxFUyB9IGZyb20gXCIuLi8uLi9tb2RlbHMvY29uc3QuanNcIjtcbmltcG9ydCB0eXBlIHsgRGV2Q29udGFpbmVyIH0gZnJvbSBcIi4uLy4uL21vZGVscy9kZXYtY29udGFpbmVyLm1vZGVsLmpzXCI7XG5pbXBvcnQge1xuXHR0eXBlIERvY2tlcmZpbGVNb2RlbCxcblx0d2l0aFBvc3RncmVzcWxDbGllbnQsXG59IGZyb20gXCIuLi8uLi9tb2RlbHMvZG9ja2VyLWltYWdlLm1vZGVsLmpzXCI7XG5pbXBvcnQgdHlwZSB7IEVudkZpbGUgfSBmcm9tIFwiLi4vLi4vbW9kZWxzL2Vudi1maWxlLm1vZGVsLmpzXCI7XG5pbXBvcnQgdHlwZSB7IEdpdGlnbm9yZSB9IGZyb20gXCIuLi8uLi9tb2RlbHMvZ2l0aWdub3JlLm1vZGVsLmpzXCI7XG5pbXBvcnQgdHlwZSB7IFBhY2thZ2VKc29uIH0gZnJvbSBcIi4uLy4uL21vZGVscy9wYWNrYWdlLWpzb24ubW9kZWwuanNcIjtcbmltcG9ydCB0eXBlIHsgUHJvamVjdFNjYWZmb2xkLCBXb3Jrc3BhY2VTY2FmZm9sZCB9IGZyb20gXCIuLi8uLi9tb2RlbHMvcHJvamVjdC5tb2RlbC5qc1wiO1xuaW1wb3J0IHR5cGUgeyBGZWF0dXJlIH0gZnJvbSBcIi4uLy4uL3NlcnZpY2VzL3NjYWZmb2xkLnNlcnZpY2UuanNcIjtcbmltcG9ydCB7IENSRUFURV9WRVJTSU9OIH0gZnJvbSBcIi4uLy4uL3ZlcnNpb24uanNcIjtcbmltcG9ydCB7IGFwaUVudlByZWZpeCwgYXBpTmFtZUZyb21Qcm9qZWN0RGlyIH0gZnJvbSBcIi4uL2FwaS1uYW1lLmpzXCI7XG5pbXBvcnQgdHlwZSB7IEFwaVBhY2thZ2VKc29uIH0gZnJvbSBcIi4uL21vZGVscy9hcGktcGFja2FnZS1qc29uLm1vZGVsLmpzXCI7XG5pbXBvcnQgdHlwZSB7IE1haW5Tb3VyY2VGaWxlIH0gZnJvbSBcIi4uL21vZGVscy9tYWluLXNvdXJjZS1maWxlLm1vZGVsLmpzXCI7XG5cbi8qKiBCaW5kIHBhdGggcmVsYXRpdmUgdG8gYC5kZXZjb250YWluZXIvZG9ja2VyLWNvbXBvc2UueW1sYCAoZGllcyB3aXRoIHRoZSB3b3JraW5nIHRyZWUpLiAqL1xuY29uc3QgUE9TVEdSRVNfREFUQV9CSU5EID0gXCIuL3Bvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhXCI7XG5jb25zdCBQT1NUR1JFU19EQVRBX0dJVElHTk9SRSA9IFwiLmRldmNvbnRhaW5lci9wb3N0Z3Jlcy1kYXRhXCI7XG5jb25zdCBEQl9SRVNFVF9TQ1JJUFQgPVxuXHRcInJtIC1yZiAuZGV2Y29udGFpbmVyL3Bvc3RncmVzLWRhdGEgJiYgZG9ja2VyIGNvbXBvc2UgLWYgLmRldmNvbnRhaW5lci9kb2NrZXItY29tcG9zZS55bWwgZG93biAmJiBkb2NrZXIgY29tcG9zZSAtZiAuZGV2Y29udGFpbmVyL2RvY2tlci1jb21wb3NlLnltbCB1cCAtZCBwb3N0Z3Jlc1wiO1xuXG5leHBvcnQgdHlwZSBEYXRhYmFzZU9wdGlvbnMgPSB7XG5cdGRhdGFiYXNlUHJvdmlkZXI/OiBcIm5vbmVcIiB8IFwicG9zdGdyZXMvdHlwZW9ybVwiO1xuXHQvKiogV2hlbiB0cnVlLCBtZXJnZSBpbnRvIGFuIGV4aXN0aW5nIHdvcmtzcGFjZSAoXFxgd2ViYXBpIGFkZFxcYCkuICovXG5cdGFkZD86IGJvb2xlYW47XG59O1xuXG4vKiogUmVhZCBhIHNpbmdsZSBLRVk9IGZyb20gYW4gZXhpc3RpbmcgdGFyZ2V0IGAuZW52YCAoaWYgYW55KS4gKi9cbmZ1bmN0aW9uIHJlYWRFeGlzdGluZ0VudlZhbHVlKHdvcmtzcGFjZTogV29ya3NwYWNlU2NhZmZvbGQsIGtleTogc3RyaW5nKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcblx0dHJ5IHtcblx0XHRjb25zdCByYXcgPSByZWFkRmlsZVN5bmMoam9pbih3b3Jrc3BhY2UudGFyZ2V0RGlyLCBcIi5lbnZcIiksIFwidXRmOFwiKTtcblx0XHRjb25zdCBsaW5lID0gcmF3LnNwbGl0KFwiXFxuXCIpLmZpbmQoKGwpID0+IGwuc3RhcnRzV2l0aChgJHtrZXl9PWApKTtcblx0XHRjb25zdCB2YWx1ZSA9IGxpbmU/LnNsaWNlKGtleS5sZW5ndGggKyAxKS50cmltKCk7XG5cdFx0cmV0dXJuIHZhbHVlIHx8IHVuZGVmaW5lZDtcblx0fSBjYXRjaCB7XG5cdFx0cmV0dXJuIHVuZGVmaW5lZDtcblx0fVxufVxuXG5mdW5jdGlvbiByYW5kb21QYXNzd29yZCgpOiBzdHJpbmcge1xuXHRyZXR1cm4gcmFuZG9tQnl0ZXMoMTYpLnRvU3RyaW5nKFwiaGV4XCIpO1xufVxuXG4vKiogU2FmZSBQb3N0Z3JlcyBpZGVudGlmaWVyIGZyYWdtZW50IGZyb20gdGhlIHByb2plY3QgbmFtZS4gKi9cbmZ1bmN0aW9uIHRvU3FsSWRlbnRGcmFnbWVudChuYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuXHRjb25zdCBjbGVhbmVkID0gbmFtZS5yZXBsYWNlKC9bXkEtWmEtejAtOV9dL2csIFwiX1wiKS5yZXBsYWNlKC9eKFteQS1aYS16X10pLywgXCJwXyQxXCIpO1xuXHRyZXR1cm4gY2xlYW5lZC5sZW5ndGggPiAwID8gY2xlYW5lZC5zbGljZSgwLCA0MCkgOiBcImFwcFwiO1xufVxuXG5mdW5jdGlvbiBwYXNzd29yZE9yRXhpc3RpbmcoXG5cdHdvcmtzcGFjZTogV29ya3NwYWNlU2NhZmZvbGQsXG5cdGtleTogc3RyaW5nLFxuXHRmYWxsYmFja0V4aXN0aW5nS2V5czogc3RyaW5nW10gPSBbXSxcbik6IHN0cmluZyB7XG5cdGNvbnN0IGV4aXN0aW5nID0gcmVhZEV4aXN0aW5nRW52VmFsdWUod29ya3NwYWNlLCBrZXkpO1xuXHRpZiAoZXhpc3RpbmcpIHJldHVybiBleGlzdGluZztcblx0Zm9yIChjb25zdCBhbHQgb2YgZmFsbGJhY2tFeGlzdGluZ0tleXMpIHtcblx0XHRjb25zdCB2ID0gcmVhZEV4aXN0aW5nRW52VmFsdWUod29ya3NwYWNlLCBhbHQpO1xuXHRcdGlmICh2KSByZXR1cm4gdjtcblx0fVxuXHRyZXR1cm4gcmFuZG9tUGFzc3dvcmQoKTtcbn1cblxuLy8gUHJvZHVjdGlvbjogcnVudGltZSBhcHAgcm9sZSBvbmx5IOKAlCBubyBib290c3RyYXAsIG5vIGFkbWluL21pZ3JhdGUgc2VjcmV0cyByZXF1aXJlZC5cbmZ1bmN0aW9uIHJlbmRlckJhc2VEYXRhU291cmNlKGFwaU5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG5cdGNvbnN0IHAgPSBhcGlFbnZQcmVmaXgoYXBpTmFtZSk7XG5cdHJldHVybiBbXG5cdFx0YCAgICAtIG5hbWU6ICR7YXBpTmFtZX1gLFxuXHRcdGAgICAgICBwcm92aWRlcjogcG9zdGdyZXNgLFxuXHRcdGAgICAgICBob3N0OiBcXCR7JHtwfV9EQl9IT1NUfWAsXG5cdFx0YCAgICAgIHBvcnQ6IFxcJHske3B9X0RCX1BPUlR9YCxcblx0XHRgICAgICAgZGF0YWJhc2U6IFxcJHske3B9X0RCX05BTUV9YCxcblx0XHRgICAgICAgdXNlcm5hbWU6IFxcJHske3B9X0RCX1VTRVJ9YCxcblx0XHRgICAgICAgcGFzc3dvcmQ6IFxcJHske3B9X0RCX1BBU1NXT1JEfWAsXG5cdFx0YCAgICAgIHN5bmNocm9uaXplOiBmYWxzZWAsXG5cdFx0YCAgICAgIHNlZWQ6IGZhbHNlYCxcblx0XHRgICAgICAgYm9vdHN0cmFwOiBmYWxzZWAsXG5cdF0uam9pbihcIlxcblwiKTtcbn1cblxuLy8gRGV2IG92ZXJsYXkgKG1lcmdlZCBieSBsaXN0IGluZGV4IG9udG8gY29uZmlnLnlhbWwpLiBFbmFibGUgYm9vdHN0cmFwICsgbWlncmF0ZSByb2xlLlxuZnVuY3Rpb24gcmVuZGVyRGV2RGF0YVNvdXJjZShhcGlOYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuXHRjb25zdCBwID0gYXBpRW52UHJlZml4KGFwaU5hbWUpO1xuXHRyZXR1cm4gW1xuXHRcdGAgICAgLSBuYW1lOiAke2FwaU5hbWV9YCxcblx0XHRgICAgICAgc3luY2hyb25pemU6IHRydWVgLFxuXHRcdGAgICAgICBzZWVkOiB0cnVlYCxcblx0XHRgICAgICAgYm9vdHN0cmFwOiB0cnVlYCxcblx0XHRgICAgICAgYWRtaW5Vc2VyOiBcXCR7REJfQURNSU5fVVNFUn1gLFxuXHRcdGAgICAgICBhZG1pblBhc3N3b3JkOiBcXCR7REJfQURNSU5fUEFTU1dPUkR9YCxcblx0XHRgICAgICAgbWlncmF0ZVVzZXJuYW1lOiBcXCR7JHtwfV9EQl9NSUdSQVRFX1VTRVJ9YCxcblx0XHRgICAgICAgbWlncmF0ZVBhc3N3b3JkOiBcXCR7JHtwfV9EQl9NSUdSQVRFX1BBU1NXT1JEfWAsXG5cdF0uam9pbihcIlxcblwiKTtcbn1cblxuZnVuY3Rpb24gcmVuZGVyRGF0YWJhc2VTZWN0aW9uKFxuXHRhcGlOYW1lczogcmVhZG9ubHkgc3RyaW5nW10sXG5cdGl0ZW1zOiAobmFtZTogc3RyaW5nKSA9PiBzdHJpbmcsXG4pOiBzdHJpbmcge1xuXHRyZXR1cm4gYGRhdGFiYXNlOlxcbiAgZGF0YVNvdXJjZXM6XFxuJHthcGlOYW1lcy5tYXAoaXRlbXMpLmpvaW4oXCJcXG5cIil9XFxuYDtcbn1cblxuLyoqIENyZWF0ZS1vci1zeW5jIGJvb3RzdHJhcCByb2xlOyBydW5zIG9uIGV2ZXJ5IFBvc3RncmVzIGNvbnRhaW5lciBzdGFydCAobm90IG9ubHkgZW1wdHkgdm9sdW1lKS4gKi9cbmNvbnN0IFBPU1RHUkVTX0VOU1VSRV9CT09UU1RSQVBfU0ggPSBgIyEvYmluL2Jhc2hcbnNldCAtZXVvIHBpcGVmYWlsXG4jIFN1cGVydXNlciBhbGlnbnMgZGVjb2hlcmVfYm9vdHN0cmFwIHdpdGggREJfQURNSU5fKiBmcm9tIGVudl9maWxlIG9uIGV2ZXJ5IHN0YXJ0LlxuIyBJbml0ZGIuZCBhbG9uZSBpcyBpbnN1ZmZpY2llbnQ6IHBhc3N3b3JkcyBpbiAuZW52IGNhbiBjaGFuZ2Ugd2hpbGUgdGhlIHZvbHVtZSBwZXJzaXN0cy5cbjogXCJcXCR7UE9TVEdSRVNfVVNFUjo/UE9TVEdSRVNfVVNFUiBpcyByZXF1aXJlZH1cIlxuOiBcIlxcJHtEQl9BRE1JTl9VU0VSOj9EQl9BRE1JTl9VU0VSIGlzIHJlcXVpcmVkfVwiXG46IFwiXFwke0RCX0FETUlOX1BBU1NXT1JEOj9EQl9BRE1JTl9QQVNTV09SRCBpcyByZXF1aXJlZH1cIlxuXG5wc3FsIC12IE9OX0VSUk9SX1NUT1A9MSAtLXVzZXJuYW1lIFwiJFBPU1RHUkVTX1VTRVJcIiAtLWRibmFtZSBwb3N0Z3JlcyBcXFxcXG5cXHQtLXNldD1ib290c3RyYXBfdXNlcj1cIiREQl9BRE1JTl9VU0VSXCIgXFxcXFxuXFx0LS1zZXQ9Ym9vdHN0cmFwX3Bhc3N3b3JkPVwiJERCX0FETUlOX1BBU1NXT1JEXCIgPDwnRU9TUUwnXG5TRUxFQ1QgZm9ybWF0KFxuXFx0J0NSRUFURSBST0xFICVJIExPR0lOIFBBU1NXT1JEICVMIENSRUFURURCIENSRUFURVJPTEUgTk9TVVBFUlVTRVIgTk9CWVBBU1NSTFMnLFxuXFx0Oidib290c3RyYXBfdXNlcicsXG5cXHQ6J2Jvb3RzdHJhcF9wYXNzd29yZCdcbilcbldIRVJFIE5PVCBFWElTVFMgKFxuXFx0U0VMRUNUIDEgRlJPTSBwZ19jYXRhbG9nLnBnX3JvbGVzIFdIRVJFIHJvbG5hbWUgPSA6J2Jvb3RzdHJhcF91c2VyJ1xuKVxcXFxnZXhlY1xuXG5TRUxFQ1QgZm9ybWF0KFxuXFx0J0FMVEVSIFJPTEUgJUkgV0lUSCBMT0dJTiBQQVNTV09SRCAlTCBDUkVBVEVEQiBDUkVBVEVST0xFIE5PU1VQRVJVU0VSIE5PQllQQVNTUkxTJyxcblxcdDonYm9vdHN0cmFwX3VzZXInLFxuXFx0Oidib290c3RyYXBfcGFzc3dvcmQnXG4pXFxcXGdleGVjXG5cbkdSQU5UIENPTk5FQ1QgT04gREFUQUJBU0UgcG9zdGdyZXMgVE8gOlwiYm9vdHN0cmFwX3VzZXJcIjtcbkVPU1FMXG5gO1xuXG5jb25zdCBQT1NUR1JFU19FTlRSWVBPSU5UX1NIID0gYCMhL2Jpbi9iYXNoXG5zZXQgLWV1byBwaXBlZmFpbFxuKFxuXFx0Zm9yIF8gaW4gJChzZXEgMSA2MCk7IGRvXG5cXHRcXHRpZiBwZ19pc3JlYWR5IC1VIFwiXFwke1BPU1RHUkVTX1VTRVI6LXBvc3RncmVzfVwiID4vZGV2L251bGwgMj4mMTsgdGhlblxuXFx0XFx0XFx0L3Vzci9sb2NhbC9iaW4vZGVjb2hlcmUtZW5zdXJlLWJvb3RzdHJhcC5zaFxuXFx0XFx0XFx0ZXhpdCAwXG5cXHRcXHRmaVxuXFx0XFx0c2xlZXAgMC41XG5cXHRkb25lXG5cXHRlY2hvIFwiZGVjb2hlcmU6IHRpbWVkIG91dCB3YWl0aW5nIGZvciBQb3N0Z3JlcyBiZWZvcmUgZW5zdXJpbmcgYm9vdHN0cmFwIHJvbGVcIiA+JjJcblxcdGV4aXQgMVxuKSAmXG5leGVjIGRvY2tlci1lbnRyeXBvaW50LnNoIFwiJEBcIlxuYDtcblxuY29uc3QgUE9TVEdSRVNfSU1BR0VfRE9DS0VSRklMRSA9IGBGUk9NIHBvc3RncmVzOjE2XG5DT1BZIGVuc3VyZS1ib290c3RyYXAtcm9sZS5zaCAvdXNyL2xvY2FsL2Jpbi9kZWNvaGVyZS1lbnN1cmUtYm9vdHN0cmFwLnNoXG5DT1BZIGVudHJ5cG9pbnQuc2ggL3Vzci9sb2NhbC9iaW4vZGVjb2hlcmUtcG9zdGdyZXMtZW50cnlwb2ludC5zaFxuUlVOIGNobW9kICt4IC91c3IvbG9jYWwvYmluL2RlY29oZXJlLWVuc3VyZS1ib290c3RyYXAuc2ggL3Vzci9sb2NhbC9iaW4vZGVjb2hlcmUtcG9zdGdyZXMtZW50cnlwb2ludC5zaFxuRU5UUllQT0lOVCBbXCIvdXNyL2xvY2FsL2Jpbi9kZWNvaGVyZS1wb3N0Z3Jlcy1lbnRyeXBvaW50LnNoXCJdXG5DTUQgW1wicG9zdGdyZXNcIl1cbmA7XG5cbmNvbnN0IFNBTVBMRV9FTlRJVFlfVFMgPSBgaW1wb3J0IHsgQ29sdW1uLCBFbnRpdHksIFByaW1hcnlHZW5lcmF0ZWRDb2x1bW4gfSBmcm9tIFwidHlwZW9ybVwiO1xuaW1wb3J0IHR5cGUgeyBFbnRpdHkgYXMgSUVudGl0eSB9IGZyb20gXCJAZGVjb2hlcmUvY29kZXhcIjtcblxuQEVudGl0eSgpXG5leHBvcnQgY2xhc3MgU2FtcGxlRW50aXR5IGltcGxlbWVudHMgSUVudGl0eSB7XG5cXHRAUHJpbWFyeUdlbmVyYXRlZENvbHVtbigpXG5cXHRwdWJsaWMgaWQhOiBudW1iZXI7XG5cblxcdEBDb2x1bW4oKVxuXFx0cHVibGljIG5hbWUhOiBzdHJpbmc7XG59XG5gO1xuXG5jb25zdCBTQU1QTEVfUkVQT1NJVE9SWV9UUyA9IGBpbXBvcnQgeyBpbmplY3RhYmxlIH0gZnJvbSBcIkBkZWNvaGVyZS9jcnVjaWJsZS9kZWNvcmF0b3JzXCI7XG5pbXBvcnQgeyBpbmplY3RVbml0T2ZXb3JrIH0gZnJvbSBcIkBkZWNvaGVyZS9jb2RleFwiO1xuaW1wb3J0IHsgVHlwZU9ybVJlcG9zaXRvcnksIFR5cGVPcm1Vbml0T2ZXb3JrIH0gZnJvbSBcIkBkZWNvaGVyZS9jb2RleC10eXBlb3JtXCI7XG5pbXBvcnQgeyBTYW1wbGVFbnRpdHkgfSBmcm9tIFwiLi4vZW50aXRpZXMvc2FtcGxlLmVudGl0eS5qc1wiO1xuXG5AaW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgU2FtcGxlRW50aXR5UmVwb3NpdG9yeSBleHRlbmRzIFR5cGVPcm1SZXBvc2l0b3J5PFNhbXBsZUVudGl0eT4ge1xuXFx0cHVibGljIGNvbnN0cnVjdG9yKEBpbmplY3RVbml0T2ZXb3JrKCkgdW5pdE9mV29yazogVHlwZU9ybVVuaXRPZldvcmspIHtcblxcdFxcdHN1cGVyKHVuaXRPZldvcmssIFNhbXBsZUVudGl0eSk7XG5cXHR9XG59XG5gO1xuXG4vKiogVHlwZU9STSBDTEkgRGF0YVNvdXJjZSDigJQgY29ubmVjdHMgYXMgdGhlIG1pZ3JhdGUgcm9sZTsgbmV2ZXIgZW5hYmxlcyBzeW5jaHJvbml6ZS4gKi9cbmZ1bmN0aW9uIHJlbmRlclR5cGVPcm1EYXRhU291cmNlKGFwaU5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG5cdGNvbnN0IHAgPSBhcGlFbnZQcmVmaXgoYXBpTmFtZSk7XG5cdHJldHVybiBgaW1wb3J0IHsgZGlybmFtZSwgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tIFwibm9kZTp1cmxcIjtcbmltcG9ydCB7IERhdGFTb3VyY2UgfSBmcm9tIFwidHlwZW9ybVwiO1xuXG5jb25zdCByb290ID0gZGlybmFtZShmaWxlVVJMVG9QYXRoKGltcG9ydC5tZXRhLnVybCkpO1xuY29uc3QgcmVxdWlyZWQgPSAoa2V5OiBzdHJpbmcpOiBzdHJpbmcgPT4ge1xuXFx0Y29uc3QgdmFsdWUgPSBwcm9jZXNzLmVudltrZXldO1xuXFx0aWYgKHZhbHVlID09IG51bGwgfHwgdmFsdWUgPT09IFwiXCIpIHtcblxcdFxcdHRocm93IG5ldyBFcnJvcihcXGBNaXNzaW5nIGVudiBcXCR7a2V5fSAobG9hZCAuZW52IHZpYSAtLWVudi1maWxlIG9yIGV4cG9ydCBpdClcXGApO1xuXFx0fVxuXFx0cmV0dXJuIHZhbHVlO1xufTtcblxuLyoqXG4gKiBDTEktb25seSBEYXRhU291cmNlIGZvciBcXGBwbnBtIGRiOm1pZ3JhdGVcXGAgLyBcXGBwbnBtIGRiOm1pZ3JhdGU6Z2VuZXJhdGVcXGAuXG4gKiBVc2VzIHRoZSBtaWdyYXRlIHJvbGUgKERETCkuIFJ1bnRpbWUgQVBJIHBvZHMgdXNlIHRoZSBhcHAgcm9sZSB3aXRoIHN5bmNocm9uaXplOiBmYWxzZS5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgbmV3IERhdGFTb3VyY2Uoe1xuXFx0dHlwZTogXCJwb3N0Z3Jlc1wiLFxuXFx0aG9zdDogcmVxdWlyZWQoXCIke3B9X0RCX0hPU1RcIiksXG5cXHRwb3J0OiBOdW1iZXIocmVxdWlyZWQoXCIke3B9X0RCX1BPUlRcIikpLFxuXFx0ZGF0YWJhc2U6IHJlcXVpcmVkKFwiJHtwfV9EQl9OQU1FXCIpLFxuXFx0dXNlcm5hbWU6IHJlcXVpcmVkKFwiJHtwfV9EQl9NSUdSQVRFX1VTRVJcIiksXG5cXHRwYXNzd29yZDogcmVxdWlyZWQoXCIke3B9X0RCX01JR1JBVEVfUEFTU1dPUkRcIiksXG5cXHRzeW5jaHJvbml6ZTogZmFsc2UsXG5cXHRlbnRpdGllczogW2pvaW4ocm9vdCwgXCJlbnRpdGllc1wiLCBcIioue3RzLGpzfVwiKV0sXG5cXHRtaWdyYXRpb25zOiBbam9pbihyb290LCBcIm1pZ3JhdGlvbnNcIiwgXCIqLnt0cyxqc31cIildLFxufSk7XG5gO1xufVxuXG5jb25zdCBEQVRBQkFTRV9ET0NTX01EID0gYCMgRGF0YWJhc2UgKFBvc3RncmVzIC8gVHlwZU9STSlcblxuIyMgUm9sZXNcblxufCBSb2xlIHwgRW52IHwgUHVycG9zZSB8XG58LS0tfC0tLXwtLS18XG58IEFwcCB8IFxcYHtQUkVGSVh9X0RCX1VTRVJcXGAgfCBSdW50aW1lIEFQSSAoRE1MIG9ubHkgaW4gcHJvZHVjdGlvbikgfFxufCBNaWdyYXRlIHwgXFxge1BSRUZJWH1fREJfTUlHUkFURV9VU0VSXFxgIHwgU2NoZW1hIGNoYW5nZXMgLyBUeXBlT1JNIG1pZ3JhdGlvbnMgKENJL2RlcGxveSkgfFxufCBCb290c3RyYXAgfCBcXGBEQl9BRE1JTl9VU0VSXFxgIHwgQ3JlYXRlIERCICsgcm9sZXMgd2hlbiBcXGBib290c3RyYXA6IHRydWVcXGAgKGRldikgfFxuXG5cXGB7UFJFRklYfVxcYCBpcyB0aGUgQVBJIHNsdWcgaW4gU0NSRUFNSU5HX1NOQUtFIChcXGBhcGlcXGAg4oaSIFxcYEFQSVxcYCwgXFxgYmlsbGluZ1xcYCDihpIgXFxgQklMTElOR1xcYCkuXG5cbiMjIERldmVsb3BtZW50IChmYXN0IGl0ZXJhdGlvbilcblxuXFxgY29uZmlnL2NvbmZpZy5kZXYueWFtbFxcYCBzZXRzIFxcYHN5bmNocm9uaXplOiB0cnVlXFxgIGFuZCBcXGBzZWVkOiB0cnVlXFxgIHNvIHRoZSBBUEkgY2FuIGNyZWF0ZSB0YWJsZXMgb24gc3RhcnR1cC4gWW91IGNhbiBrZWVwIHVzaW5nIHN5bmMgd2hpbGUgcHJvdG90eXBpbmcuXG5cbkFsdGVybmF0aXZlbHksIHR1cm4gc3luYyBvZmYgaW4gdGhlIG92ZXJsYXkgYW5kIHVzZSBtaWdyYXRpb25zIHRoZSBzYW1lIHdheSBhcyBwcm9kdWN0aW9uOlxuXG5cXGBcXGBcXGBiYXNoXG5wbnBtIGRiOnVwXG5wbnBtIGRiOm1pZ3JhdGVcbnBucG0gZGI6bWlncmF0ZTpnZW5lcmF0ZSAtLSAuL3Byb2plY3RzL2FwaS9zcmMvbWlncmF0aW9ucy9BZGRTb21ldGhpbmdcblxcYFxcYFxcYFxuXG5EbyAqKm5vdCoqIG1peCBzeW5jIGFuZCBtaWdyYXRpb25zIGZvciB0aGUgc2FtZSBzY2hlbWEgY2hhbmdlIChwaWNrIG9uZSBwYXRoIHBlciBjaGFuZ2UpLlxuXG4jIyBQcm9kdWN0aW9uIChtaWdyYXRpb25zIG9ubHkpXG5cblxcYGNvbmZpZy9jb25maWcueWFtbFxcYCBrZWVwcyBcXGBzeW5jaHJvbml6ZTogZmFsc2VcXGAgLyBcXGBzZWVkOiBmYWxzZVxcYCAvIFxcYGJvb3RzdHJhcDogZmFsc2VcXGAuIERlcGxveSBzdGVwczpcblxuMS4gRW5zdXJlIHRoZSBhcHAgZGF0YWJhc2UgYW5kIHJvbGVzIGFscmVhZHkgZXhpc3QgKHByb3Zpc2lvbmVyIC8gb3BzIOKAlCBub3QgdGhlIEFQSSBwb2QpLlxuMi4gUnVuIG1pZ3JhdGlvbnMgd2l0aCB0aGUgbWlncmF0ZSByb2xlOiBcXGBwbnBtIGRiOm1pZ3JhdGVcXGAgKG9yIFxcYHBucG0gZGI6bWlncmF0ZTo8YXBpPlxcYCBmb3IgbXVsdGktQVBJKS5cbjMuIFN0YXJ0IHRoZSBBUEkgd2l0aCB0aGUgYXBwIHJvbGUgb25seSDigJQgbm8gbWlncmF0ZS9hZG1pbiBzZWNyZXRzIG9uIHRoZSBwb2QuXG5cbiMjIFNjcmlwdHNcblxufCBTY3JpcHQgfCBXaGF0IGl0IGRvZXMgfFxufC0tLXwtLS18XG58IFxcYGRiOnVwXFxgIC8gXFxgZGI6ZG93blxcYCAvIFxcYGRiOnJlc2V0XFxgIHwgTG9jYWwgQ29tcG9zZSBQb3N0Z3JlcyB8XG58IFxcYGRiOm1pZ3JhdGVcXGAgfCBSdW4gcGVuZGluZyBtaWdyYXRpb25zIChcXGBwcm9qZWN0cy9hcGlcXGAgYnkgZGVmYXVsdCkgfFxufCBcXGBkYjptaWdyYXRlOmdlbmVyYXRlXFxgIHwgR2VuZXJhdGUgYSBtaWdyYXRpb24gZnJvbSBlbnRpdHkgZHJpZnQgKHBhc3MgcGF0aCBhZnRlciBcXGAtLVxcYCkgfFxufCBcXGBkYjptaWdyYXRlOjxhcGk+XFxgIHwgU2FtZSBmb3IgYSBuYW1lZCBBUEkgdW5kZXIgXFxgcHJvamVjdHMvPGFwaT5cXGAgfFxuXG5DTEkgZW50cnk6IFxcYHByb2plY3RzLzxhcGk+L3NyYy90eXBlb3JtLmRhdGEtc291cmNlLnRzXFxgIChtaWdyYXRlIGNyZWRlbnRpYWxzLCBcXGBzeW5jaHJvbml6ZTogZmFsc2VcXGApLlxuXG4jIyBCcm93c2UgdGhlIGRhdGFiYXNlXG5cblByZWZlciB0aGUgRGV2IENvbnRhaW5lciAqKlNRTFRvb2xzKiogZXh0ZW5zaW9uIChQb3N0Z3JlcyBkcml2ZXIpIG9yIGFueSBkZXNrdG9wIGNsaWVudCBhZ2FpbnN0IFxcYDEyNy4wLjAuMTo1NDMzXFxgIChzZWUgXFxgLmVudlxcYCBmb3IgY3JlZGVudGlhbHMpLlxuXG5BZG1pbmVyIGlzICoqbm90Kiogc2NhZmZvbGRlZC4gSWYgeW91IHdhbnQgYSBicm93c2VyIFVJIGluIENvbXBvc2UsIGFkZCB0aGlzIHVuZGVyIFxcYHNlcnZpY2VzOlxcYCBpbiBcXGAuZGV2Y29udGFpbmVyL2RvY2tlci1jb21wb3NlLnltbFxcYCAocHJvZmlsZSBcXGB0b29sc1xcYCBzbyBpdCBzdGF5cyBvZmYgYnkgZGVmYXVsdCk6XG5cblxcYFxcYFxcYHlhbWxcbiAgYWRtaW5lcjpcbiAgICBpbWFnZTogYWRtaW5lclxuICAgIHByb2ZpbGVzOlxuICAgICAgLSB0b29sc1xuICAgIHBvcnRzOlxuICAgICAgLSBcIjEyNy4wLjAuMTo4MDgxOjgwODBcIlxuICAgIG5ldHdvcmtzOlxuICAgICAgLSA8Y29tcG9zZS1uZXR3b3JrLWtleT4gIyBzYW1lIGtleSBhcyBwb3N0Z3JlcyAvIGRldmNvbnRhaW5lclxuXFxgXFxgXFxgXG5cblRoZW46IFxcYGRvY2tlciBjb21wb3NlIC1mIC5kZXZjb250YWluZXIvZG9ja2VyLWNvbXBvc2UueW1sIC0tcHJvZmlsZSB0b29scyB1cCAtZCBhZG1pbmVyXFxgXG5gO1xuXG5mdW5jdGlvbiB0eXBlb3JtQ2xpKGFyZ3M6IHN0cmluZyk6IHN0cmluZyB7XG5cdHJldHVybiBgbm9kZSAtLWltcG9ydCB0c3ggLS1lbnYtZmlsZT0uZW52IC4vbm9kZV9tb2R1bGVzL3R5cGVvcm0vY2xpLmpzICR7YXJnc31gO1xufVxuXG5mdW5jdGlvbiBtaWdyYXRlUnVuU2NyaXB0KGFwaU5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG5cdHJldHVybiB0eXBlb3JtQ2xpKGBtaWdyYXRpb246cnVuIC1kIC4vcHJvamVjdHMvJHthcGlOYW1lfS9zcmMvdHlwZW9ybS5kYXRhLXNvdXJjZS50c2ApO1xufVxuXG5mdW5jdGlvbiBtaWdyYXRlR2VuZXJhdGVTY3JpcHQoYXBpTmFtZTogc3RyaW5nKTogc3RyaW5nIHtcblx0cmV0dXJuIHR5cGVvcm1DbGkoXG5cdFx0YG1pZ3JhdGlvbjpnZW5lcmF0ZSAtLWVzbSAtZCAuL3Byb2plY3RzLyR7YXBpTmFtZX0vc3JjL3R5cGVvcm0uZGF0YS1zb3VyY2UudHNgLFxuXHQpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBnZXRPcHRpb25zKFxuXHRfd29ya3NwYWNlOiBXb3Jrc3BhY2VTY2FmZm9sZCxcblx0Y2xpT3B0aW9uczogUGFydGlhbDxEYXRhYmFzZU9wdGlvbnM+LFxuKTogUHJvbWlzZTxQYXJ0aWFsPERhdGFiYXNlT3B0aW9ucz4+IHtcblx0aWYgKGNsaU9wdGlvbnMuZGF0YWJhc2VQcm92aWRlciAhPSBudWxsKSByZXR1cm4ge307XG5cblx0Y29uc3QgY2hvaWNlcyA9IFt7IGxhYmVsOiBcIm5vbmVcIiBhcyBjb25zdCB9LCB7IGxhYmVsOiBcInBvc3RncmVzL3R5cGVvcm1cIiBhcyBjb25zdCB9XTtcblx0Y29uc3Qgc2VsZWN0ZWQgPSBhd2FpdCBwaWNrKFwiXFxuICBEYXRhYmFzZTogXCIsIGNob2ljZXMsIChjKSA9PiBjLmxhYmVsKTtcblx0cmV0dXJuIHsgZGF0YWJhc2VQcm92aWRlcjogc2VsZWN0ZWQ/LmxhYmVsID8/IFwibm9uZVwiIH07XG59XG5cbmZ1bmN0aW9uIHNjYWZmb2xkKHdvcmtzcGFjZTogV29ya3NwYWNlU2NhZmZvbGQsIG9wdGlvbnM6IERhdGFiYXNlT3B0aW9ucyk6IHZvaWQge1xuXHRpZiAob3B0aW9ucy5kYXRhYmFzZVByb3ZpZGVyICE9PSBcInBvc3RncmVzL3R5cGVvcm1cIikgcmV0dXJuO1xuXG5cdGNvbnN0IG5hbWUgPSB3b3Jrc3BhY2UubmFtZTtcblx0Y29uc3QgaWRlbnQgPSB0b1NxbElkZW50RnJhZ21lbnQobmFtZSk7XG5cdGNvbnN0IGFwaVByb2plY3RzID0gd29ya3NwYWNlLmdldFByb2plY3RzKCk7XG5cdGNvbnN0IGFwaU5hbWVzID0gYXBpUHJvamVjdHMubWFwKChwcm9qZWN0KSA9PiBhcGlOYW1lRnJvbVByb2plY3REaXIocHJvamVjdC5kaXIpKTtcblx0Ly8gQ29udGFpbmVyIGluaXQgc3VwZXJ1c2VyIOKAlCBuZXZlciByZXVzZWQgYXMgYW4gQVBJIHJ1bnRpbWUgcm9sZS5cblx0Y29uc3QgcG9zdGdyZXNTdXBlcnVzZXIgPSBcInBvc3RncmVzXCI7XG5cdGNvbnN0IHBvc3RncmVzUGFzc3dvcmQgPSBwYXNzd29yZE9yRXhpc3Rpbmcod29ya3NwYWNlLCBcIlBPU1RHUkVTX1BBU1NXT1JEXCIpO1xuXHRjb25zdCBib290c3RyYXBVc2VyID0gXCJkZWNvaGVyZV9ib290c3RyYXBcIjtcblx0Y29uc3QgYm9vdHN0cmFwUGFzc3dvcmQgPSBwYXNzd29yZE9yRXhpc3Rpbmcod29ya3NwYWNlLCBcIkRCX0FETUlOX1BBU1NXT1JEXCIsIFtcblx0XHRcIlBPU1RHUkVTX1BBU1NXT1JEXCIsXG5cdF0pO1xuXG5cdGNvbnN0IGRldkNvbnRhaW5lciA9IHdvcmtzcGFjZS5nZXRNb2RlbDxEZXZDb250YWluZXI+KFdFTExfS05PV05fRklMRVMuREVWQ09OVEFJTkVSX0pTT04pO1xuXHRjb25zdCBjb21wb3NlID0gd29ya3NwYWNlLmdldE1vZGVsPERvY2tlckNvbXBvc2U+KFdFTExfS05PV05fRklMRVMuRE9DS0VSX0NPTVBPU0UpO1xuXG5cdGNvbnN0IG5ldHdvcmtLZXkgPSBuYW1lO1xuXG5cdGlmIChjb21wb3NlKSB7XG5cdFx0Ly8gT25lIHNoYXJlZCBpbnN0YW5jZS4gRWFjaCBBUEkgb3ducyBhIGRhdGFiYXNlIG9uIGl0IChjcmVhdGVkIGF0IGJvb3RzdHJhcCkuXG5cdFx0Ly8gUG9pbnQgYHtQUkVGSVh9X0RCX0hPU1RgIC8gYHtQUkVGSVh9X0RCX1BPUlRgIGF0IGFub3RoZXIgc2VydmVyIHRvIHNwbGl0IGxhdGVyLlxuXHRcdC8vIEN1c3RvbSBpbWFnZTogc3luYyBib290c3RyYXAgcm9sZSBwYXNzd29yZCBvbiBldmVyeSBzdGFydCAoaW5pdGRiLmQgaXMgb25jZS1vbmx5KS5cblx0XHQvLyBEZWZhdWx0IGRhdGEgaXMgYSBiaW5kIG1vdW50IHVuZGVyIHRoZSBwcm9qZWN0IHNvIGBybSAtcmZgIHRoZSB0cmVlIHJlbW92ZXMgaXQuXG5cdFx0Y29tcG9zZVxuXHRcdFx0LmFkZEhlYWRlckNvbW1lbnQoXG5cdFx0XHRcdFwiUG9zdGdyZXMgZGF0YSBkZWZhdWx0cyB0byAuL3Bvc3RncmVzLWRhdGEgdW5kZXIgdGhpcyBmb2xkZXIgKGJpbmQgbW91bnQ7IGRpZXMgd2l0aCB0aGUgd29ya2luZyB0cmVlKS5cIixcblx0XHRcdFx0YEZvciBkYXRhIHRoYXQgb3V0bGl2ZXMgdGhlIGNoZWNrb3V0LCByZXBsYWNlIHRoZSBwb3N0Z3JlcyB2b2x1bWVzIGVudHJ5IHdpdGggYSBuYW1lZCB2b2x1bWVgLFxuXHRcdFx0XHRgZS5nLiBcIiR7bmFtZX0tcG9zdGdyZXMtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGFcIiBhbmQgZGVjbGFyZSBpdCB1bmRlciB0b3AtbGV2ZWwgdm9sdW1lczouYCxcblx0XHRcdClcblx0XHRcdC5hZGRTZXJ2aWNlKFwicG9zdGdyZXNcIiwge1xuXHRcdFx0XHRidWlsZDogXCIuL3Bvc3RncmVzXCIsXG5cdFx0XHRcdGNvbnRhaW5lcl9uYW1lOiBgc2FtcGxlcy0ke25hbWV9LXBvc3RncmVzYCxcblx0XHRcdFx0cmVzdGFydDogXCJ1bmxlc3Mtc3RvcHBlZFwiLFxuXHRcdFx0XHRwb3J0czogW1wiMTI3LjAuMC4xOjU0MzM6NTQzMlwiXSxcblx0XHRcdFx0dm9sdW1lczogW1BPU1RHUkVTX0RBVEFfQklORF0sXG5cdFx0XHRcdG5ldHdvcmtzOiBbeyBbbmV0d29ya0tleV06IHsgYWxpYXNlczogW1wicG9zdGdyZXNcIl0gfSB9XSxcblx0XHRcdFx0aGVhbHRoY2hlY2s6IHtcblx0XHRcdFx0XHQvLyBFbnN1cmUgcm9sZS9wYXNzd29yZCBtYXRjaCAuZW52IGJlZm9yZSByZXBvcnRpbmcgaGVhbHRoeS5cblx0XHRcdFx0XHR0ZXN0OiBbXG5cdFx0XHRcdFx0XHRcIkNNRC1TSEVMTFwiLFxuXHRcdFx0XHRcdFx0YHBnX2lzcmVhZHkgLVUgJHtwb3N0Z3Jlc1N1cGVydXNlcn0gJiYgL3Vzci9sb2NhbC9iaW4vZGVjb2hlcmUtZW5zdXJlLWJvb3RzdHJhcC5zaGAsXG5cdFx0XHRcdFx0XSxcblx0XHRcdFx0XHRpbnRlcnZhbDogXCIyc1wiLFxuXHRcdFx0XHRcdHRpbWVvdXQ6IFwiNXNcIixcblx0XHRcdFx0XHRyZXRyaWVzOiAxNSxcblx0XHRcdFx0fSxcblx0XHRcdH0pXG5cdFx0XHQuYWRkRW52RmlsZShcInBvc3RncmVzXCIsIFwiLi4vLmVudlwiKTtcblxuXHRcdHdvcmtzcGFjZS5nZXRNb2RlbDxHaXRpZ25vcmU+KFdFTExfS05PV05fRklMRVMuR0lUSUdOT1JFKT8uYWRkKFBPU1RHUkVTX0RBVEFfR0lUSUdOT1JFKTtcblxuXHRcdHdvcmtzcGFjZS5hZGRGaWxlKHtcblx0XHRcdGZpbGVzOiAoKSA9PiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHRmaWxlUGF0aDogXCIuZGV2Y29udGFpbmVyL3Bvc3RncmVzL0RvY2tlcmZpbGVcIixcblx0XHRcdFx0XHRjb250ZW50OiBQT1NUR1JFU19JTUFHRV9ET0NLRVJGSUxFLFxuXHRcdFx0XHR9LFxuXHRcdFx0XHR7XG5cdFx0XHRcdFx0ZmlsZVBhdGg6IFwiLmRldmNvbnRhaW5lci9wb3N0Z3Jlcy9lbnRyeXBvaW50LnNoXCIsXG5cdFx0XHRcdFx0Y29udGVudDogUE9TVEdSRVNfRU5UUllQT0lOVF9TSCxcblx0XHRcdFx0fSxcblx0XHRcdFx0e1xuXHRcdFx0XHRcdGZpbGVQYXRoOiBcIi5kZXZjb250YWluZXIvcG9zdGdyZXMvZW5zdXJlLWJvb3RzdHJhcC1yb2xlLnNoXCIsXG5cdFx0XHRcdFx0Y29udGVudDogUE9TVEdSRVNfRU5TVVJFX0JPT1RTVFJBUF9TSCxcblx0XHRcdFx0fSxcblx0XHRcdF0sXG5cdFx0fSk7XG5cblx0XHRjb21wb3NlLmFkZEVudkZpbGUoXCJkZXZjb250YWluZXJcIiwgXCIuLi8uZW52XCIpO1xuXHR9XG5cblx0ZGV2Q29udGFpbmVyPy5hZGRFeHRlbnNpb24oXCJtdHhyLnNxbHRvb2xzXCIsIFwibXR4ci5zcWx0b29scy1kcml2ZXItcGdcIik7XG5cdHdvcmtzcGFjZVxuXHRcdC5nZXRNb2RlbDxEb2NrZXJmaWxlTW9kZWw+KFdFTExfS05PV05fRklMRVMuRE9DS0VSRklMRSlcblx0XHQ/LmFkZExheWVyKHdpdGhQb3N0Z3Jlc3FsQ2xpZW50KCkpO1xuXG5cdGNvbnN0IHBrZyA9IHdvcmtzcGFjZVxuXHRcdC5nZXRNb2RlbDxQYWNrYWdlSnNvbj4oV0VMTF9LTk9XTl9GSUxFUy5QQUNLQUdFX0pTT04pXG5cdFx0Py5hZGREZXBlbmRlbmN5KFwiQGRlY29oZXJlL2NvZGV4XCIsIENSRUFURV9WRVJTSU9OKVxuXHRcdC5hZGREZXBlbmRlbmN5KFwidHlwZW9ybVwiLCBcIl4wLjMuMjhcIilcblx0XHQuYWRkRGVwZW5kZW5jeShcIkBkZWNvaGVyZS9jb2RleC10eXBlb3JtXCIsIENSRUFURV9WRVJTSU9OKVxuXHRcdC5hZGREZXBlbmRlbmN5KFwidHN4XCIsIFwiXjQuMjAuNVwiKVxuXHRcdC5hZGRTY3JpcHQoXCJkYjp1cFwiLCBcImRvY2tlciBjb21wb3NlIC1mIC5kZXZjb250YWluZXIvZG9ja2VyLWNvbXBvc2UueW1sIHVwIC1kIHBvc3RncmVzXCIpXG5cdFx0LmFkZFNjcmlwdChcImRiOmRvd25cIiwgXCJkb2NrZXIgY29tcG9zZSAtZiAuZGV2Y29udGFpbmVyL2RvY2tlci1jb21wb3NlLnltbCBkb3duXCIpXG5cdFx0LmFkZFNjcmlwdChcImRiOnJlc2V0XCIsIERCX1JFU0VUX1NDUklQVCk7XG5cblx0Y29uc3QgcHJpbWFyeUFwaSA9IGFwaU5hbWVzWzBdO1xuXHRpZiAocGtnICE9IG51bGwpIHtcblx0XHRmb3IgKGNvbnN0IGFwaU5hbWUgb2YgYXBpTmFtZXMpIHtcblx0XHRcdHBrZ1xuXHRcdFx0XHQuYWRkU2NyaXB0KGBkYjptaWdyYXRlOiR7YXBpTmFtZX1gLCBtaWdyYXRlUnVuU2NyaXB0KGFwaU5hbWUpKVxuXHRcdFx0XHQuYWRkU2NyaXB0KGBkYjptaWdyYXRlOmdlbmVyYXRlOiR7YXBpTmFtZX1gLCBtaWdyYXRlR2VuZXJhdGVTY3JpcHQoYXBpTmFtZSkpO1xuXHRcdH1cblx0XHQvLyBEZWZhdWx0IGFsaWFzZXMgdGFyZ2V0IHRoZSBmaXJzdCBBUEkgb2YgYSBuZXcgd29ya3NwYWNlIG9ubHkg4oCUIG5ldmVyIG92ZXJ3cml0ZSBvbiBgd2ViYXBpIGFkZGAuXG5cdFx0aWYgKCFvcHRpb25zLmFkZCAmJiBwcmltYXJ5QXBpICE9IG51bGwpIHtcblx0XHRcdHBrZ1xuXHRcdFx0XHQuYWRkU2NyaXB0KFwiZGI6bWlncmF0ZVwiLCBtaWdyYXRlUnVuU2NyaXB0KHByaW1hcnlBcGkpKVxuXHRcdFx0XHQuYWRkU2NyaXB0KFwiZGI6bWlncmF0ZTpnZW5lcmF0ZVwiLCBtaWdyYXRlR2VuZXJhdGVTY3JpcHQocHJpbWFyeUFwaSkpO1xuXHRcdH1cblx0fVxuXG5cdHdvcmtzcGFjZS5hZGRGaWxlKHtcblx0XHRmaWxlczogKCkgPT4gW1xuXHRcdFx0e1xuXHRcdFx0XHRmaWxlUGF0aDogXCJkb2NzL2RhdGFiYXNlLm1kXCIsXG5cdFx0XHRcdGNvbnRlbnQ6IERBVEFCQVNFX0RPQ1NfTUQsXG5cdFx0XHR9LFxuXHRcdF0sXG5cdH0pO1xuXG5cdHdyaXRlRGF0YWJhc2VDb25maWcod29ya3NwYWNlLCBhcGlOYW1lcyk7XG5cblx0Zm9yIChjb25zdCBwcm9qZWN0IG9mIGFwaVByb2plY3RzKSB7XG5cdFx0d2lyZUFwaURhdGFiYXNlKHByb2plY3QpO1xuXHR9XG5cblx0Y29uc3QgZW52ID0gd29ya3NwYWNlLmdldE1vZGVsPEVudkZpbGU+KFdFTExfS05PV05fRklMRVMuRU5WX0ZJTEUpO1xuXHRlbnZcblx0XHQ/LmFkZEVudHJ5KFwiUE9TVEdSRVNfREJcIiwgaWRlbnQpXG5cdFx0LmFkZEVudHJ5KFwiUE9TVEdSRVNfVVNFUlwiLCBwb3N0Z3Jlc1N1cGVydXNlcilcblx0XHQuYWRkRW50cnkoXCJQT1NUR1JFU19QQVNTV09SRFwiLCBwb3N0Z3Jlc1Bhc3N3b3JkKVxuXHRcdC5hZGRFbnRyeShcIkRCX0FETUlOX1VTRVJcIiwgYm9vdHN0cmFwVXNlcilcblx0XHQuYWRkRW50cnkoXCJEQl9BRE1JTl9QQVNTV09SRFwiLCBib290c3RyYXBQYXNzd29yZCk7XG5cblx0Zm9yIChjb25zdCBhcGlOYW1lIG9mIGFwaU5hbWVzKSB7XG5cdFx0d3JpdGVBcGlEYXRhYmFzZUVudih3b3Jrc3BhY2UsIGlkZW50LCBhcGlOYW1lKTtcblx0fVxufVxuXG5mdW5jdGlvbiB3cml0ZURhdGFiYXNlQ29uZmlnKHdvcmtzcGFjZTogV29ya3NwYWNlU2NhZmZvbGQsIGFwaU5hbWVzOiByZWFkb25seSBzdHJpbmdbXSk6IHZvaWQge1xuXHRjb25zdCBjb25maWcgPSB3b3Jrc3BhY2UuZ2V0TW9kZWw8Q29uZmlnWWFtbD4oV0VMTF9LTk9XTl9GSUxFUy5DT05GSUdfWUFNTCk7XG5cdGNvbnN0IGRldkNvbmZpZyA9IHdvcmtzcGFjZS5nZXRNb2RlbDxDb25maWdZYW1sPihXRUxMX0tOT1dOX0ZJTEVTLkNPTkZJR19ERVZfWUFNTCk7XG5cdGNvbnN0IHBlbmRpbmcgPSBhcGlOYW1lcy5maWx0ZXIoKGFwaU5hbWUpID0+ICFjb25maWc/Lmhhc0RhdGFTb3VyY2UoYXBpTmFtZSkpO1xuXHRpZiAocGVuZGluZy5sZW5ndGggPT09IDApIHJldHVybjtcblxuXHRpZiAoY29uZmlnPy5oYXNEYXRhU291cmNlcygpKSB7XG5cdFx0Zm9yIChjb25zdCBhcGlOYW1lIG9mIHBlbmRpbmcpIHtcblx0XHRcdGNvbmZpZy5hcHBlbmREYXRhU291cmNlKHJlbmRlckJhc2VEYXRhU291cmNlKGFwaU5hbWUpKTtcblx0XHRcdGRldkNvbmZpZz8uYXBwZW5kRGF0YVNvdXJjZShyZW5kZXJEZXZEYXRhU291cmNlKGFwaU5hbWUpKTtcblx0XHR9XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0Y29uZmlnPy5hZGRTZWN0aW9uKHJlbmRlckRhdGFiYXNlU2VjdGlvbihwZW5kaW5nLCByZW5kZXJCYXNlRGF0YVNvdXJjZSkpO1xuXHRkZXZDb25maWc/LmFkZFNlY3Rpb24ocmVuZGVyRGF0YWJhc2VTZWN0aW9uKHBlbmRpbmcsIHJlbmRlckRldkRhdGFTb3VyY2UpKTtcbn1cblxuZnVuY3Rpb24gd3JpdGVBcGlEYXRhYmFzZUVudihcblx0d29ya3NwYWNlOiBXb3Jrc3BhY2VTY2FmZm9sZCxcblx0aWRlbnQ6IHN0cmluZyxcblx0YXBpTmFtZTogc3RyaW5nLFxuKTogdm9pZCB7XG5cdGNvbnN0IHByZWZpeCA9IGFwaUVudlByZWZpeChhcGlOYW1lKTtcblx0Y29uc3Qgc3FsQXBpID0gdG9TcWxJZGVudEZyYWdtZW50KGFwaU5hbWUpO1xuXHR3b3Jrc3BhY2Vcblx0XHQuZ2V0TW9kZWw8RW52RmlsZT4oV0VMTF9LTk9XTl9GSUxFUy5FTlZfRklMRSlcblx0XHQ/LmFkZEVudHJ5KGAke3ByZWZpeH1fREJfSE9TVGAsIFwicG9zdGdyZXNcIilcblx0XHQuYWRkRW50cnkoYCR7cHJlZml4fV9EQl9QT1JUYCwgXCI1NDMyXCIpXG5cdFx0LmFkZEVudHJ5KGAke3ByZWZpeH1fREJfTkFNRWAsIGAke2lkZW50fV8ke3NxbEFwaX1fZGV2YClcblx0XHQuYWRkRW50cnkoYCR7cHJlZml4fV9EQl9VU0VSYCwgYCR7aWRlbnR9XyR7c3FsQXBpfV9hcHBgKVxuXHRcdC5hZGRFbnRyeShgJHtwcmVmaXh9X0RCX1BBU1NXT1JEYCwgcGFzc3dvcmRPckV4aXN0aW5nKHdvcmtzcGFjZSwgYCR7cHJlZml4fV9EQl9QQVNTV09SRGApKVxuXHRcdC5hZGRFbnRyeShgJHtwcmVmaXh9X0RCX01JR1JBVEVfVVNFUmAsIGAke2lkZW50fV8ke3NxbEFwaX1fbWlncmF0ZWApXG5cdFx0LmFkZEVudHJ5KFxuXHRcdFx0YCR7cHJlZml4fV9EQl9NSUdSQVRFX1BBU1NXT1JEYCxcblx0XHRcdHBhc3N3b3JkT3JFeGlzdGluZyh3b3Jrc3BhY2UsIGAke3ByZWZpeH1fREJfTUlHUkFURV9QQVNTV09SRGApLFxuXHRcdCk7XG59XG5cbmZ1bmN0aW9uIHdpcmVBcGlEYXRhYmFzZShwcm9qZWN0OiBQcm9qZWN0U2NhZmZvbGQpOiB2b2lkIHtcblx0Y29uc3QgYXBpTmFtZSA9IGFwaU5hbWVGcm9tUHJvamVjdERpcihwcm9qZWN0LmRpcik7XG5cdHByb2plY3Rcblx0XHQuZ2V0TW9kZWw8TWFpblNvdXJjZUZpbGU+KFdFTExfS05PV05fRklMRVMuTUFJTl9UUylcblx0XHQ/LnNldERhdGFTb3VyY2VOYW1lKGFwaU5hbWUpXG5cdFx0LmFkZFNpZGVFZmZlY3RJbXBvcnQoXCJAZGVjb2hlcmUvY29kZXgtdHlwZW9ybVwiKVxuXHRcdC5hZGRFbnRpdHkoeyBjbGFzc05hbWU6IFwiU2FtcGxlRW50aXR5XCIsIGltcG9ydFBhdGg6IFwiLi9lbnRpdGllcy9zYW1wbGUuZW50aXR5LmpzXCIgfSlcblx0XHQuYWRkUmVwb3NpdG9yeSh7XG5cdFx0XHRjbGFzc05hbWU6IFwiU2FtcGxlRW50aXR5UmVwb3NpdG9yeVwiLFxuXHRcdFx0aW1wb3J0UGF0aDogXCIuL3JlcG9zaXRvcmllcy9zYW1wbGUucmVwb3NpdG9yeS5qc1wiLFxuXHRcdH0pO1xuXHRwcm9qZWN0XG5cdFx0LmdldE1vZGVsPEFwaVBhY2thZ2VKc29uPihXRUxMX0tOT1dOX0ZJTEVTLkFQSV9QQUNLQUdFX0pTT04pXG5cdFx0Py5hZGREZXBlbmRlbmN5KFwiQGRlY29oZXJlL2NvZGV4XCIsIENSRUFURV9WRVJTSU9OKVxuXHRcdC5hZGREZXBlbmRlbmN5KFwiQGRlY29oZXJlL2NvZGV4LXR5cGVvcm1cIiwgQ1JFQVRFX1ZFUlNJT04pO1xuXHRwcm9qZWN0LmFkZEZpbGUoe1xuXHRcdGZpbGVzOiAoKSA9PiBbXG5cdFx0XHR7XG5cdFx0XHRcdGZpbGVQYXRoOiBcInNyYy9lbnRpdGllcy9zYW1wbGUuZW50aXR5LnRzXCIsXG5cdFx0XHRcdGNvbnRlbnQ6IFNBTVBMRV9FTlRJVFlfVFMsXG5cdFx0XHR9LFxuXHRcdFx0e1xuXHRcdFx0XHRmaWxlUGF0aDogXCJzcmMvcmVwb3NpdG9yaWVzL3NhbXBsZS5yZXBvc2l0b3J5LnRzXCIsXG5cdFx0XHRcdGNvbnRlbnQ6IFNBTVBMRV9SRVBPU0lUT1JZX1RTLFxuXHRcdFx0fSxcblx0XHRcdHtcblx0XHRcdFx0ZmlsZVBhdGg6IFwic3JjL3R5cGVvcm0uZGF0YS1zb3VyY2UudHNcIixcblx0XHRcdFx0Y29udGVudDogcmVuZGVyVHlwZU9ybURhdGFTb3VyY2UoYXBpTmFtZSksXG5cdFx0XHR9LFxuXHRcdFx0e1xuXHRcdFx0XHRmaWxlUGF0aDogXCJzcmMvbWlncmF0aW9ucy8uZ2l0a2VlcFwiLFxuXHRcdFx0XHRjb250ZW50OiBcIlwiLFxuXHRcdFx0fSxcblx0XHRdLFxuXHR9KTtcbn1cblxuZXhwb3J0IGNvbnN0IERhdGFiYXNlRmVhdHVyZTogRmVhdHVyZTxEYXRhYmFzZU9wdGlvbnM+ID0geyBnZXRPcHRpb25zLCBzY2FmZm9sZCB9O1xuIl0sIm5hbWVzIjpbIkRhdGFiYXNlRmVhdHVyZSIsIlBPU1RHUkVTX0RBVEFfQklORCIsIlBPU1RHUkVTX0RBVEFfR0lUSUdOT1JFIiwiREJfUkVTRVRfU0NSSVBUIiwicmVhZEV4aXN0aW5nRW52VmFsdWUiLCJ3b3Jrc3BhY2UiLCJrZXkiLCJyYXciLCJyZWFkRmlsZVN5bmMiLCJqb2luIiwidGFyZ2V0RGlyIiwibGluZSIsInNwbGl0IiwiZmluZCIsImwiLCJzdGFydHNXaXRoIiwidmFsdWUiLCJzbGljZSIsImxlbmd0aCIsInRyaW0iLCJ1bmRlZmluZWQiLCJyYW5kb21QYXNzd29yZCIsInJhbmRvbUJ5dGVzIiwidG9TdHJpbmciLCJ0b1NxbElkZW50RnJhZ21lbnQiLCJuYW1lIiwiY2xlYW5lZCIsInJlcGxhY2UiLCJwYXNzd29yZE9yRXhpc3RpbmciLCJmYWxsYmFja0V4aXN0aW5nS2V5cyIsImV4aXN0aW5nIiwiYWx0IiwidiIsInJlbmRlckJhc2VEYXRhU291cmNlIiwiYXBpTmFtZSIsInAiLCJhcGlFbnZQcmVmaXgiLCJyZW5kZXJEZXZEYXRhU291cmNlIiwicmVuZGVyRGF0YWJhc2VTZWN0aW9uIiwiYXBpTmFtZXMiLCJpdGVtcyIsIm1hcCIsIlBPU1RHUkVTX0VOU1VSRV9CT09UU1RSQVBfU0giLCJQT1NUR1JFU19FTlRSWVBPSU5UX1NIIiwiUE9TVEdSRVNfSU1BR0VfRE9DS0VSRklMRSIsIlNBTVBMRV9FTlRJVFlfVFMiLCJTQU1QTEVfUkVQT1NJVE9SWV9UUyIsInJlbmRlclR5cGVPcm1EYXRhU291cmNlIiwiREFUQUJBU0VfRE9DU19NRCIsInR5cGVvcm1DbGkiLCJhcmdzIiwibWlncmF0ZVJ1blNjcmlwdCIsIm1pZ3JhdGVHZW5lcmF0ZVNjcmlwdCIsImdldE9wdGlvbnMiLCJfd29ya3NwYWNlIiwiY2xpT3B0aW9ucyIsImRhdGFiYXNlUHJvdmlkZXIiLCJjaG9pY2VzIiwibGFiZWwiLCJzZWxlY3RlZCIsInBpY2siLCJjIiwic2NhZmZvbGQiLCJvcHRpb25zIiwiaWRlbnQiLCJhcGlQcm9qZWN0cyIsImdldFByb2plY3RzIiwicHJvamVjdCIsImFwaU5hbWVGcm9tUHJvamVjdERpciIsImRpciIsInBvc3RncmVzU3VwZXJ1c2VyIiwicG9zdGdyZXNQYXNzd29yZCIsImJvb3RzdHJhcFVzZXIiLCJib290c3RyYXBQYXNzd29yZCIsImRldkNvbnRhaW5lciIsImdldE1vZGVsIiwiV0VMTF9LTk9XTl9GSUxFUyIsIkRFVkNPTlRBSU5FUl9KU09OIiwiY29tcG9zZSIsIkRPQ0tFUl9DT01QT1NFIiwibmV0d29ya0tleSIsImFkZEhlYWRlckNvbW1lbnQiLCJhZGRTZXJ2aWNlIiwiYnVpbGQiLCJjb250YWluZXJfbmFtZSIsInJlc3RhcnQiLCJwb3J0cyIsInZvbHVtZXMiLCJuZXR3b3JrcyIsImFsaWFzZXMiLCJoZWFsdGhjaGVjayIsInRlc3QiLCJpbnRlcnZhbCIsInRpbWVvdXQiLCJyZXRyaWVzIiwiYWRkRW52RmlsZSIsIkdJVElHTk9SRSIsImFkZCIsImFkZEZpbGUiLCJmaWxlcyIsImZpbGVQYXRoIiwiY29udGVudCIsImFkZEV4dGVuc2lvbiIsIkRPQ0tFUkZJTEUiLCJhZGRMYXllciIsIndpdGhQb3N0Z3Jlc3FsQ2xpZW50IiwicGtnIiwiUEFDS0FHRV9KU09OIiwiYWRkRGVwZW5kZW5jeSIsIkNSRUFURV9WRVJTSU9OIiwiYWRkU2NyaXB0IiwicHJpbWFyeUFwaSIsIndyaXRlRGF0YWJhc2VDb25maWciLCJ3aXJlQXBpRGF0YWJhc2UiLCJlbnYiLCJFTlZfRklMRSIsImFkZEVudHJ5Iiwid3JpdGVBcGlEYXRhYmFzZUVudiIsImNvbmZpZyIsIkNPTkZJR19ZQU1MIiwiZGV2Q29uZmlnIiwiQ09ORklHX0RFVl9ZQU1MIiwicGVuZGluZyIsImZpbHRlciIsImhhc0RhdGFTb3VyY2UiLCJoYXNEYXRhU291cmNlcyIsImFwcGVuZERhdGFTb3VyY2UiLCJhZGRTZWN0aW9uIiwicHJlZml4Iiwic3FsQXBpIiwiTUFJTl9UUyIsInNldERhdGFTb3VyY2VOYW1lIiwiYWRkU2lkZUVmZmVjdEltcG9ydCIsImFkZEVudGl0eSIsImNsYXNzTmFtZSIsImltcG9ydFBhdGgiLCJhZGRSZXBvc2l0b3J5IiwiQVBJX1BBQ0tBR0VfSlNPTiJdLCJtYXBwaW5ncyI6Ijs7OzsrQkF5Z0JhQTs7O2VBQUFBOzs7NEJBemdCZTt3QkFDQzswQkFDUjt3QkFDQTt1QkFHWTtrQ0FLMUI7eUJBTXdCO3lCQUNxQjtBQUlwRCwyRkFBMkYsR0FDM0YsTUFBTUMscUJBQXFCO0FBQzNCLE1BQU1DLDBCQUEwQjtBQUNoQyxNQUFNQyxrQkFDTDtBQVFELGdFQUFnRSxHQUNoRSxTQUFTQyxxQkFBcUJDLFNBQTRCLEVBQUVDLEdBQVc7SUFDdEUsSUFBSTtRQUNILE1BQU1DLE1BQU1DLElBQUFBLG9CQUFZLEVBQUNDLElBQUFBLGNBQUksRUFBQ0osVUFBVUssU0FBUyxFQUFFLFNBQVM7UUFDNUQsTUFBTUMsT0FBT0osSUFBSUssS0FBSyxDQUFDLE1BQU1DLElBQUksQ0FBQyxDQUFDQyxJQUFNQSxFQUFFQyxVQUFVLENBQUMsR0FBR1QsSUFBSSxDQUFDLENBQUM7UUFDL0QsTUFBTVUsUUFBUUwsTUFBTU0sTUFBTVgsSUFBSVksTUFBTSxHQUFHLEdBQUdDO1FBQzFDLE9BQU9ILFNBQVNJO0lBQ2pCLEVBQUUsT0FBTTtRQUNQLE9BQU9B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
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "FinalizeFeature", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return FinalizeFeature;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _nodefs = require("node:fs");
|
|
12
|
+
const _nodepath = require("node:path");
|
|
13
|
+
const _interop = require("@decohere/nexus/interop");
|
|
14
|
+
const _prompt = require("../../domain/prompt.js");
|
|
15
|
+
const _const = require("../../models/const.js");
|
|
16
|
+
const _workspacereadme = require("../workspace-readme.js");
|
|
17
|
+
const _biomeformat = require("./biome-format.js");
|
|
18
|
+
function readExistingEnvValue(workspace, key) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = (0, _nodefs.readFileSync)((0, _nodepath.join)(workspace.targetDir, ".env"), "utf8");
|
|
21
|
+
const line = raw.split("\n").find((l)=>l.startsWith(`${key}=`));
|
|
22
|
+
const value = line?.slice(key.length + 1).trim();
|
|
23
|
+
return value || undefined;
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// State resolved during scaffold(), consumed by finalize().
|
|
29
|
+
let _pm = null;
|
|
30
|
+
let _shouldInstall = true;
|
|
31
|
+
async function getOptions(_workspace, cliOptions) {
|
|
32
|
+
if (cliOptions.installDependencies != null) {
|
|
33
|
+
_shouldInstall = cliOptions.installDependencies === "yes";
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
async function scaffold(workspace, options) {
|
|
38
|
+
// Local scaffold default — production deploys must set NODE_ENV explicitly.
|
|
39
|
+
const nodeEnv = readExistingEnvValue(workspace, "NODE_ENV") ?? "dev";
|
|
40
|
+
workspace.getModel(_const.WELL_KNOWN_FILES.ENV_FILE)?.addEntry("NODE_ENV", nodeEnv);
|
|
41
|
+
// Durable day-2 CLI examples (also refreshed on `webapi add`).
|
|
42
|
+
(0, _workspacereadme.writeWorkspaceReadme)(workspace, options);
|
|
43
|
+
if (process.env.DECOHERE_BUILDER === "1") {
|
|
44
|
+
_shouldInstall = false;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
_pm = (0, _interop.detectPackageManager)(workspace.targetDir) ?? await (0, _interop.promptForPackageManager)();
|
|
48
|
+
if (options.installDependencies != null) {
|
|
49
|
+
_shouldInstall = options.installDependencies === "yes";
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const answer = await (0, _prompt.ask)("\nInstall dependencies now? [yes/no] (yes): ", "yes");
|
|
53
|
+
_shouldInstall = answer.toLowerCase() !== "no";
|
|
54
|
+
}
|
|
55
|
+
async function finalize(workspace, options) {
|
|
56
|
+
const formatPaths = options.add ? workspace.getProjects().map((project)=>project.dir) : [
|
|
57
|
+
"."
|
|
58
|
+
];
|
|
59
|
+
(0, _biomeformat.formatScaffoldedFiles)(workspace.targetDir, formatPaths.length > 0 ? formatPaths : [
|
|
60
|
+
"."
|
|
61
|
+
]);
|
|
62
|
+
if (_shouldInstall) {
|
|
63
|
+
const pm = _pm;
|
|
64
|
+
console.log(`\nScaffolded ${workspace.name}. Installing dependencies...\n`);
|
|
65
|
+
await pm.install({
|
|
66
|
+
cwd: workspace.targetDir
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const apiNames = workspace.getProjects().map((project)=>project.dir.replace(/^projects\//, ""));
|
|
70
|
+
console.log(`\nDone! ${_shouldInstall ? "Next" : "To get started:"} steps:\n`);
|
|
71
|
+
if (!options.add) {
|
|
72
|
+
console.log(` cd ${workspace.name}`);
|
|
73
|
+
}
|
|
74
|
+
if (!_shouldInstall && options.add) {
|
|
75
|
+
const pmName = _pm?.name ?? "pnpm";
|
|
76
|
+
console.log(` ${pmName} install`);
|
|
77
|
+
}
|
|
78
|
+
if (options.add && apiNames[0] != null) {
|
|
79
|
+
console.log(` pnpm run start:${apiNames[0]}`);
|
|
80
|
+
console.log(` docker build -f Dockerfile.${apiNames[0]} .`);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(" code . # open in VS Code");
|
|
83
|
+
console.log(" # then: Reopen in Container (first create runs pnpm install)\n");
|
|
84
|
+
}
|
|
85
|
+
console.log(" See README.md for CLI examples.\n");
|
|
86
|
+
// Drop readline's stdin hold so the CLI / container can exit.
|
|
87
|
+
(0, _prompt.closePrompt)();
|
|
88
|
+
}
|
|
89
|
+
const FinalizeFeature = {
|
|
90
|
+
getOptions,
|
|
91
|
+
scaffold,
|
|
92
|
+
finalize
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL3NjYWZmb2xkL3NyYy93ZWJhcGkvZmVhdHVyZXMvZmluYWxpemUuZmVhdHVyZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyByZWFkRmlsZVN5bmMgfSBmcm9tIFwibm9kZTpmc1wiO1xuaW1wb3J0IHsgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB0eXBlIHsgUGFja2FnZU1hbmFnZXIgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL2ludGVyb3BcIjtcbmltcG9ydCB7IGRldGVjdFBhY2thZ2VNYW5hZ2VyLCBwcm9tcHRGb3JQYWNrYWdlTWFuYWdlciB9IGZyb20gXCJAZGVjb2hlcmUvbmV4dXMvaW50ZXJvcFwiO1xuaW1wb3J0IHsgYXNrLCBjbG9zZVByb21wdCB9IGZyb20gXCIuLi8uLi9kb21haW4vcHJvbXB0LmpzXCI7XG5pbXBvcnQgeyBXRUxMX0tOT1dOX0ZJTEVTIH0gZnJvbSBcIi4uLy4uL21vZGVscy9jb25zdC5qc1wiO1xuaW1wb3J0IHR5cGUgeyBFbnZGaWxlIH0gZnJvbSBcIi4uLy4uL21vZGVscy9lbnYtZmlsZS5tb2RlbC5qc1wiO1xuaW1wb3J0IHR5cGUgeyBXb3Jrc3BhY2VTY2FmZm9sZCB9IGZyb20gXCIuLi8uLi9tb2RlbHMvcHJvamVjdC5tb2RlbC5qc1wiO1xuaW1wb3J0IHR5cGUgeyBGZWF0dXJlIH0gZnJvbSBcIi4uLy4uL3NlcnZpY2VzL3NjYWZmb2xkLnNlcnZpY2UuanNcIjtcbmltcG9ydCB7IHdyaXRlV29ya3NwYWNlUmVhZG1lIH0gZnJvbSBcIi4uL3dvcmtzcGFjZS1yZWFkbWUuanNcIjtcbmltcG9ydCB7IGZvcm1hdFNjYWZmb2xkZWRGaWxlcyB9IGZyb20gXCIuL2Jpb21lLWZvcm1hdC5qc1wiO1xuXG5mdW5jdGlvbiByZWFkRXhpc3RpbmdFbnZWYWx1ZSh3b3Jrc3BhY2U6IFdvcmtzcGFjZVNjYWZmb2xkLCBrZXk6IHN0cmluZyk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG5cdHRyeSB7XG5cdFx0Y29uc3QgcmF3ID0gcmVhZEZpbGVTeW5jKGpvaW4od29ya3NwYWNlLnRhcmdldERpciwgXCIuZW52XCIpLCBcInV0ZjhcIik7XG5cdFx0Y29uc3QgbGluZSA9IHJhdy5zcGxpdChcIlxcblwiKS5maW5kKChsKSA9PiBsLnN0YXJ0c1dpdGgoYCR7a2V5fT1gKSk7XG5cdFx0Y29uc3QgdmFsdWUgPSBsaW5lPy5zbGljZShrZXkubGVuZ3RoICsgMSkudHJpbSgpO1xuXHRcdHJldHVybiB2YWx1ZSB8fCB1bmRlZmluZWQ7XG5cdH0gY2F0Y2gge1xuXHRcdHJldHVybiB1bmRlZmluZWQ7XG5cdH1cbn1cblxuLy8gU3RhdGUgcmVzb2x2ZWQgZHVyaW5nIHNjYWZmb2xkKCksIGNvbnN1bWVkIGJ5IGZpbmFsaXplKCkuXG5sZXQgX3BtOiBQYWNrYWdlTWFuYWdlciB8IG51bGwgPSBudWxsO1xubGV0IF9zaG91bGRJbnN0YWxsID0gdHJ1ZTtcblxudHlwZSBGaW5hbGl6ZU9wdGlvbnMgPSB7XG5cdGluc3RhbGxEZXBlbmRlbmNpZXM/OiBcInllc1wiIHwgXCJub1wiO1xuXHRhZGQ/OiBib29sZWFuO1xuXHRkYXRhYmFzZVByb3ZpZGVyPzogXCJub25lXCIgfCBcInBvc3RncmVzL3R5cGVvcm1cIjtcblx0YXV0aFByb3ZpZGVyPzogXCJhdXRoMFwiIHwgXCJub25lXCI7XG5cdHBvc3RtYW5Qcm92aWRlcj86IFwiYXBpXCIgfCBcImxvY2FsXCIgfCBcIm5vbmVcIjtcbn07XG5cbmFzeW5jIGZ1bmN0aW9uIGdldE9wdGlvbnMoXG5cdF93b3Jrc3BhY2U6IFdvcmtzcGFjZVNjYWZmb2xkLFxuXHRjbGlPcHRpb25zOiBQYXJ0aWFsPEZpbmFsaXplT3B0aW9ucz4sXG4pOiBQcm9taXNlPG9iamVjdD4ge1xuXHRpZiAoY2xpT3B0aW9ucy5pbnN0YWxsRGVwZW5kZW5jaWVzICE9IG51bGwpIHtcblx0XHRfc2hvdWxkSW5zdGFsbCA9IGNsaU9wdGlvbnMuaW5zdGFsbERlcGVuZGVuY2llcyA9PT0gXCJ5ZXNcIjtcblx0fVxuXHRyZXR1cm4ge307XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHNjYWZmb2xkKFxuXHR3b3Jrc3BhY2U6IFdvcmtzcGFjZVNjYWZmb2xkLFxuXHRvcHRpb25zOiBGaW5hbGl6ZU9wdGlvbnMsXG4pOiBQcm9taXNlPHZvaWQ+IHtcblx0Ly8gTG9jYWwgc2NhZmZvbGQgZGVmYXVsdCDigJQgcHJvZHVjdGlvbiBkZXBsb3lzIG11c3Qgc2V0IE5PREVfRU5WIGV4cGxpY2l0bHkuXG5cdGNvbnN0IG5vZGVFbnYgPSByZWFkRXhpc3RpbmdFbnZWYWx1ZSh3b3Jrc3BhY2UsIFwiTk9ERV9FTlZcIikgPz8gXCJkZXZcIjtcblx0d29ya3NwYWNlLmdldE1vZGVsPEVudkZpbGU+KFdFTExfS05PV05fRklMRVMuRU5WX0ZJTEUpPy5hZGRFbnRyeShcIk5PREVfRU5WXCIsIG5vZGVFbnYpO1xuXG5cdC8vIER1cmFibGUgZGF5LTIgQ0xJIGV4YW1wbGVzIChhbHNvIHJlZnJlc2hlZCBvbiBgd2ViYXBpIGFkZGApLlxuXHR3cml0ZVdvcmtzcGFjZVJlYWRtZSh3b3Jrc3BhY2UsIG9wdGlvbnMpO1xuXG5cdGlmIChwcm9jZXNzLmVudi5ERUNPSEVSRV9CVUlMREVSID09PSBcIjFcIikge1xuXHRcdF9zaG91bGRJbnN0YWxsID0gZmFsc2U7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdF9wbSA9IGRldGVjdFBhY2thZ2VNYW5hZ2VyKHdvcmtzcGFjZS50YXJnZXREaXIpID8/IChhd2FpdCBwcm9tcHRGb3JQYWNrYWdlTWFuYWdlcigpKTtcblx0aWYgKG9wdGlvbnMuaW5zdGFsbERlcGVuZGVuY2llcyAhPSBudWxsKSB7XG5cdFx0X3Nob3VsZEluc3RhbGwgPSBvcHRpb25zLmluc3RhbGxEZXBlbmRlbmNpZXMgPT09IFwieWVzXCI7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0Y29uc3QgYW5zd2VyID0gYXdhaXQgYXNrKFwiXFxuSW5zdGFsbCBkZXBlbmRlbmNpZXMgbm93PyBbeWVzL25vXSAoeWVzKTogXCIsIFwieWVzXCIpO1xuXHRfc2hvdWxkSW5zdGFsbCA9IGFuc3dlci50b0xvd2VyQ2FzZSgpICE9PSBcIm5vXCI7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGZpbmFsaXplKFxuXHR3b3Jrc3BhY2U6IFdvcmtzcGFjZVNjYWZmb2xkLFxuXHRvcHRpb25zOiBGaW5hbGl6ZU9wdGlvbnMsXG4pOiBQcm9taXNlPHZvaWQ+IHtcblx0Y29uc3QgZm9ybWF0UGF0aHMgPSBvcHRpb25zLmFkZFxuXHRcdD8gd29ya3NwYWNlLmdldFByb2plY3RzKCkubWFwKChwcm9qZWN0KSA9PiBwcm9qZWN0LmRpcilcblx0XHQ6IFtcIi5cIl07XG5cdGZvcm1hdFNjYWZmb2xkZWRGaWxlcyh3b3Jrc3BhY2UudGFyZ2V0RGlyLCBmb3JtYXRQYXRocy5sZW5ndGggPiAwID8gZm9ybWF0UGF0aHMgOiBbXCIuXCJdKTtcblxuXHRpZiAoX3Nob3VsZEluc3RhbGwpIHtcblx0XHRjb25zdCBwbSA9IF9wbSE7XG5cdFx0Y29uc29sZS5sb2coYFxcblNjYWZmb2xkZWQgJHt3b3Jrc3BhY2UubmFtZX0uIEluc3RhbGxpbmcgZGVwZW5kZW5jaWVzLi4uXFxuYCk7XG5cdFx0YXdhaXQgcG0uaW5zdGFsbCh7IGN3ZDogd29ya3NwYWNlLnRhcmdldERpciB9KTtcblx0fVxuXG5cdGNvbnN0IGFwaU5hbWVzID0gd29ya3NwYWNlXG5cdFx0LmdldFByb2plY3RzKClcblx0XHQubWFwKChwcm9qZWN0KSA9PiBwcm9qZWN0LmRpci5yZXBsYWNlKC9ecHJvamVjdHNcXC8vLCBcIlwiKSk7XG5cdGNvbnNvbGUubG9nKGBcXG5Eb25lISAke19zaG91bGRJbnN0YWxsID8gXCJOZXh0XCIgOiBcIlRvIGdldCBzdGFydGVkOlwifSBzdGVwczpcXG5gKTtcblx0aWYgKCFvcHRpb25zLmFkZCkge1xuXHRcdGNvbnNvbGUubG9nKGAgIGNkICR7d29ya3NwYWNlLm5hbWV9YCk7XG5cdH1cblx0aWYgKCFfc2hvdWxkSW5zdGFsbCAmJiBvcHRpb25zLmFkZCkge1xuXHRcdGNvbnN0IHBtTmFtZSA9IF9wbT8ubmFtZSA/PyBcInBucG1cIjtcblx0XHRjb25zb2xlLmxvZyhgICAke3BtTmFtZX0gaW5zdGFsbGApO1xuXHR9XG5cdGlmIChvcHRpb25zLmFkZCAmJiBhcGlOYW1lc1swXSAhPSBudWxsKSB7XG5cdFx0Y29uc29sZS5sb2coYCAgcG5wbSBydW4gc3RhcnQ6JHthcGlOYW1lc1swXX1gKTtcblx0XHRjb25zb2xlLmxvZyhgICBkb2NrZXIgYnVpbGQgLWYgRG9ja2VyZmlsZS4ke2FwaU5hbWVzWzBdfSAuYCk7XG5cdH0gZWxzZSB7XG5cdFx0Y29uc29sZS5sb2coXCIgIGNvZGUgLiAgICAgICAgICMgb3BlbiBpbiBWUyBDb2RlXCIpO1xuXHRcdGNvbnNvbGUubG9nKFxuXHRcdFx0XCIgICAgICAgICAgICAgICAgICMgdGhlbjogUmVvcGVuIGluIENvbnRhaW5lciAoZmlyc3QgY3JlYXRlIHJ1bnMgcG5wbSBpbnN0YWxsKVxcblwiLFxuXHRcdCk7XG5cdH1cblx0Y29uc29sZS5sb2coXCIgIFNlZSBSRUFETUUubWQgZm9yIENMSSBleGFtcGxlcy5cXG5cIik7XG5cblx0Ly8gRHJvcCByZWFkbGluZSdzIHN0ZGluIGhvbGQgc28gdGhlIENMSSAvIGNvbnRhaW5lciBjYW4gZXhpdC5cblx0Y2xvc2VQcm9tcHQoKTtcbn1cblxuZXhwb3J0IGNvbnN0IEZpbmFsaXplRmVhdHVyZTogRmVhdHVyZSA9IHsgZ2V0T3B0aW9ucywgc2NhZmZvbGQsIGZpbmFsaXplIH07XG4iXSwibmFtZXMiOlsiRmluYWxpemVGZWF0dXJlIiwicmVhZEV4aXN0aW5nRW52VmFsdWUiLCJ3b3Jrc3BhY2UiLCJrZXkiLCJyYXciLCJyZWFkRmlsZVN5bmMiLCJqb2luIiwidGFyZ2V0RGlyIiwibGluZSIsInNwbGl0IiwiZmluZCIsImwiLCJzdGFydHNXaXRoIiwidmFsdWUiLCJzbGljZSIsImxlbmd0aCIsInRyaW0iLCJ1bmRlZmluZWQiLCJfcG0iLCJfc2hvdWxkSW5zdGFsbCIsImdldE9wdGlvbnMiLCJfd29ya3NwYWNlIiwiY2xpT3B0aW9ucyIsImluc3RhbGxEZXBlbmRlbmNpZXMiLCJzY2FmZm9sZCIsIm9wdGlvbnMiLCJub2RlRW52IiwiZ2V0TW9kZWwiLCJXRUxMX0tOT1dOX0ZJTEVTIiwiRU5WX0ZJTEUiLCJhZGRFbnRyeSIsIndyaXRlV29ya3NwYWNlUmVhZG1lIiwicHJvY2VzcyIsImVudiIsIkRFQ09IRVJFX0JVSUxERVIiLCJkZXRlY3RQYWNrYWdlTWFuYWdlciIsInByb21wdEZvclBhY2thZ2VNYW5hZ2VyIiwiYW5zd2VyIiwiYXNrIiwidG9Mb3dlckNhc2UiLCJmaW5hbGl6ZSIsImZvcm1hdFBhdGhzIiwiYWRkIiwiZ2V0UHJvamVjdHMiLCJtYXAiLCJwcm9qZWN0IiwiZGlyIiwiZm9ybWF0U2NhZmZvbGRlZEZpbGVzIiwicG0iLCJjb25zb2xlIiwibG9nIiwibmFtZSIsImluc3RhbGwiLCJjd2QiLCJhcGlOYW1lcyIsInJlcGxhY2UiLCJwbU5hbWUiLCJjbG9zZVByb21wdCJdLCJtYXBwaW5ncyI6Ijs7OzsrQkErR2FBOzs7ZUFBQUE7Ozt3QkEvR2dCOzBCQUNSO3lCQUV5Qzt3QkFDN0I7dUJBQ0E7aUNBSUk7NkJBQ0M7QUFFdEMsU0FBU0MscUJBQXFCQyxTQUE0QixFQUFFQyxHQUFXO0lBQ3RFLElBQUk7UUFDSCxNQUFNQyxNQUFNQyxJQUFBQSxvQkFBWSxFQUFDQyxJQUFBQSxjQUFJLEVBQUNKLFVBQVVLLFNBQVMsRUFBRSxTQUFTO1FBQzVELE1BQU1DLE9BQU9KLElBQUlLLEtBQUssQ0FBQyxNQUFNQyxJQUFJLENBQUMsQ0FBQ0MsSUFBTUEsRUFBRUMsVUFBVSxDQUFDLEdBQUdULElBQUksQ0FBQyxDQUFDO1FBQy9ELE1BQU1VLFFBQVFMLE1BQU1NLE1BQU1YLElBQUlZLE1BQU0sR0FBRyxHQUFHQztRQUMxQyxPQUFPSCxTQUFTSTtJQUNqQixFQUFFLE9BQU07UUFDUCxPQUFPQTtJQUNSO0FBQ0Q7QUFFQSw0REFBNEQ7QUFDNUQsSUFBSUMsTUFBNkI7QUFDakMsSUFBSUMsaUJBQWlCO0FBVXJCLGVBQWVDLFdBQ2RDLFVBQTZCLEVBQzdCQyxVQUFvQztJQUVwQyxJQUFJQSxXQUFXQyxtQkFBbUIsSUFBSSxNQUFNO1FBQzNDSixpQkFBaUJHLFdBQVdDLG1CQUFtQixLQUFLO0lBQ3JEO0lBQ0EsT0FBTyxDQUFDO0FBQ1Q7QUFFQSxlQUFlQyxTQUNkdEIsU0FBNEIsRUFDNUJ1QixPQUF3QjtJQUV4Qiw0RUFBNEU7SUFDNUUsTUFBTUMsVUFBVXpCLHFCQUFxQkMsV0FBVyxlQUFlO0lBQy9EQSxVQUFVeUIsUUFBUSxDQUFVQyx1QkFBZ0IsQ0FBQ0MsUUFBUSxHQUFHQyxTQUFTLFlBQVlKO0lBRTdFLCtEQUErRDtJQUMvREssSUFBQUEscUNBQW9CLEVBQUM3QixXQUFXdUI7SUFFaEMsSUFBSU8sUUFBUUMsR0FBRyxDQUFDQyxnQkFBZ0IsS0FBSyxLQUFLO1FBQ3pDZixpQkFBaUI7UUFDakI7SUFDRDtJQUNBRCxNQUFNaUIsSUFBQUEsNkJBQW9CLEVBQUNqQyxVQUFVSyxTQUFTLEtBQU0sTUFBTTZCLElBQUFBLGdDQUF1QjtJQUNqRixJQUFJWCxRQUFRRixtQkFBbUIsSUFBSSxNQUFNO1FBQ3hDSixpQkFBaUJNLFFBQVFGLG1CQUFtQixLQUFLO1FBQ2pEO0lBQ0Q7SUFFQSxNQUFNYyxTQUFTLE1BQU1DLElBQUFBLFdBQUcsRUFBQyxnREFBZ0Q7SUFDekVuQixpQkFBaUJrQixPQUFPRSxXQUFXLE9BQU87QUFDM0M7QUFFQSxlQUFlQyxTQUNkdEMsU0FBNEIsRUFDNUJ1QixPQUF3QjtJQUV4QixNQUFNZ0IsY0FBY2hCLFFBQVFpQixHQUFHLEdBQzVCeEMsVUFBVXlDLFdBQVcsR0FBR0MsR0FBRyxDQUFDLENBQUNDLFVBQVlBLFFBQVFDLEdBQUcsSUFDcEQ7UUFBQztLQUFJO0lBQ1JDLElBQUFBLGtDQUFxQixFQUFDN0MsVUFBVUssU0FBUyxFQUFFa0MsWUFBWTFCLE1BQU0sR0FBRyxJQUFJMEIsY0FBYztRQUFDO0tBQUk7SUFFdkYsSUFBSXRCLGdCQUFnQjtRQUNuQixNQUFNNkIsS0FBSzlCO1FBQ1grQixRQUFRQyxHQUFHLENBQUMsQ0FBQyxhQUFhLEVBQUVoRCxVQUFVaUQsSUFBSSxDQUFDLDhCQUE4QixDQUFDO1FBQzFFLE1BQU1ILEdBQUdJLE9BQU8sQ0FBQztZQUFFQyxLQUFLbkQsVUFBVUssU0FBUztRQUFDO0lBQzdDO0lBRUEsTUFBTStDLFdBQVdwRCxVQUNmeUMsV0FBVyxHQUNYQyxHQUFHLENBQUMsQ0FBQ0MsVUFBWUEsUUFBUUMsR0FBRyxDQUFDUyxPQUFPLENBQUMsZUFBZTtJQUN0RE4sUUFBUUMsR0FBRyxDQUFDLENBQUMsUUFBUSxFQUFFL0IsaUJBQWlCLFNBQVMsa0JBQWtCLFNBQVMsQ0FBQztJQUM3RSxJQUFJLENBQUNNLFFBQVFpQixHQUFHLEVBQUU7UUFDakJPLFFBQVFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRWhELFVBQVVpRCxJQUFJLEVBQUU7SUFDckM7SUFDQSxJQUFJLENBQUNoQyxrQkFBa0JNLFFBQVFpQixHQUFHLEVBQUU7UUFDbkMsTUFBTWMsU0FBU3RDLEtBQUtpQyxRQUFRO1FBQzVCRixRQUFRQyxHQUFHLENBQUMsQ0FBQyxFQUFFLEVBQUVNLE9BQU8sUUFBUSxDQUFDO0lBQ2xDO0lBQ0EsSUFBSS9CLFFBQVFpQixHQUFHLElBQUlZLFFBQVEsQ0FBQyxFQUFFLElBQUksTUFBTTtRQUN2Q0wsUUFBUUMsR0FBRyxDQUFDLENBQUMsaUJBQWlCLEVBQUVJLFFBQVEsQ0FBQyxFQUFFLEVBQUU7UUFDN0NMLFFBQVFDLEdBQUcsQ0FBQyxDQUFDLDZCQUE2QixFQUFFSSxRQUFRLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQztJQUM1RCxPQUFPO1FBQ05MLFFBQVFDLEdBQUcsQ0FBQztRQUNaRCxRQUFRQyxHQUFHLENBQ1Y7SUFFRjtJQUNBRCxRQUFRQyxHQUFHLENBQUM7SUFFWiw4REFBOEQ7SUFDOURPLElBQUFBLG1CQUFXO0FBQ1o7QUFFTyxNQUFNekQsa0JBQTJCO0lBQUVvQjtJQUFZSTtJQUFVZ0I7QUFBUyJ9
|