@emarketeer/ts-microservice-commons 8.2.2 → 9.0.0

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 (30) hide show
  1. package/dist/build-handlers.js +149 -48
  2. package/dist/cdk/cjs/index.js +760 -557
  3. package/dist/cdk/cjs/index.js.map +1 -1
  4. package/dist/cdk/index.js +739 -536
  5. package/dist/cdk/index.js.map +1 -1
  6. package/dist/lib/em-commons.js +44 -43
  7. package/dist/lib/jest.config.js +2 -2
  8. package/dist/types/cdk/__tests__/config.test.d.ts +1 -0
  9. package/dist/types/cdk/__tests__/dlq-alarm.test.d.ts +1 -0
  10. package/dist/types/cdk/__tests__/handler-path.test.d.ts +1 -0
  11. package/dist/types/cdk/__tests__/logs.test.d.ts +1 -0
  12. package/dist/types/cdk/__tests__/rds-vpc.test.d.ts +1 -0
  13. package/dist/types/cdk/constructs/dlq-alarm.d.ts +6 -0
  14. package/dist/types/cdk/constructs/lambda-with-http-api.d.ts +2 -2
  15. package/dist/types/cdk/constructs/lambda-with-queue.d.ts +28 -3
  16. package/dist/types/cdk/constructs/stack.d.ts +142 -33
  17. package/dist/types/cdk/constructs/topic-queue-consumer.d.ts +5 -0
  18. package/dist/types/cdk/types/common.d.ts +18 -26
  19. package/dist/types/cdk/utils/config.d.ts +11 -62
  20. package/dist/types/cdk/utils/handler-path.d.ts +26 -4
  21. package/dist/types/cdk/utils/iam.d.ts +1 -64
  22. package/dist/types/cdk/utils/logs.d.ts +11 -9
  23. package/dist/types/cdk/utils/rds-vpc.d.ts +8 -1
  24. package/dist/types/cdk/utils/serverless-migration.d.ts +187 -3
  25. package/dist/types/find-entry-points.d.ts +20 -0
  26. package/dist/types/find-entry-points.test.d.ts +1 -0
  27. package/dist/types/utils.d.ts +1 -1
  28. package/package.json +9 -3
  29. package/dist/types/cdk/jest.config.d.ts +0 -2
  30. package/dist/types/esbuild-plugins.d.ts +0 -5
@@ -4,11 +4,108 @@ var path = require('path');
4
4
  var esbuild = require('esbuild');
5
5
  var fs = require('fs');
