worktree-add 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -22
- package/dist/app/get-unsafe-app-name-reason.d.ts +11 -0
- package/dist/app/get-unsafe-app-name-reason.js +21 -0
- package/dist/app/resolve-apps.d.ts +6 -0
- package/dist/app/resolve-apps.js +31 -0
- package/dist/cli/cleanup-worktree.d.ts +2 -0
- package/dist/cli/cleanup-worktree.js +13 -0
- package/dist/cli/open-worktree-apps.d.ts +5 -0
- package/dist/cli/open-worktree-apps.js +33 -0
- package/dist/cli/register-sigint-handler.d.ts +8 -0
- package/dist/cli/register-sigint-handler.js +19 -0
- package/dist/cli/resolve-worktree-context.d.ts +6 -0
- package/dist/cli/resolve-worktree-context.js +22 -0
- package/dist/cli/run-worktree-add.d.ts +9 -0
- package/dist/cli/run-worktree-add.js +120 -0
- package/dist/cli.d.ts +0 -6
- package/dist/cli.js +38 -76
- package/dist/git/create-worktree.d.ts +9 -0
- package/dist/git/create-worktree.js +39 -0
- package/dist/git/extract-diagnostic-line.d.ts +1 -0
- package/dist/git/extract-diagnostic-line.js +13 -0
- package/dist/git/fetch-remote-branch.d.ts +21 -0
- package/dist/git/fetch-remote-branch.js +106 -0
- package/dist/git/git.d.ts +11 -5
- package/dist/git/git.js +25 -17
- package/dist/git/remove-worktree.d.ts +1 -0
- package/dist/git/remove-worktree.js +4 -0
- package/dist/output/create-status-logger.d.ts +13 -0
- package/dist/output/create-status-logger.js +32 -0
- package/dist/output/get-status-logger.d.ts +2 -0
- package/dist/output/get-status-logger.js +12 -0
- package/dist/project/install-commands.js +2 -2
- package/dist/project/next.d.ts +4 -1
- package/dist/project/next.js +2 -2
- package/dist/project/package-manager.d.ts +7 -1
- package/dist/project/package-manager.js +12 -3
- package/dist/project/setup.d.ts +5 -1
- package/dist/project/setup.js +15 -7
- package/dist/worktree/destination-directory.d.ts +10 -3
- package/dist/worktree/destination-directory.js +94 -16
- package/dist/worktree/untracked-file-copy.d.ts +5 -1
- package/dist/worktree/untracked-file-copy.js +51 -9
- package/package.json +4 -2
- package/dist/git/parse-branch-head-mismatch-choice.d.ts +0 -7
- package/dist/git/parse-branch-head-mismatch-choice.js +0 -26
- package/dist/git/resolve-branch-head-mismatch.d.ts +0 -6
- package/dist/git/resolve-branch-head-mismatch.js +0 -79
- package/dist/git/worktree-creation.d.ts +0 -13
- package/dist/git/worktree-creation.js +0 -35
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getStatusLogger } from "../output/get-status-logger.js";
|
|
2
|
+
import { git, localBranchExists, normalizeBranchName, remoteBranchExists, } from "./git.js";
|
|
3
|
+
import { extractDiagnosticLine } from "./extract-diagnostic-line.js";
|
|
4
|
+
/**
|
|
5
|
+
* Create a worktree for the given branch at the specified destination.
|
|
6
|
+
*/
|
|
7
|
+
export function createWorktree(branch, destinationDirectory, options) {
|
|
8
|
+
const logger = getStatusLogger(options?.logger);
|
|
9
|
+
const dryRun = options?.dryRun ?? false;
|
|
10
|
+
const normalized = normalizeBranchName(branch);
|
|
11
|
+
if (localBranchExists(normalized)) {
|
|
12
|
+
logger.step(`${dryRun ? "Would run " : ""}git worktree add -- ${destinationDirectory} refs/heads/${normalized}`);
|
|
13
|
+
if (dryRun)
|
|
14
|
+
return;
|
|
15
|
+
git("worktree", "add", "--", destinationDirectory, `refs/heads/${normalized}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let branchExistsOnOrigin = options?.remoteBranchExists;
|
|
19
|
+
if (branchExistsOnOrigin === undefined) {
|
|
20
|
+
try {
|
|
21
|
+
branchExistsOnOrigin = remoteBranchExists(normalized);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const diagnostic = extractDiagnosticLine(error);
|
|
25
|
+
throw new Error(`Failed to reach origin to check whether '${normalized}' exists: ${diagnostic}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (branchExistsOnOrigin) {
|
|
29
|
+
logger.step(`${dryRun ? "Would run " : ""}git worktree add --track -b ${normalized} -- ${destinationDirectory} origin/${normalized}`);
|
|
30
|
+
if (dryRun)
|
|
31
|
+
return;
|
|
32
|
+
git("worktree", "add", "--track", "-b", normalized, "--", destinationDirectory, `origin/${normalized}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
logger.step(`${dryRun ? "Would run " : ""}git worktree add -b ${normalized} -- ${destinationDirectory}`);
|
|
36
|
+
if (dryRun)
|
|
37
|
+
return;
|
|
38
|
+
git("worktree", "add", "-b", normalized, "--", destinationDirectory);
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function extractDiagnosticLine(error: unknown): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function extractDiagnosticLine(error) {
|
|
2
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3
|
+
const trimmed = message.trim();
|
|
4
|
+
const nonEmptyLines = trimmed
|
|
5
|
+
.split("\n")
|
|
6
|
+
.map((line) => line.trim())
|
|
7
|
+
.filter(Boolean);
|
|
8
|
+
return (
|
|
9
|
+
// Best-effort: match English prefixes; localized messages fall back to the last non-empty line.
|
|
10
|
+
nonEmptyLines.find((line) => /(?:^|\s)(?:fatal:|error:)/iu.test(line)) ??
|
|
11
|
+
nonEmptyLines.at(-1) ??
|
|
12
|
+
trimmed);
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StatusLogger } from "../output/create-status-logger.js";
|
|
2
|
+
type RemoteBranchStatus = "exists" | "missing" | "unknown" | "diverged";
|
|
3
|
+
type BranchDivergence = {
|
|
4
|
+
ahead: number;
|
|
5
|
+
behind: number;
|
|
6
|
+
};
|
|
7
|
+
type NonDivergedRemoteBranchStatus = Exclude<RemoteBranchStatus, "diverged">;
|
|
8
|
+
export type FetchRemoteBranchResult = {
|
|
9
|
+
status: "diverged";
|
|
10
|
+
localExists: true;
|
|
11
|
+
divergence: BranchDivergence;
|
|
12
|
+
} | {
|
|
13
|
+
status: NonDivergedRemoteBranchStatus;
|
|
14
|
+
localExists: boolean;
|
|
15
|
+
divergence: undefined;
|
|
16
|
+
};
|
|
17
|
+
export declare function fetchRemoteBranch(branch: string, options?: {
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
logger?: StatusLogger;
|
|
20
|
+
}): FetchRemoteBranchResult;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { getStatusLogger } from "../output/get-status-logger.js";
|
|
2
|
+
import { fetchOriginBranch, getAheadBehindCounts, getLocalBranchHead, getRemoteBranchHead, git, findWorktreeByBranchName, localBranchExists, normalizeBranchName, remoteBranchExists, } from "./git.js";
|
|
3
|
+
import { extractDiagnosticLine } from "./extract-diagnostic-line.js";
|
|
4
|
+
export function fetchRemoteBranch(branch, options) {
|
|
5
|
+
const logger = getStatusLogger(options?.logger);
|
|
6
|
+
const dryRun = options?.dryRun ?? false;
|
|
7
|
+
const normalized = normalizeBranchName(branch);
|
|
8
|
+
const localExists = localBranchExists(normalized);
|
|
9
|
+
if (localExists) {
|
|
10
|
+
let remoteExists;
|
|
11
|
+
try {
|
|
12
|
+
remoteExists = remoteBranchExists(normalized);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const diagnostic = extractDiagnosticLine(error);
|
|
16
|
+
logger.warn(`Failed to query origin for '${normalized}': ${diagnostic}. Using existing local branch. (If you expected a remote branch, double-check your network connection and branch name.)`);
|
|
17
|
+
return { status: "unknown", localExists, divergence: undefined };
|
|
18
|
+
}
|
|
19
|
+
if (!remoteExists) {
|
|
20
|
+
return { status: "missing", localExists, divergence: undefined };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
if (dryRun) {
|
|
24
|
+
logger.step(`Would fetch origin/${normalized}`);
|
|
25
|
+
logger.detail(`Dry run: local '${normalized}' may be fast-forwarded after fetch if it is behind origin/${normalized}.`);
|
|
26
|
+
logger.detail("Dry run does not check local/remote divergence because it skips fetch; a real run may stop with a divergence error.");
|
|
27
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
28
|
+
}
|
|
29
|
+
logger.step(`Fetching origin/${normalized} …`);
|
|
30
|
+
fetchOriginBranch(normalized);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const diagnostic = extractDiagnosticLine(error);
|
|
34
|
+
logger.warn(`Failed to fetch origin/${normalized}: ${diagnostic}. Using existing local branch (origin status unknown).`);
|
|
35
|
+
return { status: "unknown", localExists, divergence: undefined };
|
|
36
|
+
}
|
|
37
|
+
const localHead = getLocalBranchHead(normalized);
|
|
38
|
+
const remoteHead = getRemoteBranchHead(normalized);
|
|
39
|
+
if (!localHead || !remoteHead || localHead === remoteHead) {
|
|
40
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
41
|
+
}
|
|
42
|
+
let counts;
|
|
43
|
+
try {
|
|
44
|
+
counts = getAheadBehindCounts(localHead, remoteHead);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const diagnostic = extractDiagnosticLine(error);
|
|
48
|
+
logger.warn(`Failed to compare local '${normalized}' with origin/${normalized}: ${diagnostic}. Using existing local branch as-is.`);
|
|
49
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
50
|
+
}
|
|
51
|
+
const { ahead, behind } = counts;
|
|
52
|
+
if (ahead === 0 && behind > 0) {
|
|
53
|
+
// Note: dryRun returns before fetching, so this path only runs when dryRun is false.
|
|
54
|
+
// resolveWorktreeContext already blocks this, but keep as a defensive check.
|
|
55
|
+
const activeWorktree = findWorktreeByBranchName(normalized);
|
|
56
|
+
if (activeWorktree) {
|
|
57
|
+
logger.warn(`Local branch '${normalized}' is checked out in ${activeWorktree}; skipping fast-forward.`);
|
|
58
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
59
|
+
}
|
|
60
|
+
logger.detail(`Fast-forwarding '${normalized}' from ${localHead} to ${remoteHead}.`);
|
|
61
|
+
logger.step(`Fast-forwarding local '${normalized}' to origin/${normalized} …`);
|
|
62
|
+
git("branch", "-f", "--", normalized, `origin/${normalized}`);
|
|
63
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
64
|
+
}
|
|
65
|
+
if (ahead === 0 && behind === 0) {
|
|
66
|
+
// Defensive: rev-list should report differences when heads differ,
|
|
67
|
+
// but shallow/corrupted refs can yield 0/0 counts.
|
|
68
|
+
logger.warn(`Local branch '${normalized}' differs from origin/${normalized}, but Git reports no ahead/behind differences. Using existing local branch; if you encounter issues, try re-cloning the repository.`);
|
|
69
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
70
|
+
}
|
|
71
|
+
if (ahead > 0 && behind > 0) {
|
|
72
|
+
return {
|
|
73
|
+
status: "diverged",
|
|
74
|
+
localExists,
|
|
75
|
+
divergence: { ahead, behind },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
logger.warn(`Local branch '${normalized}' is ahead of origin/${normalized} (ahead by ${ahead}); using existing local branch as-is.`);
|
|
79
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
80
|
+
}
|
|
81
|
+
let remoteExists;
|
|
82
|
+
try {
|
|
83
|
+
remoteExists = remoteBranchExists(normalized);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const diagnostic = extractDiagnosticLine(error);
|
|
87
|
+
logger.warn(`Failed to reach origin to check whether '${normalized}' exists: ${diagnostic}. (If you expected a remote branch, double-check your network connection and branch name.)`);
|
|
88
|
+
return { status: "unknown", localExists, divergence: undefined };
|
|
89
|
+
}
|
|
90
|
+
if (!remoteExists) {
|
|
91
|
+
return { status: "missing", localExists, divergence: undefined };
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
if (dryRun) {
|
|
95
|
+
logger.step(`Would fetch origin/${normalized}`);
|
|
96
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
97
|
+
}
|
|
98
|
+
logger.step(`Fetching origin/${normalized} …`);
|
|
99
|
+
fetchOriginBranch(normalized);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const diagnostic = extractDiagnosticLine(error);
|
|
103
|
+
throw new Error(`Failed to fetch origin/${normalized}: ${diagnostic}. Cannot proceed without a local branch.`);
|
|
104
|
+
}
|
|
105
|
+
return { status: "exists", localExists, divergence: undefined };
|
|
106
|
+
}
|
package/dist/git/git.d.ts
CHANGED
|
@@ -21,10 +21,16 @@ export declare function normalizeBranchName(name: string): string;
|
|
|
21
21
|
export declare function localBranchExists(branch: string): boolean;
|
|
22
22
|
/**
|
|
23
23
|
* Check if a remote branch exists on origin.
|
|
24
|
+
*
|
|
25
|
+
* Note: this function may throw on network/auth failures or when the `origin`
|
|
26
|
+
* remote is missing/misconfigured. Callers that want a degraded-mode fallback
|
|
27
|
+
* should wrap it in try/catch.
|
|
24
28
|
*/
|
|
25
29
|
export declare function remoteBranchExists(branch: string): boolean;
|
|
26
30
|
/**
|
|
27
|
-
* Fetch a branch from origin.
|
|
31
|
+
* Fetch a branch from origin into the remote-tracking ref.
|
|
32
|
+
*
|
|
33
|
+
* This does not update any local branch heads.
|
|
28
34
|
*/
|
|
29
35
|
export declare function fetchOriginBranch(branch: string): void;
|
|
30
36
|
/**
|
|
@@ -35,6 +41,10 @@ export declare function getLocalBranchHead(branch: string): string | undefined;
|
|
|
35
41
|
* Get the current head commit for a remote-tracking branch.
|
|
36
42
|
*/
|
|
37
43
|
export declare function getRemoteBranchHead(branch: string): string | undefined;
|
|
44
|
+
export declare function getAheadBehindCounts(localHead: string, remoteHead: string): {
|
|
45
|
+
ahead: number;
|
|
46
|
+
behind: number;
|
|
47
|
+
};
|
|
38
48
|
/**
|
|
39
49
|
* Convert an arbitrary string to a filesystem-safe single path segment.
|
|
40
50
|
*
|
|
@@ -50,10 +60,6 @@ export declare function fileExists(p: string): Promise<boolean>;
|
|
|
50
60
|
* Print an error message and exit the process with status 1.
|
|
51
61
|
*/
|
|
52
62
|
export declare function exitWithMessage(message: string): never;
|
|
53
|
-
/**
|
|
54
|
-
* Prompt for input and return the user's response.
|
|
55
|
-
*/
|
|
56
|
-
export declare function prompt(message: string): Promise<string>;
|
|
57
63
|
/**
|
|
58
64
|
* Prompts user for yes/no confirmation
|
|
59
65
|
* @param message The message to display to the user
|
package/dist/git/git.js
CHANGED
|
@@ -24,6 +24,8 @@ export function git(...arguments_) {
|
|
|
24
24
|
const result = spawnSync("git", gitArguments, {
|
|
25
25
|
encoding: "utf8",
|
|
26
26
|
maxBuffer: 100 * 1024 * 1024, // 100 MB to accommodate large outputs
|
|
27
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
28
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
27
29
|
...(cwd && { cwd }),
|
|
28
30
|
});
|
|
29
31
|
if (result.error)
|
|
@@ -63,30 +65,26 @@ export function localBranchExists(branch) {
|
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* Check if a remote branch exists on origin.
|
|
68
|
+
*
|
|
69
|
+
* Note: this function may throw on network/auth failures or when the `origin`
|
|
70
|
+
* remote is missing/misconfigured. Callers that want a degraded-mode fallback
|
|
71
|
+
* should wrap it in try/catch.
|
|
66
72
|
*/
|
|
67
73
|
export function remoteBranchExists(branch) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
74
|
+
const normalized = normalizeBranchName(branch);
|
|
75
|
+
// Use a fully-qualified ref to avoid option-parsing ambiguity for branch
|
|
76
|
+
// names starting with '-'.
|
|
77
|
+
return !!git("ls-remote", "--heads", "origin", `refs/heads/${normalized}`);
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
77
|
-
* Fetch a branch from origin.
|
|
80
|
+
* Fetch a branch from origin into the remote-tracking ref.
|
|
81
|
+
*
|
|
82
|
+
* This does not update any local branch heads.
|
|
78
83
|
*/
|
|
79
84
|
export function fetchOriginBranch(branch) {
|
|
80
85
|
const normalized = normalizeBranchName(branch);
|
|
81
86
|
const refspec = `+refs/heads/${normalized}:refs/remotes/origin/${normalized}`;
|
|
82
|
-
|
|
83
|
-
stdio: "inherit",
|
|
84
|
-
});
|
|
85
|
-
if (result.error)
|
|
86
|
-
throw result.error;
|
|
87
|
-
if (result.status !== 0) {
|
|
88
|
-
throw new Error(`git fetch origin ${refspec} failed`);
|
|
89
|
-
}
|
|
87
|
+
git("fetch", "origin", "--", refspec);
|
|
90
88
|
}
|
|
91
89
|
/**
|
|
92
90
|
* Get the current head commit for a local branch.
|
|
@@ -112,6 +110,16 @@ export function getRemoteBranchHead(branch) {
|
|
|
112
110
|
return undefined;
|
|
113
111
|
}
|
|
114
112
|
}
|
|
113
|
+
export function getAheadBehindCounts(localHead, remoteHead) {
|
|
114
|
+
const output = git("rev-list", "--left-right", "--count", `${localHead}...${remoteHead}`);
|
|
115
|
+
const [aheadRaw, behindRaw] = output.split(/\s+/u);
|
|
116
|
+
const ahead = Number.parseInt(aheadRaw ?? "0");
|
|
117
|
+
const behind = Number.parseInt(behindRaw ?? "0");
|
|
118
|
+
return {
|
|
119
|
+
ahead: Number.isNaN(ahead) ? 0 : ahead,
|
|
120
|
+
behind: Number.isNaN(behind) ? 0 : behind,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
115
123
|
/**
|
|
116
124
|
* Convert an arbitrary string to a filesystem-safe single path segment.
|
|
117
125
|
*
|
|
@@ -149,7 +157,7 @@ export function exitWithMessage(message) {
|
|
|
149
157
|
/**
|
|
150
158
|
* Prompt for input and return the user's response.
|
|
151
159
|
*/
|
|
152
|
-
|
|
160
|
+
async function prompt(message) {
|
|
153
161
|
const rl = readline.createInterface({
|
|
154
162
|
input: process.stdin,
|
|
155
163
|
output: process.stderr,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function removeWorktree(destinationDirectory: string): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type StatusLogger = {
|
|
2
|
+
readonly step: (message: string) => void;
|
|
3
|
+
readonly success: (message: string) => void;
|
|
4
|
+
readonly detail: (message: string) => void;
|
|
5
|
+
readonly warn: (message: string) => void;
|
|
6
|
+
};
|
|
7
|
+
type StatusLoggerOptions = {
|
|
8
|
+
readonly verbose: boolean;
|
|
9
|
+
readonly dryRun: boolean;
|
|
10
|
+
readonly decorate: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function createStatusLogger(options: StatusLoggerOptions): StatusLogger;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const emit = (message) => {
|
|
2
|
+
console.error(message);
|
|
3
|
+
};
|
|
4
|
+
export function createStatusLogger(options) {
|
|
5
|
+
const shouldLogSteps = options.verbose || options.dryRun;
|
|
6
|
+
const shouldLogDetails = options.verbose || options.dryRun;
|
|
7
|
+
const stepPrefix = options.decorate ? "➤ " : "";
|
|
8
|
+
const successPrefix = options.decorate ? "✓ " : "";
|
|
9
|
+
const warningPrefix = options.decorate ? "⚠ " : "Warning: ";
|
|
10
|
+
const detailPrefix = options.decorate ? " • " : " - ";
|
|
11
|
+
const dryRunPrefix = options.dryRun ? "DRY RUN: " : "";
|
|
12
|
+
return {
|
|
13
|
+
step(message) {
|
|
14
|
+
if (!shouldLogSteps)
|
|
15
|
+
return;
|
|
16
|
+
emit(`${dryRunPrefix}${stepPrefix}${message}`);
|
|
17
|
+
},
|
|
18
|
+
success(message) {
|
|
19
|
+
if (!options.verbose)
|
|
20
|
+
return;
|
|
21
|
+
emit(`${dryRunPrefix}${successPrefix}${message}`);
|
|
22
|
+
},
|
|
23
|
+
detail(message) {
|
|
24
|
+
if (!shouldLogDetails)
|
|
25
|
+
return;
|
|
26
|
+
emit(`${dryRunPrefix}${detailPrefix}${message}`);
|
|
27
|
+
},
|
|
28
|
+
warn(message) {
|
|
29
|
+
emit(`${warningPrefix}${message}`);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Fallback logger only emits warnings so important signals aren't dropped.
|
|
2
|
+
const fallbackLogger = {
|
|
3
|
+
step() { },
|
|
4
|
+
success() { },
|
|
5
|
+
detail() { },
|
|
6
|
+
warn(message) {
|
|
7
|
+
console.error(message);
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
export function getStatusLogger(logger) {
|
|
11
|
+
return logger ?? fallbackLogger;
|
|
12
|
+
}
|
|
@@ -40,7 +40,7 @@ export async function getInstallCommand(pm, cwd) {
|
|
|
40
40
|
*/
|
|
41
41
|
export function getInstallMessage(cmd) {
|
|
42
42
|
if (cmd.command === "npm" && cmd.args[0] === "ci") {
|
|
43
|
-
return "
|
|
43
|
+
return "Running npm ci (using package-lock.json) …";
|
|
44
44
|
}
|
|
45
|
-
return
|
|
45
|
+
return `Running ${formatCommand(cmd.command, cmd.args)} …`;
|
|
46
46
|
}
|
package/dist/project/next.d.ts
CHANGED
|
@@ -3,5 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Utilities for working with Next.js projects
|
|
5
5
|
*/
|
|
6
|
+
import type { StatusLogger } from "../output/create-status-logger.js";
|
|
6
7
|
export declare const isNextProject: (directory: string) => Promise<boolean>;
|
|
7
|
-
export declare const isNextTypegenSupported: (directory: string
|
|
8
|
+
export declare const isNextTypegenSupported: (directory: string, options?: {
|
|
9
|
+
logger?: StatusLogger;
|
|
10
|
+
}) => Promise<boolean>;
|
package/dist/project/next.js
CHANGED
|
@@ -26,9 +26,9 @@ export const isNextProject = async (directory) => {
|
|
|
26
26
|
}
|
|
27
27
|
return Boolean(packageJson.dependencies?.next ?? packageJson.devDependencies?.next);
|
|
28
28
|
};
|
|
29
|
-
export const isNextTypegenSupported = async (directory) => {
|
|
29
|
+
export const isNextTypegenSupported = async (directory, options = {}) => {
|
|
30
30
|
try {
|
|
31
|
-
const result = await runPackageManagerBinary(directory, "next", ["--help"], { stdio: "pipe" });
|
|
31
|
+
const result = await runPackageManagerBinary(directory, "next", ["--help"], { stdio: "pipe", logger: options.logger });
|
|
32
32
|
if (!result) {
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Helpers for detecting and running package managers
|
|
5
5
|
*/
|
|
6
|
+
import type { StatusLogger } from "../output/create-status-logger.js";
|
|
6
7
|
interface RunOptions {
|
|
7
8
|
readonly stdio?: "inherit" | "pipe";
|
|
9
|
+
readonly dryRun?: boolean;
|
|
10
|
+
readonly logger?: StatusLogger;
|
|
8
11
|
}
|
|
9
12
|
interface RunBinaryResult {
|
|
10
13
|
readonly stdout: string;
|
|
@@ -13,6 +16,9 @@ interface RunBinaryResult {
|
|
|
13
16
|
/**
|
|
14
17
|
* Install dependencies for a project using the detected package manager.
|
|
15
18
|
*/
|
|
16
|
-
export declare function installDependencies(cwd: string
|
|
19
|
+
export declare function installDependencies(cwd: string, options?: {
|
|
20
|
+
dryRun?: boolean;
|
|
21
|
+
logger?: StatusLogger;
|
|
22
|
+
}): Promise<void>;
|
|
17
23
|
export declare function runPackageManagerBinary(cwd: string, binary: string, arguments_?: string[], options?: RunOptions): Promise<RunBinaryResult | undefined>;
|
|
18
24
|
export {};
|
|
@@ -7,6 +7,7 @@ import { spawnSync } from "node:child_process";
|
|
|
7
7
|
import { detect } from "package-manager-detector/detect";
|
|
8
8
|
import { formatCommand, getBinaryRunCommand, } from "./package-manager-commands.js";
|
|
9
9
|
import { getInstallCommand, getInstallMessage } from "./install-commands.js";
|
|
10
|
+
import { getStatusLogger } from "../output/get-status-logger.js";
|
|
10
11
|
const runOrThrow = (command, arguments_, cwd, options = {}) => {
|
|
11
12
|
const result = spawnSync(command, arguments_, {
|
|
12
13
|
cwd,
|
|
@@ -42,16 +43,24 @@ async function detectPackageManager(cwd) {
|
|
|
42
43
|
/**
|
|
43
44
|
* Install dependencies for a project using the detected package manager.
|
|
44
45
|
*/
|
|
45
|
-
export async function installDependencies(cwd) {
|
|
46
|
+
export async function installDependencies(cwd, options = {}) {
|
|
47
|
+
const logger = getStatusLogger(options.logger);
|
|
48
|
+
const dryRun = options.dryRun ?? false;
|
|
46
49
|
const pm = await detectPackageManager(cwd);
|
|
47
50
|
const cmd = await getInstallCommand(pm, cwd);
|
|
48
|
-
|
|
51
|
+
logger.step(getInstallMessage(cmd));
|
|
52
|
+
if (dryRun)
|
|
53
|
+
return;
|
|
49
54
|
runOrThrow(cmd.command, cmd.args, cwd);
|
|
50
55
|
}
|
|
51
56
|
export async function runPackageManagerBinary(cwd, binary, arguments_ = [], options = {}) {
|
|
57
|
+
const logger = getStatusLogger(options.logger);
|
|
58
|
+
const dryRun = options.dryRun ?? false;
|
|
52
59
|
const pm = await detectPackageManager(cwd);
|
|
53
60
|
const { command, args: commandArguments } = getBinaryRunCommand(pm, binary, arguments_);
|
|
54
|
-
|
|
61
|
+
logger.step(`Running ${formatCommand(command, commandArguments)} …`);
|
|
62
|
+
if (dryRun)
|
|
63
|
+
return undefined;
|
|
55
64
|
const result = runOrThrow(command, commandArguments, cwd, options);
|
|
56
65
|
if (options.stdio === "pipe") {
|
|
57
66
|
return {
|
package/dist/project/setup.d.ts
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Utilities for setting up project dependencies and build artifacts
|
|
5
5
|
*/
|
|
6
|
+
import type { StatusLogger } from "../output/create-status-logger.js";
|
|
6
7
|
/**
|
|
7
8
|
* Install dependencies and run Next.js typegen if applicable
|
|
8
9
|
*/
|
|
9
|
-
export declare function setupProject(destinationDirectory: string
|
|
10
|
+
export declare function setupProject(destinationDirectory: string, options?: {
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
logger?: StatusLogger;
|
|
13
|
+
}): Promise<void>;
|
package/dist/project/setup.js
CHANGED
|
@@ -5,27 +5,35 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileExists } from "../git/git.js";
|
|
8
|
+
import { getStatusLogger } from "../output/get-status-logger.js";
|
|
8
9
|
import { installDependencies, runPackageManagerBinary, } from "./package-manager.js";
|
|
9
10
|
import { isNextProject, isNextTypegenSupported } from "./next.js";
|
|
10
11
|
/**
|
|
11
12
|
* Install dependencies and run Next.js typegen if applicable
|
|
12
13
|
*/
|
|
13
|
-
export async function setupProject(destinationDirectory) {
|
|
14
|
+
export async function setupProject(destinationDirectory, options = {}) {
|
|
15
|
+
const logger = getStatusLogger(options.logger);
|
|
16
|
+
const dryRun = options.dryRun ?? false;
|
|
14
17
|
const packageJsonPath = path.join(destinationDirectory, "package.json");
|
|
15
18
|
if (!(await fileExists(packageJsonPath))) {
|
|
16
19
|
return;
|
|
17
20
|
}
|
|
18
|
-
await installDependencies(destinationDirectory);
|
|
21
|
+
await installDependencies(destinationDirectory, { dryRun, logger });
|
|
19
22
|
// Run Next.js type generation if applicable
|
|
20
23
|
if (await isNextProject(destinationDirectory)) {
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
if (dryRun) {
|
|
25
|
+
logger.step("Would run next typegen if supported");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
logger.step("Checking Next.js CLI typegen support …");
|
|
29
|
+
const hasTypegenSupport = await isNextTypegenSupported(destinationDirectory, { logger });
|
|
23
30
|
if (hasTypegenSupport) {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
await runPackageManagerBinary(destinationDirectory, "next", ["typegen"], {
|
|
32
|
+
logger,
|
|
33
|
+
});
|
|
26
34
|
}
|
|
27
35
|
else {
|
|
28
|
-
|
|
36
|
+
logger.warn("Skipping next typegen: installed Next.js CLI does not support this command.");
|
|
29
37
|
}
|
|
30
38
|
}
|
|
31
39
|
}
|
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Utilities for managing worktree directories
|
|
5
5
|
*/
|
|
6
|
+
import type { StatusLogger } from "../output/create-status-logger.js";
|
|
6
7
|
/**
|
|
7
|
-
* Check if directory exists and
|
|
8
|
-
* Returns true if the directory should be created
|
|
8
|
+
* Check if directory exists and handle removal if needed.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
type HandleExistingDirectoryOptions = {
|
|
11
|
+
readonly dryRun?: boolean;
|
|
12
|
+
readonly assumeYes?: boolean;
|
|
13
|
+
readonly interactive?: boolean;
|
|
14
|
+
readonly logger?: StatusLogger;
|
|
15
|
+
};
|
|
16
|
+
export declare function handleExistingDirectory(destinationDirectory: string, options?: HandleExistingDirectoryOptions): Promise<boolean>;
|
|
17
|
+
export {};
|