@cellajs/cli 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,7 @@ pnpm cella audit
19
19
  |---------|-------------|
20
20
  | `analyze` | Dry run to see what would change on sync. It leaves your files and branches alone but updates git metadata: it fetches upstream and writes `refs/cella/last-sync` and a graft for the sync base |
21
21
  | `sync` | Merge upstream changes onto a fresh branch and open a squash-merge PR into `main` |
22
+ | `migrate` | List the upstream migration notes this app has not handled yet, read them, run their codemods, record them |
22
23
  | `audit` | Check for outdated packages & vulnerabilities |
23
24
  | `stats` | Count files by category and workspace package |
24
25
  | `forks` * | Run normal sync inside local fork repositories |
@@ -38,6 +39,7 @@ Per-service help: `pnpm cella <service> --help`.
38
39
  |---------|----------------|
39
40
  | analyze | `--log`, `--list`, `--json`, `--scope <all\|risk\|protected>`, `--diff <path>`, `--open-diff <path>` |
40
41
  | sync | `--log`, `--hard`, `--unpinned`, `--track <release\|branch>` |
42
+ | migrate | `--all`, `--json`, `--show <id>`, `--extract <id>`, `--mark <ids...>` |
41
43
  | audit | `--list`, `--force`, `--check-overrides` |
42
44
  | forks | `--fork <name>`, `--log`, `--hard`, `-V, --verbose` |
43
45
  | contributions | `--fork <name>`, `--list`, `--json`, `--diff <path>` |
@@ -108,6 +110,34 @@ git push -u origin cella/sync/<stamp>
108
110
  gh pr create --base main --head cella/sync/<stamp> --fill
