@zintrust/core 0.1.23 → 0.1.25

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 (172) hide show
  1. package/package.json +4 -3
  2. package/src/auth/Auth.d.ts.map +1 -0
  3. package/src/boot/Application.d.ts.map +1 -1
  4. package/src/boot/Application.js +8 -0
  5. package/src/boot/bootstrap.js +34 -15
  6. package/src/cache/drivers/RedisDriver.d.ts.map +1 -1
  7. package/src/cache/drivers/RedisDriver.js +10 -5
  8. package/src/cli/CLI.d.ts.map +1 -1
  9. package/src/cli/CLI.js +6 -0
  10. package/src/cli/commands/DbSeedCommand.d.ts.map +1 -1
  11. package/src/cli/commands/DbSeedCommand.js +6 -38
  12. package/src/cli/commands/MigrateCommand.d.ts.map +1 -1
  13. package/src/cli/commands/MigrateCommand.js +12 -55
  14. package/src/cli/commands/MigrateWorkerCommand.d.ts.map +1 -1
  15. package/src/cli/commands/MigrateWorkerCommand.js +8 -54
  16. package/src/cli/commands/NewCommand.d.ts.map +1 -1
  17. package/src/cli/commands/NewCommand.js +1 -13
  18. package/src/cli/commands/QueueCommand.d.ts.map +1 -1
  19. package/src/cli/commands/QueueCommand.js +89 -39
  20. package/src/cli/commands/QueueLockCommand.d.ts +7 -0
  21. package/src/cli/commands/QueueLockCommand.d.ts.map +1 -0
  22. package/src/cli/commands/QueueLockCommand.js +138 -0
  23. package/src/cli/commands/StartCommand.d.ts.map +1 -1
  24. package/src/cli/commands/StartCommand.js +16 -16
  25. package/src/cli/commands/TemplatesCommand.js +1 -1
  26. package/src/cli/commands/WorkerCommands.d.ts.map +1 -1
  27. package/src/cli/commands/WorkerCommands.js +46 -22
  28. package/src/cli/scaffolding/ProjectScaffolder.js +2 -2
  29. package/src/cli/scaffolding/RouteGenerator.d.ts.map +1 -1
  30. package/src/cli/scaffolding/RouteGenerator.js +27 -28
  31. package/src/cli/services/VersionChecker.d.ts +53 -0
  32. package/src/cli/services/VersionChecker.d.ts.map +1 -0
  33. package/src/cli/services/VersionChecker.js +176 -0
  34. package/src/cli/utils/DatabaseCliUtils.d.ts +20 -0
  35. package/src/cli/utils/DatabaseCliUtils.d.ts.map +1 -0
  36. package/src/cli/utils/DatabaseCliUtils.js +54 -0
  37. package/src/cli/workers/QueueWorkRunner.d.ts.map +1 -1
  38. package/src/cli/workers/QueueWorkRunner.js +128 -7
  39. package/src/common/ExternalServiceUtils.d.ts +2 -2
  40. package/src/config/app.d.ts +4 -0
  41. package/src/config/app.d.ts.map +1 -1
  42. package/src/config/app.js +9 -0
  43. package/src/config/constants.d.ts +140 -10
  44. package/src/config/constants.d.ts.map +1 -1
  45. package/src/config/constants.js +86 -5
  46. package/src/config/index.d.ts +1 -0
  47. package/src/config/index.d.ts.map +1 -1
  48. package/src/config/middleware.d.ts +6 -6
  49. package/src/config/middleware.d.ts.map +1 -1
  50. package/src/config/middleware.js +6 -7
  51. package/src/config/queue.d.ts +4 -0
  52. package/src/config/queue.d.ts.map +1 -1
  53. package/src/config/queue.js +1 -1
  54. package/src/config/redis.d.ts +17 -0
  55. package/src/config/redis.d.ts.map +1 -0
  56. package/src/config/redis.js +54 -0
  57. package/src/config/type.d.ts +3 -0
  58. package/src/config/type.d.ts.map +1 -1
  59. package/src/http/Request.d.ts +10 -1
  60. package/src/http/Request.d.ts.map +1 -1
  61. package/src/http/Request.js +79 -7
  62. package/src/http/error-pages/ErrorPageRenderer.d.ts.map +1 -1
  63. package/src/http/error-pages/ErrorPageRenderer.js +4 -3
  64. package/src/index.d.ts +14 -11
  65. package/src/index.d.ts.map +1 -1
  66. package/src/index.js +18 -11
  67. package/src/lang/lang.d.ts +23 -0
  68. package/src/lang/lang.d.ts.map +1 -0
  69. package/src/lang/lang.js +22 -0
  70. package/src/middleware/ErrorHandlerMiddleware.d.ts.map +1 -1
  71. package/src/middleware/ErrorHandlerMiddleware.js +9 -1
  72. package/src/migrations/schema/SchemaCompiler.js +1 -1
  73. package/src/migrations/schema/types.d.ts +1 -1
  74. package/src/migrations/schema/types.d.ts.map +1 -1
  75. package/src/node.d.ts +1 -1
  76. package/src/node.d.ts.map +1 -1
  77. package/src/node.js +1 -1
  78. package/src/orm/Database.d.ts +1 -1
  79. package/src/orm/Database.d.ts.map +1 -1
  80. package/src/orm/Database.js +22 -3
  81. package/src/performance/Optimizer.js +1 -1
  82. package/src/routing/Router.d.ts +6 -2
  83. package/src/routing/Router.d.ts.map +1 -1
  84. package/src/routing/Router.js +19 -4
  85. package/src/runtime/PluginAutoImports.d.ts.map +1 -1
  86. package/src/runtime/PluginAutoImports.js +1 -13
  87. package/src/runtime/PluginManager.d.ts.map +1 -1
  88. package/src/runtime/PluginManager.js +2 -14
  89. package/src/runtime/PluginRegistry.js +2 -2
  90. package/src/start.d.ts.map +1 -1
  91. package/src/start.js +8 -7
  92. package/src/templates/TemplateRegistry.js +2 -2
  93. package/src/templates/TemplateRegistry.ts +2 -2
  94. package/src/templates/feature/Queue.ts.tpl +114 -0
  95. package/src/templates/project/basic/app/Controllers/UserController.ts.tpl +22 -0
  96. package/src/templates/project/basic/config/queue.ts.tpl +19 -0
  97. package/src/templates/project/basic/package.json.tpl +2 -1
  98. package/src/templates/project/basic/src/index.ts.tpl +0 -3
  99. package/src/toolkit/Secrets/providers/AwsSecretsManager.d.ts.map +1 -1
  100. package/src/toolkit/Secrets/providers/AwsSecretsManager.js +1 -13
  101. package/src/toolkit/Secrets/providers/CloudflareKv.d.ts.map +1 -1
  102. package/src/toolkit/Secrets/providers/CloudflareKv.js +4 -16
  103. package/src/tools/broadcast/drivers/Redis.d.ts.map +1 -1
  104. package/src/tools/broadcast/drivers/Redis.js +8 -56
  105. package/src/tools/mail/Mail.d.ts +1 -29
  106. package/src/tools/mail/Mail.d.ts.map +1 -1
  107. package/src/tools/mail/Mail.js +1 -111
  108. package/src/tools/mail/drivers/SendGrid.d.ts.map +1 -1
  109. package/src/tools/mail/drivers/SendGrid.js +4 -3
  110. package/src/tools/mail/drivers/Smtp.d.ts.map +1 -1
  111. package/src/tools/mail/drivers/Smtp.js +32 -10
  112. package/src/tools/mail/index.d.ts +40 -0
  113. package/src/tools/mail/index.d.ts.map +1 -0
  114. package/src/tools/mail/index.js +129 -0
  115. package/src/tools/mail/template-loader.d.ts +10 -0
  116. package/src/tools/mail/template-loader.d.ts.map +1 -0
  117. package/src/tools/mail/template-loader.js +101 -0
  118. package/src/tools/mail/template-utils.d.ts +10 -0
  119. package/src/tools/mail/template-utils.d.ts.map +1 -0
  120. package/src/tools/mail/template-utils.js +16 -0
  121. package/src/tools/mail/templates/index.d.ts +30 -0
  122. package/src/tools/mail/templates/index.d.ts.map +1 -1
  123. package/src/tools/mail/templates/index.js +69 -0
  124. package/src/tools/queue/AdvancedQueue.d.ts +19 -0
  125. package/src/tools/queue/AdvancedQueue.d.ts.map +1 -0
  126. package/src/tools/queue/AdvancedQueue.js +352 -0
  127. package/src/tools/queue/DeduplicationBuilder.d.ts +20 -0
  128. package/src/tools/queue/DeduplicationBuilder.d.ts.map +1 -0
  129. package/src/tools/queue/DeduplicationBuilder.js +77 -0
  130. package/src/tools/queue/LockProvider.d.ts +25 -0
  131. package/src/tools/queue/LockProvider.d.ts.map +1 -0
  132. package/src/tools/queue/LockProvider.js +276 -0
  133. package/src/tools/queue/Queue.d.ts.map +1 -1
  134. package/src/tools/queue/Queue.js +2 -1
  135. package/src/tools/queue/QueueExtensions.d.ts +46 -0
  136. package/src/tools/queue/QueueExtensions.d.ts.map +1 -0
  137. package/src/tools/queue/QueueExtensions.js +129 -0
  138. package/src/tools/queue/QueueRuntimeRegistration.d.ts.map +1 -1
  139. package/src/tools/queue/QueueRuntimeRegistration.js +2 -2
  140. package/src/tools/queue/drivers/Database.d.ts +23 -0
  141. package/src/tools/queue/drivers/Database.d.ts.map +1 -0
  142. package/src/tools/queue/drivers/Database.js +123 -0
  143. package/src/tools/queue/drivers/Redis.d.ts.map +1 -1
  144. package/src/tools/queue/drivers/Redis.js +11 -82
  145. package/src/tools/queue/index.d.ts +9 -0
  146. package/src/tools/queue/index.d.ts.map +1 -0
  147. package/src/tools/queue/index.js +7 -0
  148. package/src/tools/redis/RedisKeyManager.d.ts +64 -0
  149. package/src/tools/redis/RedisKeyManager.d.ts.map +1 -0
  150. package/src/tools/redis/RedisKeyManager.js +124 -0
  151. package/src/tools/storage/drivers/S3.d.ts.map +1 -1
  152. package/src/tools/storage/drivers/S3.js +4 -16
  153. package/src/types/Queue.d.ts +62 -0
  154. package/src/types/Queue.d.ts.map +1 -0
  155. package/src/types/Queue.js +5 -0
  156. package/src/features/Auth.d.ts.map +0 -1
  157. package/src/features/Queue.d.ts +0 -21
  158. package/src/features/Queue.d.ts.map +0 -1
  159. package/src/features/Queue.js +0 -33
  160. package/src/templates/features/Queue.ts.tpl +0 -47
  161. package/src/tools/mail/templates/markdown/index.d.ts +0 -17
  162. package/src/tools/mail/templates/markdown/index.d.ts.map +0 -1
  163. package/src/tools/mail/templates/markdown/index.js +0 -49
  164. package/src/tools/mail/templates/markdown/registry.d.ts +0 -15
  165. package/src/tools/mail/templates/markdown/registry.d.ts.map +0 -1
  166. package/src/tools/mail/templates/markdown/registry.js +0 -34
  167. package/src/tools/mail/templates/markdown/validator.d.ts +0 -16
  168. package/src/tools/mail/templates/markdown/validator.d.ts.map +0 -1
  169. package/src/tools/mail/templates/markdown/validator.js +0 -24
  170. /package/src/{features → auth}/Auth.d.ts +0 -0
  171. /package/src/{features → auth}/Auth.js +0 -0
  172. /package/src/templates/{features → auth}/Auth.ts.tpl +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -36,13 +36,14 @@
