diff --git a/packages/core/package.json b/packages/core/package.json index f41f42b9..5f18ac9d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -52,7 +52,6 @@ "typescript": "^5.9.3" }, "dependencies": { - "@datadog/js-core": "0.0.3", "spark-md5": "^3.0.2" } } diff --git a/packages/core/src/configuration/configuration.ts b/packages/core/src/configuration/configuration.ts index 47fb6a81..0771f1e8 100644 --- a/packages/core/src/configuration/configuration.ts +++ b/packages/core/src/configuration/configuration.ts @@ -1,5 +1,5 @@ -import type { TimeStamp } from '@datadog/js-core/time' import type { EvaluationContext, FlagValueType, JsonValue, ResolutionReason } from '@openfeature/core' +import type { TimeStamp } from '../time' /** * Internal flags configuration for DatadogProvider. diff --git a/packages/core/src/configuration/exposureEvent.types.ts b/packages/core/src/configuration/exposureEvent.types.ts index 85afbc54..500c9843 100644 --- a/packages/core/src/configuration/exposureEvent.types.ts +++ b/packages/core/src/configuration/exposureEvent.types.ts @@ -1,5 +1,5 @@ -import type { TimeStamp } from '@datadog/js-core/time' import type { EvaluationContext } from '@openfeature/core' +import type { TimeStamp } from '../time' export interface ExposureEvent { allocation: { diff --git a/packages/core/src/configuration/flagEvaluationAggregator.ts b/packages/core/src/configuration/flagEvaluationAggregator.ts index 7a65ccd8..fba28568 100644 --- a/packages/core/src/configuration/flagEvaluationAggregator.ts +++ b/packages/core/src/configuration/flagEvaluationAggregator.ts @@ -1,7 +1,6 @@ -import type { TimeStamp } from '@datadog/js-core/time' -import { timeStampNow } from '@datadog/js-core/time' import type { EvaluationContext, EvaluationContextValue, EvaluationDetails, FlagValue } from '@openfeature/core' import { getMD5Hash } from '../obfuscation' +import { type TimeStamp, timeStampNow } from '../time' import { createFlagEvaluationEvent } from './flagEvaluationEvent' import type { FlagEvaluationEvent } from './flagEvaluationEvent.types' diff --git a/packages/core/src/configuration/flagEvaluationEvent.ts b/packages/core/src/configuration/flagEvaluationEvent.ts index 916ed8b3..1ef1dedd 100644 --- a/packages/core/src/configuration/flagEvaluationEvent.ts +++ b/packages/core/src/configuration/flagEvaluationEvent.ts @@ -1,5 +1,5 @@ -import type { TimeStamp } from '@datadog/js-core/time' import type { EvaluationContextValue } from '@openfeature/core' +import type { TimeStamp } from '../time' import type { FlagEvaluationEvent } from './flagEvaluationEvent.types' interface FlagEvaluationAggregationData { diff --git a/packages/core/src/configuration/flagEvaluationEvent.types.ts b/packages/core/src/configuration/flagEvaluationEvent.types.ts index 863fd457..cfbdfb23 100644 --- a/packages/core/src/configuration/flagEvaluationEvent.types.ts +++ b/packages/core/src/configuration/flagEvaluationEvent.types.ts @@ -1,5 +1,5 @@ -import type { TimeStamp } from '@datadog/js-core/time' import type { EvaluationContextValue } from '@openfeature/core' +import type { TimeStamp } from '../time' export interface FlagEvaluationEvent { flag: { diff --git a/packages/core/src/configuration/wire.ts b/packages/core/src/configuration/wire.ts index 615f8922..4bc2c1b3 100644 --- a/packages/core/src/configuration/wire.ts +++ b/packages/core/src/configuration/wire.ts @@ -1,5 +1,5 @@ -import type { TimeStamp } from '@datadog/js-core/time' import type { EvaluationContext } from '@openfeature/core' +import type { TimeStamp } from '../time' import type { FlagsConfiguration } from './configuration' type ConfigurationWire = { diff --git a/packages/core/src/evaluation/evaluateForSubject.ts b/packages/core/src/evaluation/evaluateForSubject.ts index 0fbd66b6..6e4dc200 100644 --- a/packages/core/src/evaluation/evaluateForSubject.ts +++ b/packages/core/src/evaluation/evaluateForSubject.ts @@ -1,7 +1,6 @@ -import type { TimeStamp } from '@datadog/js-core/time' -import { timeStampNow } from '@datadog/js-core/time' import type { ErrorCode, EvaluationContext, FlagValueType, Logger, ResolutionDetails } from '@openfeature/core' import type { FlagTypeToValue, PrecomputedFlagMetadata } from '../configuration' +import { type TimeStamp, timeStampNow } from '../time' import { TargetingKeyMissingError } from './errors' import { createEvaluationTimestampMetadata } from './evaluationMetadata' import { matchesShard } from './matchesShard' diff --git a/packages/core/src/evaluation/evaluation.ts b/packages/core/src/evaluation/evaluation.ts index ff8e8be9..5ddd83ef 100644 --- a/packages/core/src/evaluation/evaluation.ts +++ b/packages/core/src/evaluation/evaluation.ts @@ -1,6 +1,6 @@ -import { timeStampNow } from '@datadog/js-core/time' import type { ErrorCode, EvaluationContext, FlagValueType, Logger, ResolutionDetails } from '@openfeature/core' import type { FlagTypeToValue } from '../configuration' +import { timeStampNow } from '../time' import { TargetingKeyMissingError } from './errors' import { evaluateForSubject } from './evaluateForSubject' import { createEvaluationTimestampMetadata } from './evaluationMetadata' diff --git a/packages/core/src/evaluation/evaluationMetadata.ts b/packages/core/src/evaluation/evaluationMetadata.ts index b42a923e..d0be97d7 100644 --- a/packages/core/src/evaluation/evaluationMetadata.ts +++ b/packages/core/src/evaluation/evaluationMetadata.ts @@ -1,5 +1,5 @@ -import type { TimeStamp } from '@datadog/js-core/time' import type { PrecomputedFlagMetadata } from '../configuration' +import type { TimeStamp } from '../time' export function createEvaluationTimestampMetadata(evaluationTimestampMs: TimeStamp): PrecomputedFlagMetadata { return { __dd_eval_timestamp_ms: evaluationTimestampMs } as PrecomputedFlagMetadata diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 56deefff..018d0574 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,6 +2,7 @@ export * from './cache' export * from './configuration' export * from './evaluation' export * from './obfuscation' +export * from './time' // Build environment placeholder for testing const _SDK_VERSION = __BUILD_ENV__SDK_VERSION__ diff --git a/packages/core/src/time.ts b/packages/core/src/time.ts new file mode 100644 index 00000000..d923b141 --- /dev/null +++ b/packages/core/src/time.ts @@ -0,0 +1,6 @@ +/** Unix epoch timestamp in milliseconds. */ +export type TimeStamp = number & { t: 'Epoch time' } + +export function timeStampNow(): TimeStamp { + return new Date().getTime() as TimeStamp +} diff --git a/packages/core/test/configuration/flagEvaluationEvent.spec.ts b/packages/core/test/configuration/flagEvaluationEvent.spec.ts index 4cde63e7..08d42f38 100644 --- a/packages/core/test/configuration/flagEvaluationEvent.spec.ts +++ b/packages/core/test/configuration/flagEvaluationEvent.spec.ts @@ -1,5 +1,5 @@ -import type { TimeStamp } from '@datadog/js-core/time' import { createFlagEvaluationEvent } from '../../src/configuration/flagEvaluationEvent' +import type { TimeStamp } from '../../src/time' describe('createFlagEvaluationEvent', () => { it('should include targeting_key when it is a non-empty string', () => { diff --git a/packages/core/test/evaluation/evaluateForSubject.spec.ts b/packages/core/test/evaluation/evaluateForSubject.spec.ts index 919ed5a6..6ddc9d76 100644 --- a/packages/core/test/evaluation/evaluateForSubject.spec.ts +++ b/packages/core/test/evaluation/evaluateForSubject.spec.ts @@ -1,6 +1,6 @@ -import type { TimeStamp } from '@datadog/js-core/time' import type { EvaluationContext, Logger } from '@openfeature/core' import { evaluateForSubject, type Flag } from '../../src/evaluation' +import type { TimeStamp } from '../../src/time' describe('evaluateForSubject', () => { let logger: Logger diff --git a/packages/node-server/package.json b/packages/node-server/package.json index 8d9436fd..ffc0847c 100644 --- a/packages/node-server/package.json +++ b/packages/node-server/package.json @@ -36,8 +36,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@datadog/flagging-core": "2.0.1", - "@datadog/js-core": "0.0.3" + "@datadog/flagging-core": "2.0.1" }, "peerDependencies": { "@openfeature/server-sdk": ">=1.15.1" diff --git a/packages/node-server/src/provider.ts b/packages/node-server/src/provider.ts index 886d3f5d..380fee59 100644 --- a/packages/node-server/src/provider.ts +++ b/packages/node-server/src/provider.ts @@ -4,8 +4,8 @@ import { createExposureEvent, type ExposureEvent, LRUInMemoryAssignmentCache, + timeStampNow, } from '@datadog/flagging-core' -import { timeStampNow } from '@datadog/js-core/time' import type { EvaluationContext } from '@openfeature/core' import type { EvaluationDetails, diff --git a/scripts/assert-node-package-purity.js b/scripts/assert-node-package-purity.js new file mode 100644 index 00000000..e1df8b60 --- /dev/null +++ b/scripts/assert-node-package-purity.js @@ -0,0 +1,65 @@ +'use strict' + +const fs = require('node:fs') +const path = require('node:path') + +const forbiddenPackageNames = [/^@datadog\/browser-/, /^@datadog\/js-core$/, /^@datadog\/openfeature-browser$/] +const browserSdkRepository = /github\.com[/:]DataDog\/browser-sdk(?:\.git)?$/i + +const found = new Set() +const visited = new Set() + +function inspectPackage(packageDirectory) { + let realDirectory + try { + realDirectory = fs.realpathSync(packageDirectory) + } catch { + return + } + + if (visited.has(realDirectory)) return + visited.add(realDirectory) + + const manifestPath = path.join(realDirectory, 'package.json') + if (fs.existsSync(manifestPath)) { + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) + const repository = typeof manifest.repository === 'string' ? manifest.repository : manifest.repository?.url + const isBrowserSdkDependency = + forbiddenPackageNames.some((pattern) => pattern.test(manifest.name)) || + browserSdkRepository.test(repository ?? '') + if (isBrowserSdkDependency) { + found.add(manifest.name) + } + } + + inspectNodeModules(path.join(realDirectory, 'node_modules')) +} + +function inspectNodeModules(nodeModulesDirectory) { + if (!fs.existsSync(nodeModulesDirectory)) return + + for (const entry of fs.readdirSync(nodeModulesDirectory, { withFileTypes: true })) { + if (entry.name.startsWith('.')) continue + + const entryPath = path.join(nodeModulesDirectory, entry.name) + if (entry.name.startsWith('@')) { + for (const scopedEntry of fs.readdirSync(entryPath, { withFileTypes: true })) { + if (scopedEntry.isDirectory() || scopedEntry.isSymbolicLink()) { + inspectPackage(path.join(entryPath, scopedEntry.name)) + } + } + } else if (entry.isDirectory() || entry.isSymbolicLink()) { + inspectPackage(entryPath) + } + } +} + +inspectNodeModules(path.resolve(process.argv[2] ?? 'node_modules')) + +if (found.size > 0) { + console.error('ERROR: Node packages must not install browser SDK dependencies') + for (const packageName of [...found].sort()) console.error(packageName) + process.exit(1) +} + +console.log('Verified Node packages do not install browser SDK dependencies') diff --git a/scripts/test-node-package-install.sh b/scripts/test-node-package-install.sh index a2a97908..2e2e700f 100755 --- a/scripts/test-node-package-install.sh +++ b/scripts/test-node-package-install.sh @@ -125,6 +125,8 @@ if [ "$WITH_OPENFEATURE" = true ]; then fi fi +node "$REPO_ROOT/scripts/assert-node-package-purity.js" node_modules + # Run tests echo "" echo "Running tests..." diff --git a/yarn.lock b/yarn.lock index 6c4a57ed..ec89a87f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -663,7 +663,6 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/flagging-core@workspace:packages/core" dependencies: - "@datadog/js-core": "npm:0.0.3" "@openfeature/core": "npm:^1.9.2" "@types/jest": "npm:^30.0.0" "@types/node": "npm:^18.0.0" @@ -741,7 +740,6 @@ __metadata: resolution: "@datadog/openfeature-node-server@workspace:packages/node-server" dependencies: "@datadog/flagging-core": "npm:2.0.1" - "@datadog/js-core": "npm:0.0.3" "@openfeature/core": "npm:1.9.2" "@openfeature/server-sdk": "npm:1.20.2" "@types/jest": "npm:^30.0.0"