Skip to content

Latest commit

 

History

History
307 lines (231 loc) · 11.9 KB

AGENTS.md

File metadata and controls

307 lines (231 loc) · 11.9 KB

AGENTS.md

Rules for LLM agents working with @adapty/core repository.

Project Overview

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")

Project Structure

.
├── 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

Configuration Files

IMPORTANT: Due to "type": "module" in package.json, all CommonJS config files MUST use .cjs extension:

  • .eslintrc.cjs (NOT .eslintrc.js)
  • jest.config.cjs (NOT jest.config.js)

ESM config files use .ts or .mts extension:

  • tsdown.config.ts

TypeScript Configuration

Strict Mode: Enabled with maximum type safety.

Key compiler options:

  • strict: true - All strict type-checking enabled
  • noImplicitAny: true - No implicit any types allowed
  • strictNullChecks: true - Null/undefined checking
  • noUnusedLocals: true - Report unused variables
  • noUnusedParameters: true - Report unused parameters
  • noImplicitReturns: true - All code paths must return
  • noFallthroughCasesInSwitch: true - No fallthrough in switches
  • noUncheckedIndexedAccess: true - Index access includes undefined
  • noImplicitOverride: true - Require override keyword
  • noPropertyAccessFromIndexSignature: true - Use brackets for index access
  • allowUnreachableCode: false - Report unreachable code

Target: ES2020
Module: ESNext
Module Resolution: bundler

Architecture: Platform DI

This repo uses platform dependency injection to keep @adapty/core platform-agnostic.

Platform Adapters

  • Interface: src/adapters/interfaces.ts
  • Core rule: Coders must depend on adapters, not on platform globals.
  • OS property: use Platform.OS (capitalized) on IPlatformAdapter.
  • No select: platform-specific selection should be done using Platform.OS, not Platform.select.

Default Adapters

  • Defaults: src/adapters/defaults.ts
  • DefaultPlatformAdapter returns 'unknown' as the default OS value.
  • DefaultLoggerAdapter is a noop logger for core usage.

Coder Factory

  • Factory: src/coders/factory.ts
  • Use CoderFactory to create coders with injected dependencies:
    • IPlatformAdapter for platform OS
    • ISdkMetadataAdapter for sdkName/sdkVersion
    • ILoggerAdapter for logging
  • Instantiation rule: do not instantiate *Coder via new outside src/coders/**. In SDK / bridge / feature code, use CoderFactory as the single entry point.
  • When adding a new coder: add a corresponding create*Coder() method to CoderFactory and use it instead of direct construction.

Testing Guidance

  • Tests should create coders with mock adapters, not mutate Platform globals.
  • Prefer inline adapters: { OS: 'ios' } / { OS: 'android' }.

Usage in Downstream SDKs

Downstream SDKs (React Native, Capacitor) should:

  1. Implement platform adapters:

    • Create classes implementing IPlatformAdapter and ISdkMetadataAdapter
    • Platform adapter wraps platform-specific API (e.g., Platform.OS from react-native)
    • SDK metadata adapter provides sdkName and sdkVersion
  2. Create singleton CoderFactory:

    • Initialize with platform-specific adapters
    • Use factory methods instead of direct coder instantiation
  3. Import shared types:

    • Use exported types from @adapty/core (e.g., ActivateParamsInput)
    • Avoid duplicating types between core and SDKs

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();
  // ...
}

Build Output

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.

Local Development Configuration

Custom Build Output Directory

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):

  1. 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
  2. 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 build over yarn 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.ts reads BUILD_OUT_DIR from .env via dotenv package
  • If not set, defaults to ./dist
  • When BUILD_OUT_DIR is set, a build:done hook also copies package.json one level up (into node_modules/@adapty/core/) so the downstream SDK sees the current exports/version. For normal ./dist builds this copy is skipped.
  • See .env.example for 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

Versioning

  • 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
  • Production releases: Semantic versioning (e.g., 1.0.0, 1.0.1)
  • Downstream SDKs should pin exact dev version when testing CI

CI Publishing Policy

  • Registry: npmjs.com (public via publishConfig.access: "public")
  • Workflow: .github/workflows/publish.yml - Unified workflow for both dev and production releases
    • Auto-detects dev builds on push to the dev branch; production/beta are manual only (workflow dispatch)
    • Supports manual workflow dispatch with release-type selection
    • Includes dry-run mode for testing

Dev Builds (dev branch)

  • Trigger: Push to dev branch (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

Beta Releases (manual)

  • Trigger: Manual workflow dispatch only (release-type: beta)
  • Version: Uses version from package.json directly (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

Production Releases (manual)

  • Trigger: Manual workflow dispatch only (release-type: prod (latest)). Pushing a git tag does NOT publish.
  • Version: Uses version from package.json directly (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) under latest — bump package.json to a stable version first, or use the beta release-type
  • Command: npm publish --provenance --ignore-scripts
  • Install: npm install @adapty/core (installs latest)
  • Workflow: Bump the version in package.json and commit it, then run the workflow manually (Actions → "Publish to npm" → Run workflow) from the target ref with release-type prod (latest) and dry-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 & Tooling

Primary Tools

  • 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

Code Quality Rules

Linting

  • ESLint configured with @typescript-eslint plugin
  • Extends eslint-config-prettier (no conflicts with Prettier)
  • Rule: no-console: error (console.log forbidden, except in scripts/)

Formatting

  • Prettier for all .ts files
  • Semi: true
  • Single quotes: true
  • Trailing comma: all
  • Arrow parens: always

Testing

  • Jest with ts-jest preset
  • Test files: *.test.ts or __tests__/**/*.test.ts
  • Coverage collected from src/**/*.ts

Development Workflow

  1. Setup: yarn install
  2. Develop: Edit files in src/
  3. Check types: yarn tsc
  4. Lint: yarn lint
  5. Format: yarn format
  6. Test: yarn test
  7. Build: yarn build
  8. Verify: Check dist/ output

Common Tasks for LLM Agents

Adding New Source Files

  1. Create .ts file in src/
  2. Export from src/index.ts if public API
  3. Run yarn tsc to check types
  4. Run yarn lint to check style
  5. Run yarn build to verify output

Updating Dependencies

  • Use exact versions for critical deps (TypeScript, tsdown)
  • Use caret ranges (^) for dev tools
  • Always run yarn install after updating package.json
  • Verify with yarn lint && yarn tsc && yarn test && yarn build

Fixing Type Errors

  • No any types allowed (noImplicitAny: true)
  • Use unknown for truly unknown types
  • Provide explicit return types for public APIs
  • Mark unused params with underscore prefix: _unusedParam

Config File Issues

  • If seeing "module is not defined" error → file needs .cjs extension
  • If seeing "require is not defined" error → file needs .mjs or .ts extension
  • Remember: package.json has "type": "module" so all .js files are ESM

Do NOT

  • Do NOT publish raw TypeScript files (only publish dist/)
  • Do NOT use any type (strict mode prevents this)
  • Do NOT use console.log in library code (ESLint will error)
  • Do NOT change TypeScript version without testing
  • Do NOT rename .cjs config files to .js
  • Do NOT create builds that depend on build-on-install (tsc on user machine)

Reference Files

  • README.md - User-facing documentation