backend-manager 4.2.25 → 5.0.1

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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(rg:*)"
5
+ ],
6
+ "deny": []
7
+ }
8
+ }
package/CHANGELOG.md CHANGED
@@ -15,6 +15,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
17
  ---#
18
+ # [5.0.0] - 2025-07-10
19
+ ### ⚠️ BREAKING
20
+ - `Manager.init()` no longer wraps the initializeApp() in `try/catch` block.
21
+ - `Settings()` API tries to look for a method-specific file first (e.g., `name/get.js`, `name/post.js`, etc.) before falling back to `name/index.js`. This allows for more modular and organized code structure. Also, `name.js` is no longer valid, we now look for `name/index.js` this is to make it consistent with the `Middleware()` API.
22
+ - `Middleware()` API now tries to load method-specific files (e.g., `name/get.js`, `name/post.js`, etc.) before falling back to `name/index.js`.
23
+ - `ai.request()` no longer accepts `options.message.images`. Use `options.message.attachments` instead.
24
+
18
25
  # [4.2.22] - 2024-12-19
19
26
  ### Changed
20
27
  - `Manager.install()` now automatically binds the fn with the proper `this` context (this may be breaking).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "4.2.25",
3
+ "version": "5.0.1",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -11,8 +11,9 @@
11
11
  "_test": "npm run prepare && ./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
12
12
  "test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
13
13
  "test:usage": "./node_modules/mocha/bin/mocha test/usage.js --timeout=10000",
14
- "test:payment-resolver": "./node_modules/mocha/bin/mocha test/payment-resolver.js --timeout=10000",
14
+ "test:payment-resolver": "./node_modules/mocha/bin/mocha test/payment-resolver/index.js --timeout=10000",
15
15
  "test:user": "./node_modules/mocha/bin/mocha test/user.js --timeout=10000",
16
+ "test:ai": "./node_modules/mocha/bin/mocha test/ai/index.js --timeout=10000",
16
17
  "start": "node src/manager/index.js",
17
18
  "prepare": "node -e \"require('prepare-package')()\"",
18
19
  "prepare:watch": "nodemon -w ./src -e '*' --exec 'npm run prepare'"
@@ -6,6 +6,7 @@
6
6
  const path = require('path');
7
7
  const powertools = require('node-powertools');
8
8
  const { merge } = require('lodash');
9
+ const JSON5 = require('json5');
9
10
 
