Rules for LLM agents working with @adapty/core repository.
This repo contains @adapty/core, platform-agnostic JS/TS code shared by Adapty React Native and Capacitor SDKs. The package is public on npm but treated as an internal dependency (dev builds are mainly for SDK CI).
Package Name: @adapty/core
Language: TypeScript (strict mode)
Module System: ESM (package.json has "type": "module")
.
├── src/ # Source TypeScript files
│ └── index.ts # Main entry point
├── dist/ # Build output (gitignored, generated by tsdown)
├── package.json # Package configuration
├── tsconfig.json # TypeScript configuration (strict mode)
├── tsdown.config.ts # Build tool configuration
├── .eslintrc.cjs # ESLint config (CommonJS - note .cjs extension!)
├── .prettierrc.json # Prettier config
├── jest.config.cjs # Jest config (CommonJS - note .cjs extension!)
├── .gitignore # Git ignore rules
├── .npmignore # NPM publish filter
├── README.md # Documentation
├── AGENTS.md # This file
├── CLAUDE.md # Points to this file
└── LICENSE # MIT license
IMPORTANT: Due to "type": "module" in package.json, all CommonJS config files MUST use .cjs extension:
.eslintrc.cjs(NOT.eslintrc.js)jest.config.cjs(NOTjest.config.js)
ESM config files use .ts or .mts extension:
tsdown.config.ts
Strict Mode: Enabled with maximum type safety.
Key compiler options:
strict: true- All strict type-checking enablednoImplicitAny: true- No implicit any types allowedstrictNullChecks: true- Null/undefined checkingnoUnusedLocals: true- Report unused variablesnoUnusedParameters: true- Report unused parametersnoImplicitReturns: true- All code paths must returnnoFallthroughCasesInSwitch: true- No fallthrough in switchesnoUncheckedIndexedAccess: true- Index access includes undefinednoImplicitOverride: true- Require override keywordnoPropertyAccessFromIndexSignature: true- Use brackets for index accessallowUnreachableCode: false- Report unreachable code
Target: ES2020
Module: ESNext
Module Resolution: bundler
This repo uses platform dependency injection to keep @adapty/core platform-agnostic.
- Interface:
src/adapters/interfaces.ts - Core rule: Coders must depend on adapters, not on platform globals.
- OS property: use
Platform.OS(capitalized) onIPlatformAdapter. - No
select: platform-specific selection should be done usingPlatform.OS, notPlatform.select.
- Defaults:
src/adapters/defaults.ts - DefaultPlatformAdapter returns
'unknown'as the default OS value. - DefaultLoggerAdapter is a noop logger for core usage.
- Factory:
src/coders/factory.ts - Use
CoderFactoryto create coders with injected dependencies:IPlatformAdapterfor platform OSISdkMetadataAdapterfor sdkName/sdkVersionILoggerAdapterfor logging
- Instantiation rule: do not instantiate
*Codervianewoutsidesrc/coders/**. In SDK / bridge / feature code, useCoderFactoryas the single entry point. - When adding a new coder: add a corresponding
create*Coder()method toCoderFactoryand use it instead of direct construction.
- Tests should create coders with mock adapters, not mutate
Platformglobals. - Prefer inline adapters:
{ OS: 'ios' }/{ OS: 'android' }.
Downstream SDKs (React Native, Capacitor) should:
-
Implement platform adapters:
- Create classes implementing
IPlatformAdapterandISdkMetadataAdapter - Platform adapter wraps platform-specific API (e.g.,
Platform.OSfrom react-native) - SDK metadata adapter provides
sdkNameandsdkVersion
- Create classes implementing
-
Create singleton CoderFactory:
- Initialize with platform-specific adapters
- Use factory methods instead of direct coder instantiation
-
Import shared types:
- Use exported types from
@adapty/core(e.g.,ActivateParamsInput) - Avoid duplicating types between core and SDKs
- Use exported types from
Example:
// In React Native SDK
import { CoderFactory, type ActivateParamsInput } from '@adapty/core';
import { ReactNativePlatformAdapter, ReactNativeSdkMetadataAdapter } from './adapters';
export const coderFactory = new CoderFactory({
platform: new ReactNativePlatformAdapter(),
sdkMetadata: new ReactNativeSdkMetadataAdapter(),
});
// Use core types in method signatures
public async activate(apiKey: string, params: ActivateParamsInput = {}): Promise<void> {
const coder = coderFactory.createConfigurationCoder();
// ...
}Built with tsdown (powered by Rolldown). Outputs multiple formats:
- ESM:
dist/index.mjs(+ source map) - CommonJS:
dist/index.cjs - TypeScript Declarations:
dist/index.d.mts(for ESM imports)dist/index.d.cts(for CJS requires)- Declaration maps for both
Build command: yarn build
Published files (via files in package.json):
- Only
dist/folder - Exclude test files (
*.test.*,*.spec.*)
Source TypeScript files (src/) are NOT published - only compiled output.
When you change core and need to apply it in a downstream SDK (React Native, Capacitor), build directly into that SDK's node_modules/@adapty/core/dist. The output directory is controlled by the BUILD_OUT_DIR environment variable, read from a local .env file.
Recommended flow (one-shot build):
- Set the output path in
.env(already gitignored). The paths are pre-filled — just uncomment the target you need:# React Native SDK BUILD_OUT_DIR=../AdaptySDK-React-Native/node_modules/@adapty/core/dist # Capacitor SDK # BUILD_OUT_DIR=../AdaptySDK-Capacitor/node_modules/@adapty/core/dist
- Run a one-shot build:
yarn build
This builds core once and copies the output straight into the downstream SDK. Re-run yarn build after each change.
Agents: prefer the one-shot
yarn buildoveryarn build:watch. Watch mode is long-running and meant to be started by the user manually, not by an agent.
How it works:
tsdown.config.tsreadsBUILD_OUT_DIRfrom.envviadotenvpackage- If not set, defaults to
./dist - When
BUILD_OUT_DIRis set, abuild:donehook also copiespackage.jsonone level up (intonode_modules/@adapty/core/) so the downstream SDK sees the currentexports/version. For normal./distbuilds this copy is skipped. - See
.env.examplefor configuration examples
You can also override the path inline for a single build without editing .env:
BUILD_OUT_DIR=../AdaptySDK-React-Native/node_modules/@adapty/core/dist yarn build- Dev builds:
<base-version>-dev.<full-sha>(40-char commit SHA)- Example:
0.0.0-dev.abc123...xyz789 - Base version is read from
package.json(e.g.,0.0.0) - CI adds
-dev.<sha>suffix before publishing
- Example:
- Production releases: Semantic versioning (e.g.,
1.0.0,1.0.1) - Downstream SDKs should pin exact dev version when testing CI
- Registry: npmjs.com (public via
publishConfig.access: "public") - Workflow:
.github/workflows/publish.yml- Unified workflow for both dev and production releases- Auto-detects
devbuilds on push to thedevbranch; production/beta are manual only (workflow dispatch) - Supports manual workflow dispatch with release-type selection
- Includes dry-run mode for testing
- Auto-detects
- Trigger: Push to
devbranch (or manual workflow dispatch with release-type: dev) - Version: Automatic
-dev.<commit-sha>suffix (40-char full SHA) - Dist-tag:
dev - Command:
npm publish --provenance --tag dev --ignore-scripts - Install:
npm install @adapty/core@dev
- Trigger: Manual workflow dispatch only (release-type:
beta) - Version: Uses version from
package.jsondirectly (typically a prerelease, e.g.4.0.0-beta.1) - Dist-tag:
beta - Command:
npm publish --provenance --tag beta --ignore-scripts - Install:
npm install @adapty/core@beta
- Trigger: Manual workflow dispatch only (release-type:
prod (latest)). Pushing a git tag does NOT publish. - Version: Uses version from
package.jsondirectly (from the branch/tag ref selected when running the workflow) - Dist-tag:
latest(default) - Guard: the workflow refuses to publish a prerelease version (anything containing
-, e.g.4.0.0-beta.1) underlatest— bumppackage.jsonto a stable version first, or use thebetarelease-type - Command:
npm publish --provenance --ignore-scripts - Install:
npm install @adapty/core(installs latest) - Workflow: Bump the version in
package.jsonand commit it, then run the workflow manually (Actions → "Publish to npm" → Run workflow) from the target ref with release-typeprod (latest)anddry-run=false
Important: The --tag dev flag ensures dev builds are NOT installed by default. Only production releases (published with latest tag) are installed when users run npm install @adapty/core without specifying a version.
- Package Manager: Yarn (for install/build/scripts)
- Publish Tool: npm (for publishing to registry)
- Build Tool: tsdown v0.9.x
- TypeScript: v5.9.3
- Linter: ESLint v8.57.x + @typescript-eslint v8.21.x
- Formatter: Prettier v3.x
- Test Runner: Jest v29.3.x + ts-jest v29.x
- ESLint configured with
@typescript-eslintplugin - Extends
eslint-config-prettier(no conflicts with Prettier) - Rule:
no-console: error(console.log forbidden, except in scripts/)
- Prettier for all
.tsfiles - Semi: true
- Single quotes: true
- Trailing comma: all
- Arrow parens: always
- Jest with ts-jest preset
- Test files:
*.test.tsor__tests__/**/*.test.ts - Coverage collected from
src/**/*.ts
- Setup:
yarn install - Develop: Edit files in
src/ - Check types:
yarn tsc - Lint:
yarn lint - Format:
yarn format - Test:
yarn test - Build:
yarn build - Verify: Check
dist/output
- Create
.tsfile insrc/ - Export from
src/index.tsif public API - Run
yarn tscto check types - Run
yarn lintto check style - Run
yarn buildto verify output
- Use exact versions for critical deps (TypeScript, tsdown)
- Use caret ranges (
^) for dev tools - Always run
yarn installafter updating package.json - Verify with
yarn lint && yarn tsc && yarn test && yarn build
- No
anytypes allowed (noImplicitAny: true) - Use
unknownfor truly unknown types - Provide explicit return types for public APIs
- Mark unused params with underscore prefix:
_unusedParam
- If seeing "module is not defined" error → file needs
.cjsextension - If seeing "require is not defined" error → file needs
.mjsor.tsextension - Remember: package.json has
"type": "module"so all.jsfiles are ESM
- Do NOT publish raw TypeScript files (only publish
dist/) - Do NOT use
anytype (strict mode prevents this) - Do NOT use
console.login library code (ESLint will error) - Do NOT change TypeScript version without testing
- Do NOT rename
.cjsconfig files to.js - Do NOT create builds that depend on build-on-install (tsc on user machine)
README.md- User-facing documentation