6
6
 
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n["default"] = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
26
+ var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
27
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
28
+
29
+ /**
30
+ * Patterns that indicate a file exports a Lambda handler named `handler`.
31
+ *
32
+ * Covers:
33
+ * export const/let/var handler = ...
34
+ * export function handler / export async function handler
35
+ * export { handler } and export { foo as handler } (the export *name* is
36
+ * `handler`; the negative lookahead excludes `handler as somethingElse`
37
+ * where `handler` is only the source identifier)
38
+ * module.exports.handler = ... / exports.handler = ... (CJS interop)
39
+ *
40
+ * Intentionally excludes `export default function handler` — the esbuild wrapper
41
+ * accesses `unwrappedHandler.handler` (named export). Default exports compile to
42
+ * `module.exports.default`, so `.handler` is `undefined` at runtime.
43
+ *
44
+ * Not covered: `export * from './impl'` — resolving re-exports would require
45
+ * following the import graph. Files that match the source-naming convention
46
+ * but contain no recognised pattern produce a `console.warn` from
47
+ * `findEntryPoints`.
48
+ */
49
+ const HANDLER_EXPORT_PATTERNS = [
50
+ /export\s+(const|let|var|function|async\s+function)\s+handler\b/,
51
+ /export\s*\{[^}]*\bhandler\b(?!\s*as\b)[^}]*\}/,
52
+ /(?:module\.)?exports\.handler\s*=/,
53
+ ];
54
+ function containsHandlerExport(content) {
55
+ return HANDLER_EXPORT_PATTERNS.some(pattern => pattern.test(content));
56
+ }
57
+ /**
58
+ * Scans `handlersDir` recursively for TypeScript source files that export a
59
+ * Lambda handler. Returns esbuild-compatible `{ in, out }` entry point pairs.
60
+ *
61
+ * `in` is the absolute file path. `out` is relative to `handlersDir`:
62
+ * a file at `<handlersDir>/subdir/my-handler.ts` produces `out: 'subdir/my-handler/index'`.
63
+ * esbuild combines this with its `outdir` to produce the final path.
64
+ *
65
+ * Files matching the handler-source naming convention but containing no handler
66
+ * export (e.g. `export * from './impl'` re-exports, which cannot be detected
67
+ * without resolving the import graph) emit a `console.warn` so the omission
68
+ * surfaces in CI rather than being discovered at deploy time.
69
+ *
70
+ * Throws if a file cannot be read, with the full path included in the error message.
71
+ */
72
+ async function findEntryPoints(handlersDir) {
73
+ // Explicit { encoding: 'utf8' } selects the string[] overload of readdir
74
+ // (the recursive overload otherwise has an ambiguous return type).
75
+ const files = (await fs__namespace.promises.readdir(handlersDir, { recursive: true, encoding: 'utf8' })).filter(f => f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.includes('.test.') && !f.includes('.spec.'));
76
+ const results = await Promise.all(files.map(async (f) => {
77
+ const fullPath = path__namespace.join(handlersDir, f);
78
+ let content;
79
+ try {
80
+ content = await fs__namespace.promises.readFile(fullPath, 'utf8');
81
+ }
82
+ catch (err) {
83
+ throw new Error(`findEntryPoints: could not read ${fullPath}: ${err.message}`);
84
+ }
85
+ if (!containsHandlerExport(content)) {
86
+ console.warn(`findEntryPoints: skipping ${fullPath} — no recognised handler export found. `
87
+ + 'Supported shapes: export const/let/var/function handler, export { handler } / { foo as handler }, '
88
+ + '(module.)exports.handler = …. Re-exports (export * from …) are not detected.');
89
+ return undefined;
90
+ }
91
+ return {
92
+ in: fullPath,
93
+ out: path__namespace.join(path__namespace.dirname(f), path__namespace.basename(f, '.ts'), 'index'),
94
+ };
95
+ }));
96
+ return results.filter((entry) => entry !== undefined);
97
+ }
98
+
7
99
  const { recapDevHandlerWrapper, defaultPlugins } = require('./esbuild-plugins');
8
100
  const args = process.argv.slice(2);
9
101
  const handlersDirIndex = args.indexOf('--handlers-dir');
10
102
  const outDirIndex = args.indexOf('--out-dir');
11
103
  const targetIndex = args.indexOf('--target');
