backend-manager 5.0.114 → 5.0.116

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.114",
3
+ "version": "5.0.116",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -56,11 +56,11 @@ class LogsCommand extends BaseCommand {
56
56
 
57
57
  /**
58
58
  * Fetch historical logs.
59
- * Usage: npx bm logs:read [--fn bm_api] [--severity ERROR] [--since 1h] [--limit 50]
59
+ * Usage: npx bm logs:read [--fn bm_api] [--severity ERROR] [--since 1h] [--limit 300]
60
60
  */
61
61
  async read(projectId, argv) {
62
62
  const filter = this.buildFilter(argv);
63
- const limit = parseInt(argv.limit, 10) || 50;
63
+ const limit = parseInt(argv.limit, 10) || 300;
64
64
 
65
65
  const cmd = [
66
66
  'gcloud', 'logging', 'read',
@@ -88,8 +88,8 @@ class LogsCommand extends BaseCommand {
88
88
 
89
89
  const entries = JSON.parse(output || '[]');
90
90
 
91
- // Save raw JSON to log file for Claude/tooling to read
92
- jetpack.write(logPath, JSON.stringify(entries, null, 2));
91
+ // Save as newline-delimited JSON (matches tail format)
92
+ jetpack.write(logPath, entries.map(e => JSON.stringify(e)).join('\n'));
93
93
 
94
94
  if (entries.length === 0) {
95
95
  this.logWarning('No log entries found.');
@@ -20,6 +20,11 @@ class BemConfigTest extends BaseTest {
20
20
 
21
21
  // Loop through all the keys in the template
22
22
  powertools.getKeys(bemConfigTemplate).forEach((key) => {
23
+ // Skip if an ancestor is explicitly set to a non-object value (e.g. stripe: false)
24
+ if (this._isAncestorDisabled(key)) {
25
+ return;
26
+ }
27
+
23
28
  const userValue = _.get(this.self.bemConfigJSON, key, undefined);
24
29
 
25
30
  // If the user value is undefined, then we need to set pass to false
@@ -43,6 +48,11 @@ class BemConfigTest extends BaseTest {
43
48
 
44
49
  // Log what keys are missing
45
50
  powertools.getKeys(bemConfigTemplate).forEach((key) => {
51
+ // Skip if an ancestor is explicitly set to a non-object value (e.g. stripe: false)
52
+ if (this._isAncestorDisabled(key)) {
53
+ return;
54
+ }
55
+
46
56
  const userValue = _.get(this.self.bemConfigJSON, key, undefined);
47
57
 
48
58
  if (typeof userValue === 'undefined') {
@@ -54,6 +64,24 @@ class BemConfigTest extends BaseTest {
54
64
 
55
65
  throw new Error('Missing required backend-manager-config.json keys');
56
66
  }
67
+ /**
68
+ * Check if any ancestor of a dot-notation key is a non-object value (e.g. false)
69
+ * This means the key is intentionally disabled, not missing
70
+ */
71
+ _isAncestorDisabled(key) {
72
+ const parts = key.split('.');
73
+ let current = this.self.bemConfigJSON;
74
+
75
+ for (let i = 0; i < parts.length - 1; i++) {
76
+ current = current?.[parts[i]];
77
+
78
+ if (current !== undefined && current !== null && typeof current !== 'object') {
79
+ return true;
80
+ }
81
+ }
82
+
83
+ return false;
84
+ }
57
85
  }
58
86
 
59
87
  module.exports = BemConfigTest;
@@ -2,8 +2,8 @@
2
2
  * Data requests cron job
3
3
  *
4
4
  * Processes data request status transitions:
5
- * - pending → complete: 14 days after creation
6
- * - complete → expired: 30 days after becoming complete (44 days after creation)
5
+ * - pending → completed: 14 days after creation
6
+ * - completed → expired: 30 days after becoming completed (44 days after creation)
7
7
  *
8
8
  * Scans the entire collection (no index required) since data-requests is small.
9
9
  */
@@ -33,7 +33,7 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
33
33
  const age = nowUNIX - createdUNIX;
34
34
 
35
35
  if (data.status === 'pending' && age >= FOURTEEN_DAYS) {
36
- await doc.ref.update({ status: 'complete' })
36
+ await doc.ref.update({ status: 'completed' })
37
37
  .then(() => {
38
38
  completed++;
39
39
  assistant.log(`Completed request ${doc.id} (age: ${Math.round(age / 86400)}d)`);
@@ -41,7 +41,7 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
41
41
  .catch((e) => {
42
42
  assistant.error(`Failed to complete request ${doc.id}: ${e.message}`);
43
43
  });
44
- } else if (data.status === 'complete' && age >= FORTY_FOUR_DAYS) {
44
+ } else if (data.status === 'completed' && age >= FORTY_FOUR_DAYS) {
45
45
  await doc.ref.update({ status: 'expired' })
46
46
  .then(() => {
47
47
  expired++;
@@ -86,7 +86,7 @@ module.exports = async ({ assistant, change, context }) => {
86
86
  // Build timestamps
87
87
  const now = powertools.timestamp(new Date(), { output: 'string' });
88
88
  const nowUNIX = powertools.timestamp(now, { output: 'unix' });
89
- const webhookReceivedUNIX = dataAfter.metadata?.received?.timestampUNIX || nowUNIX;
89
+ const webhookReceivedUNIX = dataAfter.metadata?.created?.timestampUNIX || nowUNIX;
90
90
 
91
91
  // Extract orderId from resource (processor-agnostic)
92
92
  orderId = library.getOrderId(resource);
@@ -105,7 +105,7 @@ module.exports = async ({ assistant, change, context }) => {
105
105
  orderId: orderId,
106
106
  transition: transitionName,
107
107
  metadata: {
108
- processed: {
108
+ completed: {
109
109
  timestamp: now,
110
110
  timestampUNIX: nowUNIX,
111
111
  },
@@ -199,6 +199,7 @@ async function processPaymentEvent({ category, library, resource, resourceType,
199
199
  id: orderId,
200
200
  type: category,
201
201
  owner: uid,
202
+ productId: unified.product.id,
202
203
  processor: processor,
203
204
  resourceId: resourceId,
204
205
  unified: unified,
@@ -268,7 +268,7 @@ const PayPal = {
268
268
 
269
269
  return {
270
270
  product: product,
271
- status: rawResource.status === 'COMPLETED' ? 'complete' : rawResource.status?.toLowerCase() || 'unknown',
271
+ status: rawResource.status === 'COMPLETED' ? 'completed' : rawResource.status?.toLowerCase() || 'unknown',
272
272
  payment: {
273
273
  processor: 'paypal',
274
274
  orderId: customData.orderId || null,
@@ -314,6 +314,7 @@ const PayPal = {
314
314
  }
315
315
 
316
316
  // Fetch plans with full details (Prefer header includes billing_cycles in list response)
317
+ // TODO: Paginate — page_size=20 only returns first page. Fine for now (each product has ~2-4 plans in 1:1 model) but will break if a product ever accumulates >20 plans.
317
318
  const response = await this.request(`/v1/billing/plans?product_id=${paypalProductId}&page_size=20&total_required=true`, {
318
319
  headers: { 'Prefer': 'return=representation' },
319
320
  });
@@ -304,7 +304,7 @@ const Stripe = {
304
304
 
305
305
  return {
306
306
  product: product,
307
- status: rawResource.status || 'unknown',
307
+ status: rawResource.status === 'complete' ? 'completed' : rawResource.status || 'unknown',
308
308
  payment: {
309
309
  processor: 'stripe',
310
310
  orderId: rawResource.metadata?.orderId || null,
@@ -116,7 +116,7 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
116
116
  owner: uid,
117
117
  status: 'pending',
118
118
  productId: productId,
119
- productType: productType,
119
+ type: productType,
120
120
  frequency: frequency,
121
121
  trial: trial,
122
122
  raw: result.raw,
@@ -91,11 +91,11 @@ module.exports = async ({ assistant, Manager, libraries }) => {
91
91
  },
92
92
  error: null,
93
93
  metadata: {
94
- received: {
94
+ created: {
95
95
  timestamp: now,
96
96
  timestampUNIX: nowUNIX,
97
97
  },
98
- processed: {
98
+ completed: {
99
99
  timestamp: null,
100
100
  timestampUNIX: null,
101
101
  },
@@ -2,11 +2,11 @@
2
2
  * GET /user/data-request - Check data request status or download data
3
3
  *
4
4
  * action=status (default): Returns the most recent request with its stored status.
5
- * action=download: Compiles user data live and returns it. Only works when status is 'complete'.
5
+ * action=download: Compiles user data live and returns it. Only works when status is 'completed'.
6
6
  *
7
7
  * Statuses:
8
- * pending — request submitted, waiting to be processed (bm_cronDaily sets to 'complete' after 14 days)
9
- * complete — data is available for download (downloads counter tracks how many times downloaded)
8
+ * pending — request submitted, waiting to be processed (bm_cronDaily sets to 'completed' after 14 days)
9
+ * completed — data is available for download (downloads counter tracks how many times downloaded)
10
10
  */
11
11
  module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
12
12
  const { admin } = libraries;
@@ -43,8 +43,8 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
43
43
  });
44
44
  }
45
45
 
46
- // Download action — only allowed when status is 'complete'
47
- if (status !== 'complete') {
46
+ // Download action — only allowed when status is 'completed'
47
+ if (status !== 'completed') {
48
48
  return assistant.respond('Your data request is still being processed. Please check back later.', { code: 400 });
49
49
  }
50
50
 
@@ -45,7 +45,7 @@ module.exports = {
45
45
  name: 'status-completed',
46
46
  async run({ assert }) {
47
47
  const result = toUnifiedOneTime({ status: 'COMPLETED' });
48
- assert.equal(result.status, 'complete', 'PayPal COMPLETED → unified complete');
48
+ assert.equal(result.status, 'completed', 'PayPal COMPLETED → unified completed');
49
49
  },
50
50
  },
51
51
 
@@ -250,7 +250,7 @@ module.exports = {
250
250
  assert.ok(result.payment, 'Should have payment');
251
251
 
252
252
  assert.equal(result.product.id, 'credits-100', 'Product should be credits-100');
253
- assert.equal(result.status, 'complete', 'Status should be complete');
253
+ assert.equal(result.status, 'completed', 'Status should be completed');
254
254
  assert.equal(result.payment.processor, 'paypal', 'Processor should be paypal');
255
255
  assert.equal(result.payment.resourceId, 'PAYID-FULL', 'Resource ID should match');
256
256
  assert.equal(result.payment.orderId, '1234-5678-9012', 'orderId should match');
@@ -336,7 +336,7 @@ module.exports = {
336
336
  async run({ assert }) {
337
337
  const result = toUnifiedOneTime(FIXTURE_ORDER_COMPLETED);
338
338
 
339
- assert.equal(result.status, 'complete', 'COMPLETED fixture → complete');
339
+ assert.equal(result.status, 'completed', 'COMPLETED fixture → completed');
340
340
  assert.equal(result.payment.processor, 'paypal', 'Processor is paypal');
341
341
  assert.equal(result.payment.resourceId, '5UX02069M9686893E', 'Resource ID from fixture');
342
342
  assert.equal(result.payment.orderId, 'ord-test-456', 'orderId from purchase_units custom_id');
@@ -44,7 +44,7 @@ module.exports = {
44
44
  name: 'status-complete',
45
45
  async run({ assert }) {
46
46
  const result = toUnifiedOneTime({ status: 'complete' });
47
- assert.equal(result.status, 'complete', 'Status passes through as-is');
47
+ assert.equal(result.status, 'completed', 'Stripe complete unified completed');
48
48
  },
49
49
  },
50
50
 
@@ -211,7 +211,7 @@ module.exports = {
211
211
  assert.ok(result.payment, 'Should have payment');
212
212
 
213
213
  assert.equal(result.product.id, 'credits-100', 'Product should be credits-100');
214
- assert.equal(result.status, 'complete', 'Status should be complete');
214
+ assert.equal(result.status, 'completed', 'Status should be completed');
215
215
  assert.equal(result.payment.processor, 'stripe', 'Processor should be stripe');
216
216
  assert.equal(result.payment.resourceId, 'cs_test_full', 'Resource ID should match');
217
217
  assert.equal(result.payment.orderId, '1234-5678-9012', 'orderId should match');
@@ -253,7 +253,7 @@ module.exports = {
253
253
  const result = toUnifiedOneTime(FIXTURE_SESSION);
254
254
 
255
255
  assert.ok(result.product, 'Should have product');
256
- assert.equal(result.status, 'complete', 'Real session fixture → complete');
256
+ assert.equal(result.status, 'completed', 'Real session fixture → completed');
257
257
  assert.equal(result.payment.processor, 'stripe', 'Processor is stripe');
258
258
  assert.equal(result.payment.resourceId, FIXTURE_SESSION.id, 'resourceId matches fixture ID');
259
259
  },