@zintrust/core 0.1.17 → 0.1.19

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 (124) hide show
  1. package/README.md +6 -0
  2. package/package.json +10 -1
  3. package/public/index.html +1 -1
  4. package/routes/api.d.ts +7 -0
  5. package/routes/api.d.ts.map +1 -0
  6. package/routes/api.js +91 -0
  7. package/routes/broadcast.d.ts +9 -0
  8. package/routes/broadcast.d.ts.map +1 -0
  9. package/routes/broadcast.js +27 -0
  10. package/routes/health.d.ts +7 -0
  11. package/routes/health.d.ts.map +1 -0
  12. package/routes/health.js +127 -0
  13. package/routes/storage.d.ts +4 -0
  14. package/routes/storage.d.ts.map +1 -0
  15. package/routes/storage.js +35 -0
  16. package/src/boot/Application.js +1 -1
  17. package/src/boot/bootstrap.js +1 -1
  18. package/src/cli/CLI.d.ts.map +1 -1
  19. package/src/cli/CLI.js +2 -0
  20. package/src/cli/PromptHelper.d.ts.map +1 -1
  21. package/src/cli/PromptHelper.js +5 -4
  22. package/src/cli/commands/NewCommand.d.ts +1 -1
  23. package/src/cli/commands/NewCommand.d.ts.map +1 -1
  24. package/src/cli/commands/NewCommand.js +27 -10
  25. package/src/cli/commands/SimulateCommand.js +1 -1
  26. package/src/cli/commands/StartCommand.d.ts.map +1 -1
  27. package/src/cli/commands/StartCommand.js +90 -3
  28. package/src/cli/commands/UpgradeCommand.d.ts +16 -0
  29. package/src/cli/commands/UpgradeCommand.d.ts.map +1 -0
  30. package/src/cli/commands/UpgradeCommand.js +107 -0
  31. package/src/cli/commands/runner/index.d.ts +3 -0
  32. package/src/cli/commands/runner/index.d.ts.map +1 -0
  33. package/src/cli/commands/runner/index.js +139 -0
  34. package/src/cli/config/ConfigSchema.js +1 -1
  35. package/src/cli/env/EnvFileBackfill.d.ts +10 -0
  36. package/src/cli/env/EnvFileBackfill.d.ts.map +1 -0
  37. package/src/cli/env/EnvFileBackfill.js +64 -0
  38. package/src/cli/scaffolding/ProjectScaffolder.d.ts.map +1 -1
  39. package/src/cli/scaffolding/ProjectScaffolder.js +53 -23
  40. package/src/cli/utils/DistPackager.d.ts.map +1 -1
  41. package/src/cli/utils/DistPackager.js +8 -0
  42. package/src/config/broadcast.js +1 -1
  43. package/src/config/database.d.ts +6 -0
  44. package/src/config/database.d.ts.map +1 -1
  45. package/src/config/database.js +7 -1
  46. package/src/config/index.d.ts +7 -1
  47. package/src/config/index.d.ts.map +1 -1
  48. package/src/config/middleware.d.ts +2 -1
  49. package/src/config/middleware.d.ts.map +1 -1
  50. package/src/config/middleware.js +47 -11
  51. package/src/config/notification.js +1 -1
  52. package/src/config/storage.js +1 -1
  53. package/src/config/type.d.ts +7 -1
  54. package/src/config/type.d.ts.map +1 -1
  55. package/src/index.d.ts +1 -0
  56. package/src/index.d.ts.map +1 -1
  57. package/src/middleware/RateLimiter.d.ts +35 -0
  58. package/src/middleware/RateLimiter.d.ts.map +1 -1
  59. package/src/middleware/RateLimiter.js +213 -16
  60. package/src/node.d.ts +1 -1
  61. package/src/node.d.ts.map +1 -1
  62. package/src/node.js +4 -1
  63. package/src/orm/DatabaseRuntimeRegistration.d.ts.map +1 -1
  64. package/src/orm/DatabaseRuntimeRegistration.js +4 -0
  65. package/src/orm/QueryBuilder.d.ts.map +1 -1
  66. package/src/orm/QueryBuilder.js +7 -3
  67. package/src/orm/adapters/SQLiteAdapter.d.ts.map +1 -1
  68. package/src/orm/adapters/SQLiteAdapter.js +5 -1
  69. package/src/routes/api.d.ts +2 -0
  70. package/src/routes/api.d.ts.map +1 -0
  71. package/src/routes/api.js +1 -0
  72. package/src/routes/broadcast.d.ts +2 -0
  73. package/src/routes/broadcast.d.ts.map +1 -0
  74. package/src/routes/broadcast.js +1 -0
  75. package/src/routes/health.d.ts +2 -0
  76. package/src/routes/health.d.ts.map +1 -0
  77. package/src/routes/health.js +1 -0
  78. package/src/routes/storage.d.ts +2 -0
  79. package/src/routes/storage.d.ts.map +1 -0
  80. package/src/routes/storage.js +1 -0
  81. package/src/runtime/RuntimeAdapter.d.ts.map +1 -1
  82. package/src/runtime/RuntimeAdapter.js +20 -1
  83. package/src/runtime/adapters/DenoAdapter.js +2 -2
  84. package/src/scripts/TemplateImportsCheck.js +7 -7
  85. package/src/scripts/TemplateSync.js +6 -0
  86. package/src/start.d.ts +21 -0
  87. package/src/start.d.ts.map +1 -0
  88. package/src/start.js +60 -0
  89. package/src/templates/features/Queue.ts.tpl +2 -3
  90. package/src/templates/project/basic/.env.example.tpl +1 -1
  91. package/src/templates/project/basic/app/Controllers/UserController.ts.tpl +2 -4
  92. package/src/templates/project/basic/app/Middleware/ProfilerMiddleware.ts.tpl +1 -3
  93. package/src/templates/project/basic/app/Middleware/index.ts.tpl +3 -8
  94. package/src/templates/project/basic/app/Models/Post.ts.tpl +2 -3
  95. package/src/templates/project/basic/app/Models/User.ts.tpl +1 -1
  96. package/src/templates/project/basic/config/FileLogWriter.ts.tpl +1 -1
  97. package/src/templates/project/basic/config/SecretsManager.ts.tpl +2 -2
  98. package/src/templates/project/basic/config/StartupConfigValidator.ts.tpl +2 -2
  99. package/src/templates/project/basic/config/app.ts.tpl +3 -3
  100. package/src/templates/project/basic/config/broadcast.ts.tpl +4 -5
  101. package/src/templates/project/basic/config/cache.ts.tpl +2 -3
  102. package/src/templates/project/basic/config/cloudflare.ts.tpl +1 -1
  103. package/src/templates/project/basic/config/database.ts.tpl +9 -3
  104. package/src/templates/project/basic/config/env.ts.tpl +1 -1
  105. package/src/templates/project/basic/config/features.ts.tpl +2 -2
  106. package/src/templates/project/basic/config/index.ts.tpl +38 -20
  107. package/src/templates/project/basic/config/logger.ts.tpl +5 -381
  108. package/src/templates/project/basic/config/logging/HttpLogger.ts.tpl +1 -1
  109. package/src/templates/project/basic/config/logging/KvLogger.ts.tpl +2 -2
  110. package/src/templates/project/basic/config/logging/SlackLogger.ts.tpl +1 -1
  111. package/src/templates/project/basic/config/mail.ts.tpl +2 -3
  112. package/src/templates/project/basic/config/microservices.ts.tpl +1 -1
  113. package/src/templates/project/basic/config/middleware.ts.tpl +40 -13
  114. package/src/templates/project/basic/config/notification.ts.tpl +3 -4
  115. package/src/templates/project/basic/config/queue.ts.tpl +2 -2
  116. package/src/templates/project/basic/config/security.ts.tpl +3 -3
  117. package/src/templates/project/basic/config/startup.ts.tpl +1 -1
  118. package/src/templates/project/basic/config/storage.ts.tpl +3 -4
  119. package/src/templates/project/basic/config/type.ts.tpl +12 -2
  120. package/src/templates/project/basic/package.json.tpl +1 -1
  121. package/src/templates/project/basic/routes/api.ts.tpl +4 -4
  122. package/src/templates/project/basic/routes/health.ts.tpl +1 -6
  123. package/src/templates/project/basic/src/index.ts.tpl +7 -80
  124. package/src/templates/project/basic/tsconfig.json.tpl +0 -2
