@cellajs/cli 0.0.5 → 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 +259 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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."));
|
|
@@ -747,7 +755,8 @@ function readOptions(opts) {
|
|
|
747
755
|
track: opts.track === "release" || opts.track === "branch" ? opts.track : void 0,
|
|
748
756
|
force: opts.force === true,
|
|
749
757
|
checkOverrides: opts.checkOverrides === true,
|
|
750
|
-
coverage: opts.coverage === true
|
|
758
|
+
coverage: opts.coverage === true,
|
|
759
|
+
directMerge: opts.directMerge === true
|
|
751
760
|
};
|
|
752
761
|
}
|
|
753
762
|
var serviceDefinitions = [
|
|
@@ -772,7 +781,11 @@ var serviceDefinitions = [
|
|
|
772
781
|
{ flags: "--log", description: "write complete file list to cella-sync.log" },
|
|
773
782
|
{ flags: "--hard", description: "overwrite drifted files with upstream version (aggressive realignment)" },
|
|
774
783
|
{ flags: "--unpinned", description: "ignore pinned files (except package.json) to resurface upstream changes" },
|
|
775
|
-
{ flags: "--track <mode>", description: "override upstream tracking for this run: release|branch" }
|
|
784
|
+
{ flags: "--track <mode>", description: "override upstream tracking for this run: release|branch" },
|
|
785
|
+
{
|
|
786
|
+
flags: "--direct-merge",
|
|
787
|
+
description: "auto-merge (squash) the sync PR once checks pass \u2014 needs auto-merge enabled on the repo"
|
|
788
|
+
}
|
|
776
789
|
],
|
|
777
790
|
includeInMenu: (context) => !context.isUpstreamRepo,
|
|
778
791
|
menuDescription: () => "merge upstream changes + sync package.json"
|
|
@@ -859,6 +872,7 @@ function buildProgram(setSelection) {
|
|
|
859
872
|
" $ cella sync --hard",
|
|
860
873
|
" $ cella sync --unpinned",
|
|
861
874
|
" $ cella sync --track branch",
|
|
875
|
+
" $ cella sync --direct-merge",
|
|
862
876
|
" $ cella audit --check-overrides",
|
|
863
877
|
" $ cella contributions --fork raak --json"
|
|
864
878
|
].join("\n")
|
|
@@ -1036,14 +1050,14 @@ import process7 from "process";
|
|
|
1036
1050
|
// src/utils/git.ts
|
|
1037
1051
|
import { execFile } from "child_process";
|
|
1038
1052
|
import { existsSync as existsSync4 } from "fs";
|
|
1039
|
-
import { mkdir, readdir, rmdir, unlink } from "fs/promises";
|
|
1040
|
-
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";
|
|
1041
1055
|
import { promisify } from "util";
|
|
1042
1056
|
|
|
1043
1057
|
// src/utils/manifest.ts
|
|
1044
|
-
import { readFile, writeFile } from "fs/promises";
|
|
1045
|
-
import { join as join7 } from "path";
|
|
1046
|
-
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";
|
|
1047
1061
|
async function readSyncManifest(cwd) {
|
|
1048
1062
|
try {
|
|
1049
1063
|
const parsed = JSON.parse(await readFile(join7(cwd, MANIFEST_FILE), "utf8"));
|
|
@@ -1059,7 +1073,9 @@ async function readManifestBase(cwd) {
|
|
|
1059
1073
|
return manifest ? manifest.upstream.commit.toLowerCase() : null;
|
|
1060
1074
|
}
|
|
1061
1075
|
async function writeSyncManifest(cwd, manifest) {
|
|
1062
|
-
|
|
1076
|
+
const manifestPath = join7(cwd, MANIFEST_FILE);
|
|
1077
|
+
await mkdir(dirname(manifestPath), { recursive: true });
|
|
1078
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
1063
1079
|
`, "utf8");
|
|
1064
1080
|
}
|
|
1065
1081
|
|
|
@@ -1371,8 +1387,8 @@ async function batchUnstageFiles(cwd, filePaths) {
|
|
|
1371
1387
|
await git(["rm", "--cached", "-f", "--", ...filePaths], cwd, { ignoreErrors: true });
|
|
1372
1388
|
}
|
|
1373
1389
|
async function gitMv(cwd, oldPath, newPath) {
|
|
1374
|
-
const newDir =
|
|
1375
|
-
await
|
|
1390
|
+
const newDir = dirname2(join8(cwd, newPath));
|
|
1391
|
+
await mkdir2(newDir, { recursive: true });
|
|
1376
1392
|
await git(["mv", "-f", oldPath, newPath], cwd);
|
|
1377
1393
|
}
|
|
1378
1394
|
async function fileExistsInWorktree(cwd, filePath) {
|
|
@@ -1382,7 +1398,7 @@ async function removeFileFromWorktree(cwd, filePath) {
|
|
|
1382
1398
|
const fullPath = join8(cwd, filePath);
|
|
1383
1399
|
try {
|
|
1384
1400
|
await unlink(fullPath);
|
|
1385
|
-
await cleanupEmptyParentDirs(cwd,
|
|
1401
|
+
await cleanupEmptyParentDirs(cwd, dirname2(filePath));
|
|
1386
1402
|
} catch {
|
|
1387
1403
|
}
|
|
1388
1404
|
}
|
|
@@ -1397,7 +1413,7 @@ async function cleanupEmptyParentDirs(cwd, relativePath) {
|
|
|
1397
1413
|
const entries = await readdir(fullPath);
|
|
1398
1414
|
if (entries.length === 0) {
|
|
1399
1415
|
await rmdir(fullPath);
|
|
1400
|
-
await cleanupEmptyParentDirs(cwd,
|
|
1416
|
+
await cleanupEmptyParentDirs(cwd, dirname2(relativePath));
|
|
1401
1417
|
}
|
|
1402
1418
|
} catch {
|
|
1403
1419
|
}
|
|
@@ -1505,7 +1521,31 @@ async function readRootTrailerBase(cwd, headRef) {
|
|
|
1505
1521
|
const match = message.match(/^cella-base:\s*([0-9a-f]{40})$/im);
|
|
1506
1522
|
return match ? match[1].toLowerCase() : null;
|
|
1507
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
|
+
}
|
|
1508
1545
|
async function ensureSyncBase(cwd, headRef, upstreamRef) {
|
|
1546
|
+
if (await isShallowRepository(cwd)) {
|
|
1547
|
+
await unshallowRepository(cwd);
|
|
1548
|
+
}
|
|
1509
1549
|
const nativeBase = await git(["merge-base", headRef, upstreamRef], cwd, { ignoreErrors: true });
|
|
1510
1550
|
if (nativeBase) return;
|
|
1511
1551
|
const sources = [
|
|
@@ -1570,6 +1610,21 @@ async function getEffectiveMergeBase(cwd, headRef, upstreamRef) {
|
|
|
1570
1610
|
}
|
|
1571
1611
|
return { base: gitBase, isStale: false, storedRef: recorded };
|
|
1572
1612
|
}
|
|
1613
|
+
async function withTemporarySyncBaseGraft(cwd, headRef, baseSha, fn) {
|
|
1614
|
+
const headSha = await git(["rev-parse", headRef], cwd, { ignoreErrors: true });
|
|
1615
|
+
if (!headSha || !await commitObjectExists(cwd, baseSha)) return fn();
|
|
1616
|
+
if (await isAncestor(cwd, baseSha, headSha)) return fn();
|
|
1617
|
+
const existingReplacement = await git(["replace", "-l", headSha], cwd, { ignoreErrors: true });
|
|
1618
|
+
if (existingReplacement) return fn();
|
|
1619
|
+
const parentsRaw = await git(["log", "-1", "--format=%P", headSha], cwd, { ignoreErrors: true });
|
|
1620
|
+
const parents = parentsRaw ? parentsRaw.split(/\s+/) : [];
|
|
1621
|
+
await git(["replace", "-f", "--graft", headSha, ...parents, baseSha], cwd);
|
|
1622
|
+
try {
|
|
1623
|
+
return await fn();
|
|
1624
|
+
} finally {
|
|
1625
|
+
await git(["replace", "-d", headSha], cwd, { ignoreErrors: true });
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1573
1628
|
|
|
1574
1629
|
// src/utils/cleanup.ts
|
|
1575
1630
|
var WORKTREE_PREFIXES = {
|
|
@@ -1847,7 +1902,12 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
|
|
|
1847
1902
|
}
|
|
1848
1903
|
}
|
|
1849
1904
|
onProgress?.("starting merge in fork...");
|
|
1850
|
-
await
|
|
1905
|
+
await withTemporarySyncBaseGraft(
|
|
1906
|
+
forkPath,
|
|
1907
|
+
"HEAD",
|
|
1908
|
+
mergeBaseRef,
|
|
1909
|
+
() => merge(forkPath, upstreamRef, { noCommit: true, noEdit: true })
|
|
1910
|
+
);
|
|
1851
1911
|
if (batchRestorePaths.length > 0) {
|
|
1852
1912
|
onProgress?.(`batch restoring ${batchRestorePaths.length} pinned/ignored files...`);
|
|
1853
1913
|
await batchRestoreToHead(forkPath, batchRestorePaths);
|
|
@@ -2100,7 +2160,12 @@ async function runAnalyzePreview(config, ctx, onProgress, onStep) {
|
|
|
2100
2160
|
await createWorktree(forkPath, worktreePath, "HEAD");
|
|
2101
2161
|
onStep?.("worktree created", worktreePath);
|
|
2102
2162
|
onProgress?.("performing merge in worktree...");
|
|
2103
|
-
await
|
|
2163
|
+
await withTemporarySyncBaseGraft(
|
|
2164
|
+
worktreePath,
|
|
2165
|
+
"HEAD",
|
|
2166
|
+
ctx.mergeBase,
|
|
2167
|
+
() => merge(worktreePath, ctx.upstreamRef, { noCommit: true, noEdit: true })
|
|
2168
|
+
);
|
|
2104
2169
|
onStep?.("merge complete", "upstream merged into worktree");
|
|
2105
2170
|
onProgress?.("analyzing files...");
|
|
2106
2171
|
const analyzedFiles = await analyzeRefs(
|
|
@@ -3022,7 +3087,7 @@ import {
|
|
|
3022
3087
|
import { select as select2 } from "@inquirer/prompts";
|
|
3023
3088
|
|
|
3024
3089
|
// src/services/contrib-core.ts
|
|
3025
|
-
import { dirname as
|
|
3090
|
+
import { dirname as dirname3, join as join12, resolve as resolve3 } from "path";
|
|
3026
3091
|
async function listFilesAtRef(repoPath, ref) {
|
|
3027
3092
|
const raw = await git(["ls-tree", "-r", "--name-only", ref], repoPath);
|
|
3028
3093
|
return new Set(raw.split("\n").filter(Boolean));
|
|
@@ -3038,10 +3103,10 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
|
|
|
3038
3103
|
const baseFiles = await listFilesAtRef(repoPath, baseRef);
|
|
3039
3104
|
const baseDirs = /* @__PURE__ */ new Set(["."]);
|
|
3040
3105
|
for (const f of baseFiles) {
|
|
3041
|
-
let dir =
|
|
3106
|
+
let dir = dirname3(f);
|
|
3042
3107
|
while (dir !== ".") {
|
|
3043
3108
|
baseDirs.add(dir);
|
|
3044
|
-
dir =
|
|
3109
|
+
dir = dirname3(dir);
|
|
3045
3110
|
}
|
|
3046
3111
|
}
|
|
3047
3112
|
const files = [];
|
|
@@ -3054,7 +3119,7 @@ async function detectContributableFiles(repoPath, baseRef, forkRef, config, fork
|
|
|
3054
3119
|
if (inCella && !inFork) {
|
|
3055
3120
|
kind = "deleted";
|
|
3056
3121
|
} else if (!inCella && inFork) {
|
|
3057
|
-
if (!baseDirs.has(
|
|
3122
|
+
if (!baseDirs.has(dirname3(file.path))) continue;
|
|
3058
3123
|
kind = "created";
|
|
3059
3124
|
} else {
|
|
3060
3125
|
kind = "modified";
|
|
@@ -3085,7 +3150,8 @@ async function readBlobInfo(repoPath, ref, filePath) {
|
|
|
3085
3150
|
}
|
|
3086
3151
|
async function buildContribBranch(repoPath, baseRef, forkRef, detection, forkName) {
|
|
3087
3152
|
const branch = `contrib/${forkName}`;
|
|
3088
|
-
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, "_")}`);
|
|
3089
3155
|
const indexEnv = { GIT_INDEX_FILE: tmpIndex };
|
|
3090
3156
|
try {
|
|
3091
3157
|
await git(["read-tree", baseRef], repoPath, { env: indexEnv });
|
|
@@ -3129,21 +3195,25 @@ ${commitBody}`;
|
|
|
3129
3195
|
|
|
3130
3196
|
// src/services/fork-utils.ts
|
|
3131
3197
|
import { existsSync as existsSync7 } from "fs";
|
|
3132
|
-
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
|
+
}
|
|
3133
3203
|
function validateForkPath(fork, basePath, requireConfig = false) {
|
|
3134
|
-
const resolvedPath =
|
|
3135
|
-
if (!resolvedPath.startsWith(
|
|
3204
|
+
const resolvedPath = resolve4(basePath, fork.localPath);
|
|
3205
|
+
if (!resolvedPath.startsWith(resolve4(basePath, ".."))) {
|
|
3136
3206
|
return { fork, resolvedPath, valid: false, error: "path resolves outside parent directory" };
|
|
3137
3207
|
}
|
|
3138
3208
|
if (!existsSync7(resolvedPath)) return { fork, resolvedPath, valid: false, error: "path does not exist" };
|
|
3139
3209
|
if (!existsSync7(`${resolvedPath}/.git`)) return { fork, resolvedPath, valid: false, error: "not a git repository" };
|
|
3140
|
-
if (requireConfig && !existsSync7(`${resolvedPath}
|
|
3141
|
-
return { fork, resolvedPath, valid: false, error:
|
|
3210
|
+
if (requireConfig && !existsSync7(`${resolvedPath}/${CONFIG_FILE}`)) {
|
|
3211
|
+
return { fork, resolvedPath, valid: false, error: `missing ${CONFIG_FILE}` };
|
|
3142
3212
|
}
|
|
3143
3213
|
return { fork, resolvedPath, valid: true };
|
|
3144
3214
|
}
|
|
3145
3215
|
function printNoForksHint(action) {
|
|
3146
|
-
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"));
|
|
3147
3217
|
console.info(colors_default.dim(action));
|
|
3148
3218
|
console.info(colors_default.dim(` forks: [{ name: 'my-app', localPath: '../my-app', pullBranch: 'development' }]`));
|
|
3149
3219
|
}
|
|
@@ -3312,7 +3382,8 @@ async function runContributions(config) {
|
|
|
3312
3382
|
);
|
|
3313
3383
|
console.info("");
|
|
3314
3384
|
}
|
|
3315
|
-
const
|
|
3385
|
+
const forkBasePath = await resolveForkBasePath(config.forkPath);
|
|
3386
|
+
const validated = forks.map((fork2) => validateForkPath(fork2, forkBasePath));
|
|
3316
3387
|
const validForks = validated.filter((v) => v.valid);
|
|
3317
3388
|
let selectedFork;
|
|
3318
3389
|
if (config.fork) {
|
|
@@ -3514,12 +3585,59 @@ async function runContributions(config) {
|
|
|
3514
3585
|
}
|
|
3515
3586
|
|
|
3516
3587
|
// src/services/forks.ts
|
|
3517
|
-
import { resolve as
|
|
3588
|
+
import { resolve as resolve5 } from "path";
|
|
3518
3589
|
import process8 from "process";
|
|
3519
|
-
import { Separator as Separator2, select as
|
|
3590
|
+
import { Separator as Separator2, select as select4 } from "@inquirer/prompts";
|
|
3520
3591
|
|
|
3521
3592
|
// src/services/sync.ts
|
|
3593
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3594
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
3595
|
+
|
|
3596
|
+
// src/utils/gh.ts
|
|
3522
3597
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3598
|
+
function ghAvailable() {
|
|
3599
|
+
return spawnSync2("gh", ["--version"], { stdio: "ignore" }).status === 0;
|
|
3600
|
+
}
|
|
3601
|
+
function runGh(cwd, args) {
|
|
3602
|
+
const result = spawnSync2("gh", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
3603
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
3604
|
+
return { ok: result.status === 0, output };
|
|
3605
|
+
}
|
|
3606
|
+
function filterSyncPrs(prs, prefix) {
|
|
3607
|
+
return prs.filter((pr) => pr.headRefName.startsWith(`${prefix}/`));
|
|
3608
|
+
}
|
|
3609
|
+
function listOpenSyncPrs(cwd, prefix) {
|
|
3610
|
+
const { ok, output } = runGh(cwd, [
|
|
3611
|
+
"pr",
|
|
3612
|
+
"list",
|
|
3613
|
+
"--state",
|
|
3614
|
+
"open",
|
|
3615
|
+
"--json",
|
|
3616
|
+
"number,title,headRefName,url",
|
|
3617
|
+
"--limit",
|
|
3618
|
+
"50"
|
|
3619
|
+
]);
|
|
3620
|
+
if (!ok || !output) return [];
|
|
3621
|
+
let parsed;
|
|
3622
|
+
try {
|
|
3623
|
+
parsed = JSON.parse(output);
|
|
3624
|
+
} catch {
|
|
3625
|
+
return [];
|
|
3626
|
+
}
|
|
3627
|
+
return filterSyncPrs(parsed, prefix).sort((a, b) => b.number - a.number);
|
|
3628
|
+
}
|
|
3629
|
+
function buildMergeArgs(ref, options = {}) {
|
|
3630
|
+
const args = ["pr", "merge", ref, "--squash"];
|
|
3631
|
+
if (options.auto) args.push("--auto");
|
|
3632
|
+
if (options.deleteBranch) args.push("--delete-branch");
|
|
3633
|
+
return args;
|
|
3634
|
+
}
|
|
3635
|
+
function mergePrSquash(cwd, ref, options = {}) {
|
|
3636
|
+
return runGh(cwd, buildMergeArgs(String(ref), options));
|
|
3637
|
+
}
|
|
3638
|
+
function closePr(cwd, ref) {
|
|
3639
|
+
return runGh(cwd, ["pr", "close", String(ref)]);
|
|
3640
|
+
}
|
|
3523
3641
|
|
|
3524
3642
|
// src/services/packages.ts
|
|
3525
3643
|
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -3859,9 +3977,6 @@ function printFinishSteps() {
|
|
|
3859
3977
|
function hasStagedSyncChanges(result) {
|
|
3860
3978
|
return result.files.some((file) => ["behind", "diverged", "renamed", "ignored", "pinned"].includes(file.status));
|
|
3861
3979
|
}
|
|
3862
|
-
function ghAvailable() {
|
|
3863
|
-
return spawnSync2("gh", ["--version"], { stdio: "ignore" }).status === 0;
|
|
3864
|
-
}
|
|
3865
3980
|
function extractFirstUrl(output) {
|
|
3866
3981
|
return output.match(/https?:\/\/\S+/)?.[0];
|
|
3867
3982
|
}
|
|
@@ -3878,12 +3993,25 @@ async function flattenSyncBranch(forkPath, branch, base) {
|
|
|
3878
3993
|
await flattenBranch(forkPath, base, message);
|
|
3879
3994
|
return true;
|
|
3880
3995
|
}
|
|
3996
|
+
function indentLines(text) {
|
|
3997
|
+
return text.split("\n").map((line) => ` ${line}`).join("\n");
|
|
3998
|
+
}
|
|
3999
|
+
function enableAutoMerge(forkPath, branch) {
|
|
4000
|
+
console.info(colors_default.dim("enabling auto-merge (squash) once checks pass..."));
|
|
4001
|
+
const merge2 = mergePrSquash(forkPath, branch, { auto: true, deleteBranch: true });
|
|
4002
|
+
if (merge2.ok) return true;
|
|
4003
|
+
console.info(colors_default.yellow("could not enable auto-merge (is it enabled for the repo?). merge it manually:"));
|
|
4004
|
+
if (merge2.output) console.info(colors_default.dim(indentLines(merge2.output)));
|
|
4005
|
+
console.info(colors_default.dim(` gh pr merge ${branch} --squash --delete-branch`));
|
|
4006
|
+
return false;
|
|
4007
|
+
}
|
|
3881
4008
|
async function shipSyncBranch(config, branch) {
|
|
3882
4009
|
const { forkPath, settings } = config;
|
|
3883
4010
|
const base = resolveReleaseBase(settings);
|
|
3884
4011
|
const flattened = await flattenSyncBranch(forkPath, branch, base);
|
|
3885
4012
|
let prUrl;
|
|
3886
4013
|
let prOpened = false;
|
|
4014
|
+
let autoMergeEnabled = false;
|
|
3887
4015
|
const headSubject = (await getCommitInfo(forkPath, "HEAD").catch(() => null))?.message;
|
|
3888
4016
|
const prTitle = headSubject?.startsWith(SYNC_PR_TITLE) ? headSubject : SYNC_PR_TITLE;
|
|
3889
4017
|
console.info(colors_default.dim(`pushing '${branch}' to origin...`));
|
|
@@ -3899,7 +4027,7 @@ async function shipSyncBranch(config, branch) {
|
|
|
3899
4027
|
console.info(colors_default.dim("opening a pull request..."));
|
|
3900
4028
|
const prBody = await buildSyncPrBodyForBranch(forkPath, base);
|
|
3901
4029
|
const bodyArgs = prBody ? ["--body", prBody] : ["--fill"];
|
|
3902
|
-
const pr =
|
|
4030
|
+
const pr = spawnSync3("gh", ["pr", "create", "--base", base, "--head", branch, "--title", prTitle, ...bodyArgs], {
|
|
3903
4031
|
cwd: forkPath,
|
|
3904
4032
|
encoding: "utf-8",
|
|
3905
4033
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3907,7 +4035,9 @@ async function shipSyncBranch(config, branch) {
|
|
|
3907
4035
|
prUrl = extractFirstUrl(`${pr.stdout ?? ""}
|
|
3908
4036
|
${pr.stderr ?? ""}`);
|
|
3909
4037
|
prOpened = pr.status === 0;
|
|
3910
|
-
if (
|
|
4038
|
+
if (prOpened) {
|
|
4039
|
+
if (config.directMerge) autoMergeEnabled = enableAutoMerge(forkPath, branch);
|
|
4040
|
+
} else {
|
|
3911
4041
|
console.info(colors_default.yellow("could not open the PR automatically (it may already exist). open it with:"));
|
|
3912
4042
|
if (prUrl) console.info(colors_default.dim(` ${prUrl}`));
|
|
3913
4043
|
printPrCreateStep(branch, base, prTitle);
|
|
@@ -3923,7 +4053,8 @@ ${pr.stderr ?? ""}`);
|
|
|
3923
4053
|
console.info();
|
|
3924
4054
|
if (prUrl) {
|
|
3925
4055
|
console.info(`${colors_default.green("\u2713")} Sync pull request ${prOpened ? "opened" : "ready"}`);
|
|
3926
|
-
|
|
4056
|
+
const status = autoMergeEnabled ? `auto-merge on \u2014 squashes into '${base}' when checks pass` : `branch pushed, back on '${base}'`;
|
|
4057
|
+
console.info(colors_default.dim(` ${prUrl} \xB7 ${status}`));
|
|
3927
4058
|
} else {
|
|
3928
4059
|
console.info(`${colors_default.green("\u2713")} Sync branch pushed`);
|
|
3929
4060
|
console.info(colors_default.dim(` '${branch}' is on origin, back on '${base}'`));
|
|
@@ -3932,7 +4063,7 @@ ${pr.stderr ?? ""}`);
|
|
|
3932
4063
|
function finalizeWorkspace(forkPath) {
|
|
3933
4064
|
for (const args of [["install"], ["check"]]) {
|
|
3934
4065
|
console.info(colors_default.dim(`running pnpm ${args.join(" ")}...`));
|
|
3935
|
-
const result =
|
|
4066
|
+
const result = spawnSync3("pnpm", args, { cwd: forkPath, stdio: "inherit" });
|
|
3936
4067
|
if (result.status !== 0) return false;
|
|
3937
4068
|
}
|
|
3938
4069
|
return true;
|
|
@@ -3977,6 +4108,64 @@ async function resumeSyncMerge(config, branch) {
|
|
|
3977
4108
|
console.info(colors_default.green(`committed the sync on '${branch}' as '${message}'.`));
|
|
3978
4109
|
await shipSyncBranch(config, branch);
|
|
3979
4110
|
}
|
|
4111
|
+
async function mergeOpenSyncPrs(config, open) {
|
|
4112
|
+
const { forkPath, settings } = config;
|
|
4113
|
+
const base = resolveReleaseBase(settings);
|
|
4114
|
+
const [newest, ...superseded] = open;
|
|
4115
|
+
console.info();
|
|
4116
|
+
console.info(colors_default.dim(`squash-merging #${newest.number} into '${base}'...`));
|
|
4117
|
+
const merged = mergePrSquash(forkPath, newest.number, { deleteBranch: true });
|
|
4118
|
+
if (!merged.ok) {
|
|
4119
|
+
console.info();
|
|
4120
|
+
console.info(colors_default.yellow(`could not merge #${newest.number} \u2014 resolve it first, then re-run \`pnpm cella sync\`:`));
|
|
4121
|
+
if (merged.output) console.info(colors_default.dim(indentLines(merged.output)));
|
|
4122
|
+
console.info(colors_default.dim(` ${newest.url}`));
|
|
4123
|
+
return "cancel";
|
|
4124
|
+
}
|
|
4125
|
+
console.info(`${colors_default.green("\u2713")} merged #${newest.number}`);
|
|
4126
|
+
await deleteBranch(forkPath, newest.headRefName);
|
|
4127
|
+
for (const pr of superseded) {
|
|
4128
|
+
console.info(colors_default.dim(`closing #${pr.number} \u2014 its changes are included in #${newest.number}...`));
|
|
4129
|
+
closePr(forkPath, pr.number);
|
|
4130
|
+
await deleteBranch(forkPath, pr.headRefName);
|
|
4131
|
+
}
|
|
4132
|
+
console.info(colors_default.dim(`switching to '${base}' and fast-forwarding...`));
|
|
4133
|
+
await switchBranch(forkPath, base);
|
|
4134
|
+
await pullFastForward(forkPath);
|
|
4135
|
+
return "continue";
|
|
4136
|
+
}
|
|
4137
|
+
async function guardAgainstOpenSyncPr(config) {
|
|
4138
|
+
const { forkPath } = config;
|
|
4139
|
+
if (!ghAvailable() || !process.stdout.isTTY) return "continue";
|
|
4140
|
+
const open = listOpenSyncPrs(forkPath, DEFAULT_SYNC_PREFIX);
|
|
4141
|
+
if (open.length === 0) return "continue";
|
|
4142
|
+
const many = open.length > 1;
|
|
4143
|
+
console.info();
|
|
4144
|
+
console.info(
|
|
4145
|
+
`${warningMark} ${colors_default.yellow(`${many ? `${open.length} earlier sync PRs are` : "an earlier sync PR is"} still open and unmerged:`)}`
|
|
4146
|
+
);
|
|
4147
|
+
for (const pr of open) {
|
|
4148
|
+
console.info(colors_default.dim(` #${pr.number} ${pr.title}`));
|
|
4149
|
+
console.info(colors_default.dim(` ${pr.url}`));
|
|
4150
|
+
}
|
|
4151
|
+
console.info(colors_default.dim(` starting a new sync now produces a PR that re-includes ${many ? "these" : "those"} changes.`));
|
|
4152
|
+
console.info();
|
|
4153
|
+
const choice = await select3({
|
|
4154
|
+
message: "how do you want to proceed?",
|
|
4155
|
+
choices: [
|
|
4156
|
+
{
|
|
4157
|
+
value: "merge",
|
|
4158
|
+
name: `merge ${many ? "them" : `#${open[0].number}`} first, then sync ${colors_default.dim("(recommended)")}`
|
|
4159
|
+
},
|
|
4160
|
+
{ value: "continue", name: `continue anyway ${colors_default.dim("(new PR will re-include the unmerged changes)")}` },
|
|
4161
|
+
{ value: "cancel", name: colors_default.red("cancel") }
|
|
4162
|
+
],
|
|
4163
|
+
loop: false
|
|
4164
|
+
});
|
|
4165
|
+
if (choice === "cancel") return "cancel";
|
|
4166
|
+
if (choice === "continue") return "continue";
|
|
4167
|
+
return mergeOpenSyncPrs(config, open);
|
|
4168
|
+
}
|
|
3980
4169
|
async function runSyncCommand(config) {
|
|
3981
4170
|
const { forkPath } = config;
|
|
3982
4171
|
const currentBranch = await getCurrentBranch(forkPath);
|
|
@@ -4000,6 +4189,7 @@ commit them first (\`git commit --amend --no-edit\` or a new commit).`
|
|
|
4000
4189
|
return;
|
|
4001
4190
|
}
|
|
4002
4191
|
await assertClean(forkPath);
|
|
4192
|
+
if (await guardAgainstOpenSyncPr(config) === "cancel") return;
|
|
4003
4193
|
const outcome = await runSyncCycle(config);
|
|
4004
4194
|
console.info();
|
|
4005
4195
|
if (outcome.status === "noop") {
|
|
@@ -4101,14 +4291,15 @@ async function runForks(config) {
|
|
|
4101
4291
|
console.error(colors_default.red(`fork '${config.fork}' not found in config`));
|
|
4102
4292
|
return;
|
|
4103
4293
|
}
|
|
4104
|
-
const resolvedPath =
|
|
4294
|
+
const resolvedPath = resolve5(await resolveForkBasePath(config.forkPath), match.localPath);
|
|
4105
4295
|
await syncFork(config, match, resolvedPath);
|
|
4106
4296
|
return;
|
|
4107
4297
|
}
|
|
4298
|
+
const forkBasePath = await resolveForkBasePath(config.forkPath);
|
|
4108
4299
|
while (true) {
|
|
4109
|
-
const choices = await buildForkChoices(forks,
|
|
4300
|
+
const choices = await buildForkChoices(forks, forkBasePath);
|
|
4110
4301
|
const forkChoices = [...choices, new Separator2("\u2500".repeat(40)), { value: "_exit", name: colors_default.dim("exit") }];
|
|
4111
|
-
const selectedPath = await
|
|
4302
|
+
const selectedPath = await select4({
|
|
4112
4303
|
message: "select fork to sync:",
|
|
4113
4304
|
choices: forkChoices,
|
|
4114
4305
|
loop: false
|
|
@@ -4116,7 +4307,7 @@ async function runForks(config) {
|
|
|
4116
4307
|
if (selectedPath === "_exit") {
|
|
4117
4308
|
process8.exit(0);
|
|
4118
4309
|
}
|
|
4119
|
-
const resolvedForkPath =
|
|
4310
|
+
const resolvedForkPath = resolve5(forkBasePath, selectedPath);
|
|
4120
4311
|
const selectedFork = forks.find((f) => f.localPath === selectedPath);
|
|
4121
4312
|
if (!selectedFork) {
|
|
4122
4313
|
console.error(colors_default.red(`fork '${selectedPath}' not found in config`));
|
|
@@ -4132,7 +4323,7 @@ import { readFile as readFile2 } from "fs/promises";
|
|
|
4132
4323
|
import { join as join16 } from "path";
|
|
4133
4324
|
|
|
4134
4325
|
// src/utils/coverage-utils.ts
|
|
4135
|
-
import { spawnSync as
|
|
4326
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4136
4327
|
import * as fs3 from "fs";
|
|
4137
4328
|
import * as path3 from "path";
|
|
4138
4329
|
var STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -4155,7 +4346,7 @@ function formatAge(ms) {
|
|
|
4155
4346
|
}
|
|
4156
4347
|
function refreshCoverage(forkPath) {
|
|
4157
4348
|
console.info(colors_default.dim("running `pnpm test` (this spins up Docker and runs the full suite)..."));
|
|
4158
|
-
const result =
|
|
4349
|
+
const result = spawnSync4("pnpm", ["test"], { cwd: forkPath, stdio: "inherit" });
|
|
4159
4350
|
return result.status === 0;
|
|
4160
4351
|
}
|
|
4161
4352
|
function printCoverageSummary(forkPath, options = {}) {
|
|
@@ -4230,6 +4421,9 @@ function classifyFile(filePath) {
|
|
|
4230
4421
|
if (filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/tests/") || filePath.includes("/test/") || filePath.startsWith("tests/") || filePath.includes("/__tests__/")) {
|
|
4231
4422
|
return "test";
|
|
4232
4423
|
}
|
|
4424
|
+
if (filePath.includes(".stories.")) {
|
|
4425
|
+
return "stories";
|
|
4426
|
+
}
|
|
4233
4427
|
if (filePath.includes(".gen.") || filePath.includes(".gen/") || filePath.includes("/gen/") || filePath.startsWith("drizzle/") || filePath.includes("/drizzle/") || filePath.endsWith(".d.ts") || filePath.includes(".tsbuildinfo")) {
|
|
4234
4428
|
return "generated";
|
|
4235
4429
|
}
|
|
@@ -4270,6 +4464,7 @@ async function collectStats(forkPath) {
|
|
|
4270
4464
|
const skipped = allFilesCount - entries.length;
|
|
4271
4465
|
const emptyCat = () => ({
|
|
4272
4466
|
test: { files: 0, loc: 0 },
|
|
4467
|
+
stories: { files: 0, loc: 0 },
|
|
4273
4468
|
generated: { files: 0, loc: 0 },
|
|
4274
4469
|
json: { files: 0, loc: 0 },
|
|
4275
4470
|
other: { files: 0, loc: 0 }
|
|
@@ -4322,6 +4517,7 @@ function printStats(stats, verbose) {
|
|
|
4322
4517
|
const catEntries = [
|
|
4323
4518
|
["source & config", stats.categories.other],
|
|
4324
4519
|
["test", stats.categories.test],
|
|
4520
|
+
["stories", stats.categories.stories],
|
|
4325
4521
|
["generated", stats.categories.generated],
|
|
4326
4522
|
["json", stats.categories.json]
|
|
4327
4523
|
];
|
|
@@ -4334,7 +4530,7 @@ function printStats(stats, verbose) {
|
|
|
4334
4530
|
console.info(DIVIDER);
|
|
4335
4531
|
console.info();
|
|
4336
4532
|
if (verbose) {
|
|
4337
|
-
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)}`;
|
|
4338
4534
|
console.info(colors_default.dim(header));
|
|
4339
4535
|
for (const [name, data] of Object.entries(stats.packages)) {
|
|
4340
4536
|
const c = data.categories;
|
|
@@ -4342,7 +4538,7 @@ function printStats(stats, verbose) {
|
|
|
4342
4538
|
const srcLoc = c.other.loc;
|
|
4343
4539
|
if (srcFiles === 0) continue;
|
|
4344
4540
|
console.info(
|
|
4345
|
-
` ${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))}`
|
|
4346
4542
|
);
|
|
4347
4543
|
}
|
|
4348
4544
|
} else {
|
|
@@ -4374,7 +4570,7 @@ async function runStats(forkPath, options = {}) {
|
|
|
4374
4570
|
function getForkPath() {
|
|
4375
4571
|
const envPath = getEnv("CELLA_FORK_PATH");
|
|
4376
4572
|
if (envPath) {
|
|
4377
|
-
const resolved =
|
|
4573
|
+
const resolved = resolve6(envPath);
|
|
4378
4574
|
if (!existsSync10(resolved) || !statSync2(resolved).isDirectory()) {
|
|
4379
4575
|
throw new Error(`CELLA_FORK_PATH is not a valid directory: ${resolved}`);
|
|
4380
4576
|
}
|
|
@@ -4382,7 +4578,7 @@ function getForkPath() {
|
|
|
4382
4578
|
}
|
|
4383
4579
|
let cwd = process9.cwd();
|
|
4384
4580
|
if (cwd.endsWith("/cli/cella") || cwd.endsWith("\\cli\\cella")) {
|
|
4385
|
-
cwd =
|
|
4581
|
+
cwd = resolve6(cwd, "../..");
|
|
4386
4582
|
}
|
|
4387
4583
|
return cwd;
|
|
4388
4584
|
}
|