104
+ // --allow-empty: exit 0 instead of 1 when no handlers are found. Used by
105
+ // services that may legitimately have zero Lambda handlers (libraries,
106
+ // infra-only packages) so a shared `em-commons build-handlers` step can be
107
+ // run unconditionally in their CI without breaking the pipeline.
108
+ const allowEmpty = args.includes('--allow-empty');
12
109
  function resolveArg(index, flag, fallback) {
13
110
  if (index === -1) {
14
111
  return fallback;
@@ -24,63 +121,65 @@ const handlersDir = resolveArg(handlersDirIndex, '--handlers-dir', 'src/handlers
24
121
  const outDir = resolveArg(outDirIndex, '--out-dir', 'dist/handlers');
25
122
  const target = resolveArg(targetIndex, '--target', 'node24');
26
123
  const rootDir = process.cwd();
27
- const absoluteHandlersDir = path.resolve(rootDir, handlersDir);
28
- const absoluteOutDir = path.resolve(rootDir, outDir);
29
- if (!absoluteHandlersDir.startsWith(rootDir + path.sep)) {
124
+ const absoluteHandlersDir = path__namespace.resolve(rootDir, handlersDir);
125
+ const absoluteOutDir = path__namespace.resolve(rootDir, outDir);
126
+ if (!absoluteHandlersDir.startsWith(rootDir + path__namespace.sep)) {
30
127
  console.error(`--handlers-dir must be inside the project root: ${absoluteHandlersDir}`);
31
128
  process.exit(1);
32
129
  }
33
- if (!absoluteOutDir.startsWith(rootDir + path.sep)) {
130
+ if (!absoluteOutDir.startsWith(rootDir + path__namespace.sep)) {
34
131
  console.error(`--out-dir must be inside the project root: ${absoluteOutDir}`);
35
132
  process.exit(1);
36
133
  }
37
- if (!fs.existsSync(absoluteHandlersDir)) {
134
+ if (!fs__namespace.existsSync(absoluteHandlersDir)) {
38
135
  console.error(`Handlers directory not found: ${absoluteHandlersDir}`);
39
136
  process.exit(1);
40
137
  }
41
- const entryPoints = fs.readdirSync(absoluteHandlersDir, { recursive: true })
42
- .filter(f => f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.includes('.test.') && !f.includes('.spec.'))
43
- .map(f => ({
44
- in: path.join(absoluteHandlersDir, f),
45
- out: path.join(path.dirname(f), path.basename(f, '.ts'), 'index')
46
- }));
47
- if (entryPoints.length === 0) {
48
- console.log(`No handlers found in ${handlersDir}`);
49
- process.exit(0);
50
- }
51
- console.log(`Building ${entryPoints.length} handler(s) from ${handlersDir}...`);
52
- esbuild.build({
53
- entryPoints,
54
- outdir: absoluteOutDir,
55
- bundle: true,
56
- platform: 'node',
57
- target,
58
- format: 'cjs',
59
- sourcemap: false,
60
- minify: true,
61
- external: [
62
- '@aws-sdk/*',
63
- 'aws-sdk',
64
- // Unused Knex/DB drivers — matches serverless-esbuild external list
65
- 'mysql',
66
- 'pg',
67
- 'pg-native',
68
- 'sqlite3',
69
- 'mssql',
70
- 'oracledb',
71
- 'better-sqlite3',
72
- 'pg-sqlite3',
73
- 'tedious',
74
- 'pg-query-stream',
75
- 'libsql',
76
- 'mariadb'
77
- ],
78
- plugins: [recapDevHandlerWrapper, ...defaultPlugins]
79
- })
80
- .then(() => {
138
+ async function main() {
139
+ const entryPoints = await findEntryPoints(absoluteHandlersDir);
140
+ if (entryPoints.length === 0) {
141
+ if (allowEmpty) {
142
+ // Log the resolved absolute path so a misrouted --handlers-dir (typo'd
143
+ // to a real-but-empty sibling directory) is visible in CI output.
144
+ console.warn(`No handlers found in ${handlersDir} (resolved: ${absoluteHandlersDir}), `
145
+ + 'skipping build (--allow-empty)');
146
+ return;
147
+ }
148
+ console.error(`No handlers found in ${handlersDir}. Pass --allow-empty to suppress this error.`);
149
+ process.exit(1);
150
+ }
151
+ console.log(`Building ${entryPoints.length} handler(s) from ${handlersDir}...`);
152
+ await esbuild__namespace.build({
153
+ entryPoints,
154
+ outdir: absoluteOutDir,
155
+ bundle: true,
156
+ platform: 'node',
157
+ target,
158
+ format: 'cjs',
159
+ sourcemap: false,
160
+ minify: true,
161
+ external: [
162
+ '@aws-sdk/*',
163
+ 'aws-sdk',
164
+ // Unused Knex/DB drivers — matches serverless-esbuild external list
165
+ 'mysql',
166
+ 'pg',
167
+ 'pg-native',
168
+ 'sqlite3',
169
+ 'mssql',
170
+ 'oracledb',
171
+ 'better-sqlite3',
172
+ 'pg-sqlite3',
173
+ 'tedious',
174
+ 'pg-query-stream',
175
+ 'libsql',
176
+ 'mariadb'
177
+ ],
178
+ plugins: [recapDevHandlerWrapper, ...defaultPlugins]
179
+ });
81
180
  entryPoints.forEach(({ out }) => console.log(` ${outDir}/${out}.js`));
82
- })
83
- .catch(err => {
181
+ }
182
+ main().catch(err => {
84
183
  if (err?.errors?.length) {
85
184
  err.errors.forEach((e) => {
86
185
  const loc = e.location
@@ -90,7 +189,9 @@ esbuild.build({
90
189
  });
91
190
  }
92
191
  else {
93
- console.error(err);
192
+ // Print the full stack (and `cause` chain) so non-esbuild errors are
193
+ // diagnosable in CI output rather than being collapsed to the message.
194
+ console.error(err instanceof Error ? (err.stack ?? err) : err);
94
195
  }
95
196
  process.exit(1);
96
197
  });