36
36
  "bcrypt": "^6.0.0",
37
37
  "chalk": "^5.6.2",
38
38
  "commander": "^14.0.2",
39
- "inquirer": "^13.2.0",
39
+ "inquirer": "^13.2.1",
40
40
  "jsonwebtoken": "^9.0.3"
41
41
  },
42
42
  "overrides": {
43
43
  "node-forge": "1.3.3",
44
44
  "cross-spawn": "^7.0.5",
45
- "glob": "^11.1.0"
45
+ "glob": "^11.1.0",
46
+ "undici": "^6.23.0"
46
47
  },
47
48
  "bin": {
48
49
  "zintrust": "bin/zintrust.js",
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Auth.d.ts","sourceRoot":"","sources":["../../../src/auth/Auth.ts"],"names":[],"mappings":"AAEA,OAAY,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAIlE,eAAO,MAAM,IAAI;IACf;;OAEG;mBACkB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK7C;;OAEG;sBACqB,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI/D;;OAEG;2BAEQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,UACxB,MAAM,cACH,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,GAC/C,MAAM;IAKT;;OAEG;gBACS,CAAC,SAAS,MAAM,UAAU,MAAM,GAAG,CAAC;EAGhD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Application.d.ts","sourceRoot":"","sources":["../../../src/boot/Application.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,kBAAkB,CAAC;AAUxD,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,IAAI,OAAO,CAAC;IACpB,aAAa,IAAI,OAAO,CAAC;IACzB,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC;IACrB,cAAc,IAAI,MAAM,CAAC;IACzB,SAAS,IAAI,OAAO,CAAC;IACrB,YAAY,IAAI,iBAAiB,CAAC;IAClC,kBAAkB,IAAI,gBAAgB,CAAC;IACvC,WAAW,IAAI,MAAM,CAAC;CACvB;AA2YD;;GAEG;AACH,eAAO,MAAM,WAAW;IACtB;;OAEG;sBACe,MAAM,GAAG,YAAY;EA0CvC,CAAC;AAEH,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"Application.d.ts","sourceRoot":"","sources":["../../../src/boot/Application.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,kBAAkB,CAAC;AAUxD,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,IAAI,OAAO,CAAC;IACpB,aAAa,IAAI,OAAO,CAAC;IACzB,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC;IACrB,cAAc,IAAI,MAAM,CAAC;IACzB,SAAS,IAAI,OAAO,CAAC;IACrB,YAAY,IAAI,iBAAiB,CAAC;IAClC,kBAAkB,IAAI,gBAAgB,CAAC;IACvC,WAAW,IAAI,MAAM,CAAC;CACvB;AAqZD;;GAEG;AACH,eAAO,MAAM,WAAW;IACtB;;OAEG;sBACe,MAAM,GAAG,YAAY;EA0CvC,CAAC;AAEH,eAAe,WAAW,CAAC"}
@@ -291,6 +291,14 @@ const createLifecycle = (params) => {
291
291
  catch (error) {
292
292
  Logger.error('Shutdown hook failed:', error);
293
293
  }
294
+ // Ensure FileLogWriter.flush is attempted even if dynamic registration failed.
295
+ try {
296
+ const fileLogWriter = await tryImportOptional('@config/FileLogWriter');
297
+ fileLogWriter?.FileLogWriter?.flush?.();
298
+ }
299
+ catch {
300
+ /* best-effort */
301
+ }
294
302
  params.setBooted(false);
295
303
  Logger.info('✅ Application shut down successfully');
296
304
  };
@@ -87,8 +87,11 @@ const gracefulShutdown = async (signal) => {
87
87
  if (isShuttingDown)
88
88
  return;
89
89
  isShuttingDown = true;
90
- const timeoutMs = Env.getInt('SHUTDOWN_TIMEOUT', 1500);
91
- const forceExitMs = Env.getInt('SHUTDOWN_FORCE_EXIT_MS', 2500);
90
+ const shutdownBudgetMs = Env.getInt('SHUTDOWN_TIMEOUT', 1500);
91
+ const minForceExitMs = shutdownBudgetMs + 250;
92
+ const forceExitMs = Math.max(Env.getInt('SHUTDOWN_FORCE_EXIT_MS', 10000), minForceExitMs);
93
+ const deadlineMs = Date.now() + shutdownBudgetMs;
94
+ const remainingMs = () => Math.max(0, deadlineMs - Date.now());
92
95
  Logger.info(`${signal} received, shutting down gracefully...`);
93
96
  try {
94
97
  const forceExitTimer = globalThis.setTimeout(() => {
@@ -97,24 +100,30 @@ const gracefulShutdown = async (signal) => {
97
100
  // Best-effort: don't keep the process alive just for this timer
98
101
  forceExitTimer.unref?.();
99
102
  await withTimeout((async () => {
103
+ // Shutdown worker management system FIRST (before database closes)
104
+ if (appConfig.detectRuntime() === 'nodejs' || appConfig.detectRuntime() === 'lambda') {
105
+ try {
106
+ const { WorkerShutdown } = await import('../../packages/workers/src/index.js');
107
+ const workerBudgetMs = Math.min(15000, remainingMs());
108
+ await withTimeout(WorkerShutdown.shutdown({ signal, timeout: workerBudgetMs, forceExit: false }), workerBudgetMs, 'Worker shutdown timed out');
109
+ }
110
+ catch (error) {
111
+ Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
112
+ }
113
+ }
100
114
  if (serverInstance !== undefined) {
101
115
  await serverInstance.close();
102
116
  }
103
117
  if (appInstance !== undefined) {
104
- await appInstance.shutdown();
105
- }
106
- })(), timeoutMs, 'Graceful shutdown timed out');
107
- // Shutdown worker management system first
108
- if (appConfig.detectRuntime() === 'nodejs' || appConfig.detectRuntime() === 'lambda') {
109
- try {
110
- const { WorkerShutdown } = await import('../../packages/workers/src/index.js');
111
- await withTimeout(WorkerShutdown.shutdown({ signal, timeout: 30000, forceExit: false }), timeoutMs, 'Worker shutdown timed out');
112
- Logger.info('Worker management system shutdown complete');
113
- }
114
- catch (error) {
115
- Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
118
+ try {
119
+ const appBudgetMs = Math.min(5000, remainingMs());
120
+ await withTimeout(appInstance.shutdown(), appBudgetMs, 'App shutdown timed out');
121
+ }
122
+ catch (error) {
123
+ Logger.warn('App shutdown failed or timed out, forcing exit', error);
124
+ }
116
125
  }
117
- }
126
+ })(), shutdownBudgetMs, 'Graceful shutdown timed out');
118
127
  globalThis.clearTimeout(forceExitTimer);
119
128
  process.exit(0);
120
129
  }
@@ -155,6 +164,16 @@ const BootstrapFunctions = Object.freeze({
155
164
  */
156
165
  async start() {
157
166
  try {
167
+ // Ensure project-installed adapters/drivers are registered for web server.
168
+ // (This is driven by src/zintrust.plugins.ts generated by `zin plugin install`.)
169
+ try {
170
+ const { PluginAutoImports } = await import('../runtime/PluginAutoImports.js');
171
+ await PluginAutoImports.tryImportProjectAutoImports();
172
+ }
173
+ catch (error) {
174
+ // best-effort; run without plugins if loader fails (e.g. non-Node runtime)
175
+ Logger.debug('Plugin auto-imports loader skipped:', error);
176
+ }
158
177
  // Create application instance
159
178
  // if (Env.ZINTRUST_PROJECT_ROOT) {
160
179
  // }
@@ -1 +1 @@
1
- {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../../src/cache/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA+EtD;;GAEG;AACH,eAAO,MAAM,WAAW;kBA1EL,WAAW;EA4E5B,CAAC"}
1
+ {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../../src/cache/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAoFtD;;GAEG;AACH,eAAO,MAAM,WAAW;kBA9EL,WAAW;EAgF5B,CAAC"}
@@ -5,6 +5,7 @@
5
5
  import { Env } from '../../config/env.js';
6
6
  import { Logger } from '../../config/logger.js';
7
7
  import * as net from '../../node-singletons/net.js';
8
+ import { createCacheKey } from '../../tools/redis/RedisKeyManager.js';
8
9
  /**
9
10
  * Create a new Redis driver instance
10
11
  */
@@ -38,7 +39,8 @@ const create = () => {
38
39
  return {
39
40
  async get(key) {
40
41
  try {
41
- const response = await sendCommand(`GET ${key}\r\n`);
42
+ const prefixedKey = createCacheKey(key);
43
+ const response = await sendCommand(`GET ${prefixedKey}\r\n`);
42
44
  if (response.startsWith('$-1'))
43
45
  return null;
44
46
  // Basic RESP parsing
@@ -52,21 +54,24 @@ const create = () => {
52
54
  }
53
55
  },
54
56
  async set(key, value, ttl) {
57
+ const prefixedKey = createCacheKey(key);
55
58
  const jsonValue = JSON.stringify(value);
56
- let command = `SET ${key} ${jsonValue}\r\n`;
59
+ let command = `SET ${prefixedKey} ${jsonValue}\r\n`;
57
60
  if (ttl !== undefined) {
58
- command = `SETEX ${key} ${ttl} ${jsonValue}\r\n`;
61
+ command = `SETEX ${prefixedKey} ${ttl} ${jsonValue}\r\n`;
59
62
  }
60
63
  await sendCommand(command);
61
64
  },
62
65
  async delete(key) {
63
- await sendCommand(`DEL ${key}\r\n`);
66
+ const prefixedKey = createCacheKey(key);
67
+ await sendCommand(`DEL ${prefixedKey}\r\n`);
64
68
  },
65
69
  async clear() {
66
70
  await sendCommand(`FLUSHDB\r\n`);
67
71
  },
68
72
  async has(key) {
69
- const response = await sendCommand(`EXISTS ${key}\r\n`);
73
+ const prefixedKey = createCacheKey(key);
74
+ const response = await sendCommand(`EXISTS ${prefixedKey}\r\n`);
70
75
  return response.includes(':1');
71
76
  },
72
77
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CLI.d.ts","sourceRoot":"","sources":["../../../src/cli/CLI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyCH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,WAAW,IAAI;IACnB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,UAAU,IAAI,OAAO,CAAC;CACvB;AAqMD;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG;cACJ,IAAI;EAed,CAAC"}
1
+ {"version":3,"file":"CLI.d.ts","sourceRoot":"","sources":["../../../src/cli/CLI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA0CH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,WAAW,IAAI;IACnB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,UAAU,IAAI,OAAO,CAAC;CACvB;AA2MD;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG;cACJ,IAAI;EAed,CAAC"}
package/src/cli/CLI.js CHANGED
@@ -32,6 +32,7 @@ import { TemplatesCommand } from './commands/TemplatesCommand.js';
32
32
  import { UpgradeCommand } from './commands/UpgradeCommand.js';
33
33
  import { WorkerCommands } from './commands/WorkerCommands.js';
34
34
  import { ErrorHandler } from './ErrorHandler.js';
35
+ import { VersionChecker } from './services/VersionChecker.js';
35
36
  import { esmDirname } from '../common/index.js';
36
37
  import { Logger } from '../config/logger.js';
37
38
  import { ErrorFactory } from '../exceptions/ZintrustError.js';
@@ -194,6 +195,11 @@ const runCLI = async (program, version, args) => {
194
195
  }
195
196
  // Always show banner for normal commands
196
197
  ErrorHandler.banner(version);
198
+ // Run version check in background (non-blocking)
199
+ VersionChecker.runVersionCheck().catch((error) => {
200
+ // Version check should never crash the CLI
201
+ Logger.debug('Version check encountered an error', error);
202
+ });
197
203
  // Show help if no arguments provided
198
204
  if (args.length === 0) {
199
205
  program.help();
@@ -1 +1 @@
1
- {"version":3,"file":"DbSeedCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/DbSeedCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA6MrE,eAAO,MAAM,aAAa;cACd,YAAY;EAUtB,CAAC"}
1
+ {"version":3,"file":"DbSeedCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/DbSeedCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAkKrE,eAAO,MAAM,aAAa;cACd,YAAY;EAUtB,CAAC"}
@@ -5,7 +5,7 @@
5
5
  import { SeederDiscovery } from '../../seeders/SeederDiscovery.js';
6
6
  import { SeederLoader } from '../../seeders/SeederLoader.js';
7
7
  import { BaseCommand } from '../BaseCommand.js';
8
- import { PromptHelper } from '../PromptHelper.js';
8
+ import { confirmProductionRun, mapConnectionToOrmConfig } from '../utils/DatabaseCliUtils.js';
9
9
  import { databaseConfig } from '../../config/database.js';
10
10
  import { Env } from '../../config/env.js';
11
11
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
@@ -23,42 +23,6 @@ const ensureNonD1Driver = (driver) => {
23
23
  throw ErrorFactory.createCliError('This project is configured for D1. Seeding via `zin db:seed` is not supported yet.');
24
24
  }
25
25
  };
26
- const mapConnectionToOrmConfig = (conn) => {
27
- switch (conn.driver) {
28
- case 'sqlite':
29
- return { driver: 'sqlite', database: conn.database };
30
- case 'postgresql':
31
- return {
32
- driver: 'postgresql',
33
- host: conn.host,
34
- port: conn.port,
35
- database: conn.database,
36
- username: conn.username,
37
- password: conn.password,
38
- };
39
- case 'mysql':
40
- return {
41
- driver: 'mysql',
42
- host: conn.host,
43
- port: conn.port,
44
- database: conn.database,
45
- username: conn.username,
46
- password: conn.password,
47
- };
48
- default:
49
- return { driver: 'sqlite', database: ':memory:' };
50
- }
51
- };
52
- const confirmProductionRun = async (cmd, interactive) => {
53
- if (Env.NODE_ENV !== 'production')
54
- return true;
55
- const confirmed = await PromptHelper.confirm('NODE_ENV=production. Continue running seeders?', false, interactive);
56
- if (!confirmed) {
57
- cmd.warn('Cancelled.');
58
- return false;
59
- }
60
- return true;
61
- };
62
26
  const getServiceArgs = (options) => {
63
27
  let serviceArg;
64
28
  if (typeof options['onlyService'] === 'string') {
@@ -105,7 +69,11 @@ const selectSeederFiles = (files, seederName) => {
105
69
  };
106
70
  const executeSeed = async (options, cmd) => {
107
71
  const interactive = getInteractive(options);
108
- const okToProceed = await confirmProductionRun(cmd, interactive);
72
+ const okToProceed = await confirmProductionRun({
73
+ cmd,
74
+ interactive,
75
+ message: 'NODE_ENV=production. Continue running seeders?',
76
+ });
109
77
  if (!okToProceed)
110
78
  return;
111
79
  const conn = databaseConfig.getConnection();
@@ -1 +1 @@
1
- {"version":3,"file":"MigrateCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/MigrateCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAkdrE;;GAEG;AACH,eAAO,MAAM,cAAc;IACzB;;OAEG;cACO,YAAY;EAUtB,CAAC"}
1
+ {"version":3,"file":"MigrateCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/MigrateCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA4ZrE;;GAEG;AACH,eAAO,MAAM,cAAc;IACzB;;OAEG;cACO,YAAY;EAUtB,CAAC"}
@@ -7,8 +7,9 @@ import { D1SqlMigrations } from '../d1/D1SqlMigrations.js';
7
7
  import { WranglerConfig } from '../d1/WranglerConfig.js';
8
8
  import { WranglerD1 } from '../d1/WranglerD1.js';
9
9
  import { PromptHelper } from '../PromptHelper.js';
10
+ import { confirmProductionRun, mapConnectionToOrmConfig, parseRollbackSteps, } from '../utils/DatabaseCliUtils.js';
11
+ import { readEnvString } from '../../common/ExternalServiceUtils.js';
10
12
  import { databaseConfig } from '../../config/database.js';
11
- import { Env } from '../../config/env.js';
12
13
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
13
14
  import { Migrator } from '../../migrations/Migrator.js';
14
15
  import * as path from '../../node-singletons/path.js';
@@ -32,9 +33,10 @@ const addMigrateOptions = (command) => {
32
33
  };
33
34
  const getInteractive = (options) => options['interactive'] !== false;
34
35
  const getMigrationDirs = () => {
35
- const globalDir = Env.get('MIGRATIONS_GLOBAL_DIR', databaseConfig.migrations.directory);
36
+ const globalDir = readEnvString('MIGRATIONS_GLOBAL_DIR', databaseConfig.migrations.directory);
36
37
  const extension = databaseConfig.migrations.extension;
37
- const separateTracking = Env.getBool('MIGRATIONS_SEPARATE_TRACKING', false);
38
+ const separateTrackingRaw = readEnvString('MIGRATIONS_SEPARATE_TRACKING', '').trim();
39
+ const separateTracking = separateTrackingRaw === '1' || separateTrackingRaw.toLowerCase() === 'true';
38
40
  return { globalDir, extension, separateTracking };
39
41
  };
40
42
  const getServiceArgs = (options) => {
@@ -64,53 +66,6 @@ const describeTargetDatabase = (conn) => {
64
66
  return `${conn.driver}`;
65
67
  }
66
68
  };
67
- const mapConnectionToOrmConfig = (conn) => {
68
- switch (conn.driver) {
69
- case 'sqlite':
70
- return { driver: 'sqlite', database: conn.database };
71
- case 'postgresql':
72
- return {
73
- driver: 'postgresql',
74
- host: conn.host,
75
- port: conn.port,
76
- database: conn.database,
77
- username: conn.username,
78
- password: conn.password,
79
- };
80
- case 'mysql':
81
- return {
82
- driver: 'mysql',
83
- host: conn.host,
84
- port: conn.port,
85
- database: conn.database,
86
- username: conn.username,
87
- password: conn.password,
88
- };
89
- case 'sqlserver':
90
- return {
91
- driver: 'sqlserver',
92
- host: conn.host,
93
- port: conn.port,
94
- database: conn.database,
95
- username: conn.username,
96
- password: conn.password,
97
- };
98
- default:
99
- return { driver: 'sqlite', database: ':memory:' };
100
- }
101
- };
102
- const confirmProductionRun = async (cmd, interactive, destructive, force) => {
103
- if (Env.NODE_ENV !== 'production')
104
- return true;
105
- if (force)
106
- return true;
107
- const confirmed = await PromptHelper.confirm(`NODE_ENV=production. Continue running migrations${destructive ? ' (destructive)' : ''}?`, false, interactive);
108
- if (!confirmed) {
109
- cmd.warn('Cancelled.');
110
- return false;
111
- }
112
- return true;
113
- };
114
69
  const printStatus = (cmd, rows) => {
115
70
  if (rows.length === 0) {
116
71
  cmd.info('No migrations found.');
@@ -133,10 +88,6 @@ const logAppliedMigrations = (cmd, appliedNames) => {
133
88
  cmd.info(`✓ ${name}`);
134
89
  }
135
90
  };
136
- const parseRollbackSteps = (options) => {
137
- const stepRaw = typeof options['step'] === 'string' ? options['step'] : '1';
138
- return Math.max(1, Number.parseInt(stepRaw, 10) || 1);
139
- };
140
91
  const handleStatusAction = async (migrator, cmd, driver) => {
141
92
  cmd.info(`Adapter: ${driver}`);
142
93
  const rows = await migrator.status();
@@ -267,7 +218,13 @@ const processConnection = async (conn, options, cmd, interactive) => {
267
218
  const ormConfig = mapConnectionToOrmConfig(conn);
268
219
  const destructive = isDestructiveAction(options);
269
220
  const force = options['force'] === true;
270
- const okToProceed = await confirmProductionRun(cmd, interactive, destructive, force);
221
+ const okToProceed = await confirmProductionRun({
222
+ cmd,
223
+ interactive,
224
+ destructive,
225
+ force,
226
+ message: 'NODE_ENV=production. Continue running migrations?',
227
+ });
271
228
  if (!okToProceed)
272
229
  return;
273
230
  cmd.info(`[i] Target database: ${describeTargetDatabase(conn)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"MigrateWorkerCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/MigrateWorkerCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAkOrE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAUtB,CAAC"}
1
+ {"version":3,"file":"MigrateWorkerCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/MigrateWorkerCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAmKrE,eAAO,MAAM,oBAAoB;cACrB,YAAY;EAUtB,CAAC"}
@@ -3,9 +3,8 @@
3
3
  * Run worker package migrations
4
4
  */
5
5
  import { BaseCommand } from '../BaseCommand.js';
6
- import { PromptHelper } from '../PromptHelper.js';
6
+ import { confirmProductionRun, mapConnectionToOrmConfig, parseRollbackSteps, } from '../utils/DatabaseCliUtils.js';
7
7
  import { databaseConfig } from '../../config/database.js';
8
- import { Env } from '../../config/env.js';
9
8
  import { Migrator } from '../../migrations/Migrator.js';
10
9
  import * as path from '../../node-singletons/path.js';
11
10
  import { Database } from '../../orm/Database.js';
@@ -22,58 +21,7 @@ const addOptions = (command) => {
22
21
  .option('--no-interactive', 'Disable interactive prompts (useful for CI/CD)');
23
22
  };
24
23
  const getInteractive = (options) => options['interactive'] !== false;
25
- const mapConnectionToOrmConfig = (conn) => {
26
- switch (conn.driver) {
27
- case 'sqlite':
28
- return { driver: 'sqlite', database: conn.database };
29
- case 'postgresql':
30
- return {
31
- driver: 'postgresql',
32
- host: conn.host,
33
- port: conn.port,
34
- database: conn.database,
35
- username: conn.username,
36
- password: conn.password,
37
- };
38
- case 'mysql':
39
- return {
40
- driver: 'mysql',
41
- host: conn.host,
42
- port: conn.port,
43
- database: conn.database,
44
- username: conn.username,
45
- password: conn.password,
46
- };
47
- case 'sqlserver':
48
- return {
49
- driver: 'sqlserver',
50
- host: conn.host,
51
- port: conn.port,
52
- database: conn.database,
53
- username: conn.username,
54
- password: conn.password,
55
- };
56
- default:
57
- return { driver: 'sqlite', database: ':memory:' };
58
- }
59
- };
60
24
  const isDestructiveAction = (options) => options['fresh'] === true || options['reset'] === true || options['rollback'] === true;
61
- const parseRollbackSteps = (options) => {
62
- const stepRaw = typeof options['step'] === 'string' ? options['step'] : '1';
63
- return Math.max(1, Number.parseInt(stepRaw, 10) || 1);
64
- };
65
- const confirmProductionRun = async (cmd, interactive, destructive, force) => {
66
- if (Env.NODE_ENV !== 'production')
67
- return true;
68
- if (force)
69
- return true;
70
- const confirmed = await PromptHelper.confirm(`NODE_ENV=production. Continue running worker migrations${destructive ? ' (destructive)' : ''}?`, false, interactive);
71
- if (!confirmed) {
72
- cmd.warn('Cancelled.');
73
- return false;
74
- }
75
- return true;
76
- };
77
25
  const printStatus = async (migrator, cmd) => {
78
26
  const rows = await migrator.status();
79
27
  if (rows.length === 0) {
@@ -123,7 +71,13 @@ const runActions = async (migrator, options, cmd, driver) => {
123
71
  };
124
72
  const runForConnection = async (conn, options, cmd, interactive) => {
125
73
  const destructive = isDestructiveAction(options);
126
- const proceed = await confirmProductionRun(cmd, interactive, destructive, options['force'] === true);
74
+ const proceed = await confirmProductionRun({
75
+ cmd,
76
+ interactive,
77
+ destructive,
78
+ force: options['force'] === true,
79
+ message: 'NODE_ENV=production. Continue running worker migrations?',
80
+ });
127
81
  if (!proceed)
128
82
  return;
129
83
  if (!DatabaseAdapterRegistry.has(conn.driver)) {
@@ -1 +1 @@
1
- {"version":3,"file":"NewCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/NewCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAerE,KAAK,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,cAAc,GAAG,WAAW,CAAC;AACnE,KAAK,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,CAAC;AAYhF,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmQhD,UAAU,WAAY,SAAQ,YAAY;IACxC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxF,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACzF,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,EAAE,CAAC;IACjF,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC;IAChC,YAAY,IAAI,MAAM,CAAC;IACvB,cAAc,CACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,sBAAsB,EAC9B,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpE;AAwOD;;;GAGG;AACH,eAAO,MAAM,UAAU;IACrB;;OAEG;cACO,WAAW;EAGrB,CAAC"}
1
+ {"version":3,"file":"NewCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/NewCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAerE,KAAK,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,cAAc,GAAG,WAAW,CAAC;AACnE,KAAK,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,CAAC;AAYhF,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAwPhD,UAAU,WAAY,SAAQ,YAAY;IACxC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxF,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACzF,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,EAAE,CAAC;IACjF,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC;IAChC,YAAY,IAAI,MAAM,CAAC;IACvB,cAAc,CACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,sBAAsB,EAC9B,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpE;AAwOD;;;GAGG;AACH,eAAO,MAAM,UAAU;IACrB;;OAEG;cACO,WAAW;EAGrB,CAAC"}
@@ -3,9 +3,9 @@ import { PromptHelper } from '../PromptHelper.js';
3
3
  import { GovernanceScaffolder } from '../scaffolding/GovernanceScaffolder.js';
4
4
  import { ProjectScaffolder } from '../scaffolding/ProjectScaffolder.js';
5
5
  import { SpawnUtil } from '../utils/spawn.js';
6
+ import { readEnvString } from '../../common/ExternalServiceUtils.js';
6
7
  import { extractErrorMessage, resolvePackageManager } from '../../common/index.js';
7
8
  import { appConfig } from '../../config/app.js';
8
- import { Env } from '../../config/env.js';
9
9
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
10
10
  import { execFileSync } from '../../node-singletons/child-process.js';
11
11
  import * as path from '../../node-singletons/path.js';
@@ -193,18 +193,6 @@ const promptForPackageManager = async (defaultPm) => {
193
193
  };
194
194
  const installDependencies = async (projectPath, log, packageManager, force = false) => {
195
195
  // Respect CI by default — avoid network installs in CI unless explicitly allowed
196
- const readEnvString = (key) => {
197
- const anyEnv = Env;
198
- const fromEnv = typeof anyEnv.get === 'function' ? anyEnv.get(key, '') : '';
199
- if (typeof fromEnv === 'string' && fromEnv.trim() !== '')
200
- return fromEnv;
201
- if (typeof process !== 'undefined') {
202
- const raw = process.env?.[key];
203
- if (typeof raw === 'string')
204
- return raw;
205
- }
206
- return fromEnv ?? '';
207
- };
208
196
  const ciRaw = readEnvString('CI').trim().toLowerCase();
209
197
  const isCi = ciRaw !== '' && ciRaw !== '0' && ciRaw !== 'false';
210
198
  const allowAuto = readEnvString('ZINTRUST_ALLOW_AUTO_INSTALL') === '1' || force;
@@ -1 +1 @@
1
- {"version":3,"file":"QueueCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/QueueCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAYvF,eAAO,MAAM,YAAY;cACb,YAAY;EA0EtB,CAAC;AAEH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"QueueCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/QueueCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAqHvF,eAAO,MAAM,YAAY;cACb,YAAY;EAmBtB,CAAC;AAEH,eAAe,YAAY,CAAC"}