@@ -1,9 +1,10 @@
1
1
  import { BaseCommand } from '../BaseCommand.js';
2
+ import { DENO_RUNNER_SOURCE, LAMBDA_RUNNER_SOURCE } from '../commands/runner/index.js';
2
3
  import { EnvFileLoader } from '../utils/EnvFileLoader.js';
3
4
  import { SpawnUtil } from '../utils/spawn.js';
4
5
  import { resolveNpmPath } from '../../common/index.js';
5
6
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
6
- import { existsSync, readFileSync } from '../../node-singletons/fs.js';
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from '../../node-singletons/fs.js';
7
8
  import * as path from '../../node-singletons/path.js';
8
9
  const isValidModeInput = (value) => value === 'development' ||
9
10
  value === 'dev' ||
@@ -59,6 +60,22 @@ const resolveRuntime = (options) => {
59
60
  const raw = typeof options.runtime === 'string' ? options.runtime.trim() : '';
60
61
  return raw === '' ? undefined : raw;
61
62
  };
63
+ const resolveStartVariant = (options) => {
64
+ const wantWrangler = options.wrangler === true || options.wg === true;
65
+ const wantDeno = options.deno === true;
66
+ const wantLambda = options.lambda === true;
67
+ const enabled = [wantWrangler, wantDeno, wantLambda].filter(Boolean).length;
68
+ if (enabled > 1) {
69
+ throw ErrorFactory.createCliError('Error: Choose only one of --wrangler/--wg, --deno, or --lambda.');
70
+ }
71
+ if (wantWrangler)
72
+ return 'wrangler';
73
+ if (wantDeno)
74
+ return 'deno';
75
+ if (wantLambda)
76
+ return 'lambda';
77
+ return 'node';
78
+ };
62
79
  const hasFlag = (flag) => process.argv.includes(flag);
