Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
098439d
chore(api-service, worker, ws): upgrade to nest js v11
scopsy Apr 10, 2026
321f3ae
Use string types for enum ApiProperty metadata
scopsy Apr 10, 2026
2c609cf
fix:
scopsy Apr 10, 2026
e6b4846
fix(api): unblock novu-v2 e2e (mocha flags, setup, swagger, testing d…
cursoragent Apr 10, 2026
18f794e
Update .source
scopsy Apr 11, 2026
e6e7bab
Merge branch 'next' into upgrade-nestjs-version
scopsy Apr 11, 2026
59a996a
כןס
scopsy Apr 11, 2026
a7a5694
Use static imports for ee-billing in e2e tests
scopsy Apr 11, 2026
b7db4e1
Condense EE billing imports and trim whitespace
scopsy Apr 11, 2026
fc46706
Update create-environment-request.dto.ts
scopsy Apr 11, 2026
211622f
Test config, forwardRef, and spec cleanups
scopsy Apr 11, 2026
d4879be
Update run-novu-v2-e2e-shard.cjs
scopsy Apr 11, 2026
ead9f70
fix(api, application-generic): restore api unit tests and preference …
cursoragent Apr 11, 2026
11a746e
chore(source): sync enterprise submodule with next (017a588)
cursoragent Apr 11, 2026
f1949f2
chore(source): point enterprise submodule at KeylessStrategy Nest 11 fix
cursoragent Apr 11, 2026
1441066
fix(api-service,ws,worker,shared): CORS delegate typing, safe WS toke…
cursoragent Apr 11, 2026
8b72d5e
Refactor internal-sdk models and request params
scopsy Apr 11, 2026
241780d
Introduce creation source enums; make source optional
scopsy Apr 11, 2026
7278941
Update ui-schema-property.dto.ts
scopsy Apr 11, 2026
ae73bb1
Add UiComponentEnum, export and disable enum patch
scopsy Apr 11, 2026
d6a6571
Update uischemaproperty.ts
scopsy Apr 11, 2026
b1d9ee0
Refactor preferences, WS token & Swagger enums
scopsy Apr 11, 2026
5f06329
Update swagger.controller.ts
scopsy Apr 11, 2026
ba7ec04
fix
scopsy Apr 11, 2026
77c967b
Merge branch 'next' into upgrade-nestjs-version
scopsy Apr 11, 2026
83110fb
Update run-novu-v2-e2e-shard.cjs
scopsy Apr 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .source
6 changes: 6 additions & 0 deletions apps/api/.mocharc.e2e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"timeout": 35000,
"require": "@swc-node/register",
"file": ["e2e/setup.ts"],
"exit": true
}
5 changes: 2 additions & 3 deletions apps/api/.mocharc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"timeout": 35000,
"require": "@swc-node/register",
"node-option": ["no-experimental-strip-types"],
"require": ["@swc-node/register", "./src/config/load-test-env.ts"],
"file": ["e2e/setup.ts"],
"exit": true,
"files": ["e2e/**/*.e2e.ts", "src/**/*.e2e.ts", "src/**/**/*.spec.ts"]
"files": ["e2e/**/*.e2e.ts", "src/**/*.e2e.ts", "src/**/*.spec.ts"]
}
30 changes: 15 additions & 15 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
"pretest": "pnpm build:metadata",
"generate:swagger": "ts-node exportOpenAPIJSON.ts",
"generate:sdk": " (cd ../../libs/internal-sdk && speakeasy run --skip-compile --minimal --skip-versioning) && (cd ../../libs/internal-sdk && pnpm build) ",
"test": "cross-env TS_NODE_PROJECT=tsconfig.spec.json TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test NOVU_ENTERPRISE=true CLERK_ENABLED=true NODE_OPTIONS=--no-experimental-strip-types mocha --timeout 15000 --require ts-node/register --exit 'src/**/*.spec.ts'",
"test:e2e:novu-v0": "cross-env TS_NODE_PROJECT=tsconfig.spec.json NODE_ENV=test NODE_OPTIONS=--no-experimental-strip-types mocha --timeout 15000 --retries 3 --grep '#novu-v0' --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e{,-ee}.ts",
"test:e2e:novu-v2": "cross-env TS_NODE_PROJECT=tsconfig.spec.json NODE_ENV=test CI_EE_TEST=true CLERK_ENABLED=true NODE_OPTIONS='--max_old_space_size=8192 --no-experimental-strip-types' node scripts/run-novu-v2-e2e-shard.cjs",
"test": "cross-env TS_NODE_PROJECT=tsconfig.spec.json TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test NOVU_ENTERPRISE=true CLERK_ENABLED=true mocha --timeout 15000 --require ts-node/register --require ./src/config/load-test-env.ts --exit 'src/**/*.spec.ts' --ignore 'src/**/e2e/**/*.spec.ts'",
"test:e2e:novu-v0": "cross-env TS_NODE_PROJECT=tsconfig.spec.json NODE_ENV=test mocha --config .mocharc.e2e.json --timeout 15000 --retries 3 --grep '#novu-v0' --require ts-node/register --exit src/**/*.e2e{,-ee}.ts",
"test:e2e:novu-v2": "cross-env TS_NODE_PROJECT=tsconfig.spec.json NODE_ENV=test CI_EE_TEST=true CLERK_ENABLED=true NODE_OPTIONS=--max_old_space_size=8192 node scripts/run-novu-v2-e2e-shard.cjs",
"migration": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly",
"seed:clickhouse": "cross-env NODE_ENV=local ts-node --transpileOnly scripts/seed-clickhouse.ts",
"seed:triggers": "cross-env NODE_ENV=local ts-node --transpileOnly scripts/seed-triggers.ts",
Expand All @@ -46,15 +46,15 @@
"@aws-sdk/client-secrets-manager": "^3.971.0",
"@godaddy/terminus": "^4.12.1",
"@google-cloud/storage": "^6.2.3",
"@nestjs/axios": "3.0.3",
"@nestjs/common": "10.4.18",
"@nestjs/core": "10.4.18",
"@nestjs/jwt": "10.2.0",
"@nestjs/passport": "10.0.3",
"@nestjs/platform-express": "10.4.18",
"@nestjs/swagger": "7.4.0",
"@nestjs/terminus": "10.2.3",
"@nestjs/throttler": "6.2.1",
"@nestjs/axios": "4.0.1",
"@nestjs/common": "11.1.18",
"@nestjs/core": "11.1.18",
"@nestjs/jwt": "11.0.2",
"@nestjs/passport": "11.0.5",
"@nestjs/platform-express": "11.1.18",
"@nestjs/swagger": "11.2.6",
"@nestjs/terminus": "11.1.1",
"@nestjs/throttler": "6.5.0",
Comment on lines +49 to +57
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== nest-raven usage in apps/api =="
rg -n -C2 "from ['\"]nest-raven['\"]|\\bRaven(Module|Interceptor)\\b" apps/api || true