109
111
  ```
110
112
 
113
+ ## Migration notes
114
+
115
+ Upstream ships a note for every change that app code has to follow, in `cella/migrations/<id>/`:
116
+ a README (frontmatter, title, summary, then the steps) and sometimes a codemod. The notes stay
117
+ upstream. The sync never brings that folder into the app and removes a copy it finds; `migrate`
118
+ reads the notes from the upstream commit the app last synced to, which the sync already fetched.
119
+
120
+ The app keeps one small record, `cella/cella.migrations.json`, listing the notes it has not
121
+ handled yet. The sync adds the notes that arrive with it, `migrate --mark` removes them, and the
122
+ file is deleted once the list is empty. No file means nothing is pending, so a new app starts
123
+ without one.
124
+
125
+ Notes are information, never a gate. Each sync run, and `analyze`, ends with one line:
126
+
127
+ ```
128
+ migration notes: 94 of 97 handled · 3 arrived with this sync · 3 open: pnpm cella migrate
129
+ ```
130
+
131
+ and the sync PR lists the open notes with links. Handle them in the sync PR or later; they stay
132
+ listed until marked.
133
+
134
+ ```bash
135
+ pnpm cella migrate # open notes, with summary and links
136
+ pnpm cella migrate --show <id> # one note's README
137
+ pnpm cella migrate --extract <id> # its folder under node_modules/.cache/cella/migrations/<id>/, to run the codemod
138
+ pnpm cella migrate --mark <id> [...] # record notes as handled
139
+ ```
140
+
111
141
  ## Sync rules
112
142
 
113
143
  For each file, `sync` compares fork and upstream **content** (blob comparison) and resolves it
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/types.ts"],"sourcesContent":["/**\n * Sync CLI v2 - Configuration Types\n *\n * Simplified configuration for worktree-based merge approach.\n */\n\nimport { z } from 'zod';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PUBLIC API - for cella/cella.config.ts\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Valid package.json keys that can be synced */\nexport type PackageJsonSyncKey =\n | 'dependencies'\n | 'devDependencies'\n | 'peerDependencies'\n | 'optionalDependencies'\n | 'scripts'\n | 'engines'\n | 'packageManager'\n | 'overrides'\n | 'exports'\n | 'pnpm';\n\n/**\n * Sync settings - all configurable options for the sync CLI.\n * Hover over each property for documentation.\n */\nexport interface SyncSettings {\n /** Upstream repository URL (e.g., 'git@github.com:cellajs/cella.git') */\n upstreamUrl: string;\n\n /**\n * Upstream branch, used for `upstreamTrack: 'branch'` and for GitHub file links.\n * Defaults to 'main'.\n */\n upstreamBranch?: string;\n\n /**\n * How to track upstream cella. Defaults to 'release'.\n * - 'release' (default): sync to a published cella release tag (`v*`). Stable and\n * reviewable — each bump maps to a changelog. Uses the latest release.\n * - 'branch': follow the bleeding-edge tip of `upstreamBranch`. For cella\n * maintainers and forks doing active development on top of unreleased changes.\n */\n upstreamTrack?: 'release' | 'branch';\n\n /**\n * Trunk branch that `pnpm cella sync` cuts the temporary branch from and that you open the\n * squash-merge PR into. Defaults to 'main'. Cutting fresh from trunk each cycle keeps every\n * PR limited to that cycle's upstream delta (no accumulation) and `main` linear.\n */\n releaseBase?: string;\n\n /** Which package.json keys to sync (default: ['dependencies', 'devDependencies']) */\n packageJsonSync?: PackageJsonSyncKey[];\n\n /**\n * Automatically run packages sync after the sync service completes.\n * When true (default), the packages service is hidden from the menu\n * and runs automatically as part of sync.\n * Set to false to keep packages as a separate manual service.\n */\n syncWithPackages?: boolean;\n\n /**\n * How to link files in CLI output.\n * - 'commit' (default): Link to the commit that changed the file on GitHub.\n * - 'file': Link to the file in the repo at the upstream branch on GitHub.\n * - 'local': Deprecated (the upstream view worktree was replaced by browser diffs); treated as 'file'.\n */\n fileLinkMode?: 'commit' | 'file' | 'local';\n}\n\n/**\n * Configuration for a downstream fork repository that cella interacts with.\n *\n * Cella is the upstream/source-of-truth. For each fork it can:\n * - pull contributions FROM the fork's `pullBranch` (contributions service)\n * - run the normal temporary-branch sync flow inside the fork (forks service)\n */\nexport interface ForkConfig {\n /** Display name for the fork (also used as the contrib/<name> branch slug) */\n name: string;\n /** Local path to the fork repository (relative to this config or absolute) */\n localPath: string;\n /**\n * Git remote URL of the fork (e.g. 'git@github.com:org/fork.git').\n * When set, the contributions service fetches the fork's `pullBranch` from this\n * remote (the authoritative committed ref) instead of the local clone, so the\n * comparison no longer depends on the local checkout being up to date. The local\n * clone is still used to read the fork's owned-folder territory when available.\n */\n remoteUrl?: string;\n /** Fork branch that cella pulls contributions from (contributions service) */\n pullBranch: string;\n}\n\n/**\n * User-configurable sync options for cella/cella.config.ts.\n * Simplified for v2 - no long-lived sync branch or squash options.\n */\nexport interface CellaCliConfig {\n /** Core sync settings */\n settings: SyncSettings;\n\n /**\n * File overrides for controlling sync behavior per folder/file.\n */\n overrides?: {\n /**\n * Paths the fork fully owns — never synced (existing or new).\n * Exact paths or directory prefixes, not globs: 'bench' matches 'bench/' and\n * everything under it, 'README.md' matches that exact file.\n * Local territory: upstream cannot add, modify, or delete anything under these.\n */\n ignored?: string[];\n\n /**\n * Paths pinned to fork — fork wins on conflicts.\n * Exact paths or directory prefixes, not globs: 'bench' matches 'bench/' and\n * everything under it, 'README.md' matches that exact file.\n * Non-conflicting upstream changes merge normally.\n * If fork deleted a pinned file, deletion is respected.\n */\n pinned?: string[];\n };\n\n /**\n * Downstream fork repositories that cella interacts with (for upstream template repos).\n * When configured, the 'forks' and 'contributions' services appear in the CLI menu.\n */\n forks?: ForkConfig[];\n}\n\n/**\n * Helper function for defining cella/cella.config.ts with type checking.\n */\nexport function defineConfig(config: CellaCliConfig): CellaCliConfig {\n return config;\n}\n\n/**\n * Strict runtime schema mirroring {@link CellaCliConfig}.\n *\n * The TS interfaces above stay the source of truth (and keep their hover docs for\n * config authors). This schema is what's enforced at load time: `.strict()` rejects\n * unknown keys, so a typo like `ignoredFolders` instead of `ignored` fails closed\n * instead of silently dropping sync protection (tsx does not typecheck the config).\n *\n * The `satisfies z.ZodType<CellaCliConfig>` assertion keeps schema and interface in\n * lockstep: if either drifts (a renamed/removed/mis-typed field), TypeScript errors here.\n */\nexport const cellaConfigSchema = z\n .object({\n settings: z\n .object({\n upstreamUrl: z.string().min(1),\n upstreamBranch: z.string().min(1).optional(),\n upstreamTrack: z.enum(['release', 'branch']).optional(),\n releaseBase: z.string().min(1).optional(),\n packageJsonSync: z\n .array(\n z.enum([\n 'dependencies',\n 'devDependencies',\n 'peerDependencies',\n 'optionalDependencies',\n 'scripts',\n 'engines',\n 'packageManager',\n 'overrides',\n 'exports',\n 'pnpm',\n ]),\n )\n .optional(),\n syncWithPackages: z.boolean().optional(),\n fileLinkMode: z.enum(['commit', 'file', 'local']).optional(),\n })\n .strict(),\n overrides: z\n .object({\n ignored: z.array(z.string()).optional(),\n pinned: z.array(z.string()).optional(),\n })\n .strict()\n .optional(),\n forks: z\n .array(\n z\n .object({\n name: z.string().min(1),\n localPath: z.string().min(1),\n remoteUrl: z.string().optional(),\n pullBranch: z.string().min(1),\n })\n .strict(),\n )\n .optional(),\n })\n .strict() satisfies z.ZodType<CellaCliConfig>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// INTERNAL TYPES - for sync modules\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Sync services available in the CLI */\nexport type SyncService = 'analyze' | 'sync' | 'audit' | 'forks' | 'contributions' | 'stats';\n\n/** Analyze output scope for interactive and machine-readable flows */\nexport type AnalyzeScope = 'all' | 'risk' | 'protected';\n\n/** Runtime configuration with all resolved values */\nexport interface RuntimeConfig extends CellaCliConfig {\n /** Resolved fork repository path */\n forkPath: string;\n\n /** Full upstream remote ref (e.g., 'cella-upstream/development') */\n upstreamRef: string;\n\n /** Selected sync service */\n service: SyncService;\n\n /** Write full file list to log file */\n logFile: boolean;\n\n /** Non-interactive list output (for LLM/agent usage) */\n list: boolean;\n\n /** Machine-readable JSON output (for tooling/agent usage) */\n json: boolean;\n\n /** Print the unified diff for a single contributed file, then exit (contributions; for tooling/agents) */\n diff?: string;\n\n /** Open a browser diff for one file, then exit (analyze) */\n openDiff?: string;\n\n /** Scope of files returned in analyze list/json output */\n scope?: AnalyzeScope;\n\n /** Show verbose output */\n verbose: boolean;\n\n /** Pre-selected fork name (skips fork selection prompt) */\n fork?: string;\n\n /** Overwrite drifted files with upstream version (aggressive realignment) */\n hard?: boolean;\n\n /**\n * Disable pinned entries (cella/cella.config.ts overrides.pinned) for this sync so upstream\n * versions surface as behind/diverged. Managed files stay pinned because cella\n * handles them separately. Like --hard, uses the natural merge-base to resurface\n * full upstream history.\n */\n unpinned?: boolean;\n\n /**\n * Override upstream tracking for this run (analyze/sync), ignoring `settings.upstreamTrack`.\n * 'branch' follows the upstream branch tip (bleeding edge); 'release' uses a release tag.\n */\n track?: 'release' | 'branch';\n\n /** Bypass pnpm metadata cache for fresh registry data (audit service) */\n force?: boolean;\n\n /** Check which pnpm.overrides are still needed (audit service) */\n checkOverrides?: boolean;\n\n /** Regenerate test coverage before showing the stats summary (stats service) */\n coverage?: boolean;\n\n /**\n * After opening the sync PR, enable GitHub auto-merge (squash) so it merges itself once\n * required checks pass (sync service). Opt-in per run via `--direct-merge`; needs auto-merge\n * enabled on the repo. Not the advised default — a sync PR is usually worth a human look.\n */\n directMerge?: boolean;\n}\n\n/** File status after analysis */\nexport type FileStatus =\n | 'identical' // No changes needed\n | 'ahead' // Fork is ahead, protected (pinned/ignored)\n | 'local' // Local file (never existed upstream)\n | 'drifted' // Fork is ahead, NOT protected (at risk)\n | 'behind' // Upstream has changes to sync\n | 'diverged' // Both changed, will merge\n | 'pinned' // Fork wins on conflict\n | 'ignored' // Excluded from sync entirely\n | 'deleted' // Fork deleted, will stay deleted\n | 'renamed'; // Upstream renamed file\n\n/** Analyzed file with status and metadata */\nexport interface AnalyzedFile {\n path: string;\n status: FileStatus;\n /** True if file is in ignored list */\n isIgnored: boolean;\n /** True if file is in pinned list */\n isPinned: boolean;\n /** True if file exists in fork */\n existsInFork: boolean;\n /** True if file exists in upstream */\n existsInUpstream: boolean;\n /** True if file has merge conflict */\n hasConflict?: boolean;\n /** Relative date when file was last changed (since merge-base) */\n changedAt?: string;\n /** Unix epoch seconds of the last change, for sorting */\n changedTs?: number;\n /** Short commit hash of the last change */\n changedCommit?: string;\n /** For diverged files: relative date of upstream change */\n upstreamChangedAt?: string;\n /** For diverged files: Unix epoch seconds of the upstream change, for sorting */\n upstreamChangedTs?: number;\n /** For diverged files: short commit hash of the upstream change */\n upstreamCommit?: string;\n /** For renamed files: the original path before rename */\n renamedFrom?: string;\n /**\n * Protected (pinned/ignored) files only: both sides changed since the merge-base. The fork\n * side wins whole-file, so upstream's changes to this path are dropped by the sync.\n */\n upstreamChanged?: boolean;\n /** For `upstreamChanged` files: lines upstream changed since the merge-base (undefined for binary) */\n upstreamChangedLines?: number;\n /**\n * Ignored files only: upstream changed, added or deleted the file since the merge-base and the\n * fork did not touch it. Ignored paths never sync, so without this flag the change stays unseen\n * (new config keys, version bumps). Managed files and generated output (`isGeneratedFile`) are\n * never flagged.\n */\n upstreamOnly?: boolean;\n /**\n * Pinned `ahead` files only: lines present upstream but absent from the fork (undefined for\n * binary). Independent of the sync point, so it also shows upstream content dropped by an\n * earlier sync — or removed on purpose; only a diff can tell.\n */\n upstreamLinesAbsent?: number;\n}\n\n/** Summary counts by status */\nexport interface AnalysisSummary {\n /** Changed package/config files that are handled separately from normal sync categories */\n managed: number;\n identical: number;\n ahead: number;\n local: number;\n drifted: number;\n behind: number;\n diverged: number;\n pinned: number;\n ignored: number;\n deleted: number;\n renamed: number;\n total: number;\n}\n\n/** Merge result from merge-engine */\nexport interface MergeResult {\n success: boolean;\n /** Upstream branch name for file links */\n upstreamBranch?: string;\n /** Resolved concrete upstream ref that was merged (branch ref or release tag ref) */\n upstreamRef?: string;\n /** Upstream release tag synced (when tracking releases) */\n upstreamTag?: string;\n files: AnalyzedFile[];\n summary: AnalysisSummary;\n conflicts: string[];\n /** Upstream GitHub URL base for commit links */\n upstreamGitHubUrl?: string;\n /** Upstream commit info */\n upstreamCommit?: {\n hash: string;\n message: string;\n date: string;\n };\n /** Commits included in this sync range (oldest-first when rendered) */\n upstreamCommits?: Array<{\n hash: string;\n message: string;\n date: string;\n }>;\n /** Files that were auto-merged by git (diverged without remaining conflicts) */\n autoMergedFiles?: string[];\n /**\n * Protected (pinned/ignored) files resolved to the fork side although upstream also changed\n * them: upstream's hunks were dropped, not merged. Review each against upstream.\n */\n protectedConflicts?: string[];\n /** Pasteable `<merge-base>..<upstream>` range of short SHAs, for `git diff` hints. */\n upstreamDiffRange?: string;\n /** How upstream's own `overrides` changed since the merge-base, where the fork config does not follow. */\n upstreamOverrides?: UpstreamOverridesReport;\n /** Files flagged `upstreamOnly`, grouped by the `ignored` entry they fall under (config order). */\n ignoredUpstreamChanges?: IgnoredUpstreamGroup[];\n}\n\n/** Entries of one override list (`pinned` or `ignored`) that the fork config does not mirror. */\nexport interface OverrideListChanges {\n /** Entries upstream added since the merge-base that the fork list lacks. */\n added: string[];\n /** Entries upstream removed since the merge-base that the fork list still carries. */\n removed: string[];\n}\n\n/**\n * Upstream override changes the fork should review. The sync config is managed (never merged),\n * so new upstream `pinned`/`ignored` entries only reach the fork by hand. `unreadable` means\n * upstream's config was missing or unparsable at one side (`base` = merge-base, `incoming` =\n * upstream ref), so nothing was compared.\n */\nexport type UpstreamOverridesReport =\n | { kind: 'changes'; pinned: OverrideListChanges; ignored: OverrideListChanges }\n | { kind: 'unreadable'; side: 'base' | 'incoming' };\n\n/** Upstream-only changes under one `ignored` entry. */\nexport interface IgnoredUpstreamGroup {\n /** The `ignored` entry (configured or derived from an app-owned module) the files fall under. */\n entry: string;\n /** Changed paths under the entry, sorted. */\n paths: string[];\n /** Paths the fork lacks (new upstream files). */\n added: number;\n /** Paths upstream deleted (the fork keeps them). */\n deleted: number;\n}\n"],"mappings":";AAMA,SAAS,SAAS;AAqIX,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;AAaO,IAAM,oBAAoB,EAC9B,OAAO;AAAA,EACN,UAAU,EACP,OAAO;AAAA,IACN,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IAC7B,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,IAC3C,eAAe,EAAE,KAAK,CAAC,WAAW,QAAQ,CAAC,EAAE,SAAS;AAAA,IACtD,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,IACxC,iBAAiB,EACd;AAAA,MACC,EAAE,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,EACC,SAAS;AAAA,IACZ,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AAAA,IACvC,cAAc,EAAE,KAAK,CAAC,UAAU,QAAQ,OAAO,CAAC,EAAE,SAAS;AAAA,EAC7D,CAAC,EACA,OAAO;AAAA,EACV,WAAW,EACR,OAAO;AAAA,IACN,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,IACtC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACvC,CAAC,EACA,OAAO,EACP,SAAS;AAAA,EACZ,OAAO,EACJ;AAAA,IACC,EACG,OAAO;AAAA,MACN,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACtB,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC3B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,MAC/B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IAC9B,CAAC,EACA,OAAO;AAAA,EACZ,EACC,SAAS;AACd,CAAC,EACA,OAAO;","names":[]}
1
+ {"version":3,"sources":["../src/config/types.ts"],"sourcesContent":["/**\n * Sync CLI v2 - Configuration Types\n *\n * Simplified configuration for worktree-based merge approach.\n */\n\nimport { z } from 'zod';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PUBLIC API - for cella/cella.config.ts\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Valid package.json keys that can be synced */\nexport type PackageJsonSyncKey =\n | 'dependencies'\n | 'devDependencies'\n | 'peerDependencies'\n | 'optionalDependencies'\n | 'scripts'\n | 'engines'\n | 'packageManager'\n | 'overrides'\n | 'exports'\n | 'pnpm';\n\n/**\n * Sync settings - all configurable options for the sync CLI.\n * Hover over each property for documentation.\n */\nexport interface SyncSettings {\n /** Upstream repository URL (e.g., 'git@github.com:cellajs/cella.git') */\n upstreamUrl: string;\n\n /**\n * Upstream branch, used for `upstreamTrack: 'branch'` and for GitHub file links.\n * Defaults to 'main'.\n */\n upstreamBranch?: string;\n\n /**\n * How to track upstream cella. Defaults to 'release'.\n * - 'release' (default): sync to a published cella release tag (`v*`). Stable and\n * reviewable — each bump maps to a changelog. Uses the latest release.\n * - 'branch': follow the bleeding-edge tip of `upstreamBranch`. For cella\n * maintainers and forks doing active development on top of unreleased changes.\n */\n upstreamTrack?: 'release' | 'branch';\n\n /**\n * Trunk branch that `pnpm cella sync` cuts the temporary branch from and that you open the\n * squash-merge PR into. Defaults to 'main'. Cutting fresh from trunk each cycle keeps every\n * PR limited to that cycle's upstream delta (no accumulation) and `main` linear.\n */\n releaseBase?: string;\n\n /** Which package.json keys to sync (default: ['dependencies', 'devDependencies']) */\n packageJsonSync?: PackageJsonSyncKey[];\n\n /**\n * Automatically run packages sync after the sync service completes.\n * When true (default), the packages service is hidden from the menu\n * and runs automatically as part of sync.\n * Set to false to keep packages as a separate manual service.\n */\n syncWithPackages?: boolean;\n\n /**\n * How to link files in CLI output.\n * - 'commit' (default): Link to the commit that changed the file on GitHub.\n * - 'file': Link to the file in the repo at the upstream branch on GitHub.\n * - 'local': Deprecated (the upstream view worktree was replaced by browser diffs); treated as 'file'.\n */\n fileLinkMode?: 'commit' | 'file' | 'local';\n}\n\n/**\n * Configuration for a downstream fork repository that cella interacts with.\n *\n * Cella is the upstream/source-of-truth. For each fork it can:\n * - pull contributions FROM the fork's `pullBranch` (contributions service)\n * - run the normal temporary-branch sync flow inside the fork (forks service)\n */\nexport interface ForkConfig {\n /** Display name for the fork (also used as the contrib/<name> branch slug) */\n name: string;\n /** Local path to the fork repository (relative to this config or absolute) */\n localPath: string;\n /**\n * Git remote URL of the fork (e.g. 'git@github.com:org/fork.git').\n * When set, the contributions service fetches the fork's `pullBranch` from this\n * remote (the authoritative committed ref) instead of the local clone, so the\n * comparison no longer depends on the local checkout being up to date. The local\n * clone is still used to read the fork's owned-folder territory when available.\n */\n remoteUrl?: string;\n /** Fork branch that cella pulls contributions from (contributions service) */\n pullBranch: string;\n}\n\n/**\n * User-configurable sync options for cella/cella.config.ts.\n * Simplified for v2 - no long-lived sync branch or squash options.\n */\nexport interface CellaCliConfig {\n /** Core sync settings */\n settings: SyncSettings;\n\n /**\n * File overrides for controlling sync behavior per folder/file.\n */\n overrides?: {\n /**\n * Paths the fork fully owns — never synced (existing or new).\n * Exact paths or directory prefixes, not globs: 'bench' matches 'bench/' and\n * everything under it, 'README.md' matches that exact file.\n * Local territory: upstream cannot add, modify, or delete anything under these.\n */\n ignored?: string[];\n\n /**\n * Paths pinned to fork — fork wins on conflicts.\n * Exact paths or directory prefixes, not globs: 'bench' matches 'bench/' and\n * everything under it, 'README.md' matches that exact file.\n * Non-conflicting upstream changes merge normally.\n * If fork deleted a pinned file, deletion is respected.\n */\n pinned?: string[];\n };\n\n /**\n * Downstream fork repositories that cella interacts with (for upstream template repos).\n * When configured, the 'forks' and 'contributions' services appear in the CLI menu.\n */\n forks?: ForkConfig[];\n}\n\n/**\n * Helper function for defining cella/cella.config.ts with type checking.\n */\nexport function defineConfig(config: CellaCliConfig): CellaCliConfig {\n return config;\n}\n\n/**\n * Strict runtime schema mirroring {@link CellaCliConfig}.\n *\n * The TS interfaces above stay the source of truth (and keep their hover docs for\n * config authors). This schema is what's enforced at load time: `.strict()` rejects\n * unknown keys, so a typo like `ignoredFolders` instead of `ignored` fails closed\n * instead of silently dropping sync protection (tsx does not typecheck the config).\n *\n * The `satisfies z.ZodType<CellaCliConfig>` assertion keeps schema and interface in\n * lockstep: if either drifts (a renamed/removed/mis-typed field), TypeScript errors here.\n */\nexport const cellaConfigSchema = z\n .object({\n settings: z\n .object({\n upstreamUrl: z.string().min(1),\n upstreamBranch: z.string().min(1).optional(),\n upstreamTrack: z.enum(['release', 'branch']).optional(),\n releaseBase: z.string().min(1).optional(),\n packageJsonSync: z\n .array(\n z.enum([\n 'dependencies',\n 'devDependencies',\n 'peerDependencies',\n 'optionalDependencies',\n 'scripts',\n 'engines',\n 'packageManager',\n 'overrides',\n 'exports',\n 'pnpm',\n ]),\n )\n .optional(),\n syncWithPackages: z.boolean().optional(),\n fileLinkMode: z.enum(['commit', 'file', 'local']).optional(),\n })\n .strict(),\n overrides: z\n .object({\n ignored: z.array(z.string()).optional(),\n pinned: z.array(z.string()).optional(),\n })\n .strict()\n .optional(),\n forks: z\n .array(\n z\n .object({\n name: z.string().min(1),\n localPath: z.string().min(1),\n remoteUrl: z.string().optional(),\n pullBranch: z.string().min(1),\n })\n .strict(),\n )\n .optional(),\n })\n .strict() satisfies z.ZodType<CellaCliConfig>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// INTERNAL TYPES - for sync modules\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Sync services available in the CLI */\nexport type SyncService = 'analyze' | 'sync' | 'migrate' | 'audit' | 'forks' | 'contributions' | 'stats';\n\n/** Analyze output scope for interactive and machine-readable flows */\nexport type AnalyzeScope = 'all' | 'risk' | 'protected';\n\n/** Runtime configuration with all resolved values */\nexport interface RuntimeConfig extends CellaCliConfig {\n /** Resolved fork repository path */\n forkPath: string;\n\n /** Full upstream remote ref (e.g., 'cella-upstream/development') */\n upstreamRef: string;\n\n /** Selected sync service */\n service: SyncService;\n\n /** Write full file list to log file */\n logFile: boolean;\n\n /** Non-interactive list output (for LLM/agent usage) */\n list: boolean;\n\n /** Machine-readable JSON output (for tooling/agent usage) */\n json: boolean;\n\n /** Print the unified diff for a single contributed file, then exit (contributions; for tooling/agents) */\n diff?: string;\n\n /** Open a browser diff for one file, then exit (analyze) */\n openDiff?: string;\n\n /** Scope of files returned in analyze list/json output */\n scope?: AnalyzeScope;\n\n /** Show verbose output */\n verbose: boolean;\n\n /** Pre-selected fork name (skips fork selection prompt) */\n fork?: string;\n\n /** Overwrite drifted files with upstream version (aggressive realignment) */\n hard?: boolean;\n\n /**\n * Disable pinned entries (cella/cella.config.ts overrides.pinned) for this sync so upstream\n * versions surface as behind/diverged. Managed files stay pinned because cella\n * handles them separately. Like --hard, uses the natural merge-base to resurface\n * full upstream history.\n */\n unpinned?: boolean;\n\n /**\n * Override upstream tracking for this run (analyze/sync), ignoring `settings.upstreamTrack`.\n * 'branch' follows the upstream branch tip (bleeding edge); 'release' uses a release tag.\n */\n track?: 'release' | 'branch';\n\n /** Bypass pnpm metadata cache for fresh registry data (audit service) */\n force?: boolean;\n\n /** Check which pnpm.overrides are still needed (audit service) */\n checkOverrides?: boolean;\n\n /** Regenerate test coverage before showing the stats summary (stats service) */\n coverage?: boolean;\n\n /** List every upstream migration note, not only the open ones (migrate service) */\n all?: boolean;\n\n /** Print one migration note's README (migrate service) */\n show?: string;\n\n /** Write one migration note's folder where its codemod can run (migrate service) */\n extract?: string;\n\n /** Migration note ids to record as handled (migrate service) */\n mark?: string[];\n}\n\n/** File status after analysis */\nexport type FileStatus =\n | 'identical' // No changes needed\n | 'ahead' // Fork is ahead, protected (pinned/ignored)\n | 'local' // Local file (never existed upstream)\n | 'drifted' // Fork is ahead, NOT protected (at risk)\n | 'behind' // Upstream has changes to sync\n | 'diverged' // Both changed, will merge\n | 'pinned' // Fork wins on conflict\n | 'ignored' // Excluded from sync entirely\n | 'deleted' // Fork deleted, will stay deleted\n | 'renamed'; // Upstream renamed file\n\n/** Analyzed file with status and metadata */\nexport interface AnalyzedFile {\n path: string;\n status: FileStatus;\n /** True if file is in ignored list */\n isIgnored: boolean;\n /** True if file is in pinned list */\n isPinned: boolean;\n /** True if file exists in fork */\n existsInFork: boolean;\n /** True if file exists in upstream */\n existsInUpstream: boolean;\n /** True if file has merge conflict */\n hasConflict?: boolean;\n /** Relative date when file was last changed (since merge-base) */\n changedAt?: string;\n /** Unix epoch seconds of the last change, for sorting */\n changedTs?: number;\n /** Short commit hash of the last change */\n changedCommit?: string;\n /** For diverged files: relative date of upstream change */\n upstreamChangedAt?: string;\n /** For diverged files: Unix epoch seconds of the upstream change, for sorting */\n upstreamChangedTs?: number;\n /** For diverged files: short commit hash of the upstream change */\n upstreamCommit?: string;\n /** For renamed files: the original path before rename */\n renamedFrom?: string;\n /**\n * Protected (pinned/ignored) files only: both sides changed since the merge-base. The fork\n * side wins whole-file, so upstream's changes to this path are dropped by the sync.\n */\n upstreamChanged?: boolean;\n /** For `upstreamChanged` files: lines upstream changed since the merge-base (undefined for binary) */\n upstreamChangedLines?: number;\n /**\n * Ignored files only: upstream changed, added or deleted the file since the merge-base and the\n * fork did not touch it. Ignored paths never sync, so without this flag the change stays unseen\n * (new config keys, version bumps). Managed files and generated output (`isGeneratedFile`) are\n * never flagged.\n */\n upstreamOnly?: boolean;\n /**\n * Pinned `ahead` files only: lines present upstream but absent from the fork (undefined for\n * binary). Independent of the sync point, so it also shows upstream content dropped by an\n * earlier sync — or removed on purpose; only a diff can tell.\n */\n upstreamLinesAbsent?: number;\n}\n\n/** Summary counts by status */\nexport interface AnalysisSummary {\n /** Changed package/config files that are handled separately from normal sync categories */\n managed: number;\n identical: number;\n ahead: number;\n local: number;\n drifted: number;\n behind: number;\n diverged: number;\n pinned: number;\n ignored: number;\n deleted: number;\n renamed: number;\n total: number;\n}\n\n/** Merge result from merge-engine */\nexport interface MergeResult {\n success: boolean;\n /** Upstream branch name for file links */\n upstreamBranch?: string;\n /** Resolved concrete upstream ref that was merged (branch ref or release tag ref) */\n upstreamRef?: string;\n /** Upstream release tag synced (when tracking releases) */\n upstreamTag?: string;\n files: AnalyzedFile[];\n summary: AnalysisSummary;\n conflicts: string[];\n /** Upstream GitHub URL base for commit links */\n upstreamGitHubUrl?: string;\n /** Upstream commit info */\n upstreamCommit?: {\n hash: string;\n message: string;\n date: string;\n };\n /** Commits included in this sync range (oldest-first when rendered) */\n upstreamCommits?: Array<{\n hash: string;\n message: string;\n date: string;\n }>;\n /** Files that were auto-merged by git (diverged without remaining conflicts) */\n autoMergedFiles?: string[];\n /**\n * Protected (pinned/ignored) files resolved to the fork side although upstream also changed\n * them: upstream's hunks were dropped, not merged. Review each against upstream.\n */\n protectedConflicts?: string[];\n /** Pasteable `<merge-base>..<upstream>` range of short SHAs, for `git diff` hints. */\n upstreamDiffRange?: string;\n /** How upstream's own `overrides` changed since the merge-base, where the fork config does not follow. */\n upstreamOverrides?: UpstreamOverridesReport;\n /** Files flagged `upstreamOnly`, grouped by the `ignored` entry they fall under (config order). */\n ignoredUpstreamChanges?: IgnoredUpstreamGroup[];\n /**\n * Upstream migration notes: how many the incoming ref holds, which ones upstream added since the\n * sync point, and how many stay open in the fork once those are added.\n */\n migrationNotes?: { total: number; arrived: string[]; open: number };\n}\n\n/** Entries of one override list (`pinned` or `ignored`) that the fork config does not mirror. */\nexport interface OverrideListChanges {\n /** Entries upstream added since the merge-base that the fork list lacks. */\n added: string[];\n /** Entries upstream removed since the merge-base that the fork list still carries. */\n removed: string[];\n}\n\n/**\n * Upstream override changes the fork should review. The sync config is managed (never merged),\n * so new upstream `pinned`/`ignored` entries only reach the fork by hand. `unreadable` means\n * upstream's config was missing or unparsable at one side (`base` = merge-base, `incoming` =\n * upstream ref), so nothing was compared.\n */\nexport type UpstreamOverridesReport =\n | { kind: 'changes'; pinned: OverrideListChanges; ignored: OverrideListChanges }\n | { kind: 'unreadable'; side: 'base' | 'incoming' };\n\n/** Upstream-only changes under one `ignored` entry. */\nexport interface IgnoredUpstreamGroup {\n /** The `ignored` entry (configured or derived from an app-owned module) the files fall under. */\n entry: string;\n /** Changed paths under the entry, sorted. */\n paths: string[];\n /** Paths the fork lacks (new upstream files). */\n added: number;\n /** Paths upstream deleted (the fork keeps them). */\n deleted: number;\n}\n"],"mappings":";AAMA,SAAS,SAAS;AAqIX,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;AAaO,IAAM,oBAAoB,EAC9B,OAAO;AAAA,EACN,UAAU,EACP,OAAO;AAAA,IACN,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IAC7B,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,IAC3C,eAAe,EAAE,KAAK,CAAC,WAAW,QAAQ,CAAC,EAAE,SAAS;AAAA,IACtD,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,IACxC,iBAAiB,EACd;AAAA,MACC,EAAE,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,EACC,SAAS;AAAA,IACZ,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AAAA,IACvC,cAAc,EAAE,KAAK,CAAC,UAAU,QAAQ,OAAO,CAAC,EAAE,SAAS;AAAA,EAC7D,CAAC,EACA,OAAO;AAAA,EACV,WAAW,EACR,OAAO;AAAA,IACN,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,IACtC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACvC,CAAC,EACA,OAAO,EACP,SAAS;AAAA,EACZ,OAAO,EACJ;AAAA,IACC,EACG,OAAO;AAAA,MACN,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACtB,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC3B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,MAC/B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IAC9B,CAAC,EACA,OAAO;AAAA,EACZ,EACC,SAAS;AACd,CAAC,EACA,OAAO;","names":[]}