@aspruyt/xfg 4.0.0 → 4.0.2
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 +1 -2
- package/dist/cli/index.d.ts +1 -2
- package/dist/cli/index.js +0 -1
- package/dist/cli/program.js +7 -2
- package/dist/cli/{settings/results-collector.d.ts → results-collector.d.ts} +1 -1
- package/dist/cli/{settings/results-collector.js → results-collector.js} +2 -1
- package/dist/cli/settings-report-builder.d.ts +1 -3
- package/dist/cli/sync-command.d.ts +2 -24
- package/dist/cli/sync-command.js +295 -301
- package/dist/cli/types.d.ts +60 -40
- package/dist/cli/types.js +1 -12
- package/dist/config/errors.d.ts +9 -0
- package/dist/config/errors.js +11 -0
- package/dist/config/file-reference-resolver.d.ts +2 -1
- package/dist/config/file-reference-resolver.js +10 -8
- package/dist/config/formatter.d.ts +3 -2
- package/dist/config/index.d.ts +4 -6
- package/dist/config/index.js +4 -8
- package/dist/config/loader.js +4 -2
- package/dist/config/merge.d.ts +0 -9
- package/dist/config/merge.js +2 -7
- package/dist/config/normalizer.d.ts +4 -0
- package/dist/config/normalizer.js +61 -110
- package/dist/config/types.d.ts +15 -19
- package/dist/config/types.js +1 -1
- package/dist/config/validator.d.ts +0 -4
- package/dist/config/validator.js +286 -363
- package/dist/config/validators/file-validator.d.ts +2 -8
- package/dist/config/validators/file-validator.js +6 -17
- package/dist/config/validators/index.d.ts +3 -3
- package/dist/config/validators/index.js +3 -3
- package/dist/config/validators/repo-settings-validator.d.ts +0 -6
- package/dist/config/validators/repo-settings-validator.js +9 -9
- package/dist/config/validators/ruleset-validator.d.ts +0 -14
- package/dist/config/validators/ruleset-validator.js +28 -28
- package/dist/lifecycle/ado-migration-source.js +2 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -5
- package/dist/lifecycle/github-lifecycle-provider.js +79 -90
- package/dist/lifecycle/index.d.ts +2 -6
- package/dist/lifecycle/index.js +0 -4
- package/dist/lifecycle/lifecycle-formatter.d.ts +2 -1
- package/dist/lifecycle/lifecycle-formatter.js +4 -0
- package/dist/lifecycle/lifecycle-helpers.d.ts +3 -2
- package/dist/lifecycle/repo-lifecycle-manager.js +4 -11
- package/dist/lifecycle/types.d.ts +0 -8
- package/dist/output/github-summary.d.ts +5 -0
- package/dist/output/github-summary.js +9 -2
- package/dist/output/index.d.ts +2 -2
- package/dist/output/index.js +1 -1
- package/dist/output/lifecycle-report.js +5 -23
- package/dist/output/settings-report.d.ts +14 -3
- package/dist/output/settings-report.js +137 -197
- package/dist/output/summary-utils.d.ts +1 -1
- package/dist/output/summary-utils.js +2 -1
- package/dist/output/sync-report.js +5 -8
- package/dist/output/unified-summary.d.ts +2 -1
- package/dist/output/unified-summary.js +71 -133
- package/dist/settings/base-processor.d.ts +67 -0
- package/dist/settings/base-processor.js +91 -0
- package/dist/settings/index.d.ts +4 -3
- package/dist/settings/index.js +3 -3
- package/dist/settings/labels/converter.d.ts +2 -1
- package/dist/settings/labels/github-labels-strategy.d.ts +9 -18
- package/dist/settings/labels/github-labels-strategy.js +17 -73
- package/dist/settings/labels/index.d.ts +2 -6
- package/dist/settings/labels/index.js +1 -9
- package/dist/settings/labels/processor.d.ts +6 -30
- package/dist/settings/labels/processor.js +62 -152
- package/dist/settings/labels/types.d.ts +5 -8
- package/dist/settings/repo-settings/formatter.d.ts +2 -2
- package/dist/settings/repo-settings/formatter.js +6 -6
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +11 -12
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +32 -79
- package/dist/settings/repo-settings/index.d.ts +2 -5
- package/dist/settings/repo-settings/index.js +1 -9
- package/dist/settings/repo-settings/processor.d.ts +6 -27
- package/dist/settings/repo-settings/processor.js +51 -104
- package/dist/settings/repo-settings/types.d.ts +7 -9
- package/dist/settings/rulesets/diff-algorithm.d.ts +0 -4
- package/dist/settings/rulesets/diff-algorithm.js +1 -10
- package/dist/settings/rulesets/diff.d.ts +1 -1
- package/dist/settings/rulesets/diff.js +2 -21
- package/dist/settings/rulesets/formatter.d.ts +1 -3
- package/dist/settings/rulesets/formatter.js +1 -8
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +11 -51
- package/dist/settings/rulesets/github-ruleset-strategy.js +24 -85
- package/dist/settings/rulesets/index.d.ts +3 -6
- package/dist/settings/rulesets/index.js +5 -9
- package/dist/settings/rulesets/processor.d.ts +8 -33
- package/dist/settings/rulesets/processor.js +58 -151
- package/dist/settings/rulesets/types.d.ts +35 -6
- package/dist/shared/command-executor.d.ts +2 -22
- package/dist/shared/command-executor.js +8 -7
- package/dist/shared/env.d.ts +0 -8
- package/dist/shared/env.js +14 -70
- package/dist/shared/file-status.d.ts +2 -0
- package/dist/shared/file-status.js +13 -0
- package/dist/shared/gh-api-utils.d.ts +46 -0
- package/dist/shared/gh-api-utils.js +107 -0
- package/dist/shared/index.d.ts +5 -5
- package/dist/shared/index.js +3 -3
- package/dist/shared/interpolation-engine.d.ts +31 -0
- package/dist/shared/interpolation-engine.js +50 -0
- package/dist/shared/logger.d.ts +3 -7
- package/dist/shared/logger.js +4 -1
- package/dist/shared/repo-detector.d.ts +17 -2
- package/dist/shared/repo-detector.js +27 -0
- package/dist/shared/retry-utils.d.ts +9 -17
- package/dist/shared/retry-utils.js +22 -28
- package/dist/shared/sanitize-utils.d.ts +0 -7
- package/dist/shared/sanitize-utils.js +0 -7
- package/dist/shared/shell-utils.d.ts +1 -0
- package/dist/shared/shell-utils.js +3 -0
- package/dist/shared/string-utils.d.ts +4 -0
- package/dist/shared/string-utils.js +6 -0
- package/dist/shared/type-guards.d.ts +17 -0
- package/dist/shared/type-guards.js +26 -0
- package/dist/shared/workspace-utils.d.ts +0 -4
- package/dist/shared/workspace-utils.js +0 -4
- package/dist/{sync → shared}/xfg-template.d.ts +3 -2
- package/dist/{sync → shared}/xfg-template.js +13 -54
- package/dist/sync/auth-options-builder.d.ts +4 -5
- package/dist/sync/auth-options-builder.js +15 -26
- package/dist/sync/branch-manager.d.ts +5 -0
- package/dist/sync/branch-manager.js +12 -10
- package/dist/sync/commit-push-manager.d.ts +1 -1
- package/dist/sync/commit-push-manager.js +22 -18
- package/dist/sync/diff-utils.d.ts +4 -9
- package/dist/sync/diff-utils.js +2 -19
- package/dist/sync/file-sync-orchestrator.js +9 -8
- package/dist/sync/file-writer.d.ts +2 -1
- package/dist/sync/file-writer.js +3 -6
- package/dist/sync/index.d.ts +2 -15
- package/dist/sync/index.js +0 -19
- package/dist/sync/manifest-manager.d.ts +4 -0
- package/dist/sync/manifest-manager.js +5 -1
- package/dist/sync/manifest.d.ts +10 -41
- package/dist/sync/manifest.js +11 -56
- package/dist/sync/pr-merge-handler.d.ts +2 -6
- package/dist/sync/pr-merge-handler.js +6 -3
- package/dist/sync/repository-processor.d.ts +1 -2
- package/dist/sync/repository-processor.js +20 -12
- package/dist/sync/repository-session.js +5 -14
- package/dist/sync/sync-workflow.js +31 -38
- package/dist/sync/types.d.ts +43 -178
- package/dist/vcs/authenticated-git-ops.d.ts +27 -70
- package/dist/vcs/authenticated-git-ops.js +70 -96
- package/dist/vcs/azure-pr-strategy.d.ts +6 -4
- package/dist/vcs/azure-pr-strategy.js +34 -82
- package/dist/vcs/branch-utils.d.ts +6 -0
- package/dist/vcs/branch-utils.js +29 -0
- package/dist/vcs/commit-strategy-selector.d.ts +5 -0
- package/dist/vcs/commit-strategy-selector.js +10 -0
- package/dist/vcs/git-commit-strategy.js +1 -2
- package/dist/vcs/git-ops.d.ts +15 -59
- package/dist/vcs/git-ops.js +46 -110
- package/dist/vcs/github-app-token-manager.d.ts +0 -6
- package/dist/vcs/github-app-token-manager.js +5 -12
- package/dist/vcs/github-pr-strategy.d.ts +5 -5
- package/dist/vcs/github-pr-strategy.js +44 -122
- package/dist/vcs/gitlab-pr-strategy.d.ts +6 -4
- package/dist/vcs/gitlab-pr-strategy.js +39 -87
- package/dist/vcs/graphql-commit-strategy.d.ts +3 -4
- package/dist/vcs/graphql-commit-strategy.js +31 -63
- package/dist/vcs/index.d.ts +3 -16
- package/dist/vcs/index.js +2 -33
- package/dist/vcs/pr-creator.d.ts +9 -9
- package/dist/vcs/pr-creator.js +11 -10
- package/dist/vcs/pr-strategy-factory.d.ts +5 -0
- package/dist/vcs/pr-strategy-factory.js +17 -0
- package/dist/vcs/pr-strategy.d.ts +13 -26
- package/dist/vcs/pr-strategy.js +20 -25
- package/dist/vcs/types.d.ts +87 -21
- package/package.json +2 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { escapeShellArg } from "../shared/shell-utils.js";
|
|
2
2
|
import { defaultExecutor, } from "../shared/command-executor.js";
|
|
3
3
|
import { withRetry, DEFAULT_PERMANENT_ERROR_PATTERNS, } from "../shared/retry-utils.js";
|
|
4
|
-
import {
|
|
4
|
+
import { assertGitHubRepo, } from "../shared/repo-detector.js";
|
|
5
5
|
import { logger } from "../shared/logger.js";
|
|
6
|
+
import { toErrorMessage } from "../shared/type-guards.js";
|
|
7
|
+
import { buildTokenEnv, getHostnameFlag } from "../shared/gh-api-utils.js";
|
|
6
8
|
/**
|
|
7
9
|
* Error messages that indicate "repo not found" vs actual errors.
|
|
8
10
|
*/
|
|
@@ -15,25 +17,21 @@ const REPO_NOT_FOUND_PATTERNS = [
|
|
|
15
17
|
* Check if an error indicates repo not found (vs network/auth error).
|
|
16
18
|
*/
|
|
17
19
|
function isRepoNotFoundError(error) {
|
|
18
|
-
const message = error
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const message = toErrorMessage(error) +
|
|
21
|
+
((error instanceof Error
|
|
22
|
+
? error.stderr
|
|
23
|
+
: undefined) ?? "");
|
|
21
24
|
return REPO_NOT_FOUND_PATTERNS.some((pattern) => message.includes(pattern));
|
|
22
25
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Get the hostname flag for gh commands.
|
|
25
|
-
* Returns "--hostname HOST" for GHE, empty string for github.com.
|
|
26
|
-
*/
|
|
27
|
-
function getHostnameFlag(repoInfo) {
|
|
28
|
-
if (repoInfo.host && repoInfo.host !== "github.com") {
|
|
29
|
-
return `--hostname ${escapeShellArg(repoInfo.host)}`;
|
|
30
|
-
}
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
26
|
/**
|
|
34
27
|
* Default timeout for waiting for fork readiness (60 seconds).
|
|
35
28
|
*/
|
|
36
29
|
const FORK_READY_TIMEOUT_MS = 60_000;
|
|
30
|
+
/**
|
|
31
|
+
* After repo creation, GitHub may return 404 due to eventual consistency.
|
|
32
|
+
* Exclude 404/not-found from permanent errors so withRetry retries them.
|
|
33
|
+
*/
|
|
34
|
+
const POST_CREATE_PERMANENT_PATTERNS = DEFAULT_PERMANENT_ERROR_PATTERNS.filter((p) => !p.test("404 Not Found"));
|
|
37
35
|
/**
|
|
38
36
|
* Interval between fork readiness checks (2 seconds).
|
|
39
37
|
*/
|
|
@@ -58,49 +56,47 @@ export class GitHubLifecycleProvider {
|
|
|
58
56
|
* Uses gh api to query the user/org endpoint.
|
|
59
57
|
*/
|
|
60
58
|
async isOrganization(owner, repoInfo, token) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const hostnamePart = hostnameFlag ? `${hostnameFlag} ` : "";
|
|
64
|
-
const command = `${tokenPrefix}gh api ${hostnamePart}users/${escapeShellArg(owner)}`;
|
|
59
|
+
const { tokenEnv, prefix } = this.buildGhApiPrefix(repoInfo, token);
|
|
60
|
+
const command = `${prefix}users/${escapeShellArg(owner)}`;
|
|
65
61
|
try {
|
|
66
|
-
const stdout = await withRetry(() => this.executor.exec(command, this.cwd), { retries: this.retries });
|
|
62
|
+
const stdout = await withRetry(() => this.executor.exec(command, this.cwd, { env: tokenEnv }), { retries: this.retries });
|
|
67
63
|
const data = JSON.parse(stdout);
|
|
68
64
|
return data.type === "Organization";
|
|
69
65
|
}
|
|
70
66
|
catch (error) {
|
|
71
67
|
// If we can't determine, assume it's an org (safer - uses --org flag).
|
|
72
68
|
// This may cause fork to fail with a misleading error for personal accounts.
|
|
73
|
-
const errMsg =
|
|
69
|
+
const errMsg = toErrorMessage(error);
|
|
74
70
|
logger.debug(`Could not determine if '${owner}' is an organization, defaulting to org behavior: ${errMsg}`);
|
|
75
|
-
logger.
|
|
71
|
+
logger.warn(`Could not verify if '${owner}' is an organization or user account. ` +
|
|
76
72
|
`If fork fails, check your authentication (gh auth status) and ensure the ` +
|
|
77
73
|
`target owner is correct.`);
|
|
78
74
|
return true;
|
|
79
75
|
}
|
|
80
76
|
}
|
|
81
77
|
assertGitHub(repoInfo) {
|
|
82
|
-
|
|
83
|
-
throw new Error(`GitHubLifecycleProvider requires GitHub repo, got: ${repoInfo.type}`);
|
|
84
|
-
}
|
|
78
|
+
assertGitHubRepo(repoInfo, "GitHubLifecycleProvider");
|
|
85
79
|
}
|
|
86
80
|
/**
|
|
87
|
-
*
|
|
88
|
-
* Returns
|
|
89
|
-
*
|
|
81
|
+
* Builds the common gh API command prefix parts for a given repo.
|
|
82
|
+
* Returns tokenEnv for exec options, and the command prefix string
|
|
83
|
+
* (e.g., "gh api --hostname host repos/owner/repo").
|
|
90
84
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
buildGhApiPrefix(repoInfo, token) {
|
|
86
|
+
const tokenEnv = buildTokenEnv(token);
|
|
87
|
+
const hostnameFlag = getHostnameFlag(repoInfo);
|
|
88
|
+
const hostnamePart = hostnameFlag ? `${hostnameFlag} ` : "";
|
|
89
|
+
const apiPath = `repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)}`;
|
|
90
|
+
return { tokenEnv, prefix: `gh api ${hostnamePart}`, apiPath };
|
|
93
91
|
}
|
|
94
92
|
async exists(repoInfo, token) {
|
|
95
93
|
this.assertGitHub(repoInfo);
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const hostnamePart = hostnameFlag ? `${hostnameFlag} ` : "";
|
|
99
|
-
const command = `${tokenPrefix}gh api ${hostnamePart}repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)}`;
|
|
94
|
+
const { tokenEnv, prefix, apiPath } = this.buildGhApiPrefix(repoInfo, token);
|
|
95
|
+
const command = `${prefix}${apiPath}`;
|
|
100
96
|
try {
|
|
101
97
|
// Note: withRetry already classifies 404/not-found as permanent errors,
|
|
102
98
|
// so retries are aborted immediately for non-existent repos.
|
|
103
|
-
await withRetry(() => this.executor.exec(command, this.cwd), {
|
|
99
|
+
await withRetry(() => this.executor.exec(command, this.cwd, { env: tokenEnv }), {
|
|
104
100
|
retries: this.retries,
|
|
105
101
|
});
|
|
106
102
|
return true;
|
|
@@ -116,9 +112,9 @@ export class GitHubLifecycleProvider {
|
|
|
116
112
|
}
|
|
117
113
|
async create(repoInfo, settings, token) {
|
|
118
114
|
this.assertGitHub(repoInfo);
|
|
119
|
-
const
|
|
115
|
+
const tokenEnv = buildTokenEnv(token);
|
|
120
116
|
const parts = [
|
|
121
|
-
|
|
117
|
+
"gh repo create",
|
|
122
118
|
escapeShellArg(`${repoInfo.owner}/${repoInfo.repo}`),
|
|
123
119
|
];
|
|
124
120
|
// Visibility flag (default to private for safety)
|
|
@@ -146,28 +142,24 @@ export class GitHubLifecycleProvider {
|
|
|
146
142
|
// This avoids empty repos where HEAD doesn't resolve.
|
|
147
143
|
parts.push("--add-readme");
|
|
148
144
|
const command = parts.join(" ");
|
|
149
|
-
await withRetry(() => this.executor.exec(command, this.cwd), {
|
|
145
|
+
await withRetry(() => this.executor.exec(command, this.cwd, { env: tokenEnv }), {
|
|
150
146
|
retries: this.retries,
|
|
151
147
|
});
|
|
152
148
|
// Rename default branch if requested and it differs from what GitHub created.
|
|
153
149
|
if (settings?.defaultBranch) {
|
|
154
|
-
const
|
|
155
|
-
const hostnameFlag = getHostnameFlag(repoInfo);
|
|
156
|
-
const hostnamePart = hostnameFlag ? `${hostnameFlag} ` : "";
|
|
157
|
-
const apiPath = `repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)}`;
|
|
158
|
-
// After repo creation, GitHub may return 404 due to eventual consistency.
|
|
159
|
-
// Exclude 404/not-found from permanent errors so withRetry retries them.
|
|
160
|
-
const postCreatePermanentPatterns = DEFAULT_PERMANENT_ERROR_PATTERNS.filter((p) => !p.test("404 Not Found"));
|
|
150
|
+
const { tokenEnv: branchTokenEnv, prefix, apiPath, } = this.buildGhApiPrefix(repoInfo, token);
|
|
161
151
|
// Detect the actual default branch name
|
|
162
|
-
const actualBranch = (await withRetry(() => this.executor.exec(`${
|
|
152
|
+
const actualBranch = (await withRetry(() => this.executor.exec(`${prefix}${apiPath} --jq '.default_branch'`, this.cwd, { env: branchTokenEnv }), {
|
|
163
153
|
retries: this.retries,
|
|
164
|
-
permanentErrorPatterns:
|
|
154
|
+
permanentErrorPatterns: POST_CREATE_PERMANENT_PATTERNS,
|
|
165
155
|
})).trim();
|
|
166
156
|
if (actualBranch !== settings.defaultBranch) {
|
|
167
157
|
await this.renameBranch(repoInfo, actualBranch, settings.defaultBranch, token);
|
|
168
158
|
// Wait for the rename to propagate — GitHub's API may still report
|
|
169
159
|
// the old default branch for a few seconds after the rename call.
|
|
170
|
-
await this.waitForDefaultBranch(repoInfo, settings.defaultBranch,
|
|
160
|
+
await this.waitForDefaultBranch(repoInfo, settings.defaultBranch, {
|
|
161
|
+
token,
|
|
162
|
+
});
|
|
171
163
|
}
|
|
172
164
|
}
|
|
173
165
|
// Delete the README so xfg sync starts from a clean state.
|
|
@@ -183,12 +175,12 @@ export class GitHubLifecycleProvider {
|
|
|
183
175
|
}
|
|
184
176
|
// Determine if target owner is an organization or user
|
|
185
177
|
const isOrg = await this.isOrganization(target.owner, target, token);
|
|
186
|
-
const
|
|
178
|
+
const tokenEnv = buildTokenEnv(token);
|
|
187
179
|
// Build fork command
|
|
188
180
|
// For orgs: gh repo fork <upstream> --org <target-org> --fork-name <name> --clone=false
|
|
189
181
|
// For users: gh repo fork <upstream> --fork-name <name> --clone=false
|
|
190
182
|
const parts = [
|
|
191
|
-
|
|
183
|
+
"gh repo fork",
|
|
192
184
|
escapeShellArg(`${upstream.owner}/${upstream.repo}`),
|
|
193
185
|
];
|
|
194
186
|
if (isOrg) {
|
|
@@ -196,11 +188,15 @@ export class GitHubLifecycleProvider {
|
|
|
196
188
|
}
|
|
197
189
|
parts.push("--fork-name", escapeShellArg(target.repo), "--clone=false");
|
|
198
190
|
const forkCommand = parts.join(" ");
|
|
199
|
-
await withRetry(() => this.executor.exec(forkCommand, this.cwd), {
|
|
191
|
+
await withRetry(() => this.executor.exec(forkCommand, this.cwd, { env: tokenEnv }), {
|
|
200
192
|
retries: this.retries,
|
|
201
193
|
});
|
|
202
194
|
// GitHub forks are async - wait for the fork to be ready for git operations
|
|
203
|
-
await this.waitForForkReady(target,
|
|
195
|
+
await this.waitForForkReady(target, {
|
|
196
|
+
timeoutMs: this.forkReadyTimeoutMs,
|
|
197
|
+
pollMs: this.forkPollIntervalMs,
|
|
198
|
+
token,
|
|
199
|
+
});
|
|
204
200
|
// Apply settings after fork (visibility, description, etc.)
|
|
205
201
|
if (settings?.visibility || settings?.description) {
|
|
206
202
|
await this.applyRepoSettings(target, settings, token);
|
|
@@ -210,7 +206,10 @@ export class GitHubLifecycleProvider {
|
|
|
210
206
|
* Wait for a forked repo to become available via the GitHub API.
|
|
211
207
|
* GitHub forks are created asynchronously; polls exists() with a timeout.
|
|
212
208
|
*/
|
|
213
|
-
async waitForForkReady(repoInfo,
|
|
209
|
+
async waitForForkReady(repoInfo, options) {
|
|
210
|
+
const timeoutMs = options?.timeoutMs ?? FORK_READY_TIMEOUT_MS;
|
|
211
|
+
const intervalMs = options?.pollMs ?? FORK_POLL_INTERVAL_MS;
|
|
212
|
+
const token = options?.token;
|
|
214
213
|
const deadline = Date.now() + timeoutMs;
|
|
215
214
|
while (Date.now() < deadline) {
|
|
216
215
|
try {
|
|
@@ -219,8 +218,8 @@ export class GitHubLifecycleProvider {
|
|
|
219
218
|
return;
|
|
220
219
|
}
|
|
221
220
|
}
|
|
222
|
-
catch {
|
|
223
|
-
|
|
221
|
+
catch (error) {
|
|
222
|
+
logger.debug(`Polling fork readiness: ${toErrorMessage(error)}`);
|
|
224
223
|
}
|
|
225
224
|
const remaining = deadline - Date.now();
|
|
226
225
|
if (remaining <= 0)
|
|
@@ -234,9 +233,9 @@ export class GitHubLifecycleProvider {
|
|
|
234
233
|
* Apply settings to an existing repo using gh repo edit.
|
|
235
234
|
*/
|
|
236
235
|
async applyRepoSettings(repoInfo, settings, token) {
|
|
237
|
-
const
|
|
236
|
+
const tokenEnv = buildTokenEnv(token);
|
|
238
237
|
const parts = [
|
|
239
|
-
|
|
238
|
+
"gh repo edit",
|
|
240
239
|
escapeShellArg(`${repoInfo.owner}/${repoInfo.repo}`),
|
|
241
240
|
];
|
|
242
241
|
if (settings.visibility) {
|
|
@@ -246,20 +245,20 @@ export class GitHubLifecycleProvider {
|
|
|
246
245
|
parts.push("--description", escapeShellArg(settings.description));
|
|
247
246
|
}
|
|
248
247
|
const command = parts.join(" ");
|
|
249
|
-
await withRetry(() => this.executor.exec(command, this.cwd), {
|
|
248
|
+
await withRetry(() => this.executor.exec(command, this.cwd, { env: tokenEnv }), {
|
|
250
249
|
retries: this.retries,
|
|
251
250
|
});
|
|
252
251
|
}
|
|
253
252
|
async receiveMigration(repoInfo, sourceDir, settings, token) {
|
|
254
253
|
this.assertGitHub(repoInfo);
|
|
255
|
-
const
|
|
254
|
+
const tokenEnv = buildTokenEnv(token);
|
|
256
255
|
// Remove existing "origin" remote if present (e.g., from git clone --mirror).
|
|
257
256
|
// gh repo create --source --push needs to set its own origin remote.
|
|
258
257
|
try {
|
|
259
258
|
await this.executor.exec(`git -C ${escapeShellArg(sourceDir)} remote remove origin`, this.cwd);
|
|
260
259
|
}
|
|
261
|
-
catch {
|
|
262
|
-
|
|
260
|
+
catch (error) {
|
|
261
|
+
logger.debug(`Cleanup: remote remove origin skipped - ${toErrorMessage(error)}`);
|
|
263
262
|
}
|
|
264
263
|
// Remove all non-standard refs that GitHub rejects on push.
|
|
265
264
|
// Mirror clones include ALL refs from the source, but GitHub only
|
|
@@ -276,8 +275,8 @@ export class GitHubLifecycleProvider {
|
|
|
276
275
|
}
|
|
277
276
|
}
|
|
278
277
|
}
|
|
279
|
-
catch {
|
|
280
|
-
|
|
278
|
+
catch (error) {
|
|
279
|
+
logger.debug(`Cleanup: ref cleanup skipped - ${toErrorMessage(error)}`);
|
|
281
280
|
}
|
|
282
281
|
// Rename default branch in mirror clone if requested.
|
|
283
282
|
if (settings?.defaultBranch) {
|
|
@@ -297,7 +296,7 @@ export class GitHubLifecycleProvider {
|
|
|
297
296
|
// For bare repos (from git clone --mirror), --push mirrors all refs.
|
|
298
297
|
// This uses gh CLI authentication, avoiding raw git auth issues with GHE.
|
|
299
298
|
const parts = [
|
|
300
|
-
|
|
299
|
+
"gh repo create",
|
|
301
300
|
escapeShellArg(`${repoInfo.owner}/${repoInfo.repo}`),
|
|
302
301
|
"--source",
|
|
303
302
|
escapeShellArg(sourceDir),
|
|
@@ -325,7 +324,7 @@ export class GitHubLifecycleProvider {
|
|
|
325
324
|
parts.push("--disable-wiki");
|
|
326
325
|
}
|
|
327
326
|
const command = parts.join(" ");
|
|
328
|
-
await withRetry(() => this.executor.exec(command, this.cwd), {
|
|
327
|
+
await withRetry(() => this.executor.exec(command, this.cwd, { env: tokenEnv }), {
|
|
329
328
|
retries: this.retries,
|
|
330
329
|
});
|
|
331
330
|
}
|
|
@@ -334,12 +333,9 @@ export class GitHubLifecycleProvider {
|
|
|
334
333
|
* GitHub automatically updates the default branch pointer.
|
|
335
334
|
*/
|
|
336
335
|
async renameBranch(repoInfo, current, desired, token) {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const apiPath = `repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)}`;
|
|
341
|
-
await withRetry(() => this.executor.exec(`${renameTokenPrefix}gh api ${hostnamePart}${apiPath}/branches/${escapeShellArg(current)}/rename ` +
|
|
342
|
-
`--method POST -f new_name=${escapeShellArg(desired)}`, this.cwd), {
|
|
336
|
+
const { tokenEnv, prefix, apiPath } = this.buildGhApiPrefix(repoInfo, token);
|
|
337
|
+
await withRetry(() => this.executor.exec(`${prefix}${apiPath}/branches/${escapeShellArg(current)}/rename ` +
|
|
338
|
+
`--method POST -f new_name=${escapeShellArg(desired)}`, this.cwd, { env: tokenEnv }), {
|
|
343
339
|
retries: this.retries,
|
|
344
340
|
});
|
|
345
341
|
}
|
|
@@ -351,21 +347,20 @@ export class GitHubLifecycleProvider {
|
|
|
351
347
|
* The command arguments are constructed from trusted RepoInfo values
|
|
352
348
|
* (validated during config parsing), not user input.
|
|
353
349
|
*/
|
|
354
|
-
async waitForDefaultBranch(repoInfo, expectedBranch,
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
const
|
|
358
|
-
const apiPath = `repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)}`;
|
|
350
|
+
async waitForDefaultBranch(repoInfo, expectedBranch, options) {
|
|
351
|
+
const timeoutMs = options?.timeoutMs ?? 15000;
|
|
352
|
+
const pollMs = options?.pollMs ?? 1000;
|
|
353
|
+
const { tokenEnv, prefix, apiPath } = this.buildGhApiPrefix(repoInfo, options?.token);
|
|
359
354
|
const startTime = Date.now();
|
|
360
355
|
while (Date.now() - startTime < timeoutMs) {
|
|
361
356
|
try {
|
|
362
|
-
const branch = (await this.executor.exec(`${
|
|
357
|
+
const branch = (await this.executor.exec(`${prefix}${apiPath} --jq '.default_branch'`, this.cwd, { env: tokenEnv })).trim();
|
|
363
358
|
if (branch === expectedBranch) {
|
|
364
359
|
return;
|
|
365
360
|
}
|
|
366
361
|
}
|
|
367
|
-
catch {
|
|
368
|
-
|
|
362
|
+
catch (error) {
|
|
363
|
+
logger.debug(`Polling default branch: ${toErrorMessage(error)}`);
|
|
369
364
|
}
|
|
370
365
|
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
371
366
|
}
|
|
@@ -377,24 +372,18 @@ export class GitHubLifecycleProvider {
|
|
|
377
372
|
* commit) but no files, so xfg sync starts from a clean state.
|
|
378
373
|
*/
|
|
379
374
|
async deleteReadme(repoInfo, token) {
|
|
380
|
-
const
|
|
381
|
-
const hostnameFlag = getHostnameFlag(repoInfo);
|
|
382
|
-
const hostnamePart = hostnameFlag ? `${hostnameFlag} ` : "";
|
|
383
|
-
const apiPath = `repos/${escapeShellArg(repoInfo.owner)}/${escapeShellArg(repoInfo.repo)}`;
|
|
384
|
-
// After repo creation, GitHub may return 404 due to eventual consistency.
|
|
385
|
-
// Exclude 404/not-found from permanent errors so withRetry retries them.
|
|
386
|
-
const postCreatePermanentPatterns = DEFAULT_PERMANENT_ERROR_PATTERNS.filter((p) => !p.test("404 Not Found"));
|
|
375
|
+
const { tokenEnv, prefix, apiPath } = this.buildGhApiPrefix(repoInfo, token);
|
|
387
376
|
// Get the SHA of the README.md created by --add-readme
|
|
388
|
-
const fileInfo = await withRetry(() => this.executor.exec(`${
|
|
377
|
+
const fileInfo = await withRetry(() => this.executor.exec(`${prefix}${apiPath}/contents/README.md --jq '.sha'`, this.cwd, { env: tokenEnv }), {
|
|
389
378
|
retries: this.retries,
|
|
390
|
-
permanentErrorPatterns:
|
|
379
|
+
permanentErrorPatterns: POST_CREATE_PERMANENT_PATTERNS,
|
|
391
380
|
});
|
|
392
381
|
const sha = fileInfo.trim();
|
|
393
382
|
// Delete the README.md to leave the repo clean
|
|
394
|
-
await withRetry(() => this.executor.exec(`${
|
|
395
|
-
`--method DELETE -f message='Remove initialization file' -f sha=${escapeShellArg(sha)}`, this.cwd), {
|
|
383
|
+
await withRetry(() => this.executor.exec(`${prefix}${apiPath}/contents/README.md ` +
|
|
384
|
+
`--method DELETE -f message='Remove initialization file' -f sha=${escapeShellArg(sha)}`, this.cwd, { env: tokenEnv }), {
|
|
396
385
|
retries: this.retries,
|
|
397
|
-
permanentErrorPatterns:
|
|
386
|
+
permanentErrorPatterns: POST_CREATE_PERMANENT_PATTERNS,
|
|
398
387
|
});
|
|
399
388
|
}
|
|
400
389
|
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export { GitHubLifecycleProvider, type GitHubLifecycleProviderOptions, } from "./github-lifecycle-provider.js";
|
|
3
|
-
export { AdoMigrationSource } from "./ado-migration-source.js";
|
|
4
|
-
export { RepoLifecycleFactory } from "./repo-lifecycle-factory.js";
|
|
1
|
+
export type { IRepoLifecycleManager } from "./types.js";
|
|
5
2
|
export { RepoLifecycleManager } from "./repo-lifecycle-manager.js";
|
|
6
|
-
export {
|
|
7
|
-
export { runLifecycleCheck, toCreateRepoSettings, type LifecycleCheckOptions, type LifecycleCheckResult, } from "./lifecycle-helpers.js";
|
|
3
|
+
export { runLifecycleCheck, toCreateRepoSettings, } from "./lifecycle-helpers.js";
|
package/dist/lifecycle/index.js
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
export { GitHubLifecycleProvider, } from "./github-lifecycle-provider.js";
|
|
2
|
-
export { AdoMigrationSource } from "./ado-migration-source.js";
|
|
3
|
-
export { RepoLifecycleFactory } from "./repo-lifecycle-factory.js";
|
|
4
1
|
export { RepoLifecycleManager } from "./repo-lifecycle-manager.js";
|
|
5
|
-
export { formatLifecycleAction, } from "./lifecycle-formatter.js";
|
|
6
2
|
export { runLifecycleCheck, toCreateRepoSettings, } from "./lifecycle-helpers.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LifecycleResult } from "./types.js";
|
|
2
|
-
|
|
2
|
+
interface FormatOptions {
|
|
3
3
|
upstream?: string;
|
|
4
4
|
source?: string;
|
|
5
5
|
settings?: {
|
|
@@ -12,3 +12,4 @@ export interface FormatOptions {
|
|
|
12
12
|
* Returns empty array if action is "existed" (no output needed).
|
|
13
13
|
*/
|
|
14
14
|
export declare function formatLifecycleAction(result: LifecycleResult, options?: FormatOptions): string[];
|
|
15
|
+
export {};
|
|
@@ -20,6 +20,10 @@ export function formatLifecycleAction(result, options) {
|
|
|
20
20
|
case "migrated":
|
|
21
21
|
lines.push(chalk.green(`+ MIGRATE ${options?.source ?? "source"} -> ${repoDisplay}`));
|
|
22
22
|
break;
|
|
23
|
+
default: {
|
|
24
|
+
const _exhaustive = result.action;
|
|
25
|
+
throw new Error(`Unknown lifecycle action: ${_exhaustive}`);
|
|
26
|
+
}
|
|
23
27
|
}
|
|
24
28
|
// Add settings details if provided
|
|
25
29
|
if (options?.settings) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RepoConfig, GitHubRepoSettings } from "../config/types.js";
|
|
2
2
|
import type { RepoInfo } from "../shared/repo-detector.js";
|
|
3
3
|
import type { IRepoLifecycleManager, CreateRepoSettings, LifecycleResult } from "./types.js";
|
|
4
|
-
|
|
4
|
+
interface LifecycleCheckOptions {
|
|
5
5
|
dryRun: boolean;
|
|
6
6
|
/** Base work directory (combined with repoIndex to compute full path). */
|
|
7
7
|
workDir?: string;
|
|
@@ -16,7 +16,7 @@ export interface LifecycleCheckOptions {
|
|
|
16
16
|
* Extracts only the fields relevant for repo creation.
|
|
17
17
|
*/
|
|
18
18
|
export declare function toCreateRepoSettings(repo: GitHubRepoSettings | undefined): CreateRepoSettings | undefined;
|
|
19
|
-
|
|
19
|
+
interface LifecycleCheckResult {
|
|
20
20
|
lifecycleResult: LifecycleResult;
|
|
21
21
|
outputLines: string[];
|
|
22
22
|
}
|
|
@@ -25,3 +25,4 @@ export interface LifecycleCheckResult {
|
|
|
25
25
|
* Returns the lifecycle result and formatted output lines.
|
|
26
26
|
*/
|
|
27
27
|
export declare function runLifecycleCheck(repoConfig: RepoConfig, repoInfo: RepoInfo, repoIndex: number, options: LifecycleCheckOptions, lifecycleManager: IRepoLifecycleManager, repoSettings?: GitHubRepoSettings): Promise<LifecycleCheckResult>;
|
|
28
|
+
export {};
|
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
3
|
import { parseGitUrl } from "../shared/repo-detector.js";
|
|
4
4
|
import { logger } from "../shared/logger.js";
|
|
5
|
+
import { safeCleanup } from "../shared/type-guards.js";
|
|
5
6
|
import { RepoLifecycleFactory } from "./repo-lifecycle-factory.js";
|
|
6
7
|
/**
|
|
7
8
|
* Orchestrates repo lifecycle operations before sync.
|
|
@@ -21,11 +22,11 @@ export class RepoLifecycleManager {
|
|
|
21
22
|
if (repoConfig.upstream || repoConfig.source) {
|
|
22
23
|
throw error;
|
|
23
24
|
}
|
|
24
|
-
// Platform doesn't support lifecycle operations yet - skip
|
|
25
|
+
// Platform doesn't support lifecycle operations yet - log and skip
|
|
26
|
+
logger.debug(`Lifecycle: skipping unsupported platform "${repoInfo.type}"`);
|
|
25
27
|
return { repoInfo, action: "existed" };
|
|
26
28
|
}
|
|
27
29
|
const { token } = options;
|
|
28
|
-
// Check if repo exists
|
|
29
30
|
const exists = await provider.exists(repoInfo, token);
|
|
30
31
|
if (exists) {
|
|
31
32
|
// Repo exists - nothing to do (ignore upstream/source)
|
|
@@ -72,7 +73,6 @@ export class RepoLifecycleManager {
|
|
|
72
73
|
if (!provider.fork) {
|
|
73
74
|
throw new Error(`Platform '${repoInfo.type}' does not support forking`);
|
|
74
75
|
}
|
|
75
|
-
// Parse upstream URL to get repo info
|
|
76
76
|
const upstreamInfo = parseGitUrl(repoConfig.upstream, {
|
|
77
77
|
githubHosts: options.githubHosts,
|
|
78
78
|
});
|
|
@@ -110,14 +110,7 @@ export class RepoLifecycleManager {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
finally {
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
await rm(sourceDir, { recursive: true, force: true });
|
|
116
|
-
}
|
|
117
|
-
catch (cleanupError) {
|
|
118
|
-
// Log cleanup errors at debug level for troubleshooting
|
|
119
|
-
logger.debug(`Failed to clean up migration source directory ${sourceDir}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
120
|
-
}
|
|
113
|
+
await safeCleanup(() => rm(sourceDir, { recursive: true, force: true }), `failed to remove ${sourceDir}`, logger);
|
|
121
114
|
}
|
|
122
115
|
}
|
|
123
116
|
/**
|
|
@@ -8,22 +8,16 @@ export type LifecyclePlatform = "github" | "azure-devops" | "gitlab";
|
|
|
8
8
|
* Result of a lifecycle operation.
|
|
9
9
|
*/
|
|
10
10
|
export interface LifecycleResult {
|
|
11
|
-
/** The repo info (may be updated) */
|
|
12
11
|
repoInfo: RepoInfo;
|
|
13
|
-
/** What action was taken */
|
|
14
12
|
action: "existed" | "created" | "forked" | "migrated";
|
|
15
|
-
/** True if skipped due to dry-run */
|
|
16
13
|
skipped?: boolean;
|
|
17
14
|
}
|
|
18
15
|
/**
|
|
19
16
|
* Options for lifecycle operations.
|
|
20
17
|
*/
|
|
21
18
|
export interface LifecycleOptions {
|
|
22
|
-
/** Dry-run mode - don't make changes */
|
|
23
19
|
dryRun: boolean;
|
|
24
|
-
/** Working directory for git operations */
|
|
25
20
|
workDir: string;
|
|
26
|
-
/** GitHub Enterprise hostnames for URL detection */
|
|
27
21
|
githubHosts?: string[];
|
|
28
22
|
/** Auth token (GitHub App installation token or PAT) for gh CLI commands */
|
|
29
23
|
token?: string;
|
|
@@ -44,7 +38,6 @@ export interface CreateRepoSettings {
|
|
|
44
38
|
* Implementations handle create/fork/receive for a specific platform.
|
|
45
39
|
*/
|
|
46
40
|
export interface IRepoLifecycleProvider {
|
|
47
|
-
/** Platform this provider handles */
|
|
48
41
|
readonly platform: LifecyclePlatform;
|
|
49
42
|
/**
|
|
50
43
|
* Check if a repository exists on this platform.
|
|
@@ -70,7 +63,6 @@ export interface IRepoLifecycleProvider {
|
|
|
70
63
|
* Implementations handle cloning from a source platform.
|
|
71
64
|
*/
|
|
72
65
|
export interface IMigrationSource {
|
|
73
|
-
/** Platform this source handles */
|
|
74
66
|
readonly platform: LifecyclePlatform;
|
|
75
67
|
/**
|
|
76
68
|
* Clone repository with all refs for migration.
|
|
@@ -46,4 +46,9 @@ export interface SummaryData {
|
|
|
46
46
|
}
|
|
47
47
|
export declare function formatSummary(data: SummaryData): string;
|
|
48
48
|
export declare function isGitHubActions(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Append markdown content to GITHUB_STEP_SUMMARY.
|
|
51
|
+
* No-op outside GitHub Actions.
|
|
52
|
+
*/
|
|
53
|
+
export declare function writeGitHubStepSummary(markdown: string): void;
|
|
49
54
|
export declare function writeSummary(data: SummaryData): void;
|
|
@@ -224,10 +224,17 @@ export function formatSummary(data) {
|
|
|
224
224
|
export function isGitHubActions() {
|
|
225
225
|
return !!process.env.GITHUB_STEP_SUMMARY;
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Append markdown content to GITHUB_STEP_SUMMARY.
|
|
229
|
+
* No-op outside GitHub Actions.
|
|
230
|
+
*/
|
|
231
|
+
export function writeGitHubStepSummary(markdown) {
|
|
228
232
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
229
233
|
if (!summaryPath)
|
|
230
234
|
return;
|
|
231
|
-
const markdown = formatSummary(data);
|
|
232
235
|
appendFileSync(summaryPath, "\n" + markdown + "\n");
|
|
233
236
|
}
|
|
237
|
+
export function writeSummary(data) {
|
|
238
|
+
const markdown = formatSummary(data);
|
|
239
|
+
writeGitHubStepSummary(markdown);
|
|
240
|
+
}
|
package/dist/output/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { formatSyncReportCLI, formatSyncReportMarkdown, writeSyncReportSummary, type SyncReport, type RepoFileChanges, type FileChange, } from "./sync-report.js";
|
|
2
|
-
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary,
|
|
3
|
-
export { formatUnifiedSummaryMarkdown, writeUnifiedSummary,
|
|
2
|
+
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary, type SettingsReport, type RepoChanges, type RulesetChange, type SettingChange, } from "./settings-report.js";
|
|
3
|
+
export { formatUnifiedSummaryMarkdown, writeUnifiedSummary, } from "./unified-summary.js";
|
|
4
4
|
export { formatSummary, isGitHubActions, writeSummary, type MergeOutcome, type FileChanges, type RulesetPlanDetail, type RepoSettingsPlanDetail, type RepoResult, type SummaryData, } from "./github-summary.js";
|
|
5
5
|
export { getMergeOutcome, toFileChanges, buildRepoResult, buildErrorResult, } from "./summary-utils.js";
|
package/dist/output/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Sync report (repo-grouped file changes)
|
|
2
2
|
export { formatSyncReportCLI, formatSyncReportMarkdown, writeSyncReportSummary, } from "./sync-report.js";
|
|
3
3
|
// Settings report (repo-grouped settings changes)
|
|
4
|
-
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary,
|
|
4
|
+
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary, } from "./settings-report.js";
|
|
5
5
|
// Unified summary (lifecycle + sync + settings in one diff block)
|
|
6
6
|
export { formatUnifiedSummaryMarkdown, writeUnifiedSummary, } from "./unified-summary.js";
|
|
7
7
|
// GitHub Actions summary
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// src/output/lifecycle-report.ts
|
|
2
|
-
import { appendFileSync } from "node:fs";
|
|
3
2
|
import chalk from "chalk";
|
|
4
|
-
|
|
5
|
-
// Builder
|
|
6
|
-
// =============================================================================
|
|
3
|
+
import { writeGitHubStepSummary } from "./github-summary.js";
|
|
7
4
|
export function buildLifecycleReport(results) {
|
|
8
5
|
const actions = [];
|
|
9
6
|
const totals = { created: 0, forked: 0, migrated: 0, existed: 0 };
|
|
@@ -19,10 +16,7 @@ export function buildLifecycleReport(results) {
|
|
|
19
16
|
}
|
|
20
17
|
return { actions, totals };
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
// Helpers
|
|
24
|
-
// =============================================================================
|
|
25
|
-
function formatSummary(totals) {
|
|
19
|
+
function formatLifecycleSummary(totals) {
|
|
26
20
|
const total = totals.created + totals.forked + totals.migrated;
|
|
27
21
|
if (total === 0) {
|
|
28
22
|
return "No changes";
|
|
@@ -43,9 +37,6 @@ function formatSummary(totals) {
|
|
|
43
37
|
export function hasLifecycleChanges(report) {
|
|
44
38
|
return report.actions.some((a) => a.action !== "existed");
|
|
45
39
|
}
|
|
46
|
-
// =============================================================================
|
|
47
|
-
// CLI Formatter
|
|
48
|
-
// =============================================================================
|
|
49
40
|
export function formatLifecycleReportCLI(report) {
|
|
50
41
|
if (!hasLifecycleChanges(report)) {
|
|
51
42
|
return [];
|
|
@@ -76,12 +67,9 @@ export function formatLifecycleReportCLI(report) {
|
|
|
76
67
|
}
|
|
77
68
|
lines.push("");
|
|
78
69
|
// Summary
|
|
79
|
-
lines.push(
|
|
70
|
+
lines.push(formatLifecycleSummary(report.totals));
|
|
80
71
|
return lines;
|
|
81
72
|
}
|
|
82
|
-
// =============================================================================
|
|
83
|
-
// Markdown Formatter
|
|
84
|
-
// =============================================================================
|
|
85
73
|
export function formatLifecycleReportMarkdown(report, dryRun) {
|
|
86
74
|
if (!hasLifecycleChanges(report)) {
|
|
87
75
|
return "";
|
|
@@ -129,18 +117,12 @@ export function formatLifecycleReportMarkdown(report, dryRun) {
|
|
|
129
117
|
lines.push("");
|
|
130
118
|
}
|
|
131
119
|
// Summary
|
|
132
|
-
lines.push(`**${
|
|
120
|
+
lines.push(`**${formatLifecycleSummary(report.totals)}**`);
|
|
133
121
|
return lines.join("\n");
|
|
134
122
|
}
|
|
135
|
-
// =============================================================================
|
|
136
|
-
// File Writer
|
|
137
|
-
// =============================================================================
|
|
138
123
|
export function writeLifecycleReportSummary(report, dryRun) {
|
|
139
|
-
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
140
|
-
if (!summaryPath)
|
|
141
|
-
return;
|
|
142
124
|
const markdown = formatLifecycleReportMarkdown(report, dryRun);
|
|
143
125
|
if (!markdown)
|
|
144
126
|
return;
|
|
145
|
-
|
|
127
|
+
writeGitHubStepSummary(markdown);
|
|
146
128
|
}
|