63
80
  const resolveWatchPreference = (options, mode) => {
64
81
  const hasWatch = hasFlag('--watch');
@@ -104,6 +121,10 @@ const findWranglerConfig = (cwd) => {
104
121
  return undefined;
105
122
  };
106
123
  const resolveWranglerEntry = (cwd) => {
124
+ const indexEntry = path.join(cwd, 'src/index.ts');
125
+ if (existsSync(indexEntry))
126
+ return 'src/index.ts';
127
+ // Legacy fallback
107
128
  const entry = path.join(cwd, 'src/functions/cloudflare.ts');
108
129
  return existsSync(entry) ? 'src/functions/cloudflare.ts' : undefined;
109
130
  };
@@ -167,6 +188,55 @@ const executeWranglerStart = async (cmd, cwd, port, runtime) => {
167
188
  const exitCode = await SpawnUtil.spawnAndWait({ command: 'wrangler', args: wranglerArgs });
168
189
  process.exit(exitCode);
169
190
  };
191
+ const ensureTmpRunnerFile = (cwd, filename, content) => {
192
+ const tmpDir = path.join(cwd, 'tmp');
193
+ try {
194
+ mkdirSync(tmpDir, { recursive: true });
195
+ }
196
+ catch (error) {
197
+ throw ErrorFactory.createTryCatchError('Failed to create tmp directory', error);
198
+ }
199
+ const fullPath = path.join(tmpDir, filename);
200
+ try {
201
+ writeFileSync(fullPath, content, 'utf-8');
202
+ }
203
+ catch (error) {
204
+ throw ErrorFactory.createTryCatchError('Failed to write start runner', error);
205
+ }
206
+ return fullPath;
207
+ };
208
+ const executeDenoStart = async (cmd, cwd, mode, watchEnabled, _port, runtime) => {
209
+ if (runtime !== undefined) {
210
+ throw ErrorFactory.createCliError('Error: --runtime cannot be used with --deno.');
211
+ }
212
+ if (mode === 'testing') {
213
+ throw ErrorFactory.createCliError('Error: Cannot start server in testing mode. Use development or production.');
214
+ }
215
+ const denoRunner = ensureTmpRunnerFile(cwd, 'zin-start-deno.ts', DENO_RUNNER_SOURCE);
216
+ const args = [];
217
+ if (mode === 'development' && watchEnabled)
218
+ args.push('watch');
219
+ args.push(denoRunner);
220
+ cmd.info('Starting in Deno adapter mode...');
221
+ const exitCode = await SpawnUtil.spawnAndWait({ command: 'tsx', args });
222
+ process.exit(exitCode);
223
+ };
224
+ const executeLambdaStart = async (cmd, cwd, mode, watchEnabled, _port, runtime) => {
225
+ if (runtime !== undefined) {
226
+ throw ErrorFactory.createCliError('Error: --runtime cannot be used with --lambda.');
227
+ }
228
+ if (mode === 'testing') {
229
+ throw ErrorFactory.createCliError('Error: Cannot start server in testing mode. Use development or production.');
230
+ }
231
+ const lambdaRunner = ensureTmpRunnerFile(cwd, 'zin-start-lambda.ts', LAMBDA_RUNNER_SOURCE);
232
+ const args = [];
233
+ if (mode === 'development' && watchEnabled)
234
+ args.push('watch');
235
+ args.push(lambdaRunner);
236
+ cmd.info('Starting in Lambda adapter mode...');
237
+ const exitCode = await SpawnUtil.spawnAndWait({ command: 'tsx', args });
238
+ process.exit(exitCode);
239
+ };
170
240
  const executeNodeStart = async (cmd, cwd, mode, watchEnabled) => {
171
241
  if (mode === 'testing') {
172
242
  throw ErrorFactory.createCliError('Error: Cannot start server in testing mode. Use --force to override (not supported).');
@@ -196,12 +266,26 @@ const executeStart = async (options, cmd) => {
196
266
  const mode = resolveMode(options);
197
267
  const port = resolvePort(options);
198
268
  const runtime = resolveRuntime(options);
199
- EnvFileLoader.applyCliOverrides({ nodeEnv: mode, port, runtime });
200
- if (options.wrangler === true) {
269
+ const variant = resolveStartVariant(options);
270
+ let effectiveRuntime = runtime;
271
+ if (variant === 'deno')
272
+ effectiveRuntime = 'deno';
273
+ if (variant === 'lambda')
274
+ effectiveRuntime = 'lambda';
275
+ EnvFileLoader.applyCliOverrides({ nodeEnv: mode, port, runtime: effectiveRuntime });
276
+ if (variant === 'wrangler') {
201
277
  await executeWranglerStart(cmd, cwd, port, runtime);
202
278
  return;
203
279
  }
204
280
  const watchEnabled = resolveWatchPreference(options, mode);
281
+ if (variant === 'deno') {
282
+ await executeDenoStart(cmd, cwd, mode, watchEnabled, port, runtime);
283
+ return;
284
+ }
285
+ if (variant === 'lambda') {
286
+ await executeLambdaStart(cmd, cwd, mode, watchEnabled, port, runtime);
287
+ return;
288
+ }
205
289
  await executeNodeStart(cmd, cwd, mode, watchEnabled);
206
290
  };
207
291
  export const StartCommand = Object.freeze({
@@ -210,6 +294,9 @@ export const StartCommand = Object.freeze({
210
294
  command.alias('s');
211
295
  command
212
296
  .option('-w, --wrangler', 'Start with Wrangler dev mode (Cloudflare Workers)')
297
+ .option('--wg', 'Alias for --wrangler')
298
+ .option('--deno', 'Start a local server using the Deno runtime adapter')
299
+ .option('--lambda', 'Start a local server using the AWS Lambda runtime adapter')
213
300
  .option('--watch', 'Force watch mode (Node only)')
214
301
  .option('--no-watch', 'Disable watch mode (Node only)')
215
302
  .option('--mode <development|production|testing>', 'Override app mode')
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Upgrade Command
3
+ *
4
+ * Goal: help existing Zintrust projects adopt new safe defaults without forcing
5
+ * a full re-scaffold.
6
+ *
7
+ * Current scope (minimal/safe):
8
+ * - Backfill required .env defaults (HOST, PORT, LOG_LEVEL) when missing or blank.
9
+ * - Never overwrite a non-empty user value.
10
+ */
11
+ import { type IBaseCommand } from '../BaseCommand';
12
+ export declare const UpgradeCommand: Readonly<{
13
+ create(): IBaseCommand;
14
+ }>;
15
+ export default UpgradeCommand;
16
+ //# sourceMappingURL=UpgradeCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpgradeCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/UpgradeCommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAoFvF,eAAO,MAAM,cAAc;cACf,YAAY;EA6BtB,CAAC;AAEH,eAAe,cAAc,CAAC"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Upgrade Command
3
+ *
4
+ * Goal: help existing Zintrust projects adopt new safe defaults without forcing
5
+ * a full re-scaffold.
6
+ *
7
+ * Current scope (minimal/safe):
8
+ * - Backfill required .env defaults (HOST, PORT, LOG_LEVEL) when missing or blank.
9
+ * - Never overwrite a non-empty user value.
10
+ */
11
+ import { BaseCommand } from '../BaseCommand.js';
12
+ import { ErrorHandler } from '../ErrorHandler.js';
13
+ import { EnvFileBackfill } from '../env/EnvFileBackfill.js';
14
+ import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from '../../node-singletons/fs.js';
15
+ import * as path from '../../node-singletons/path.js';
16
+ const ensureEnvFileExists = (envPath) => {
17
+ const dir = path.dirname(envPath);
18
+ mkdirSync(dir, { recursive: true });
19
+ try {
20
+ readFileSync(envPath, 'utf8');
21
+ }
22
+ catch {
23
+ writeFileSync(envPath, '\n', 'utf8');
24
+ }
25
+ };
26
+ const resolveCwd = (cwd) => {
27
+ if (typeof cwd === 'string' && cwd.trim() !== '')
28
+ return cwd;
29
+ return process.cwd();
30
+ };
31
+ const getEnvDefaults = () => Object.freeze({
32
+ HOST: 'localhost',
33
+ PORT: '7777',
34
+ LOG_LEVEL: 'debug',
35
+ });
36
+ const envFileExists = (envPath) => {
37
+ try {
38
+ readFileSync(envPath, 'utf8');
39
+ return true;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ };
45
+ const formatBackfillSummary = (result) => {
46
+ const filled = result.filledKeys.join(', ') || '(none)';
47
+ const appended = result.appendedKeys.join(', ') || '(none)';
48
+ return `filled=${filled}; appended=${appended}`;
49
+ };
50
+ const runDryRun = (envPath, defaults) => {
51
+ const exists = envFileExists(envPath);
52
+ if (exists === false) {
53
+ ErrorHandler.info(`[dry-run] Would create ${envPath}`);
54
+ return;
55
+ }
56
+ const raw = readFileSync(envPath, 'utf8');
57
+ const tmpPath = `${envPath}.zin-upgrade.tmp`;
58
+ writeFileSync(tmpPath, raw, 'utf8');
59
+ try {
60
+ const result = EnvFileBackfill.backfillEnvDefaults(tmpPath, defaults);
61
+ if (result.changed === false) {
62
+ ErrorHandler.success('No changes needed.');
63
+ return;
64
+ }
65
+ ErrorHandler.info(`[dry-run] Would backfill .env: ${formatBackfillSummary(result)}`);
66
+ }
67
+ finally {
68
+ try {
69
+ unlinkSync(tmpPath);
70
+ }
71
+ catch {
72
+ // ignore
73
+ }
74
+ }
75
+ };
76
+ const runUpgrade = (envPath, defaults) => {
77
+ ensureEnvFileExists(envPath);
78
+ return EnvFileBackfill.backfillEnvDefaults(envPath, defaults);
79
+ };
80
+ export const UpgradeCommand = Object.freeze({
81
+ create() {
82
+ return BaseCommand.create({
83
+ name: 'upgrade',
84
+ description: 'Upgrade an existing Zintrust project in-place (safe, non-destructive)',
85
+ addOptions: (command) => {
86
+ command.option('--cwd <path>', 'Project directory (default: current working directory)');
87
+ command.option('--dry-run', 'Print planned changes without writing files');
88
+ },
89
+ execute: (options) => {
90
+ const cwd = resolveCwd(options.cwd);
91
+ const envPath = path.resolve(cwd, '.env');
92
+ const defaults = getEnvDefaults();
93
+ if (options.dryRun === true) {
94
+ runDryRun(envPath, defaults);
95
+ return;
96
+ }
97
+ const result = runUpgrade(envPath, defaults);
98
+ if (result.changed === false) {
99
+ ErrorHandler.success('No changes needed.');
100
+ return;
101
+ }
102
+ ErrorHandler.success(`Upgraded .env: ${formatBackfillSummary(result)}`);
103
+ },
104
+ });
105
+ },
106
+ });
107
+ export default UpgradeCommand;
@@ -0,0 +1,3 @@
1
+ export declare const DENO_RUNNER_SOURCE: string;
2
+ export declare const LAMBDA_RUNNER_SOURCE: string;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/runner/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,QAwDnB,CAAC;AAEb,eAAO,MAAM,oBAAoB,QAiFrB,CAAC"}
@@ -0,0 +1,139 @@
1
+ export const DENO_RUNNER_SOURCE = [
2
+ "import http from 'node:http';",
3
+ "import { deno } from '@zintrust/core/start';",
4
+ '',
5
+ "const port = Number.parseInt(process.env.PORT ?? '3000', 10) || 3000;",
6
+ "const host = process.env.HOST ?? 'localhost';",
7
+ '',
8
+ 'const readBody = async (req: http.IncomingMessage): Promise<Buffer | undefined> => {',
9
+ " if (req.method === 'GET' || req.method === 'HEAD') return undefined;",
10
+ ' const chunks: Buffer[] = [];',
11
+ ' for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));',
12
+ ' return chunks.length > 0 ? Buffer.concat(chunks) : undefined;',
13
+ '};',
14
+ '',
15
+ 'const server = http.createServer(async (req, res) => {',
16
+ ' try {',
17
+ " const proto = (req.headers['x-forwarded-proto'] ?? 'http').toString();",
18
+ ' const authority = (req.headers.host ?? host).toString();',
19
+ " const url = new URL(req.url ?? '/', proto + '://' + authority);",
20
+ '',
21
+ ' const headers = new Headers();',
22
+ ' for (const [key, value] of Object.entries(req.headers)) {',
23
+ ' if (value === undefined) continue;',
24
+ ' if (Array.isArray(value)) {',
25
+ ' for (const v of value) headers.append(key, v);',
26
+ ' } else {',
27
+ ' headers.set(key, String(value));',
28
+ ' }',
29
+ ' }',
30
+ '',
31
+ ' const body = await readBody(req);',
32
+ ' const request = new Request(url.toString(), {',
33
+ " method: req.method ?? 'GET',",
34
+ ' headers,',
35
+ ' body,',
36
+ ' });',
37
+ '',
38
+ ' const response = await deno(request);',
39
+ '',
40
+ ' res.statusCode = response.status;',
41
+ ' response.headers.forEach((v, k) => res.setHeader(k, v));',
42
+ '',
43
+ ' const ab = await response.arrayBuffer();',
44
+ ' res.end(Buffer.from(ab));',
45
+ ' } catch (err) {',
46
+ ' res.statusCode = 500;',
47
+ " res.setHeader('content-type', 'text/plain');",
48
+ " res.end('Internal Server Error' + (err as Error).message);",
49
+ ' }',
50
+ '});',
51
+ '',
52
+ 'server.listen(port, host, () => {',
53
+ ' // eslint-disable-next-line no-console',
54
+ " console.log('Deno adapter server listening at http://' + host + ':' + port);",
55
+ '});',
56
+ '',
57
+ ].join('\n');
58
+ export const LAMBDA_RUNNER_SOURCE = [
59
+ "import http from 'node:http';",
60
+ "import { handler as lambdaHandler } from '@zintrust/core/start';",
61
+ '',
62
+ "const port = Number.parseInt(process.env.PORT ?? '3000', 10) || 3000;",
63
+ "const host = process.env.HOST ?? 'localhost';",
64
+ '',
65
+ 'const readBody = async (req: http.IncomingMessage): Promise<string | undefined> => {',
66
+ " if (req.method === 'GET' || req.method === 'HEAD') return undefined;",
67
+ ' const chunks: Buffer[] = [];',
68
+ ' for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));',
69
+ " return chunks.length > 0 ? Buffer.concat(chunks).toString('utf-8') : undefined;",
70
+ '};',
71
+ '',
72
+ 'const server = http.createServer(async (req, res) => {',
73
+ ' try {',
74
+ " const proto = (req.headers['x-forwarded-proto'] ?? 'http').toString();",
75
+ ' const authority = (req.headers.host ?? host).toString();',
76
+ " const url = new URL(req.url ?? '/', proto + '://' + authority);",
77
+ '',
78
+ ' const body = await readBody(req);',
79
+ ' const headers: Record<string, string> = {};',
80
+ ' for (const [k, v] of Object.entries(req.headers)) {',
81
+ ' if (v === undefined) continue;',
82
+ " headers[k] = Array.isArray(v) ? v.join(',') : String(v);",
83
+ ' }',
84
+ '',
85
+ ' const queryStringParameters: Record<string, string> = {};',
86
+ ' url.searchParams.forEach((value, key) => {',
87
+ ' if (queryStringParameters[key] === undefined) queryStringParameters[key] = value;',
88
+ ' });',
89
+ '',
90
+ ' const event = {',
91
+ " version: '2.0',",
92
+ " routeKey: '$default',",
93
+ ' rawPath: url.pathname,',
94
+ ' rawQueryString: url.searchParams.toString(),',
95
+ ' headers,',
96
+ ' queryStringParameters,',
97
+ ' requestContext: {',
98
+ ' http: {',
99
+ " method: req.method ?? 'GET',",
100
+ ' path: url.pathname,',
101
+ " sourceIp: (req.socket.remoteAddress ?? '').toString(),",
102
+ ' },',
103
+ ' },',
104
+ ' body: body ?? null,',
105
+ ' isBase64Encoded: false,',
106
+ ' };',
107
+ '',
108
+ ' const result = (await lambdaHandler(event, {})) as {',
109
+ ' statusCode?: number;',
110
+ ' headers?: Record<string, string | string[]>;',
111
+ ' body?: string;',
112
+ ' isBase64Encoded?: boolean;',
113
+ ' };',
114
+ '',
115
+ ' res.statusCode = typeof result.statusCode === "number" ? result.statusCode : 200;',
116
+ ' if (result.headers) {',
117
+ ' for (const [k, v] of Object.entries(result.headers)) {',
118
+ ' res.setHeader(k, v as any);',
119
+ ' }',
120
+ ' }',
121
+ '',
122
+ ' if (result.isBase64Encoded === true && typeof result.body === "string") {',
123
+ " res.end(Buffer.from(result.body, 'base64'));",
124
+ ' } else {',
125
+ " res.end(result.body ?? '');",
126
+ ' }',
127
+ ' } catch {',
128
+ ' res.statusCode = 500;',
129
+ " res.setHeader('content-type', 'application/json');",
130
+ " res.end(JSON.stringify({ message: 'Internal Server Error' }));",
131
+ ' }',
132
+ '});',
133
+ '',
134
+ 'server.listen(port, host, () => {',
135
+ ' // eslint-disable-next-line no-console',
136
+ " console.log('Lambda adapter server listening at http://' + host + ':' + port);",
137
+ '});',
138
+ '',
139
+ ].join('\n');
@@ -18,7 +18,7 @@ export const DEFAULT_CONFIG = Object.freeze({
18
18
  logging: false,
19
19
  },
20
20
  server: {
21
- port: 3000,
21
+ port: 7777,
22
22
  host: '0.0.0.0',
23
23
  environment: 'development',
24
24
  debug: false,
@@ -0,0 +1,10 @@
1
+ export type EnvBackfillResult = {
2
+ changed: boolean;
3
+ filledKeys: string[];
4
+ appendedKeys: string[];
5
+ };
6
+ export declare const EnvFileBackfill: Readonly<{
7
+ stripEnvInlineComment: (value: string) => string;
8
+ backfillEnvDefaults: (envPath: string, defaults: Record<string, string>) => EnvBackfillResult;
9
+ }>;
10
+ //# sourceMappingURL=EnvFileBackfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnvFileBackfill.d.ts","sourceRoot":"","sources":["../../../../src/cli/env/EnvFileBackfill.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAsEF,eAAO,MAAM,eAAe;mCApEU,MAAM,KAAG,MAAM;mCAqB1C,MAAM,YACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC/B,iBAAiB;EAgDlB,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { readFileSync, writeFileSync } from '../../node-singletons/fs.js';
2
+ const stripEnvInlineComment = (value) => {
3
+ let inSingle = false;
4
+ let inDouble = false;
5
+ for (let i = 0; i < value.length; i += 1) {
6
+ const ch = value[i];
7
+ if (ch === "'" && !inDouble)
8
+ inSingle = !inSingle;
9
+ if (ch === '"' && !inSingle)
10
+ inDouble = !inDouble;
11
+ if (!inSingle && !inDouble && ch === '#') {
12
+ const prev = value[i - 1];
13
+ if (prev === undefined || prev === ' ' || prev === '\t') {
14
+ return value.slice(0, i).trimEnd();
15
+ }
16
+ }
17
+ }
18
+ return value;
19
+ };
20
+ const backfillEnvDefaults = (envPath, defaults) => {
21
+ const raw = readFileSync(envPath, 'utf8');
22
+ const lines = raw.split(/\r?\n/);
23
+ const seen = new Set();
24
+ const filledKeys = [];
25
+ const appendedKeys = [];
26
+ const out = lines.map((line) => {
27
+ const trimmed = line.trim();
28
+ if (trimmed === '' || trimmed.startsWith('#'))
29
+ return line;
30
+ const withoutExport = trimmed.startsWith('export ') ? trimmed.slice('export '.length) : trimmed;
31
+ const eq = withoutExport.indexOf('=');
32
+ if (eq <= 0)
33
+ return line;
34
+ const key = withoutExport.slice(0, eq).trim();
35
+ if (key === '')
36
+ return line;
37
+ if (!Object.hasOwn(defaults, key))
38
+ return line;
39
+ if (seen.has(key))
40
+ return line;
41
+ seen.add(key);
42
+ const rhs = withoutExport.slice(eq + 1);
43
+ const withoutComment = stripEnvInlineComment(rhs);
44
+ const value = withoutComment.trim();
45
+ if (value !== '')
46
+ return line;
47
+ filledKeys.push(key);
48
+ return `${key}=${defaults[key]}`;
49
+ });
50
+ const missingKeys = Object.keys(defaults).filter((k) => !seen.has(k));
51
+ if (missingKeys.length > 0) {
52
+ appendedKeys.push(...missingKeys);
53
+ out.push(...missingKeys.map((k) => `${k}=${defaults[k]}`));
54
+ }
55
+ const changed = filledKeys.length > 0 || appendedKeys.length > 0;
56
+ if (!changed)
57
+ return { changed: false, filledKeys, appendedKeys };
58
+ writeFileSync(envPath, out.join('\n') + (out.at(-1) === '' ? '' : '\n'));
59
+ return { changed: true, filledKeys, appendedKeys };
60
+ };
61
+ export const EnvFileBackfill = Object.freeze({
62
+ stripEnvInlineComment,
63
+ backfillEnvDefaults,
64
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/ProjectScaffolder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACtD,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IACpE,cAAc,IAAI,MAAM,CAAC;IACzB,sBAAsB,IAAI,OAAO,CAAC;IAClC,iBAAiB,IAAI,MAAM,CAAC;IAC5B,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACtD,gBAAgB,IAAI,OAAO,CAAC;IAC5B,aAAa,IAAI,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC3E;AAyaD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAsBrE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG;IAChE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAsBA;AA0ID;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,GAAE,MAAsB,GAAG,kBAAkB,CAsB/F;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAEhC;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;EAM5B,CAAC"}
1
+ {"version":3,"file":"ProjectScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/ProjectScaffolder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACtD,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IACpE,cAAc,IAAI,MAAM,CAAC;IACzB,sBAAsB,IAAI,OAAO,CAAC;IAClC,iBAAiB,IAAI,MAAM,CAAC;IAC5B,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACtD,gBAAgB,IAAI,OAAO,CAAC;IAC5B,aAAa,IAAI,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC3E;AA0cD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAsBrE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG;IAChE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAsBA;AA0ID;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,GAAE,MAAsB,GAAG,kBAAkB,CAsB/F;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAEhC;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;EAM5B,CAAC"}
@@ -2,6 +2,7 @@
2
2
  * Project Scaffolder - New project generation
3
3
  * Handles directory structure and boilerplate file creation
4
4
  */
5
+ import { EnvFileBackfill } from '../env/EnvFileBackfill.js';
5
6
  import { Logger } from '../../config/logger.js';
6
7
  import { randomBytes } from '../../node-singletons/crypto.js';
7
8
  import fs from '../../node-singletons/fs.js';
@@ -87,7 +88,7 @@ const createProjectConfigFile = (projectPath, variables) => {
87
88
  connection: variables['database'] ?? 'sqlite',
88
89
  },
89
90
  server: {
90
- port: variables['port'] ?? 3000,
91
+ port: variables['port'] ?? 7777,
91
92
  },
92
93
  };
93
94
  fs.writeFileSync(fullPath, JSON.stringify(config, null, 2));
@@ -97,18 +98,59 @@ const createProjectConfigFile = (projectPath, variables) => {
97
98
  return false;
98
99
  }
99
100
  };
101
+ const buildDatabaseEnvLines = (database) => {
102
+ if (database === 'postgresql' || database === 'postgres') {
103
+ return [
104
+ 'DB_HOST=localhost',
105
+ 'DB_PORT=5432',
106
+ 'DB_DATABASE=zintrust',
107
+ 'DB_USERNAME=postgres',
108
+ 'DB_PASSWORD=',
109
+ ];
110
+ }
111
+ if (database === 'mysql') {
112
+ return [
113
+ 'DB_HOST=localhost',
114
+ 'DB_PORT=3306',
115
+ 'DB_DATABASE=zintrust',
116
+ 'DB_USERNAME=root',
117
+ 'DB_PASSWORD=',
118
+ ];
119
+ }
120
+ if (database === 'sqlite') {
121
+ // Provide both DB_DATABASE (used by the framework) and DB_PATH (common alias)
122
+ return ['DB_DATABASE=./database.sqlite', 'DB_PATH=./database.sqlite'];
123
+ }
124
+ if (database === 'd1-remote' || database === 'd1-proxy') {
125
+ return [
126
+ '# Cloudflare D1 Remote Proxy (HTTPS)',
127
+ 'D1_REMOTE_URL=',
128
+ 'D1_REMOTE_KEY_ID=',
129
+ 'D1_REMOTE_SECRET=',
130
+ 'D1_REMOTE_MODE=registry',
131
+ 'ZT_PROXY_TIMEOUT_MS=15000',
132
+ ];
133
+ }
134
+ return [];
135
+ };
100
136
  const createEnvFile = (projectPath, variables) => {
101
137
  try {
102
138
  if (!fs.existsSync(projectPath)) {
103
139
  fs.mkdirSync(projectPath, { recursive: true });
104
140
  }
105
141
  const fullPath = path.join(projectPath, '.env');
106
- // If the template already produced an .env, do not overwrite it here.
142
+ // If an .env already exists (e.g., from a template), do not overwrite user values.
143
+ // But we *do* backfill safe defaults for common bootstrap keys when missing/blank.
107
144
  if (fs.existsSync(fullPath)) {
145
+ EnvFileBackfill.backfillEnvDefaults(fullPath, {
146
+ HOST: 'localhost',
147
+ PORT: String(Number(variables['port'] ?? 7777)),
148
+ LOG_LEVEL: 'debug',
149
+ });
108
150
  return true;
109
151
  }
110
152
  const name = typeof variables['projectName'] === 'string' ? variables['projectName'] : 'zintrust-app';
111
- const port = Number(variables['port'] ?? 3000);
153
+ const port = Number(variables['port'] ?? 7777);
112
154
  const database = typeof variables['database'] === 'string' ? variables['database'] : 'sqlite';
113
155
  // Generate a secure APP_KEY (32 bytes = 256-bit, base64 encoded)
114
156
  const appKeyBytes = randomBytes(32);
@@ -119,28 +161,12 @@ const createEnvFile = (projectPath, variables) => {
119
161
  `APP_NAME=${name}`,
120
162
  'HOST=localhost',
121
163
  `PORT=${port}`,
122
- `APP_PORT=${port}`,
123
164
  'APP_DEBUG=true',
124
165
  // Auto-generated secure key for storage signing and encryption
125
166
  `APP_KEY=${appKey}`,
126
167
  `DB_CONNECTION=${database}`,
127
168
  ];
128
- const dbLines = (() => {
129
- if (database === 'postgresql' || database === 'postgres') {
130
- return [
131
- 'DB_HOST=localhost',
132
- 'DB_PORT=5432',
133
- 'DB_DATABASE=zintrust',
134
- 'DB_USERNAME=postgres',
135
- 'DB_PASSWORD=',
136
- ];
137
- }
138
- if (database === 'sqlite') {
139
- // Provide both DB_DATABASE (used by the framework) and DB_PATH (common alias)
140
- return ['DB_DATABASE=./database.sqlite', 'DB_PATH=./database.sqlite'];
141
- }
142
- return [];
143
- })();
169
+ const dbLines = buildDatabaseEnvLines(database);
144
170
  const placeholderLines = [
145
171
  '',
146
172
  '# Logging',
@@ -162,7 +188,7 @@ const createEnvFile = (projectPath, variables) => {
162
188
  '',
163
189
  '# Microservices',
164
190
  'SERVICE_DISCOVERY_ENABLED=false',
165
- 'SERVICE_DISCOVERY_DRIVER=local',
191
+ 'SERVICE_DISCOVERY_TYPE=filesystem',
166
192
  'SERVICE_NAME=',
167
193
  'SERVICE_VERSION=1.0.0',
168
194
  ];
@@ -245,11 +271,15 @@ const loadTemplateFiles = (templateDir) => {
245
271
  if (relPath === 'template.json')
246
272
  return false;
247
273
  const normalized = normalizeRelPath(relPath);
274
+ // Project `.env` is generated by createEnvFile() so it can set defaults and create a secure APP_KEY.
275
+ // Some templates ship `.env.tpl` (which would become `.env`), but that file is intentionally ignored.
276
+ const outputRel = normalizeRelPath(getOutputRelPath(relPath));
277
+ if (outputRel === '.env')
278
+ return false;
248
279
  if (!normalized.startsWith('config/'))
249
280
  return true;
250
281
  // Starter apps should only ship app-level config modules.
251
282
  // Core/framework config internals (e.g. config/logging/*) remain core-owned.
252
- const outputRel = normalizeRelPath(getOutputRelPath(relPath));
253
283
  return allowedConfigFiles.has(outputRel);
254
284
  };
255
285
  const readUtf8FileOrUndefined = (absPath) => {
@@ -424,7 +454,7 @@ const prepareContext = (state, options) => {
424
454
  projectSlug: options.name,
425
455
  author: options.author ?? 'Your Name',
426
456
  description: options.description ?? '',
427
- port: options.port ?? 3000,
457
+ port: options.port ?? 7777,
428
458
  database: options.database ?? 'sqlite',
429
459
  template: state.templateName,
430
460
  migrationTimestamp,
@@ -1 +1 @@
1
- {"version":3,"file":"DistPackager.d.ts","sourceRoot":"","sources":["../../../../src/cli/utils/DistPackager.ts"],"names":[],"mappings":"AAgHA,eAAO,MAAM,YAAY;IACvB;;;OAGG;sBACe,MAAM,aAAY,MAAM,GAAmB,IAAI;EAgBjE,CAAC"}
1
+ {"version":3,"file":"DistPackager.d.ts","sourceRoot":"","sources":["../../../../src/cli/utils/DistPackager.ts"],"names":[],"mappings":"AAoIA,eAAO,MAAM,YAAY;IACvB;;;OAGG;sBACe,MAAM,aAAY,MAAM,GAAmB,IAAI;EAgBjE,CAAC"}