@cellajs/cli 0.0.6 → 0.1.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/dist/config.d.ts CHANGED
@@ -73,7 +73,7 @@ interface ForkConfig {
73
73
  pullBranch: string;
74
74
  }
75
75
  /**
76
- * User-configurable sync options for cella.config.ts.
76
+ * User-configurable sync options for cella/cella.config.ts.
77
77
  * Simplified for v2 - no long-lived sync branch or squash options.
78
78
  */
79
79
  interface CellaCliConfig {
@@ -106,7 +106,7 @@ interface CellaCliConfig {
106
106
  forks?: ForkConfig[];
107
107
  }
108
108
  /**
109
- * Helper function for defining cella.config.ts with type checking.
109
+ * Helper function for defining cella/cella.config.ts with type checking.
110
110
  */
111
111
  declare function defineConfig(config: CellaCliConfig): CellaCliConfig;
112
112
 
@@ -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.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 | '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.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.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 '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.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\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"],"mappings":";AAMA,SAAS,SAAS;AAoIX,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,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 | '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 '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\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"],"mappings":";AAMA,SAAS,SAAS;AAoIX,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,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":[]}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cella-cli.ts
4
4
  import { existsSync as existsSync10, statSync as statSync2 } from "fs";
5
- import { join as join17, resolve as resolve5 } from "path";
5
+ import { join as join17, resolve as resolve6 } from "path";
6
6
  import process9 from "process";
7
7
 
8
8
  // src/cli.ts
@@ -95,11 +95,25 @@ var cellaConfigSchema = z.object({
95
95
  ).optional()
96
96
  }).strict();
97
97
 
98
+ // src/utils/managed-files.ts
99
+ var CONFIG_FILE = "cella/cella.config.ts";
100
+ function isPackageJson(filePath) {
101
+ return filePath === "package.json" || filePath.endsWith("/package.json");
102
+ }
103
+ function isManagedFile(filePath) {
104
+ return isPackageJson(filePath) || filePath === "pnpm-lock.yaml" || filePath === CONFIG_FILE;
105
+ }
106
+
98
107
  // src/utils/module-territory.ts
99
108
  import { existsSync } from "fs";
100
109
  import { basename, join, relative, sep } from "path";
101
110
  import { Project, SyntaxKind } from "ts-morph";
102
- var MODULE_FILE_GLOBS = ["backend/src/modules/*/*-module.ts", "frontend/src/modules/*/*-module.ts"];
111
+ var MODULE_FILE_GLOBS = [
112
+ "backend/src/modules/*/*-module.ts",
113
+ "frontend/src/modules/*/*-module.ts",
114
+ "frontend/src/modules/*/*-module.tsx"
115
+ ];
116
+ var MODULE_REGISTER_CALLS = /* @__PURE__ */ new Set(["registerModule", "defineBackendModule", "defineFrontendModule"]);
103
117
  var FRONTEND_STATIC_ROOT = "frontend/public/static";