10
11
  function Middleware(m, req, res) {
11
12
  const self = this;
@@ -27,17 +28,6 @@ Middleware.prototype.run = function (libPath, options) {
27
28
  return cors(req, res, async () => {
28
29
  const assistant = Manager.Assistant({req: req, res: res});
29
30
 
30
- // Set properties
31
- const data = assistant.request.data;
32
- const headers = assistant.request.headers;
33
- const method = assistant.request.method;
34
- const url = assistant.request.url;
35
- const geolocation = assistant.request.geolocation;
36
- const client = assistant.request.client;
37
-
38
- // Strip URL
39
- const strippedUrl = stripUrl(url);
40
-
41
31
  // Set options
42
32
  options = options || {};
43
33
  options.authenticate = typeof options.authenticate === 'boolean' ? options.authenticate : true;
@@ -47,11 +37,42 @@ Middleware.prototype.run = function (libPath, options) {
47
37
  options.cleanSettings = typeof options.cleanSettings === 'undefined' ? true : options.cleanSettings;
48
38
  options.includeNonSchemaSettings = typeof options.includeNonSchemaSettings === 'undefined' ? false : options.includeNonSchemaSettings;
49
39
  options.schema = typeof options.schema === 'undefined' ? undefined : options.schema;
40
+ options.parseMultipartFormData = typeof options.parseMultipartFormData === 'undefined' ? true : options.parseMultipartFormData;
50
41
 
51
42
  // Set base path
52
43
  options.routesDir = typeof options.routesDir === 'undefined' ? `${Manager.cwd}/routes` : options.routesDir;
53
44
  options.schemasDir = typeof options.schemasDir === 'undefined' ? `${Manager.cwd}/schemas` : options.schemasDir;
54
45
 
46
+ // Parse multipart/form-data if needed
47
+ if (options.parseMultipartFormData && req.headers['content-type']?.includes('multipart/form-data')) {
48
+ try {
49
+ const parsed = await assistant.parseMultipartFormData();
50
+ const json = parsed.fields.json || '{}';
51
+
52
+ // Parsed JSON
53
+ if (json) {
54
+ assistant.request.body = JSON5.parse(json);
55
+ assistant.request.data = merge({}, assistant.request.body, assistant.request.query);
56
+ }
57
+
58
+ // Log that it was parsed successfully
59
+ assistant.log(`Middleware.run(): Parsed multipart form data successfully`);
60
+ } catch (e) {
61
+ return assistant.respond(new Error(`Failed to parse multipart form data: ${e.message}`), {code: 400, sentry: true});
62
+ }
63
+ }
64
+
65
+ // Set properties
66
+ const data = assistant.request.data;
67
+ const headers = assistant.request.headers;
68
+ const method = assistant.request.method.toLowerCase();
69
+ const url = assistant.request.url;
70
+ const geolocation = assistant.request.geolocation;
71
+ const client = assistant.request.client;
72
+
73
+ // Strip URL
74
+ const strippedUrl = stripUrl(url);
75
+
55
76
  // Log
56
77
  assistant.log(`Middleware.process(): Request (${geolocation.ip} @ ${geolocation.country}, ${geolocation.region}, ${geolocation.city}) [${method} > ${strippedUrl}]`, JSON.stringify(data));
57
78
  assistant.log(`Middleware.process(): Headers`, JSON.stringify(headers));
@@ -68,11 +89,24 @@ Middleware.prototype.run = function (libPath, options) {
68
89
  }
69
90
 
70
91
  // Load library
71
- // TODO: Support for methods, so first check for method specific file then fallback to index.js
92
+ // Support for methods, so first check for method specific file then fallback to index.js
72
93
  let library;
94
+
73
95
  try {
74
- libPath = path.resolve(routesDir, `index.js`);
75
- library = new (require(libPath))();
96
+ // First try to load method-specific file (e.g., get.js, post.js, etc.)
97
+ const methodFile = `${method}.js`
98
+ const methodFilePath = path.resolve(routesDir, methodFile);
99
+
100
+ try {
101
+ const finalPath = methodFilePath;
102
+ library = new (require(finalPath))();
103
+ assistant.log(`Middleware.process(): Loaded method-specific library: ${methodFile}`);
104
+ } catch (methodError) {
105
+ // Fallback to index.js if method-specific file doesn't exist or fails to load
106
+ const finalPath = path.resolve(routesDir, `index.js`);
107
+ library = new (require(finalPath))();
108
+ assistant.log(`Middleware.process(): Method-specific file (${methodFile}) not found, using index.js fallback`);
109
+ }
76
110
  } catch (e) {
77
111
  return assistant.respond(new Error(`Unable to load library @ (${libPath}): ${e.message}`), {code: 500, sentry: true});
78
112
  }
@@ -113,7 +147,7 @@ Middleware.prototype.run = function (libPath, options) {
113
147
  // assistant.schema.name = options.schema;
114
148
  assistant.settings = Manager.Settings().resolve(assistant, undefined, data, {dir: schemasDir, schema: options.schema});
115
149
  } catch (e) {
116
- return assistant.respond(new Error(`Unable to resolve schema ${options.schema}: ${e.message}`), {code: 500, sentry: true});
150
+ return assistant.respond(new Error(`Unable to resolve schema ${options.schema}: ${e.message}`), {code: e.code || 500, sentry: true});
117
151
  }
118
152
 
119
153
  // Merge settings with data
@@ -128,6 +162,12 @@ Middleware.prototype.run = function (libPath, options) {
128
162
 
129
163
  // Log
130
164
  assistant.log(`Middleware.process(): Resolved settings with schema=${options.schema}`, JSON.stringify(assistant.settings));
165
+
166
+ // Log multipart files if they exist
167
+ const files = assistant.request.multipartData.files || {};
168
+ if (files) {
169
+ assistant.log(`Middleware.process(): Multipart files`, JSON.stringify(files));
170
+ }
131
171
  } else {
132
172
  assistant.settings = data;
133
173
  }
@@ -42,9 +42,24 @@ Settings.prototype.resolve = function (assistant, schema, settings, options) {
42
42
  typeof schema === 'undefined'
43
43
  && typeof options.schema !== 'undefined'
44
44
  ) {
45
- const schemaPath = path.resolve(options.dir, `${options.schema.replace('.js', '')}.js`);
46
-
47
- schema = loadSchema(assistant, schemaPath, settings, options);
45
+ // Try to load method-specific schema first, then fallback to main schema
46
+ const method = (assistant?.request?.method || '').toLowerCase();
47
+ const methodFile = `${method}.js`;
48
+ const schemaFile = options.schema.replace('.js', '');
49
+ let schemaPath;
50
+
51
+ // First try method-specific schema (e.g., test/get.js, test/post.js)
52
+ const methodSchemaPath = path.resolve(options.dir, `${schemaFile}/${methodFile}`);
53
+
54
+ try {
55
+ schema = loadSchema(assistant, methodSchemaPath, settings, options);
56
+ assistant.log(`Settings.resolve(): Loaded method-specific schema: ${schemaFile}/${methodFile}`);
57
+ } catch (e) {
58
+ // Fallback to main schema if method-specific doesn't exist
59
+ schemaPath = path.resolve(options.dir, `${schemaFile}/index.js`);
60
+ schema = loadSchema(assistant, schemaPath, settings, options);
61
+ assistant.log(`Settings.resolve(): Method-specific schema not found, using main schema fallback`);
62
+ }
48
63
  }
49
64
 
50
65
  // If schema is not an object, throw an error
@@ -16,7 +16,7 @@ const wrappers = './functions/wrappers';
16
16
 
17
17
  const BEM_CONFIG_TEMPLATE_PATH = path.resolve(__dirname, '../../templates/backend-manager-config.json');
18
18
 
19
- function Manager(exporter, options) {
19
+ function Manager() {
20
20
  const self = this;
21
21
 
22
22
  // Constants
@@ -64,7 +64,9 @@ Manager.prototype.init = function (exporter, options) {
64
64
  options.useFirebaseLogger = typeof options.useFirebaseLogger === 'undefined' ? true : options.useFirebaseLogger;
65
65
  options.serviceAccountPath = typeof options.serviceAccountPath === 'undefined' ? 'service-account.json' : options.serviceAccountPath;
66
66
  options.backendManagerConfigPath = typeof options.backendManagerConfigPath === 'undefined' ? 'backend-manager-config.json' : options.backendManagerConfigPath;
67
- options.fetchStats = typeof options.fetchStats === 'undefined' ? true : options.fetchStats;
67
+ options.fetchStats = typeof options.fetchStats === 'undefined'
68
+ ? options.projectType === 'firebase'
69
+ : options.fetchStats;
68
70
  options.checkNodeVersion = typeof options.checkNodeVersion === 'undefined' ? true : options.checkNodeVersion;
69
71
  options.uniqueAppName = options.uniqueAppName || undefined;
70
72
  options.assistant = options.assistant || {};
@@ -167,10 +169,9 @@ Manager.prototype.init = function (exporter, options) {
167
169
 
168
170
  // Handle dev environments
169
171
  if (self.assistant.isDevelopment()) {
170
- const semverMajor = require('semver/functions/major')
171
- const semverCoerce = require('semver/functions/coerce')
172
- const semverUsing = semverMajor(semverCoerce(process.versions.node));
173
- const semverRequired = semverMajor(semverCoerce(self.package?.engines?.node || '0.0.0'));
172
+ const version = require('wonderful-version');
173
+ const nodeUsing = version.major(process.versions.node);
174
+ const nodeRequired = version.major(self.package?.engines?.node || '0.0.0');
174
175
 
175
176
  // Fix firebase-tools overwriting console.log
176
177
  // https://stackoverflow.com/questions/56026747/firebase-console-log-on-localhost
@@ -182,9 +183,9 @@ Manager.prototype.init = function (exporter, options) {
182
183
  console.info = logFix;
183
184
  }
184
185
 
185
- // Reject if package.json does not exist
186
- if (semverUsing !== semverRequired) {
187
- const msg = `Node.js version mismatch: using ${semverUsing} but asked for ${semverRequired}`;
186
+ // Reject if we're using an unsupported Node.js version
187
+ if (version.is(nodeUsing, '<', nodeRequired)) {
188
+ const msg = `Node.js version mismatch: using ${nodeUsing} but asked for ${nodeRequired}`;
188
189
  if (options.checkNodeVersion) {
189
190
  self.assistant.error(new Error(msg));
190
191
  return process.exit(1);