backend-manager 5.0.193 → 5.0.195
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/CHANGELOG.md +15 -0
- package/CLAUDE.md +83 -42
- package/README.md +11 -11
- package/TODO-2.md +20 -0
- package/package.json +1 -1
- package/src/cli/commands/logs.js +14 -3
- package/src/manager/libraries/disposable-domains.json +1 -0
- package/src/manager/libraries/email/transactional/index.js +15 -11
- package/src/manager/routes/payments/cancel/post.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
14
14
|
- `Fixed` for any bug fixes.
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
|
+
# [5.0.195] - 2026-04-10
|
|
18
|
+
### Fixed
|
|
19
|
+
- 24-hour cancellation guard in `payments/cancel` was comparing `Date.now()` (milliseconds) against `startDateUNIX` (seconds), producing an "age" of ~56 years for every subscription — guard never fired and users could cancel brand-new subscriptions. Now multiplies `startDateUNIX` by 1000 before subtraction.
|
|
20
|
+
### Changed
|
|
21
|
+
- Standardized CLI examples in `CLAUDE.md` and `README.md` to use `npx mgr` instead of the deprecated `npx bm` alias
|
|
22
|
+
|
|
23
|
+
# [5.0.194] - 2026-04-08
|
|
24
|
+
### Fixed
|
|
25
|
+
- Fix email template data merge: caller's `settings.data` is now deep-merged at root of template data tree, removing the broken `data.` prefix indirection that caused empty order confirmation emails since 5.0.185
|
|
26
|
+
### Added
|
|
27
|
+
- `preview` as a top-level setting on `email.send()` (alongside `subject`)
|
|
28
|
+
- `logs:read` CLI: `--search`, `--order`, `--filter` flags and increased default limit to 300
|
|
29
|
+
### Changed
|
|
30
|
+
- Email templates now access caller data at root (`{{order.id}}`, `{{body.message}}`) instead of under `data.*`
|
|
31
|
+
|
|
17
32
|
# [5.0.192] - 2026-04-02
|
|
18
33
|
### Added
|
|
19
34
|
- Setup test to create `hooks/auth/` and `hooks/cron/daily/` directories in consumer projects during `npx bm setup`
|
package/CLAUDE.md
CHANGED
|
@@ -600,30 +600,30 @@ The `POST /admin/post` route creates blog posts via GitHub's API. It handles ima
|
|
|
600
600
|
### Running Tests
|
|
601
601
|
```bash
|
|
602
602
|
# Option 1: Two terminals
|
|
603
|
-
npx
|
|
604
|
-
npx
|
|
603
|
+
npx mgr emulator # Terminal 1 - keeps emulator running
|
|
604
|
+
npx mgr test # Terminal 2 - runs tests
|
|
605
605
|
|
|
606
606
|
# Option 2: Single command (auto-starts emulator)
|
|
607
|
-
npx
|
|
607
|
+
npx mgr test
|
|
608
608
|
```
|
|
609
609
|
|
|
610
610
|
### Log Files
|
|
611
611
|
BEM CLI commands automatically save all output to log files in `functions/` while still streaming to the console:
|
|
612
|
-
- **`functions/serve.log`** — Output from `npx
|
|
612
|
+
- **`functions/serve.log`** — Output from `npx mgr serve` (Firebase serve)
|
|
613
613
|
- **`functions/emulator.log`** — Full emulator output (Firebase emulator + Cloud Functions logs)
|
|
614
614
|
- **`functions/test.log`** — Test runner output (when running against an existing emulator)
|
|
615
|
-
- **`functions/logs.log`** — Cloud Function logs from `npx
|
|
615
|
+
- **`functions/logs.log`** — Cloud Function logs from `npx mgr logs:read` or `npx mgr logs:tail` (raw JSON for `read`, streaming text for `tail`)
|
|
616
616
|
|
|
617
|
-
When `npx
|
|
617
|
+
When `npx mgr test` starts its own emulator, logs go to `emulator.log` (since it delegates to the emulator command). When running against an already-running emulator, logs go to `test.log`.
|
|
618
618
|
|
|
619
619
|
These files are overwritten on each run and are gitignored (`*.log`). Use them to search for errors, debug webhook pipelines, or review full function output after a test run.
|
|
620
620
|
|
|
621
621
|
### Filtering Tests
|
|
622
622
|
```bash
|
|
623
|
-
npx
|
|
624
|
-
npx
|
|
625
|
-
npx
|
|
626
|
-
npx
|
|
623
|
+
npx mgr test rules/ # Run rules tests (both BEM and project)
|
|
624
|
+
npx mgr test bem:rules/ # Only BEM's rules tests
|
|
625
|
+
npx mgr test project:rules/ # Only project's rules tests
|
|
626
|
+
npx mgr test user/ admin/ # Multiple paths
|
|
627
627
|
```
|
|
628
628
|
|
|
629
629
|
### Test Locations
|
|
@@ -716,7 +716,7 @@ assert.fail(message) // Explicit fail
|
|
|
716
716
|
|
|
717
717
|
## Stripe Webhook Forwarding
|
|
718
718
|
|
|
719
|
-
BEM auto-starts Stripe CLI webhook forwarding when running `npx
|
|
719
|
+
BEM auto-starts Stripe CLI webhook forwarding when running `npx mgr serve` or `npx mgr emulator`. This forwards Stripe test webhooks to the local server so the full payment pipeline works end-to-end during development.
|
|
720
720
|
|
|
721
721
|
**Requirements:**
|
|
722
722
|
- `STRIPE_SECRET_KEY` set in `functions/.env`
|
|
@@ -725,7 +725,7 @@ BEM auto-starts Stripe CLI webhook forwarding when running `npx bm serve` or `np
|
|
|
725
725
|
|
|
726
726
|
**Standalone usage:**
|
|
727
727
|
```bash
|
|
728
|
-
npx
|
|
728
|
+
npx mgr stripe
|
|
729
729
|
```
|
|
730
730
|
|
|
731
731
|
If any prerequisite is missing, webhook forwarding is silently skipped with an info message.
|
|
@@ -736,26 +736,28 @@ The forwarding URL is: `http://localhost:{hostingPort}/backend-manager/payments/
|
|
|
736
736
|
|
|
737
737
|
Quick commands for reading/writing Firestore and managing Auth users directly from the terminal. Works in any BEM consumer project (requires `functions/service-account.json` for production, or `--emulator` for local).
|
|
738
738
|
|
|
739
|
+
**IMPORTANT: All CLI commands (`npx mgr ...`) MUST be run from the consumer project's `functions/` subdirectory** (e.g., `cd /path/to/my-project/functions && npx mgr ...`). The `mgr` binary lives in `functions/node_modules/.bin/` — running from the project root or any other directory will fail.
|
|
740
|
+
|
|
739
741
|
### Firestore Commands
|
|
740
742
|
|
|
741
743
|
```bash
|
|
742
|
-
npx
|
|
743
|
-
npx
|
|
744
|
-
npx
|
|
745
|
-
npx
|
|
744
|
+
npx mgr firestore:get <path> # Read a document
|
|
745
|
+
npx mgr firestore:set <path> '<json>' # Write/merge a document
|
|
746
|
+
npx mgr firestore:set <path> '<json>' --no-merge # Overwrite a document entirely
|
|
747
|
+
npx mgr firestore:query <collection> # Query a collection (default limit 25)
|
|
746
748
|
--where "field==value" # Filter (repeatable for AND)
|
|
747
749
|
--orderBy "field:desc" # Sort
|
|
748
750
|
--limit N # Limit results
|
|
749
|
-
npx
|
|
751
|
+
npx mgr firestore:delete <path> # Delete a document (prompts for confirmation)
|
|
750
752
|
```
|
|
751
753
|
|
|
752
754
|
### Auth Commands
|
|
753
755
|
|
|
754
756
|
```bash
|
|
755
|
-
npx
|
|
756
|
-
npx
|
|
757
|
-
npx
|
|
758
|
-
npx
|
|
757
|
+
npx mgr auth:get <uid-or-email> # Get user by UID or email (auto-detected via @)
|
|
758
|
+
npx mgr auth:list [--limit N] [--page-token T] # List users (default 100)
|
|
759
|
+
npx mgr auth:delete <uid-or-email> # Delete user (prompts for confirmation)
|
|
760
|
+
npx mgr auth:set-claims <uid-or-email> '<json>' # Set custom claims
|
|
759
761
|
```
|
|
760
762
|
|
|
761
763
|
### Logs Commands
|
|
@@ -763,24 +765,63 @@ npx bm auth:set-claims <uid-or-email> '<json>' # Set custom claims
|
|
|
763
765
|
Fetch or stream Cloud Function logs from Google Cloud Logging. Requires `gcloud` CLI installed and authenticated. Auto-resolves the project ID from `service-account.json`, `.firebaserc`, or `GCLOUD_PROJECT`.
|
|
764
766
|
|
|
765
767
|
```bash
|
|
766
|
-
npx
|
|
767
|
-
npx
|
|
768
|
-
npx
|
|
769
|
-
npx
|
|
770
|
-
npx
|
|
771
|
-
npx
|
|
768
|
+
npx mgr logs:read # Read last 1h of logs (default: 300 entries, newest first)
|
|
769
|
+
npx mgr logs:read --fn bm_api # Filter by function name
|
|
770
|
+
npx mgr logs:read --fn bm_api --severity ERROR # Filter by severity (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
771
|
+
npx mgr logs:read --since 2d --limit 100 # Custom time range and limit
|
|
772
|
+
npx mgr logs:read --search "72.134.242.25" # Search textPayload for a string (IP, email, error, etc.)
|
|
773
|
+
npx mgr logs:read --fn bm_authBeforeCreate --search "ian@example.com" --since 7d # Combined filters
|
|
774
|
+
npx mgr logs:read --order asc # Oldest first (default: desc/newest first)
|
|
775
|
+
npx mgr logs:read --filter 'jsonPayload.level="error"' # Raw gcloud filter passthrough
|
|
776
|
+
npx mgr logs:tail # Stream live logs
|
|
777
|
+
npx mgr logs:tail --fn bm_paymentsWebhookOnWrite # Stream filtered live logs
|
|
772
778
|
```
|
|
773
779
|
|
|
774
780
|
Both commands save output to `functions/logs.log` (overwritten on each run). `logs:read` saves raw JSON; `logs:tail` streams text.
|
|
775
781
|
|
|
782
|
+
**Cloud Logs vs Local Logs:** These commands query **production** Google Cloud Logging. For **local/dev** logs, read `functions/serve.log` (from `npx mgr serve`) or `functions/emulator.log` (from `npx mgr test`) directly — they are plain text files, not gcloud.
|
|
783
|
+
|
|
776
784
|
| Flag | Description | Default | Commands |
|
|
777
785
|
|------|-------------|---------|----------|
|
|
778
|
-
| `--fn <name>` | Filter by Cloud Function name | all | both |
|
|
779
|
-
| `--severity <level>` | Minimum severity
|
|
786
|
+
| `--fn <name>` | Filter by Cloud Function name (see table below) | all | both |
|
|
787
|
+
| `--severity <level>` | Minimum severity: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` | all | both |
|
|
788
|
+
| `--search <text>` | Search textPayload for a substring (IP, email, uid, error message) | none | both |
|
|
789
|
+
| `--filter <expr>` | Raw gcloud logging filter expression (appended to built-in filters) | none | both |
|
|
780
790
|
| `--since <duration>` | Time range (`30m`, `1h`, `2d`, `1w`) | `1h` | read only |
|
|
781
|
-
| `--limit <n>` | Max entries | `
|
|
791
|
+
| `--limit <n>` | Max entries | `300` | read only |
|
|
792
|
+
| `--order <dir>` | Sort order: `asc` (oldest first) or `desc` (newest first) | `desc` | read only |
|
|
793
|
+
| `--interval <sec>` | Polling interval in seconds | `5` | tail only |
|
|
782
794
|
| `--raw` | Output raw JSON | false | both |
|
|
783
795
|
|
|
796
|
+
#### `--fn` Function Name Reference
|
|
797
|
+
|
|
798
|
+
The `--fn` flag uses the **deployed Cloud Function name**, not the route path.
|
|
799
|
+
|
|
800
|
+
**BEM built-in functions (always deployed):**
|
|
801
|
+
|
|
802
|
+
| Function name | Type | Description |
|
|
803
|
+
|---------------|------|-------------|
|
|
804
|
+
| `bm_api` | HTTPS | Main API router — all consumer routes (GET/POST/PUT/DELETE) go through this |
|
|
805
|
+
| `bm_authBeforeCreate` | Auth blocking | Before user creation: disposable email blocking, IP rate limiting, consumer hooks |
|
|
806
|
+
| `bm_authBeforeSignIn` | Auth blocking | Before sign-in: consumer hooks |
|
|
807
|
+
| `bm_authOnCreate` | Auth event | After user creation: user doc setup |
|
|
808
|
+
| `bm_authOnDelete` | Auth event | After user deletion |
|
|
809
|
+
| `bm_paymentsWebhookOnWrite` | Firestore trigger | Processes payment webhooks |
|
|
810
|
+
| `bm_paymentsDisputeOnWrite` | Firestore trigger | Processes payment disputes |
|
|
811
|
+
| `bm_notificationsOnWrite` | Firestore trigger | Sends push notifications |
|
|
812
|
+
| `bm_cronDaily` | Scheduled | Daily cron (midnight UTC) |
|
|
813
|
+
| `bm_cronFrequent` | Scheduled | Frequent cron (every 10 min) |
|
|
814
|
+
|
|
815
|
+
**Consumer-defined functions** use the export name from `functions/index.js` (e.g., `exports.items = ...` → `--fn items`).
|
|
816
|
+
|
|
817
|
+
**Quick lookup — which function to query:**
|
|
818
|
+
- API route errors → `--fn bm_api`
|
|
819
|
+
- Signup/auth blocked → `--fn bm_authBeforeCreate`
|
|
820
|
+
- Sign-in issues → `--fn bm_authBeforeSignIn`
|
|
821
|
+
- User doc not created → `--fn bm_authOnCreate`
|
|
822
|
+
- Payment not processing → `--fn bm_paymentsWebhookOnWrite`
|
|
823
|
+
- Cron job issues → `--fn bm_cronDaily` or `--fn bm_cronFrequent`
|
|
824
|
+
|
|
784
825
|
### Shared Flags
|
|
785
826
|
|
|
786
827
|
| Flag | Description |
|
|
@@ -793,22 +834,22 @@ Both commands save output to `functions/logs.log` (overwritten on each run). `lo
|
|
|
793
834
|
|
|
794
835
|
```bash
|
|
795
836
|
# Read a user document from production
|
|
796
|
-
npx
|
|
837
|
+
npx mgr firestore:get users/abc123
|
|
797
838
|
|
|
798
839
|
# Write to emulator
|
|
799
|
-
npx
|
|
840
|
+
npx mgr firestore:set users/test123 '{"name":"Test User"}' --emulator
|
|
800
841
|
|
|
801
842
|
# Query with filters
|
|
802
|
-
npx
|
|
843
|
+
npx mgr firestore:query users --where "subscription.status==active" --limit 10
|
|
803
844
|
|
|
804
845
|
# Look up auth user by email
|
|
805
|
-
npx
|
|
846
|
+
npx mgr auth:get user@example.com
|
|
806
847
|
|
|
807
848
|
# Set admin claims
|
|
808
|
-
npx
|
|
849
|
+
npx mgr auth:set-claims user@example.com '{"admin":true}'
|
|
809
850
|
|
|
810
851
|
# Delete from emulator (no confirmation needed)
|
|
811
|
-
npx
|
|
852
|
+
npx mgr firestore:delete users/test123 --emulator
|
|
812
853
|
```
|
|
813
854
|
|
|
814
855
|
## Usage & Rate Limiting
|
|
@@ -1325,7 +1366,7 @@ Campaigns reference segments by SSOT key: `segments: ['subscription_free']`. Aut
|
|
|
1325
1366
|
|
|
1326
1367
|
### Seed Campaigns
|
|
1327
1368
|
|
|
1328
|
-
Created by `npx
|
|
1369
|
+
Created by `npx mgr setup` (idempotent, enforced fields checked every run):
|
|
1329
1370
|
|
|
1330
1371
|
| ID | Type | Description |
|
|
1331
1372
|
|----|------|-------------|
|
|
@@ -1378,7 +1419,7 @@ marketing: {
|
|
|
1378
1419
|
|
|
1379
1420
|
8. **Increment usage before update** - Call `usage.increment()` then `usage.update()`
|
|
1380
1421
|
|
|
1381
|
-
9. **Add Firestore composite indexes for new compound queries** - Any new Firestore query using multiple `.where()` clauses or `.where()` + `.orderBy()` requires a composite index. Add it to `src/cli/commands/setup-tests/helpers/required-indexes.js` (the SSOT). Consumer projects pick these up via `npx
|
|
1422
|
+
9. **Add Firestore composite indexes for new compound queries** - Any new Firestore query using multiple `.where()` clauses or `.where()` + `.orderBy()` requires a composite index. Add it to `src/cli/commands/setup-tests/helpers/required-indexes.js` (the SSOT). Consumer projects pick these up via `npx mgr setup`, which syncs them into `firestore.indexes.json`. Without the index, the query will crash with `FAILED_PRECONDITION` in production.
|
|
1382
1423
|
|
|
1383
1424
|
## Key Files Reference
|
|
1384
1425
|
|
|
@@ -1422,7 +1463,7 @@ marketing: {
|
|
|
1422
1463
|
```javascript
|
|
1423
1464
|
assistant.isDevelopment() // true when ENVIRONMENT !== 'production' or in emulator
|
|
1424
1465
|
assistant.isProduction() // true when ENVIRONMENT === 'production'
|
|
1425
|
-
assistant.isTesting() // true when running tests (via npx
|
|
1466
|
+
assistant.isTesting() // true when running tests (via npx mgr test)
|
|
1426
1467
|
```
|
|
1427
1468
|
|
|
1428
1469
|
## Model Context Protocol (MCP)
|
|
@@ -1432,7 +1473,7 @@ BEM includes a built-in MCP server that exposes BEM routes as tools for Claude C
|
|
|
1432
1473
|
### Architecture
|
|
1433
1474
|
|
|
1434
1475
|
Two transport modes:
|
|
1435
|
-
- **Stdio** (local): `npx
|
|
1476
|
+
- **Stdio** (local): `npx mgr mcp` — for Claude Code / Claude Desktop
|
|
1436
1477
|
- **Streamable HTTP** (remote): `POST /backend-manager/mcp` — for Claude Chat (stateless, Firebase Functions compatible)
|
|
1437
1478
|
|
|
1438
1479
|
### Available Tools (19)
|
|
@@ -1466,7 +1507,7 @@ Two transport modes:
|
|
|
1466
1507
|
|
|
1467
1508
|
### Hosting Rewrites
|
|
1468
1509
|
|
|
1469
|
-
The `npx
|
|
1510
|
+
The `npx mgr setup` command automatically adds required Firebase Hosting rewrites for MCP OAuth:
|
|
1470
1511
|
```json
|
|
1471
1512
|
{
|
|
1472
1513
|
"source": "{/backend-manager,/backend-manager/**,/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server,/authorize,/token}",
|
|
@@ -1477,7 +1518,7 @@ The `npx bm setup` command automatically adds required Firebase Hosting rewrites
|
|
|
1477
1518
|
### CLI Usage
|
|
1478
1519
|
|
|
1479
1520
|
```bash
|
|
1480
|
-
npx
|
|
1521
|
+
npx mgr mcp # Start stdio MCP server (for Claude Code)
|
|
1481
1522
|
```
|
|
1482
1523
|
|
|
1483
1524
|
### Claude Code Configuration
|
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ module.exports = function (assistant) {
|
|
|
89
89
|
Run the setup command:
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
npx
|
|
92
|
+
npx mgr setup
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
## Initialization Options
|
|
@@ -802,28 +802,28 @@ BEM includes an integration test framework that runs against the Firebase emulat
|
|
|
802
802
|
|
|
803
803
|
```bash
|
|
804
804
|
# Option 1: Two terminals (recommended for development)
|
|
805
|
-
npx
|
|
806
|
-
npx
|
|
805
|
+
npx mgr emulator # Terminal 1 - keeps emulator running
|
|
806
|
+
npx mgr test # Terminal 2 - runs tests
|
|
807
807
|
|
|
808
808
|
# Option 2: Single command (auto-starts emulator, shuts down after)
|
|
809
|
-
npx
|
|
809
|
+
npx mgr test
|
|
810
810
|
```
|
|
811
811
|
|
|
812
812
|
### Filtering Tests
|
|
813
813
|
|
|
814
814
|
```bash
|
|
815
|
-
npx
|
|
816
|
-
npx
|
|
817
|
-
npx
|
|
818
|
-
npx
|
|
815
|
+
npx mgr test rules/ # Run rules tests (both BEM and project)
|
|
816
|
+
npx mgr test bem:rules/ # Only BEM's rules tests
|
|
817
|
+
npx mgr test project:rules/ # Only project's rules tests
|
|
818
|
+
npx mgr test user/ admin/ # Multiple paths
|
|
819
819
|
```
|
|
820
820
|
|
|
821
821
|
### Log Files
|
|
822
822
|
|
|
823
823
|
BEM CLI commands automatically save output to log files in the project directory:
|
|
824
|
-
- **`emulator.log`** — Full emulator + Cloud Functions output (`npx
|
|
825
|
-
- **`test.log`** — Test runner output (`npx
|
|
826
|
-
- **`logs.log`** — Cloud Function logs (`npx
|
|
824
|
+
- **`emulator.log`** — Full emulator + Cloud Functions output (`npx mgr emulator`)
|
|
825
|
+
- **`test.log`** — Test runner output (`npx mgr test`, when running against an existing emulator)
|
|
826
|
+
- **`logs.log`** — Cloud Function logs (`npx mgr logs:read` or `npx mgr logs:tail`)
|
|
827
827
|
|
|
828
828
|
Logs are overwritten on each run. Use them to debug failing tests or review function output.
|
|
829
829
|
|
package/TODO-2.md
CHANGED
|
@@ -23,6 +23,26 @@ waht about when they request a cancel
|
|
|
23
23
|
Read cancellation-requested.js
|
|
24
24
|
The category is order/cancellation-requested (line 13).
|
|
25
25
|
|
|
26
|
+
----
|
|
27
|
+
add a dedicated BEM JSON field for usage to reset
|
|
28
|
+
* this way we can have clear LIMITS with their definitions like
|
|
29
|
+
* [
|
|
30
|
+
{
|
|
31
|
+
name: 'credits'
|
|
32
|
+
reset: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'agents',
|
|
36
|
+
reset: false,
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
* mirrors: [
|
|
40
|
+
{
|
|
41
|
+
collection: 'agents',
|
|
42
|
+
fields: ['usage.credits.daily', 'runs.replies.daily],
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
|
|
26
46
|
---
|
|
27
47
|
MIRROR settigns in BEM JSON so that usage reset can properly get MIRRED DOCS liek slapform forms or chatsy agents DOCS
|
|
28
48
|
|
package/package.json
CHANGED
package/src/cli/commands/logs.js
CHANGED
|
@@ -56,11 +56,12 @@ 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 300]
|
|
59
|
+
* Usage: npx bm logs:read [--fn bm_api] [--severity ERROR] [--since 1h] [--limit 300] [--search "text"] [--order desc] [--filter 'raw gcloud filter']
|
|
60
60
|
*/
|
|
61
61
|
async read(projectId, argv) {
|
|
62
62
|
const filter = this.buildFilter(argv);
|
|
63
63
|
const limit = parseInt(argv.limit, 10) || 300;
|
|
64
|
+
const order = argv.order || 'desc';
|
|
64
65
|
|
|
65
66
|
const cmd = [
|
|
66
67
|
'gcloud', 'logging', 'read',
|
|
@@ -68,7 +69,7 @@ class LogsCommand extends BaseCommand {
|
|
|
68
69
|
`--project=${projectId}`,
|
|
69
70
|
`--limit=${limit}`,
|
|
70
71
|
'--format=json',
|
|
71
|
-
|
|
72
|
+
`--order=${order}`,
|
|
72
73
|
].filter(Boolean).join(' ');
|
|
73
74
|
|
|
74
75
|
// Set up log file in the project directory
|
|
@@ -83,7 +84,7 @@ class LogsCommand extends BaseCommand {
|
|
|
83
84
|
const output = execSync(cmd, {
|
|
84
85
|
encoding: 'utf8',
|
|
85
86
|
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
86
|
-
timeout:
|
|
87
|
+
timeout: 60000,
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
const entries = JSON.parse(output || '[]');
|
|
@@ -233,6 +234,16 @@ class LogsCommand extends BaseCommand {
|
|
|
233
234
|
parts.push(`severity>=${argv.severity.toUpperCase()}`);
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
// Text search filter (searches textPayload)
|
|
238
|
+
if (argv.search) {
|
|
239
|
+
parts.push(`textPayload:"${argv.search}"`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Raw filter passthrough (appended as-is)
|
|
243
|
+
if (argv.filter) {
|
|
244
|
+
parts.push(argv.filter);
|
|
245
|
+
}
|
|
246
|
+
|
|
236
247
|
// Timestamp filter (read only, not tail)
|
|
237
248
|
if (!options.excludeTimestamp) {
|
|
238
249
|
const since = argv.since || '1h';
|
|
@@ -142,6 +142,8 @@ Transactional.prototype.build = async function (settings) {
|
|
|
142
142
|
throw errorWithCode('Parameter subject is required', 400);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
const preview = settings.preview || settings?.data?.email?.preview || null;
|
|
146
|
+
|
|
145
147
|
const templateId = TEMPLATES[settings.template] || settings.template || TEMPLATES['default'];
|
|
146
148
|
|
|
147
149
|
// Resolve sender category
|
|
@@ -176,7 +178,7 @@ Transactional.prototype.build = async function (settings) {
|
|
|
176
178
|
const unsubSig = crypto.createHmac('sha256', process.env.UNSUBSCRIBE_HMAC_KEY).update(to[0].email.toLowerCase()).digest('hex');
|
|
177
179
|
const unsubscribeUrl = `${Manager.project.websiteUrl}/portal/email-preferences?email=${encode(to[0].email)}&asmId=${encode(groupId)}&templateId=${encode(templateId)}&sig=${unsubSig}`;
|
|
178
180
|
|
|
179
|
-
// Build signoff
|
|
181
|
+
// Build signoff defaults
|
|
180
182
|
const signoff = settings?.data?.signoff || {};
|
|
181
183
|
signoff.type = signoff.type || 'team';
|
|
182
184
|
|
|
@@ -188,12 +190,12 @@ Transactional.prototype.build = async function (settings) {
|
|
|
188
190
|
signoff.urlText = signoff.urlText || '@ianwieds';
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
// Build dynamic template data defaults
|
|
193
|
+
// Build dynamic template data — system-generated defaults
|
|
192
194
|
const dynamicTemplateData = {
|
|
193
195
|
email: {
|
|
194
196
|
id: Manager.require('uuid').v4(),
|
|
195
197
|
subject,
|
|
196
|
-
preview
|
|
198
|
+
preview,
|
|
197
199
|
body: null,
|
|
198
200
|
unsubscribeUrl,
|
|
199
201
|
categories,
|
|
@@ -209,18 +211,20 @@ Transactional.prototype.build = async function (settings) {
|
|
|
209
211
|
signoff,
|
|
210
212
|
brand: brandData,
|
|
211
213
|
user: userProperties,
|
|
212
|
-
data: {},
|
|
213
214
|
};
|
|
214
215
|
|
|
215
|
-
// Deep-merge caller's data on top
|
|
216
|
-
//
|
|
216
|
+
// Deep-merge caller's data on top of defaults.
|
|
217
|
+
// This is the single template data tree — everything the template can access.
|
|
218
|
+
// Callers can override any field (email.preview, signoff.type, etc.)
|
|
219
|
+
// and add custom data (order.*, body.*, abandonedCart.*, etc.) at the root.
|
|
220
|
+
// Templates access all fields at the root: {{order.id}}, {{email.preview}}, {{brand.name}}.
|
|
217
221
|
if (settings.data) {
|
|
218
222
|
_.merge(dynamicTemplateData, settings.data);
|
|
219
223
|
}
|
|
220
224
|
|
|
221
|
-
// Process markdown in body fields (after merge so
|
|
222
|
-
if (dynamicTemplateData.
|
|
223
|
-
dynamicTemplateData.
|
|
225
|
+
// Process markdown in body fields (after merge so caller data is resolved)
|
|
226
|
+
if (dynamicTemplateData.body?.message) {
|
|
227
|
+
dynamicTemplateData.body.message = md.render(dynamicTemplateData.body.message);
|
|
224
228
|
}
|
|
225
229
|
if (dynamicTemplateData.email?.body) {
|
|
226
230
|
dynamicTemplateData.email.body = md.render(dynamicTemplateData.email.body);
|
|
@@ -235,8 +239,8 @@ Transactional.prototype.build = async function (settings) {
|
|
|
235
239
|
utm: settings.utm,
|
|
236
240
|
};
|
|
237
241
|
|
|
238
|
-
if (dynamicTemplateData.
|
|
239
|
-
dynamicTemplateData.
|
|
242
|
+
if (dynamicTemplateData.body?.message) {
|
|
243
|
+
dynamicTemplateData.body.message = tagLinks(dynamicTemplateData.body.message, utmOptions);
|
|
240
244
|
}
|
|
241
245
|
if (dynamicTemplateData.email?.body) {
|
|
242
246
|
dynamicTemplateData.email.body = tagLinks(dynamicTemplateData.email.body, utmOptions);
|
|
@@ -35,7 +35,7 @@ module.exports = async ({ assistant, user, settings }) => {
|
|
|
35
35
|
// Guard: subscription younger than 24 hours
|
|
36
36
|
const startDateUNIX = subscription.payment?.startDate?.timestampUNIX;
|
|
37
37
|
if (startDateUNIX) {
|
|
38
|
-
const ageMs = Date.now() - startDateUNIX;
|
|
38
|
+
const ageMs = Date.now() - (startDateUNIX * 1000);
|
|
39
39
|
const twentyFourHoursMs = 24 * 60 * 60 * 1000;
|
|
40
40
|
if (ageMs < twentyFourHoursMs) {
|
|
41
41
|
assistant.log(`Cancel rejected: uid=${uid}, subscription is only ${Math.round(ageMs / 1000 / 60)} minutes old`);
|