104
118
  function readOwner(call) {
105
119
  const arg = call.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
@@ -116,7 +130,7 @@ function resolveAppModuleFolders(repoPath) {
116
130
  const folders = /* @__PURE__ */ new Set();
117
131
  const moduleNames = /* @__PURE__ */ new Set();
118
132
  for (const sourceFile of sourceFiles) {
119
- const ownsApp = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).some((call) => call.getExpression().getText() === "registerModule" && readOwner(call) === "app");
133
+ const ownsApp = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).some((call) => MODULE_REGISTER_CALLS.has(call.getExpression().getText()) && readOwner(call) === "app");
120
134
  if (!ownsApp) continue;
121
135
  const folder = relative(repoPath, sourceFile.getDirectoryPath()).split(sep).join("/");
122
136
  folders.add(folder);
@@ -155,7 +169,7 @@ function resolveUpstream(settings) {
155
169
  return { track, branchRef };
156
170
  }
157
171
  async function loadConfig(forkPath) {
158
- const configPath = join2(forkPath, "cella.config.ts");
172
+ const configPath = join2(forkPath, CONFIG_FILE);
159
173
  if (!existsSync2(configPath)) {
160
174
  throw new Error(`config file not found: ${configPath}`);
161
175
  }
@@ -196,7 +210,7 @@ import process4 from "process";
196
210
  // package.json
197
211
  var package_default = {
198
212
  name: "@cellajs/cli",
199
- version: "0.0.6",
213
+ version: "0.1.0",
200
214
  private: false,
201
215
  license: "MIT",
202
216
  description: "CLI for managing your monorepo and syncing with upstream Cella.",
@@ -318,14 +332,6 @@ var package_default = {
318
332
  }
319
333
  };
320
334
 
321
- // src/utils/managed-files.ts
322
- function isPackageJson(filePath) {
323
- return filePath === "package.json" || filePath.endsWith("/package.json");
324
- }
325
- function isManagedFile(filePath) {
326
- return isPackageJson(filePath) || filePath === "pnpm-lock.yaml" || filePath === "cella.config.ts";
327
- }
328
-
329
335
  // src/utils/display.ts
330
336
  var NAME = "cella cli";
331
337
  var VERSION = package_default.version;
@@ -502,7 +508,7 @@ var statusConfig = {
502
508
  diverged: { icon: colors_default.magenta("\u21C5"), label: "diverged", color: colors_default.magenta, description: "both changed, will merge" },
503
509
  drifted: { icon: colors_default.yellow("!"), label: "drifted", color: colors_default.yellow, description: "fork changed (at risk)" },
504
510
  ahead: { icon: colors_default.blue("\u2191"), label: "ahead", color: colors_default.blue, description: "fork changed (protected)" },
505
- local: { icon: colors_default.green("+"), label: "local", color: colors_default.green, description: "local file" },
511
+ local: { icon: colors_default.green("+"), label: "local", color: colors_default.green, description: "local file in cella folders" },
506
512
  pinned: { icon: colors_default.green("\u2A00"), label: "pinned", color: colors_default.green, description: "both changed, fork wins" },
507
513
  ignored: { icon: colors_default.gray("\u2A02"), label: "ignored", color: colors_default.gray },
508
514
  identical: { icon: colors_default.gray("\u2713"), label: "identical", color: colors_default.gray, description: "no changes" },
@@ -515,7 +521,7 @@ var summaryStatusConfig = {
515
521
  icon: colors_default.cyan("\u25C7"),
516
522
  label: "managed",
517
523
  color: colors_default.cyan,
518
- description: "package.jsons & cella.config.ts handled separately"
524
+ description: "package.jsons & cella/cella.config.ts handled separately"
519
525
  },
520
526
  ignored: { ...statusConfig.ignored, description: "protected by ignored config" }
521
527
  };
@@ -585,7 +591,7 @@ function printAnalysisFileGroups(files, linkOptions) {
585
591
  });
586
592
  printFileGroup(files, "ahead", linkOptions, {
587
593
  title: `${colors_default.blue("\u2191 protected in fork")}`,
588
- hint: "these files have fork changes and are protected by pinned or ignored config."
594
+ hint: "these files have fork changes but are protected (pinned)."
589
595
  });
590
596
  printFileGroup(files, "drifted", linkOptions, {
591
597
  title: `${warningMark} ${colors_default.yellow("drifted from upstream")}`,
@@ -598,7 +604,7 @@ function printAnalysisFileGroups(files, linkOptions) {
598
604
  });
599
605
  printFileGroup(files, "pinned", linkOptions, {
600
606
  title: `${colors_default.green("\u2A00 pinned")}`,
601
- hint: "both changed, fork wins (pinned in cella.config.ts).",
607
+ hint: "both changed, fork wins (pinned in cella/cella.config.ts).",
602
608
  dateSource: "upstream"
603
609
  });
604
610
  }
@@ -653,7 +659,9 @@ function printFlagWarnings(options) {
653
659
  }
654
660
  if (unpinned) {
655
661
  console.info(
656
- colors_default.yellow("\u26A0 --unpinned used: pinned files in cella.config.ts were ignored, this can result in more drifts.")
662
+ colors_default.yellow(
663
+ "\u26A0 --unpinned used: pinned files in cella/cella.config.ts were ignored, this can result in more drifts."
664
+ )
657
665
  );
658
666
  }
659
667
  console.info(colors_default.yellow(" Be extra careful & cherrypick what you want only."));
@@ -1042,14 +1050,14 @@ import process7 from "process";
1042
1050
  // src/utils/git.ts
1043
1051
  import { execFile } from "child_process";
1044
1052
  import { existsSync as existsSync4 } from "fs";
1045
- import { mkdir, readdir, rmdir, unlink } from "fs/promises";
1046
- import { dirname, join as join8 } from "path";
1053
+ import { mkdir as mkdir2, readdir, rmdir, unlink } from "fs/promises";
1054
+ import { dirname as dirname2, join as join8 } from "path";
1047
1055
  import { promisify } from "util";
1048
1056
 
1049
1057
  // src/utils/manifest.ts
1050
- import { readFile, writeFile } from "fs/promises";
1051
- import { join as join7 } from "path";
1052
- var MANIFEST_FILE = "cella.manifest.json";
1058
+ import { mkdir, readFile, writeFile } from "fs/promises";
1059
+ import { dirname, join as join7 } from "path";
1060
+ var MANIFEST_FILE = "cella/cella.manifest.json";
1053
1061
  async function readSyncManifest(cwd) {
1054
1062
  try {
1055
1063
  const parsed = JSON.parse(await readFile(join7(cwd, MANIFEST_FILE), "utf8"));
@@ -1065,7 +1073,9 @@ async function readManifestBase(cwd) {
1065
1073
  return manifest ? manifest.upstream.commit.toLowerCase() : null;
1066
1074
  }
1067
1075
  async function writeSyncManifest(cwd, manifest) {
1068
- await writeFile(join7(cwd, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
1076
+ const manifestPath = join7(cwd, MANIFEST_FILE);
1077
+ await mkdir(dirname(manifestPath), { recursive: true });
1078
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
1069
1079
  `, "utf8");
1070
1080
  }
1071
1081
 
@@ -1377,8 +1387,8 @@ async function batchUnstageFiles(cwd, filePaths) {
1377
1387
  await git(["rm", "--cached", "-f", "--", ...filePaths], cwd, { ignoreErrors: true });
1378
1388
  }
1379
1389
  async function gitMv(cwd, oldPath, newPath) {
1380
- const newDir = dirname(join8(cwd, newPath));
1381
- await mkdir(newDir, { recursive: true });
1390
+ const newDir = dirname2(join8(cwd, newPath));
1391
+ await mkdir2(newDir, { recursive: true });
1382
1392
  await git(["mv", "-f", oldPath, newPath], cwd);
1383
1393
  }
1384
1394
  async function fileExistsInWorktree(cwd, filePath) {
@@ -1388,7 +1398,7 @@ async function removeFileFromWorktree(cwd, filePath) {
1388
1398
  const fullPath = join8(cwd, filePath);
1389
1399
  try {
1390
1400
  await unlink(fullPath);
1391
- await cleanupEmptyParentDirs(cwd, dirname(filePath));
1401
+ await cleanupEmptyParentDirs(cwd, dirname2(filePath));
1392
1402
  } catch {
1393
1403
  }
1394
1404
  }
@@ -1403,7 +1413,7 @@ async function cleanupEmptyParentDirs(cwd, relativePath) {
1403
1413
  const entries = await readdir(fullPath);
1404
1414
  if (entries.length === 0) {
1405
1415
  await rmdir(fullPath);
1406
- await cleanupEmptyParentDirs(cwd, dirname(relativePath));
1416
+ await cleanupEmptyParentDirs(cwd, dirname2(relativePath));
1407
1417
  }
1408
1418
  } catch {
1409
1419
  }
@@ -1511,7 +1521,31 @@ async function readRootTrailerBase(cwd, headRef) {
1511
1521
  const match = message.match(/^cella-base:\s*([0-9a-f]{40})$/im);
1512
1522
  return match ? match[1].toLowerCase() : null;
1513
1523
  }
1524
+ async function isShallowRepository(cwd) {
1525
+ const out = await git(["rev-parse", "--is-shallow-repository"], cwd, { ignoreErrors: true });
1526
+ return out === "true";
1527
+ }
1528
+ async function unshallowRepository(cwd) {
1529
+ try {
1530
+ await git(["fetch", "--unshallow"], cwd, { skipEditor: true });
1531
+ } catch (error) {
1532
+ const detail = error instanceof Error ? `
1533
+
1534
+ ${error.message}` : "";
1535
+ throw new Error(
1536
+ `this is a shallow clone and its full history could not be restored automatically.
1537
+ Sync needs the complete history to find the common ancestor with upstream. Restore it with:
1538
+
1539
+ git fetch --unshallow
1540
+
1541
+ then re-run sync.${detail}`
1542
+ );
1543
+ }
1544
+ }
1514
1545
  async function ensureSyncBase(cwd, headRef, upstreamRef) {
1546
+ if (await isShallowRepository(cwd)) {
1547
+ await unshallowRepository(cwd);
1548
+ }
1515
1549
  const nativeBase = await git(["merge-base", headRef, upstreamRef], cwd, { ignoreErrors: true });
1516
1550
  if (nativeBase) return;
1517
1551
  const sources = [
@@ -3053,7 +3087,7 @@ import {
3053
3087
  import { select as select2 } from "@inquirer/prompts";
3054
3088
 
3055
3089
  // src/services/contrib-core.ts
3056
- import { dirname as dirname2, join as join12 } from "path";
3090
+ import { dirname as dirname3, join as join12, resolve as resolve3 } from "path";
3057
3091
  async function listFilesAtRef(repoPath, ref) {
3058
3092
  const raw = await git(["ls-tree", "-r", "--name-only", ref], repoPath);
3059
3093
  return new Set(raw.split("\n").filter(Boolean));
@@ -3069,10 +3103,10 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
3069
3103
  const baseFiles = await listFilesAtRef(repoPath, baseRef);
3070
3104
  const baseDirs = /* @__PURE__ */ new Set(["."]);
3071
3105
  for (const f of baseFiles) {
3072
- let dir = dirname2(f);
3106
+ let dir = dirname3(f);
3073
3107
  while (dir !== ".") {
3074
3108
  baseDirs.add(dir);
3075
- dir = dirname2(dir);
3109
+ dir = dirname3(dir);
3076
3110
  }
3077
3111
  }
3078
3112
  const files = [];
@@ -3085,7 +3119,7 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
3085
3119
  if (inCella && !inFork) {
3086
3120
  kind = "deleted";
3087
3121
  } else if (!inCella && inFork) {
3088
- if (!baseDirs.has(dirname2(file.path))) continue;
3122
+ if (!baseDirs.has(dirname3(file.path))) continue;
3089
3123
  kind = "created";
3090
3124
  } else {
3091
3125
  kind = "modified";
@@ -3116,7 +3150,8 @@ async function readBlobInfo(repoPath, ref, filePath) {
3116
3150
  }
3117
3151
  async function buildContribBranch(repoPath, baseRef, forkRef, detection, forkName) {
3118
3152
  const branch = `contrib/${forkName}`;
3119
- const tmpIndex = join12(repoPath, ".git", `tmp-contrib-index-${forkName.replace(/[^\w.-]/g, "_")}`);
3153
+ const gitDir = resolve3(repoPath, await git(["rev-parse", "--git-dir"], repoPath));
3154
+ const tmpIndex = join12(gitDir, `tmp-contrib-index-${forkName.replace(/[^\w.-]/g, "_")}`);
3120
3155
  const indexEnv = { GIT_INDEX_FILE: tmpIndex };
3121
3156
  try {
3122
3157
  await git(["read-tree", baseRef], repoPath, { env: indexEnv });
@@ -3160,21 +3195,25 @@ ${commitBody}`;
3160
3195
 
3161
3196
  // src/services/fork-utils.ts
3162
3197
  import { existsSync as existsSync7 } from "fs";
3163
- import { resolve as resolve3 } from "path";
3198
+ import { dirname as dirname4, resolve as resolve4 } from "path";
3199
+ async function resolveForkBasePath(repoPath) {
3200
+ const commonDir = await git(["rev-parse", "--git-common-dir"], repoPath, { ignoreErrors: true });
3201
+ return commonDir ? dirname4(resolve4(repoPath, commonDir)) : repoPath;
3202
+ }
3164
3203
  function validateForkPath(fork, basePath, requireConfig = false) {
3165
- const resolvedPath = resolve3(basePath, fork.localPath);
3166
- if (!resolvedPath.startsWith(resolve3(basePath, ".."))) {
3204
+ const resolvedPath = resolve4(basePath, fork.localPath);
3205
+ if (!resolvedPath.startsWith(resolve4(basePath, ".."))) {
3167
3206
  return { fork, resolvedPath, valid: false, error: "path resolves outside parent directory" };
3168
3207
  }
3169
3208
  if (!existsSync7(resolvedPath)) return { fork, resolvedPath, valid: false, error: "path does not exist" };
3170
3209
  if (!existsSync7(`${resolvedPath}/.git`)) return { fork, resolvedPath, valid: false, error: "not a git repository" };
3171
- if (requireConfig && !existsSync7(`${resolvedPath}/cella.config.ts`)) {
3172
- return { fork, resolvedPath, valid: false, error: "missing cella.config.ts" };
3210
+ if (requireConfig && !existsSync7(`${resolvedPath}/${CONFIG_FILE}`)) {
3211
+ return { fork, resolvedPath, valid: false, error: `missing ${CONFIG_FILE}` };
3173
3212
  }
3174
3213
  return { fork, resolvedPath, valid: true };
3175
3214
  }
3176
3215
  function printNoForksHint(action) {
3177
- console.info(colors_default.yellow("no forks configured in cella.config.ts"));
3216
+ console.info(colors_default.yellow("no forks configured in cella/cella.config.ts"));
3178
3217
  console.info(colors_default.dim(action));
3179
3218
  console.info(colors_default.dim(` forks: [{ name: 'my-app', localPath: '../my-app', pullBranch: 'development' }]`));
3180
3219
  }
@@ -3343,7 +3382,8 @@ async function runContributions(config) {
3343
3382
  );
3344
3383
  console.info("");
3345
3384
  }
3346
- const validated = forks.map((fork2) => validateForkPath(fork2, config.forkPath));
3385
+ const forkBasePath = await resolveForkBasePath(config.forkPath);
3386
+ const validated = forks.map((fork2) => validateForkPath(fork2, forkBasePath));
3347
3387
  const validForks = validated.filter((v) => v.valid);
3348
3388
  let selectedFork;
3349
3389
  if (config.fork) {
@@ -3545,7 +3585,7 @@ async function runContributions(config) {
3545
3585
  }
3546
3586
 
3547
3587
  // src/services/forks.ts
3548
- import { resolve as resolve4 } from "path";
3588
+ import { resolve as resolve5 } from "path";
3549
3589
  import process8 from "process";
3550
3590
  import { Separator as Separator2, select as select4 } from "@inquirer/prompts";
3551
3591
 
@@ -4251,12 +4291,13 @@ async function runForks(config) {
4251
4291
  console.error(colors_default.red(`fork '${config.fork}' not found in config`));
4252
4292
  return;
4253
4293
  }
4254
- const resolvedPath = resolve4(config.forkPath, match.localPath);
4294
+ const resolvedPath = resolve5(await resolveForkBasePath(config.forkPath), match.localPath);
4255
4295
  await syncFork(config, match, resolvedPath);
4256
4296
  return;
4257
4297
  }
4298
+ const forkBasePath = await resolveForkBasePath(config.forkPath);
4258
4299
  while (true) {
4259
- const choices = await buildForkChoices(forks, config.forkPath);
4300
+ const choices = await buildForkChoices(forks, forkBasePath);
4260
4301
  const forkChoices = [...choices, new Separator2("\u2500".repeat(40)), { value: "_exit", name: colors_default.dim("exit") }];
4261
4302
  const selectedPath = await select4({
4262
4303
  message: "select fork to sync:",
@@ -4266,7 +4307,7 @@ async function runForks(config) {
4266
4307
  if (selectedPath === "_exit") {
4267
4308
  process8.exit(0);
4268
4309
  }
4269
- const resolvedForkPath = resolve4(config.forkPath, selectedPath);
4310
+ const resolvedForkPath = resolve5(forkBasePath, selectedPath);
4270
4311
  const selectedFork = forks.find((f) => f.localPath === selectedPath);
4271
4312
  if (!selectedFork) {
4272
4313
  console.error(colors_default.red(`fork '${selectedPath}' not found in config`));
@@ -4380,6 +4421,9 @@ function classifyFile(filePath) {
4380
4421
  if (filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/tests/") || filePath.includes("/test/") || filePath.startsWith("tests/") || filePath.includes("/__tests__/")) {
4381
4422
  return "test";
4382
4423
  }
4424
+ if (filePath.includes(".stories.")) {
4425
+ return "stories";
4426
+ }
4383
4427
  if (filePath.includes(".gen.") || filePath.includes(".gen/") || filePath.includes("/gen/") || filePath.startsWith("drizzle/") || filePath.includes("/drizzle/") || filePath.endsWith(".d.ts") || filePath.includes(".tsbuildinfo")) {
4384
4428
  return "generated";
4385
4429
  }
@@ -4420,6 +4464,7 @@ async function collectStats(forkPath) {
4420
4464
  const skipped = allFilesCount - entries.length;
4421
4465
  const emptyCat = () => ({
4422
4466
  test: { files: 0, loc: 0 },
4467
+ stories: { files: 0, loc: 0 },
4423
4468
  generated: { files: 0, loc: 0 },
4424
4469
  json: { files: 0, loc: 0 },
4425
4470
  other: { files: 0, loc: 0 }
@@ -4472,6 +4517,7 @@ function printStats(stats, verbose) {
4472
4517
  const catEntries = [
4473
4518
  ["source & config", stats.categories.other],
4474
4519
  ["test", stats.categories.test],
4520
+ ["stories", stats.categories.stories],
4475
4521
  ["generated", stats.categories.generated],
4476
4522
  ["json", stats.categories.json]
4477
4523
  ];
@@ -4484,7 +4530,7 @@ function printStats(stats, verbose) {
4484
4530
  console.info(DIVIDER);
4485
4531
  console.info();
4486
4532
  if (verbose) {
4487
- const header = ` ${"".padEnd(6)} ${"package".padEnd(18)} ${"loc".padStart(8)} ${"test".padStart(6)} ${"gen".padStart(6)} ${"json".padStart(6)} ${"src".padStart(6)}`;
4533
+ const header = ` ${"".padEnd(6)} ${"package".padEnd(18)} ${"loc".padStart(8)} ${"test".padStart(6)} ${"story".padStart(6)} ${"gen".padStart(6)} ${"json".padStart(6)} ${"src".padStart(6)}`;
4488
4534
  console.info(colors_default.dim(header));
4489
4535
  for (const [name, data] of Object.entries(stats.packages)) {
4490
4536
  const c = data.categories;
@@ -4492,7 +4538,7 @@ function printStats(stats, verbose) {
4492
4538
  const srcLoc = c.other.loc;
4493
4539
  if (srcFiles === 0) continue;
4494
4540
  console.info(
4495
- ` ${colors_default.dim(pad(srcFiles))} ${name.padEnd(18)} ${colors_default.cyan(formatLocColumn(srcLoc))} ${colors_default.dim(pad(c.test.files))} ${colors_default.dim(pad(c.generated.files))} ${colors_default.dim(pad(c.json.files))} ${colors_default.dim(pad(srcFiles))}`
4541
+ ` ${colors_default.dim(pad(srcFiles))} ${name.padEnd(18)} ${colors_default.cyan(formatLocColumn(srcLoc))} ${colors_default.dim(pad(c.test.files))} ${colors_default.dim(pad(c.stories.files))} ${colors_default.dim(pad(c.generated.files))} ${colors_default.dim(pad(c.json.files))} ${colors_default.dim(pad(srcFiles))}`
4496
4542
  );
4497
4543
  }
4498
4544
  } else {
@@ -4524,7 +4570,7 @@ async function runStats(forkPath, options = {}) {
4524
4570
  function getForkPath() {
4525
4571
  const envPath = getEnv("CELLA_FORK_PATH");
4526
4572
  if (envPath) {
4527
- const resolved = resolve5(envPath);
4573
+ const resolved = resolve6(envPath);
4528
4574
  if (!existsSync10(resolved) || !statSync2(resolved).isDirectory()) {
4529
4575
  throw new Error(`CELLA_FORK_PATH is not a valid directory: ${resolved}`);
4530
4576
  }
@@ -4532,7 +4578,7 @@ function getForkPath() {
4532
4578
  }
4533
4579
  let cwd = process9.cwd();
4534
4580
  if (cwd.endsWith("/cli/cella") || cwd.endsWith("\\cli\\cella")) {
4535
- cwd = resolve5(cwd, "../..");
4581
+ cwd = resolve6(cwd, "../..");
4536
4582
  }
4537
4583
  return cwd;
4538
4584
  }