echo
python - <<'PY'
import json, pathlib
pkg = json.loads(pathlib.Path("apps/api/package.json").read_text())
for name in ("@nestjs/common", "@nestjs/core", "nest-raven", "@sentry/nestjs"):
    value = pkg.get("dependencies", {}).get(name) or pkg.get("devDependencies", {}).get(name)
    print(f"{name}: {value}")
PY

echo
npm view "nest-raven@10.1.0" peerDependencies --json
npm view "@sentry/nestjs@8.49.0" peerDependencies --json

Repository: novuhq/novu

Length of output: 1060


Remove the unused nest-raven dependency; it doesn't support NestJS 11.

The nest-raven@10.1.0 package has a peer dependency constraint of @nestjs/common@^10.0.0, which excludes NestJS 11. Since nest-raven is not imported anywhere in the codebase, it should be removed to avoid a broken peer dependency after this upgrade.

Additionally, @sentry/nestjs@8.49.0 also does not yet support NestJS 11 (max supported is v10). Verify that Sentry integration is either disabled or plan a migration to a compatible version before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/package.json` around lines 49 - 57, Remove the unused nest-raven
dependency from apps/api/package.json (delete the "nest-raven@10.1.0" entry)
because it requires `@nestjs/common` v10 and will break peer deps after upgrading
to NestJS 11; also check for any imports/usages of nest-raven in the codebase
(search for "nest-raven") and delete them if present. For Sentry, locate any
references to "@sentry/nestjs" (package.json and any imports like SentryModule
or SentryService) and either disable the Sentry integration or plan/make a
migration to a version that supports NestJS 11 before merging. Ensure
package.json and lockfile are updated (run install) after removing the
dependency.

"@novu/api": "workspace:*",
"@novu/application-generic": "workspace:*",
"@novu/dal": "workspace:*",
Expand Down Expand Up @@ -129,9 +129,9 @@
},
"devDependencies": {
"@faker-js/faker": "^6.0.0",
"@nestjs/cli": "10.4.5",
"@nestjs/schematics": "10.1.4",
"@nestjs/testing": "10.4.18",
"@nestjs/cli": "11.0.17",
"@nestjs/schematics": "11.0.10",
"@nestjs/testing": "11.1.18",
"@stoplight/spectral-cli": "^6.15.0",
"@swc-node/register": "1.10.10",
"@types/async": "^3.2.1",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/scripts/generate-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
import fs from 'node:fs';
import path from 'node:path';
import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins';
import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins/plugin-metadata-generator';
import { ReadonlyVisitor } from '@nestjs/swagger/dist/plugin';

const tsconfigPath = 'tsconfig.build.json';
Expand Down
15 changes: 9 additions & 6 deletions apps/api/scripts/run-novu-v2-e2e-shard.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const DEFAULT_MOCHA_REPORTER = process.env.CI ? 'dot' : 'spec';
const MOCHA_REPORTER = process.env.NOVU_V2_MOCHA_REPORTER || DEFAULT_MOCHA_REPORTER;

const MOCHA_ARGS = [
'--config',
'.mocharc.e2e.json',
'--timeout',
'30000',
'--retries',
Expand All @@ -23,11 +25,7 @@ const MOCHA_ARGS = [
MOCHA_REPORTER,
'--grep',
NOVU_V2_TAG,
'--require',
'./swc-register.js',
'--exit',
'--file',
'e2e/setup.ts',
];

function toPosixPath(filePath) {
Expand Down Expand Up @@ -152,7 +150,10 @@ function collectWeightedFiles() {
}

function isLighterShard(candidate, current) {
return candidate.weight < current.weight || (candidate.weight === current.weight && candidate.files.length < current.files.length);
return (
candidate.weight < current.weight ||
(candidate.weight === current.weight && candidate.files.length < current.files.length)
);
}

function pickLightestShard(shards) {
Expand Down Expand Up @@ -187,7 +188,9 @@ function getShard(weightedFiles, shardIndex, totalShards) {
}

function printShardSummary(shardIndex, totalShards, shard) {
console.log(`Running Novu V2 E2E shard ${shardIndex}/${totalShards} with ${shard.files.length} files (weight ${shard.weight}).`);
console.log(
`Running Novu V2 E2E shard ${shardIndex}/${totalShards} with ${shard.files.length} files (weight ${shard.weight}).`
);
}

function runMocha(filePaths) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class CommunityUserAuthGuard extends AuthGuard([PassportStrategyEnum.JWT,
this.logger.setContext(this.constructor.name);
}

getAuthenticateOptions(context: ExecutionContext): IAuthModuleOptions<any> {
getAuthenticateOptions(context: ExecutionContext): IAuthModuleOptions {
const request = context.switchToHttp().getRequest();
const authorizationHeader = request.headers.authorization;

Expand Down
31 changes: 16 additions & 15 deletions apps/api/src/app/auth/services/passport/apikey.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,25 @@ export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
private readonly inMemoryLRUCacheService: InMemoryLRUCacheService
) {
super(
{ header: HttpRequestHeaderKeysEnum.AUTHORIZATION, prefix: `${ApiAuthSchemeEnum.API_KEY} ` },
true,
async (apikey: string, verified: (err: Error | null, user?: UserSessionData | false) => void) => {
try {
const user = await this.validateApiKey(apikey);
{
header: HttpRequestHeaderKeysEnum.AUTHORIZATION,
prefix: `${ApiAuthSchemeEnum.API_KEY} `,
},
false
);
}

if (!user) {
return verified(null, false);
}
async validate(apiKey: string): Promise<UserSessionData | false> {
const user = await this.validateApiKey(apiKey);

addNewRelicTraceAttributes(user);
if (!user) {

return verified(null, user);
} catch (err) {
return verified(err as Error, false);
}
}
);
return false;
}

addNewRelicTraceAttributes(user);

return user;
}

private async validateApiKey(apiKey: string): Promise<UserSessionData | null> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CheckoutSessionCompletedHandler, GetPrices, VerifyCustomer } from '@novu/ee-billing';
import { ApiServiceLevelEnum, StripeBillingIntervalEnum } from '@novu/shared';
import { expect } from 'chai';
import sinon from 'sinon';
Expand Down Expand Up @@ -147,13 +148,6 @@ describe('webhook event - checkout.session.completed #novu-v2', () => {
},
};

const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}

const { CheckoutSessionCompletedHandler, VerifyCustomer, GetPrices } = eeBilling;

let verifyCustomerStub: sinon.SinonStub;
let getPricesStub: sinon.SinonStub;
const analyticsServiceStub = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CreateCheckoutSession } from '@novu/ee-billing';
import { ApiServiceLevelEnum, StripeBillingIntervalEnum } from '@novu/shared';
import { expect } from 'chai';
import sinon from 'sinon';
Expand All @@ -23,12 +24,10 @@ const checkoutSessionCreateParamsMock = {
};

describe('Create checkout session #novu-v2', async () => {
if (!require('@novu/ee-billing').CreateCheckoutSession) {
if (!CreateCheckoutSession) {
throw new Error("CreateCheckoutSession doesn't exist");
}

const { CreateCheckoutSession } = require('@novu/ee-billing');

const getOrCreateCustomer = {
execute: () => Promise.resolve({ id: 'customer_id' }),
};
Expand Down
15 changes: 7 additions & 8 deletions apps/api/src/app/billing/e2e/create-subscription.e2e-ee.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import {
CreateSubscription,
CreateSubscriptionCommand,
GetPrices,
UpdateServiceLevel,
} from '@novu/ee-billing';
import { StripeSubscriptionStatusEnum, StripeUsageTypeEnum } from '@novu/ee-billing/src/stripe/types';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

EE_PKG="$(fd -a package.json | rg '/ee-billing/package.json$' | head -n1)"
echo "ee-billing package file: $EE_PKG"
jq '{name, exports, main, module, types}' "$EE_PKG"

EE_DIR="$(dirname "$EE_PKG")"
echo "Checking enum declarations/exports in ee-billing..."
rg -n "enum Stripe(SubscriptionStatusEnum|UsageTypeEnum)|export .*Stripe(SubscriptionStatusEnum|UsageTypeEnum)" "$EE_DIR/src" -g '*.ts'

echo "Checking deep-import usage in billing e2e tests..."
rg -n "@novu/ee-billing/src/stripe/types" apps/api/src/app/billing/e2e -g '*.ts'

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Step 1: Find ee-billing package location
find . -type f -name "package.json" | grep -E "ee-billing" | head -5

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Step 2: Search more broadly for billing packages and workspace configuration
find . -type f -name "pnpm-workspace.yaml" -o -name "package.json" | head -3
ls -la | head -20

Repository: novuhq/novu

Length of output: 1358


🏁 Script executed:

# Step 3: Check the test file and search for enum definitions
cat -n apps/api/src/app/billing/e2e/create-subscription.e2e-ee.ts | head -30

# Also search for StripeSubscriptionStatusEnum definition
rg "StripeSubscriptionStatusEnum|StripeUsageTypeEnum" -g '*.ts' | head -20

Repository: novuhq/novu

Length of output: 2591


🏁 Script executed:

# Step 4: Find ee-billing package location and check its exports
find . -type d -name "*ee-billing*" | grep -v node_modules | grep -v ".git"

# Also check tsconfig for path mappings
cat tsconfig.base.json 2>/dev/null | jq '.compilerOptions.paths | select(. != null) | keys[] | select(contains("ee-billing"))' 2>/dev/null || echo "Checking alternate tsconfig..."

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Step 4b: Broader search for the actual location
ls -la libs/ 2>/dev/null | head -20 || echo "No libs directory"
ls -la packages/ 2>/dev/null | head -20 || echo "No packages directory"

# Search directly for stripe/types file
find . -path "*/stripe/types.ts" -not -path "*/node_modules/*" -not -path "*/.git/*"

Repository: novuhq/novu

Length of output: 1556


🏁 Script executed:

# Step 5: Check packages/novu structure for ee-billing
ls -la packages/novu/ | head -20

# Search for stripe/types.ts
find . -path "*/stripe/types.ts" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null
find . -name "types.ts" -path "*/stripe/*" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null

Repository: novuhq/novu

Length of output: 795


🏁 Script executed:

# Step 6: Explore packages/novu/src structure
ls -la packages/novu/src/

# Find stripe/types.ts more directly
find packages/novu -name "types.ts" 2>/dev/null | head -10

# Also search for StripeSubscriptionStatusEnum definition
rg "StripeSubscriptionStatusEnum|StripeUsageTypeEnum" packages/novu/ -g '*.ts' 2>/dev/null | head -15

Repository: novuhq/novu

Length of output: 809


🏁 Script executed:

# Step 7: Search for `@novu/ee-billing` references and StripeSubscriptionStatusEnum definition
rg "@novu/ee-billing" --type json | head -10

# Search for where StripeSubscriptionStatusEnum is defined
rg "enum StripeSubscriptionStatusEnum|export.*StripeSubscriptionStatusEnum" -g '*.ts' 2>/dev/null | head -10

# Check for any ee-billing directories
find . -type d -name "*ee*billing*" -not -path "*/node_modules/*" -not -path "*/.git/*"

Repository: novuhq/novu

Length of output: 321


🏁 Script executed:

# Step 8: Explore enterprise/packages/billing structure
ls -la enterprise/packages/billing/

ls -la enterprise/packages/billing/src/ 2>/dev/null || echo "No src directory"

# Check for stripe/types.ts
find enterprise/packages/billing -name "types.ts" -path "*/stripe/*" 2>/dev/null

# Check package.json exports
cat enterprise/packages/billing/package.json | jq '{name, exports, main, types, files}' 2>/dev/null

Repository: novuhq/novu

Length of output: 758


🏁 Script executed:

# Step 9: Find the actual source and check index.ts
find . -type d -path "*/.source/billing/src" -not -path "*/node_modules/*"

# Also check relative path from enterprise/packages/billing
ls -la enterprise/packages/billing/../../../.source/billing/src/ 2>/dev/null || echo "Path not found"

# Try to find .source directory
find . -type d -name ".source" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -5

Repository: novuhq/novu

Length of output: 79


🏁 Script executed:

# Step 10: Check .source/billing/src structure
ls -la .source/billing/src/ 2>/dev/null || echo "Directory structure not found"

# List all files in .source/billing
find .source/billing -type f -name "*.ts" | head -20

# Check if index.ts exists and what it exports
cat .source/billing/src/index.ts 2>/dev/null | head -50

Repository: novuhq/novu

Length of output: 152


🏁 Script executed:

# Step 11: Check .source directory
ls -la .source/ | head -30

# Search for stripe directory anywhere
find . -type d -name "stripe" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null

# Search for any stripe/types.ts anywhere
find . -type f -path "*/stripe/types.ts" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null

# Also search for StripeSubscriptionStatusEnum definition
rg "enum StripeSubscriptionStatusEnum" -g '*.ts' 2>/dev/null

Repository: novuhq/novu

Length of output: 167


🏁 Script executed:

# Step 12: Check enterprise/packages/billing directly for any files
find enterprise/packages/billing -type f -name "*.ts" | head -20

# Search for stripe in entire repo more carefully
find . -type d -name "stripe" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null

# Search for StripeSubscriptionStatusEnum or StripeUsageTypeEnum definition more broadly
rg "StripeSubscriptionStatusEnum|StripeUsageTypeEnum" -g '*.ts' --type-list 2>/dev/null | grep -v "import\|from" | head -20

# Check actual dist output if it exists
find enterprise/packages/billing/dist -type f 2>/dev/null | head -10

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Step 13: Check for submodules and gitignore
cat .gitmodules 2>/dev/null | grep -i billing || echo "No billing submodule"

cat .gitignore | grep -i billing || echo "No billing in gitignore"

# Check enterprise/.gitignore
cat enterprise/.gitignore 2>/dev/null | grep -i billing || echo "No billing in enterprise/.gitignore"

# Check what package.json says about main field - follow the reference
cat enterprise/packages/billing/package.json | jq '.main'

# Check the full package.json
cat enterprise/packages/billing/package.json

Repository: novuhq/novu

Length of output: 1907


🏁 Script executed:

# Step 14: Check the build script and source structure
cat enterprise/packages/billing/check-ee.mjs

# Also check if src is actually accessible (since symlink is broken, show what's in dist)
ls -la enterprise/packages/billing/dist/ 2>/dev/null | head -20

# Try to find the real source by checking if there's a different path
find . -path "*/billing/src/stripe/types.ts" -not -path "*/node_modules/*" 2>/dev/null

# Also check tsconfig to understand source mapping
cat enterprise/packages/billing/tsconfig.json

Repository: novuhq/novu

Length of output: 1938


🏁 Script executed:

# Step 15: Search for index files and stripe enum exports in ee-billing context
rg "export.*Stripe(Subscription|Usage)" enterprise/packages/billing/ -g '*.ts' 2>/dev/null || echo "No exports found"

# Check if there's any src directory (maybe not symlinked but actual)
find enterprise/packages/billing -type d -name src 2>/dev/null

# Search entire repo for where StripeSubscriptionStatusEnum is defined and exported
rg "export.*(enum|const) Stripe(Subscription|Usage)" -g '*.ts' | head -20

Repository: novuhq/novu

Length of output: 71


Avoid deep-importing @novu/ee-billing/src/... in this test

The import on line 7 pulls enums from an internal source path (@novu/ee-billing/src/stripe/types), which bypasses the package's public API. This approach is fragile because:

  1. The src directory is a symlink that may not exist in all build contexts
  2. Only the dist/ output is published; src is not distributed to consumers
  3. The package has no explicit export maps, making the public API unclear
Suggested fix
-import { StripeSubscriptionStatusEnum, StripeUsageTypeEnum } from '@novu/ee-billing/src/stripe/types';
+import { StripeSubscriptionStatusEnum, StripeUsageTypeEnum } from '@novu/ee-billing';

If these enums are not currently exported from the public entrypoint, re-export them from @novu/ee-billing's main index file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/billing/e2e/create-subscription.e2e-ee.ts` at line 7, The
test deep-imports internal symbols StripeSubscriptionStatusEnum and
StripeUsageTypeEnum from '@novu/ee-billing/src/stripe/types' which is fragile;
instead add re-exports for those enums in the public entrypoint of the
`@novu/ee-billing` package (e.g., export { StripeSubscriptionStatusEnum,
StripeUsageTypeEnum } from './src/stripe/types' in the package's main index) and
update the test import in create-subscription.e2e-ee.ts to import them from
'@novu/ee-billing' so the test uses the package public API.

import { ApiServiceLevelEnum, StripeBillingIntervalEnum } from '@novu/shared';
import { expect } from 'chai';
import sinon from 'sinon';

const { StripeSubscriptionStatusEnum, StripeUsageTypeEnum } = require('@novu/ee-billing/src/stripe/types');

describe('CreateSubscription #novu-v2', () => {
const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}

const { CreateSubscription, GetPrices, UpdateServiceLevel, CreateSubscriptionCommand } = eeBilling;

const stripeStub = {
subscriptions: {
Expand Down
10 changes: 2 additions & 8 deletions apps/api/src/app/billing/e2e/create-usage-records.e2e-ee.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// biome-ignore lint/style/noRestrictedImports: <explanation>
import { Logger } from '@nestjs/common';
import { CreateUsageRecords, CreateUsageRecordsCommand } from '@novu/ee-billing';
import { StripeUsageTypeEnum } from '@novu/ee-billing/src/stripe/types';
import { ApiServiceLevelEnum, StripeBillingIntervalEnum } from '@novu/shared';
import { expect } from 'chai';
import sinon from 'sinon';

const { StripeUsageTypeEnum } = require('@novu/ee-billing/src/stripe/types');

const mockMonthlyBusinessSubscription = {
id: 'subscription_id',
items: {
Expand All @@ -23,12 +23,6 @@ const mockMonthlyBusinessSubscription = {
};

describe('CreateUsageRecords #novu-v2', () => {
const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}
const { CreateUsageRecords, CreateUsageRecordsCommand } = eeBilling;

const stripeStub = {
subscriptionItems: {
createUsageRecord: () => {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CustomerSubscriptionCreatedHandler, UpdateServiceLevel, VerifyCustomer } from '@novu/ee-billing';
import { ApiServiceLevelEnum, StripeBillingIntervalEnum } from '@novu/shared';
import { expect } from 'chai';
import sinon from 'sinon';
Expand Down Expand Up @@ -127,13 +128,6 @@ const verifyCustomerMock = {
};

describe('webhook event - customer.subscription.created #novu-v2', () => {
const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}

const { CustomerSubscriptionCreatedHandler, VerifyCustomer, UpdateServiceLevel } = eeBilling;

let verifyCustomerStub: sinon.SinonStub;
let updateServiceLevelStub: sinon.SinonStub;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import {
CreateSubscription,
CustomerSubscriptionDeletedHandler,
UpdateServiceLevel,
UpdateServiceLevelCommand,
VerifyCustomer,
} from '@novu/ee-billing';
import { ApiServiceLevelEnum, StripeBillingIntervalEnum } from '@novu/shared';
import { expect } from 'chai';
import sinon from 'sinon';
Expand Down Expand Up @@ -98,19 +105,6 @@ const verifyCustomerMock = {
};

describe.skip('webhook event - customer.subscription.deleted #novu-v2', () => {
const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}

const {
CustomerSubscriptionDeletedHandler,
VerifyCustomer,
UpdateServiceLevel,
UpdateServiceLevelCommand,
CreateSubscription,
} = eeBilling;

const stripeStub = {
customers: {
update: sinon.stub(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { PinoLogger } from '@novu/application-generic';
import {
GetPlatformNotificationUsage,
GetPlatformNotificationUsageCommand,
} from '@novu/ee-billing';
import { CommunityOrganizationRepository, EnvironmentRepository, NotificationRepository } from '@novu/dal';
import { ApiServiceLevelEnum, isClerkEnabled } from '@novu/shared';
import { UserSession } from '@novu/testing';
import { expect } from 'chai';
import sinon from 'sinon';

describe('GetPlatformNotificationUsage #novu-v2', () => {
const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}

const { GetPlatformNotificationUsage, GetPlatformNotificationUsageCommand } = eeBilling;

const environmentRepo = new EnvironmentRepository();
const notificationRepo = new NotificationRepository();
const communityOrganizationRepo = new CommunityOrganizationRepository();
Expand Down
5 changes: 3 additions & 2 deletions apps/api/src/app/billing/e2e/get-portal-link.e2e-ee.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { GetPortalLink } from '@novu/ee-billing';
import { expect } from 'chai';
import sinon from 'sinon';

const dashboardOrigin = process.env.DASHBOARD_URL;

describe('Get portal link #novu-v2', async () => {
it('Get portal link', async () => {
if (!require('@novu/ee-billing').GetPortalLink) {
if (!GetPortalLink) {
throw new Error("GetPortalLink doesn't exist");
}
const stubObject = {
Expand All @@ -27,7 +28,7 @@ describe('Get portal link #novu-v2', async () => {

const stub = sinon.stub(stubObject.billingPortal.sessions, 'create').resolves({ url: 'url' });

const usecase = new (require('@novu/ee-billing').GetPortalLink)(stubObject, getCustomerUsecase);
const usecase = new GetPortalLink(stubObject, getCustomerUsecase);

const result = await usecase.execute({
environmentId: 'environment_dd',
Expand Down
8 changes: 1 addition & 7 deletions apps/api/src/app/billing/e2e/get-prices.e2e-ee.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { GetPrices, GetPricesCommand } from '@novu/ee-billing';
import { ApiServiceLevelEnum, StripeBillingIntervalEnum } from '@novu/shared';
import { expect } from 'chai';
import sinon from 'sinon';

describe('GetPrices #novu-v2', () => {
const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}

const { GetPrices, GetPricesCommand } = eeBilling;

const stripeStub = {
prices: {
list: sinon.stub(),
Expand Down
8 changes: 1 addition & 7 deletions apps/api/src/app/billing/e2e/get-subscription.e2e-ee.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GetPlatformNotificationUsageCommand, GetSubscription, GetSubscriptionCommand } from '@novu/ee-billing';
import { ApiServiceLevelEnum } from '@novu/shared';
import { UserSession } from '@novu/testing';
import { expect } from 'chai';
Expand Down Expand Up @@ -56,13 +57,6 @@ const mockedStripeCustomer: DeepPartial<Stripe.Customer> = {
describe('GetSubscription #novu-v2', async () => {
let session: UserSession;

const eeBilling = require('@novu/ee-billing');
if (!eeBilling) {
throw new Error('ee-billing does not exist');
}

const { GetPlatformNotificationUsageCommand, GetSubscription, GetSubscriptionCommand } = eeBilling;

const communityOrganizationRepo = {
findById: () =>
Promise.resolve({
Expand Down
Loading
Loading