@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 +2 -2
- package/dist/config.js.map +1 -1
- package/dist/index.js +94 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
package/dist/config.js.map
CHANGED
|
@@ -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
|
|
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 = [
|
|
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()
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
1381
|
-
await
|
|
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,
|
|
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,
|
|
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
|
|
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 =
|
|
3106
|
+
let dir = dirname3(f);
|
|
3073
3107
|
while (dir !== ".") {
|
|
3074
3108
|
baseDirs.add(dir);
|
|
3075
|
-
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(
|
|
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
|
|
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
|
|
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 =
|
|
3166
|
-
if (!resolvedPath.startsWith(
|
|
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}
|
|
3172
|
-
return { fork, resolvedPath, valid: false, error:
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4581
|
+
cwd = resolve6(cwd, "../..");
|
|
4536
4582
|
}
|
|
4537
4583
|
return cwd;
|
|
4538
4584
|
}
|