{"_id":"@armcompany/armfly-core","_rev":"4-b463d21391e23f05452adc4514b14ec6","name":"@armcompany/armfly-core","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@armcompany/armfly-core","version":"0.1.0","_id":"@armcompany/armfly-core@0.1.0","maintainers":[{"name":"armcompany","email":"dev@armbuilds.com"}],"homepage":"https://github.com/armcompany/storefly#readme","bugs":{"url":"https://github.com/armcompany/storefly/issues"},"dist":{"shasum":"a6c5bbed109cf23fdad15cb6842ef32856c63cad","tarball":"https://registry.npmjs.org/@armcompany/armfly-core/-/armfly-core-0.1.0.tgz","fileCount":45,"integrity":"sha512-4IOvIC8B76j9EgOmY9ixgVPrkocdn/06S3micGOV+Tc8VIIOSZy5zMrv7/v03r1Mya9gFgr9YpOif2tUjDJtLQ==","signatures":[{"sig":"MEUCIQCW/u2qR10jL4zA6hAxgA2yGqupXVy50zoR+o28exzpYgIgX4sPBVm6xacPs8DwgDKV5sAQrfIYL7Pg7/5iWrLGhTg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118489},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=18.17"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"0dc8890334dff9c27fd3ddef2e863c01b1ce3eb5","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"tsc","prepack":"npm run build"},"_npmUser":{"name":"armcompany","email":"dev@armbuilds.com"},"repository":{"url":"git+https://github.com/armcompany/storefly.git","type":"git","directory":"packages/core"},"_npmVersion":"11.12.1","description":"Core release automation engine for ARM FLY","directories":{},"_nodeVersion":"25.9.0","dependencies":{"zod":"^3.23.8","jose":"^5.9.6","yaml":"^2.6.0","fflate":"^0.8.2","@resvg/resvg-js":"^2.6.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/armfly-core_0.1.0_1784832460445_0.46811256908961996","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@armcompany/armfly-core","version":"0.1.1","_id":"@armcompany/armfly-core@0.1.1","maintainers":[{"name":"armdevelop","email":"dev@armbuilds.com"}],"homepage":"https://github.com/armcompany/armfly#readme","bugs":{"url":"https://github.com/armcompany/armfly/issues"},"dist":{"shasum":"cd133861a3b4e67b59f6202a93bad1cb23802027","tarball":"https://registry.npmjs.org/@armcompany/armfly-core/-/armfly-core-0.1.1.tgz","fileCount":46,"integrity":"sha512-NS2xG9erfadqBHlo+yQ9ERqzNwIQ+NBdCSP+4bWbOLje8+HDHJ5istMpzpG1hXPfZ2GRedWEc+3JevcF6qNBEQ==","signatures":[{"sig":"MEQCIAUohtaTVMFxp/IvzNx0dGdd+2kS6lov36kfcbVeVWdVAiAGU8Sh56FBXDjDkvZ1Mp9uS6IIzUUJoSyxvEKAZy8afA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":129284},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=18.17"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"48a61e818a4fac449007af5af562655d76e91dd0","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"tsc","prepack":"npm run build"},"_npmUser":{"name":"armdevelop","email":"dev@armbuilds.com"},"repository":{"url":"git+https://github.com/armcompany/armfly.git","type":"git","directory":"packages/core"},"_npmVersion":"11.18.0","description":"Core release automation engine for ARM FLY","directories":{},"_nodeVersion":"20.19.4","dependencies":{"zod":"^3.23.8","jose":"^5.9.6","yaml":"^2.6.0","fflate":"^0.8.2","@resvg/resvg-js":"^2.6.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/armfly-core_0.1.1_1786495350487_0.01844178959472309","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@armcompany/armfly-core","version":"0.1.2","description":"Core release automation engine for ARM FLY","type":"module","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"repository":{"type":"git","url":"git+https://github.com/armcompany/armfly.git","directory":"packages/core"},"homepage":"https://github.com/armcompany/armfly#readme","bugs":{"url":"https://github.com/armcompany/armfly/issues"},"publishConfig":{"access":"public"},"engines":{"node":">=18.17"},"scripts":{"build":"tsc","prepack":"npm run build","test":"vitest run","lint":"tsc --noEmit"},"dependencies":{"@resvg/resvg-js":"^2.6.2","fflate":"^0.8.2","jose":"^5.9.6","yaml":"^2.6.0","zod":"^3.23.8"},"gitHead":"c31e728226ff9f27f310e15ce0d2b7eeafa4ca26","_id":"@armcompany/armfly-core@0.1.2","_nodeVersion":"20.19.4","_npmVersion":"11.18.0","dist":{"integrity":"sha512-qcv7Ly3KA2qt07IDAHNKFCbHqNLllziI9mK+I9Edc8htIbRtZJ8iuvbAlglYOksMGbIIJ2gX3tQNSQq5QsHE6A==","shasum":"106168247ecbb4934b019bbeb50d3cb9ab6080c1","tarball":"https://registry.npmjs.org/@armcompany/armfly-core/-/armfly-core-0.1.2.tgz","fileCount":48,"unpackedSize":130386,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD3dBTtHa8aNKlM1saNu9u7Nj9SetEvwYgukVSrThFv+gIgMVTnxSUkf/XvdwdDqbNMSGGLbJXMl1P67Mh1R6+z5GQ="}]},"_npmUser":{"name":"armdevelop","email":"dev@armbuilds.com"},"directories":{},"maintainers":[{"name":"armdevelop","email":"dev@armbuilds.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/armfly-core_0.1.2_1786904988636_0.2819876886849988"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-23T18:47:40.241Z","modified":"2026-08-16T18:29:48.948Z","0.1.0":"2026-07-23T18:47:40.593Z","0.1.1":"2026-08-12T00:42:30.632Z","0.1.2":"2026-08-16T18:29:48.772Z"},"bugs":{"url":"https://github.com/armcompany/armfly/issues"},"homepage":"https://github.com/armcompany/armfly#readme","repository":{"type":"git","url":"git+https://github.com/armcompany/armfly.git","directory":"packages/core"},"description":"Core release automation engine for ARM FLY","maintainers":[{"name":"armdevelop","email":"dev@armbuilds.com"}],"readme":"# @armcompany/armfly-core\n\nCore release automation engine for ARM FLY — the shared library behind the\n[`armfly` CLI](https://github.com/armcompany/armfly), the ARM FLY API and the\nworker. It generates store metadata, builds, uploads and submits releases to the\n**Apple App Store** and **Google Play**, generates store screenshots and promo\nvideos, analyzes app artifacts, and coordinates it all through a resumable,\nidempotent step pipeline.\n\nRequires Node.js **>= 18.17**.\n\n## Install\n\n```sh\nnpm install @armcompany/armfly-core\n```\n\nTypeScript types are bundled (`dist/index.d.ts`).\n\n## What you get\n\nThe package exports everything from these modules:\n\n| Module | Purpose |\n| --- | --- |\n| `engine` | The resumable step pipeline (`runPipeline`, `Ctx`, `Step`) |\n| `steps` | Ready-made pipeline steps: metadata, build, publish (Apple/Google) |\n| `config` | `armfly.yaml` loading and validation (`loadConfig`, `parseConfig`) |\n| `state` | Persistent job journal (`Journal`) that makes deploys resumable |\n| `workspace` | Multi-project workspace loading for white-label/batch runs |\n| `batch` | Runs a command across many projects with stagger/concurrency |\n| `ai` | ASO metadata generation, localization and rejection diagnosis |\n| `analyze` | Feature detection from IPA/APK/AAB artifacts + marketing copy |\n| `assets` | Store-sized screenshot compositor and PNG rasterizer |\n| `video` | Promo-video timeline planning and ffmpeg encoding |\n| `stores` | App Store Connect and Google Play clients |\n| `logger`, `util` | Shared logger, `sh`, `sleep`, `fingerprint` |\n\n## Quick start\n\nThe simplest path is config-driven: write an `armfly.yaml` (see the full\n[config reference](#configuration)) and run the built-in pipeline:\n\n```ts\nimport {\n  loadConfig,\n  Journal,\n  detectPlatforms,\n  pipelineSteps,\n  runPipeline,\n  logger,\n  type Ctx,\n} from '@armcompany/armfly-core'\n\nconst cwd = process.cwd()\nconst cfg = loadConfig(cwd, 'armfly.yaml')\n\nconst ctx: Ctx = {\n  cfg,\n  cwd,\n  dryRun: true,        // plan only — touch nothing\n  submit: true,        // auto-submit for review at the end\n  platforms: detectPlatforms(cwd, cfg),\n  journal: Journal.load(cwd, /* persist */ true),\n  log: logger,\n  outputs: {},\n}\n\nawait runPipeline(pipelineSteps(), ctx)\n```\n\n`pipelineSteps()` returns the canonical sequence:\n\n1. `metadata` — generate localized ASO copy (base locale via AI, then translate)\n2. `build:ios` / `build:android` — run the build command or use a pre-built artifact\n3. `publish:apple` — upload IPA, create version, push localizations + screenshots, submit\n4. `publish:google` — upload AAB, set listings + images, assign track release\n\n### Resumability\n\n`Journal` lives in `.armfly/state.json`. Every step records a `fingerprint`; when\na step previously finished with the same fingerprint, it is skipped. Re-run\n`runPipeline` after any failure and only the failed steps execute again. Use\n`journal.reset()` or `journal.delete(name)` to force re-execution.\n\n### Dry runs\n\nSet `ctx.dryRun = true` to print what each step *would* do without touching any\nstore or running build commands. This is the recommended way to validate a config\nin CI before a real release.\n\n## Configuration\n\n`armfly.yaml` is a YAML file validated by `ConfigSchema` (zod). Values can\nreference environment variables with `${VAR}` interpolation.\n\n```yaml\napp:\n  name: My App\n  version: \"1.0.0\"\n  bundleId: com.example.myapp      # iOS (or stores.apple.bundleId)\n  packageName: com.example.myapp   # Android (or stores.google.packageName)\n  category: UTILITIES\n  audience: \"for freelancers\"       # feeds the AI metadata prompt\n  website: \"https://example.com\"\n\n# Optional — auto-detected from the project (ios/, *.xcodeproj, android/, pubspec.yaml)\n# platforms: [ios, android]\n\nbuild:\n  ios:\n    command: \"fastlane gym\"        # optional: omit when CI already produced the IPA\n    artifact: build/MyApp.ipa\n  android:\n    command: \"./gradlew bundleRelease\"\n    artifact: android/app/build/outputs/bundle/release/app-release.aab\n\nstores:\n  apple:\n    keyId: ${ASC_KEY_ID}           # App Store Connect API key id\n    issuerId: ${ASC_ISSUER_ID}\n    keyPath: ./secrets/AuthKey.p8\n  google:\n    serviceAccountPath: ./secrets/play-service-account.json\n    track: internal                # internal | alpha | beta | production\n\nmetadata:\n  locales: [en-US, pt-BR]          # base locale is generated, the rest are translated\n  baseLocale: en-US                # default: first in `locales`\n  releaseNotes: \"...\"              # optional; otherwise AI-generated\n  glossary: [ARM FLY]              # brand terms kept verbatim across translations\n\nai:\n  provider: template               # anthropic (needs OPENCODE_ZEN_API_KEY or ANTHROPIC_API_KEY) | template\n  model: claude-sonnet-4-6\n\nrelease:\n  submit: true                     # auto-submit for review after upload\n  stagedRollout: 0.2               # Play production staged rollout (20%)\n```\n\n`loadConfig(cwd, 'armfly.yaml')` throws a friendly, line-referenced validation\nerror on invalid config.\n\n## Programmatic API\n\n### Pipeline building blocks\n\n```ts\nimport { buildStep, metadataStep, publishAppleStep, publishGoogleStep, type Step } from '@armcompany/armfly-core'\n\n// Compose your own pipeline from the built-in steps:\nconst steps: Step[] = [metadataStep(), buildStep('ios'), publishAppleStep()]\n\n// Or run a single ad-hoc step inside the pipeline:\nconst custom: Step = {\n  name: 'notify',\n  fingerprint: () => 'v1',           // change to invalidate and re-run\n  retries: 2,                        // exponential backoff on failure\n  enabled: (ctx) => !ctx.dryRun,\n  async run(ctx) {\n    ctx.outputs['notify'] = { at: Date.now() }\n    return ctx.outputs['notify']\n  },\n}\n```\n\nStep contract:\n\n- `name` — stable identifier, used in the journal.\n- `fingerprint(ctx)` — a hash of everything that determines the step's output.\n  Same fingerprint + previous success ⇒ step skipped.\n- `run(ctx)` — do the work, return serializable output (stored in the journal).\n- `enabled?(ctx)` — skip silently when not applicable (e.g. platform not targeted).\n- `retries?` — extra attempts with `2s · 2^n` backoff.\n\n### Store clients (direct use)\n\n```ts\nimport { AppleStore, GooglePlay, uploadIpa } from '@armcompany/armfly-core'\n\n// App Store Connect\nconst apple = new AppleStore({ keyId, issuerId, keyPath: './AuthKey.p8' })\nconst app = await apple.findApp('com.example.myapp')\nawait uploadIpa('build/MyApp.ipa', appleAuth, cwd) // via xcrun altool\nconst ver = await apple.ensureVersion(app.id, '1.0.0')\nawait apple.setLocalization(ver.id, 'en-US', meta)\nawait apple.attachBuild(ver.id, buildId)\nawait apple.submitForReview(ver.id)\n\n// Google Play\nconst play = new GooglePlay('./play-service-account.json')\nconst result = await play.publish({\n  packageName: 'com.example.myapp',\n  aabPath: 'app-release.aab',\n  track: 'internal',\n  listings: [{ language: 'en-US', title, shortDescription, fullDescription }],\n  images: [{ language: 'en-US', imageType: 'phoneScreenshots', paths: ['01.png'] }],\n})\n// => { versionCode, editId }\nconst status = await play.trackStatus('com.example.myapp', 'internal')\n```\n\n### AI metadata, translation and rejection diagnosis\n\nAll AI functions follow the same contract: they use Claude (via the OpenCode\nZen gateway — `OPENCODE_ZEN_API_KEY`, falling back to `ANTHROPIC_API_KEY`, with\n`ai.provider: anthropic`) when available and otherwise\n**fall back to deterministic rules/templates** — an AI outage never blocks a\ndeploy. Each returns a `source: 'anthropic' | 'template'` field.\n\n```ts\nimport { generateMetadata, translateMetadata, diagnoseRejection } from '@armcompany/armfly-core'\n\nconst { meta, source } = await generateMetadata(cfg, 'en-US')        // base locale\nconst { meta: pt } = await translateMetadata(meta, 'en-US', 'pt-BR', cfg)\nconst { cause, suggestedFix, reviewerReply } = await diagnoseRejection(\n  { store: 'apple', guideline: '2.3.1', message: '...', appName: cfg.app.name },\n  cfg,\n)\n```\n\n`enforceLimits()` and `LIMITS` guarantee every string respects the stores' hard\ncharacter limits (title ≤ 30, subtitle ≤ 30, etc.).\n\n### App analysis (feature detection)\n\n```ts\nimport { analyzeApp, featureKeysFor, detectFeatures, marketingBullets } from '@armcompany/armfly-core'\n\nconst report = analyzeApp(cfg, cwd, logger)   // writes .armfly/analysis.json\nreport.features // e.g. [{ key: 'payments', signals: ['...Stripe.framework'], userFacing: true }]\nreport.bullets  // localized marketing bullets for the primary locale\n```\n\nDetection is pure: `detectFeatures(signals)` matches SDK/file signals found inside\nan IPA/APK/AAB (`listArtifactEntries`) against known feature rules (payments, chat,\nmarketplace, scheduling, notifications, geolocation, media, social, …).\n\n### Screenshot & video studio\n\n```ts\nimport { generateAssets, screenshotsYaml, generateVideo } from '@armcompany/armfly-core'\n\nconst generated = generateAssets(cfg, cwd, logger)      // renders store-sized PNGs\nconsole.log(screenshotsYaml(generated))                  // ready-to-paste screenshots: block\n\nconst result = generateVideo(cfg, cwd, logger)           // needs ffmpeg for encoding\n// result.video (mp4), result.frames, result.durationMs\n```\n\nWithout `ffmpeg`, `generateVideo` writes the rendered frames plus an `encode.sh`\nscript instead of failing.\n\n### Multi-project / white-label\n\n```ts\nimport { loadWorkspace, resolveProjects, runBatch } from '@armcompany/armfly-core'\n\nconst ws = loadWorkspace(cwd, 'armfly.workspace.yaml')\nconst projects = resolveProjects(ws)\n\nconst results = await runBatch(\n  projects,\n  ['deploy'],               // command args passed to each project's runner\n  ws.env,                   // shared env injected into every project\n  { concurrency: 1, staggerMs: 5_000 }, // sequential + stagger dodges store rate limits\n  async (project, args, env) => { /* spawn the CLI for `project.dir` */ return 0 },\n)\n```\n\n### Workspace file\n\n```yaml\nname: white-label-portfolio\nenv:\n  BRAND_BACKGROUND: \"#1a2030\"     # injected as env vars into every project\nprojects:\n  - dir: apps/client-a            # uses armfly.yaml by default\n  - dir: apps/client-b\n    config: custom.yaml\n```\n\n## Environment variables\n\n| Variable | Used by | Required |\n| --- | --- | --- |\n| `OPENCODE_ZEN_API_KEY` | `generateMetadata`, `translateMetadata`, `diagnoseRejection` | Only for AI provider |\n| `ANTHROPIC_API_KEY` | Fallback key when `OPENCODE_ZEN_API_KEY` is unset | Only for AI provider |\n| `ASC_KEY_ID`, `ASC_ISSUER_ID` | Apple auth (or set in `armfly.yaml` via `${VAR}`) | For App Store publishing |\n| `NO_COLOR` | Disables ANSI color in `logger` | No |\n| Any `${VAR}` in `armfly.yaml` | Config interpolation | Per config |\n\n## Development\n\n```sh\nnpm install\nnpm run build -w @armcompany/armfly-core   # tsc → dist/\nnpm run test -w @armcompany/armfly-core    # vitest\nnpm run lint -w @armcompany/armfly-core    # tsc --noEmit\n```\n\n## License\n\nSee the repository root. Not for redistribution without ARM Company authorization.\n","readmeFilename":"README.md"}