@decohere/scaffold 0.0.1-dev.18 → 0.0.1-dev.80

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