@fuzdev/fuz_gitops 0.78.0 → 0.79.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/README.md +91 -17
- package/dist/ModulesDetail.svelte +22 -27
- package/dist/ModulesDetail.svelte.d.ts.map +1 -1
- package/dist/ModulesNav.svelte +8 -5
- package/dist/ModulesNav.svelte.d.ts.map +1 -1
- package/dist/ModulesPage.svelte +3 -5
- package/dist/ModulesPage.svelte.d.ts.map +1 -1
- package/dist/PageFooter.svelte +2 -2
- package/dist/PageHeader.svelte +6 -6
- package/dist/PageHeader.svelte.d.ts +0 -3
- package/dist/PageHeader.svelte.d.ts.map +1 -1
- package/dist/PullRequestsDetail.svelte +29 -20
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
- package/dist/PullRequestsPage.svelte +4 -6
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
- package/dist/ReposTable.svelte +43 -55
- package/dist/ReposTable.svelte.d.ts.map +1 -1
- package/dist/ReposTree.svelte +9 -21
- package/dist/ReposTree.svelte.d.ts.map +1 -1
- package/dist/ReposTreeNav.svelte +9 -12
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
- package/dist/TablePage.svelte +3 -5
- package/dist/TablePage.svelte.d.ts.map +1 -1
- package/dist/TreeItemPage.svelte +3 -5
- package/dist/TreeItemPage.svelte.d.ts.map +1 -1
- package/dist/TreePage.svelte +3 -5
- package/dist/TreePage.svelte.d.ts.map +1 -1
- package/dist/changeset_generator.d.ts.map +1 -1
- package/dist/changeset_generator.js +2 -2
- package/dist/changeset_reader.d.ts.map +1 -1
- package/dist/changeset_reader.js +3 -3
- package/dist/ci_reconcile.d.ts +6 -10
- package/dist/ci_reconcile.d.ts.map +1 -1
- package/dist/ci_reconcile.js +6 -8
- package/dist/dependency_graph.d.ts +26 -49
- package/dist/dependency_graph.d.ts.map +1 -1
- package/dist/dependency_graph.js +40 -83
- package/dist/dependency_updater.d.ts +1 -1
- package/dist/dependency_updater.d.ts.map +1 -1
- package/dist/dependency_updater.js +14 -9
- package/dist/fetch_repo_data.d.ts +34 -8
- package/dist/fetch_repo_data.d.ts.map +1 -1
- package/dist/fetch_repo_data.js +44 -22
- package/dist/fs_fetch_value_cache.d.ts +1 -1
- package/dist/fs_fetch_value_cache.d.ts.map +1 -1
- package/dist/fs_fetch_value_cache.js +3 -3
- package/dist/git_operations.d.ts +6 -49
- package/dist/git_operations.d.ts.map +1 -1
- package/dist/git_operations.js +13 -126
- package/dist/github.d.ts +17 -2
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +17 -8
- package/dist/github_helpers.d.ts.map +1 -1
- package/dist/github_helpers.js +3 -11
- package/dist/gitops_analyze.task.d.ts +24 -2
- package/dist/gitops_analyze.task.d.ts.map +1 -1
- package/dist/gitops_analyze.task.js +47 -34
- package/dist/gitops_config.d.ts +34 -79
- package/dist/gitops_config.d.ts.map +1 -1
- package/dist/gitops_config.js +76 -53
- package/dist/gitops_constants.d.ts +6 -4
- package/dist/gitops_constants.d.ts.map +1 -1
- package/dist/gitops_constants.js +6 -4
- package/dist/gitops_plan.task.d.ts +29 -3
- package/dist/gitops_plan.task.d.ts.map +1 -1
- package/dist/gitops_plan.task.js +83 -61
- package/dist/gitops_publish.task.d.ts +62 -2
- package/dist/gitops_publish.task.d.ts.map +1 -1
- package/dist/gitops_publish.task.js +193 -105
- package/dist/gitops_run.task.d.ts +74 -0
- package/dist/gitops_run.task.d.ts.map +1 -1
- package/dist/gitops_run.task.js +176 -162
- package/dist/gitops_sync.task.d.ts +65 -3
- package/dist/gitops_sync.task.d.ts.map +1 -1
- package/dist/gitops_sync.task.js +163 -37
- package/dist/gitops_task_helpers.d.ts +86 -59
- package/dist/gitops_task_helpers.d.ts.map +1 -1
- package/dist/gitops_task_helpers.js +120 -79
- package/dist/gitops_validate.task.d.ts +1 -2
- package/dist/gitops_validate.task.d.ts.map +1 -1
- package/dist/gitops_validate.task.js +25 -32
- package/dist/graph_validation.d.ts +16 -27
- package/dist/graph_validation.d.ts.map +1 -1
- package/dist/graph_validation.js +24 -65
- package/dist/local_repo.d.ts +50 -62
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +83 -196
- package/dist/log_helpers.d.ts +8 -8
- package/dist/log_helpers.d.ts.map +1 -1
- package/dist/multi_repo_publisher.d.ts +10 -0
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +125 -61
- package/dist/npm_registry.d.ts +23 -19
- package/dist/npm_registry.d.ts.map +1 -1
- package/dist/npm_registry.js +21 -48
- package/dist/operations.d.ts +75 -180
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +15 -7
- package/dist/operations_defaults.d.ts +17 -2
- package/dist/operations_defaults.d.ts.map +1 -1
- package/dist/operations_defaults.js +111 -128
- package/dist/output_helpers.d.ts +36 -0
- package/dist/output_helpers.d.ts.map +1 -1
- package/dist/output_helpers.js +37 -3
- package/dist/preflight_checks.d.ts +20 -27
- package/dist/preflight_checks.d.ts.map +1 -1
- package/dist/preflight_checks.js +37 -126
- package/dist/publish_gate.d.ts.map +1 -1
- package/dist/publish_steps.d.ts +2 -4
- package/dist/publish_steps.d.ts.map +1 -1
- package/dist/publish_steps.js +4 -8
- package/dist/publishing_event.d.ts +2 -8
- package/dist/publishing_event.d.ts.map +1 -1
- package/dist/publishing_event.js +20 -17
- package/dist/publishing_event_handler.d.ts +10 -8
- package/dist/publishing_event_handler.d.ts.map +1 -1
- package/dist/publishing_event_handler.js +19 -17
- package/dist/publishing_plan.d.ts +16 -1
- package/dist/publishing_plan.d.ts.map +1 -1
- package/dist/publishing_plan.js +332 -254
- package/dist/publishing_plan_helpers.d.ts.map +1 -1
- package/dist/publishing_plan_helpers.js +3 -3
- package/dist/publishing_plan_logging.d.ts +1 -1
- package/dist/publishing_plan_logging.d.ts.map +1 -1
- package/dist/publishing_plan_logging.js +22 -15
- package/dist/repo.svelte.d.ts +7 -0
- package/dist/repo.svelte.d.ts.map +1 -1
- package/dist/repo.svelte.js +3 -0
- package/dist/repo_readiness.d.ts +210 -0
- package/dist/repo_readiness.d.ts.map +1 -0
- package/dist/repo_readiness.js +516 -0
- package/dist/repos_status.d.ts +3340 -0
- package/dist/repos_status.d.ts.map +1 -0
- package/dist/repos_status.js +555 -0
- package/dist/repos_status_load.d.ts +65 -0
- package/dist/repos_status_load.d.ts.map +1 -0
- package/dist/repos_status_load.js +131 -0
- package/dist/version_utils.js +1 -1
- package/package.json +15 -34
- package/src/lib/cargo_toml.ts +3 -3
- package/src/lib/changeset_generator.ts +19 -19
- package/src/lib/changeset_reader.ts +18 -18
- package/src/lib/ci_reconcile.ts +12 -16
- package/src/lib/dependency_graph.ts +61 -113
- package/src/lib/dependency_updater.ts +36 -30
- package/src/lib/fetch_repo_data.ts +71 -34
- package/src/lib/fs_fetch_value_cache.ts +15 -11
- package/src/lib/git_operations.ts +22 -194
- package/src/lib/github.ts +33 -21
- package/src/lib/github_helpers.ts +9 -16
- package/src/lib/gitops_analyze.task.ts +104 -66
- package/src/lib/gitops_config.ts +90 -139
- package/src/lib/gitops_constants.ts +6 -4
- package/src/lib/gitops_plan.task.ts +128 -76
- package/src/lib/gitops_publish.task.ts +280 -144
- package/src/lib/gitops_run.task.ts +265 -193
- package/src/lib/gitops_sync.task.ts +236 -52
- package/src/lib/gitops_task_helpers.ts +170 -114
- package/src/lib/gitops_validate.task.ts +44 -54
- package/src/lib/graph_validation.ts +28 -89
- package/src/lib/local_repo.ts +128 -335
- package/src/lib/log_helpers.ts +18 -28
- package/src/lib/multi_repo_publisher.ts +192 -110
- package/src/lib/npm_registry.ts +42 -67
- package/src/lib/operations.ts +99 -160
- package/src/lib/operations_defaults.ts +157 -201
- package/src/lib/output_helpers.ts +58 -6
- package/src/lib/preflight_checks.ts +52 -188
- package/src/lib/publish_gate.ts +7 -9
- package/src/lib/publish_steps.ts +27 -25
- package/src/lib/publishing_event.ts +26 -23
- package/src/lib/publishing_event_handler.ts +24 -22
- package/src/lib/publishing_plan.ts +474 -310
- package/src/lib/publishing_plan_helpers.ts +16 -12
- package/src/lib/publishing_plan_logging.ts +53 -44
- package/src/lib/repo.svelte.ts +16 -8
- package/src/lib/repo_readiness.ts +647 -0
- package/src/lib/repos_status.ts +671 -0
- package/src/lib/repos_status_load.ts +173 -0
- package/src/lib/version_utils.ts +3 -3
- package/dist/config_reconcile.d.ts +0 -46
- package/dist/config_reconcile.d.ts.map +0 -1
- package/dist/config_reconcile.js +0 -60
- package/dist/paths.d.ts +0 -7
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js +0 -6
- package/dist/repo_ops.d.ts +0 -59
- package/dist/repo_ops.d.ts.map +0 -1
- package/dist/repo_ops.js +0 -170
- package/dist/resolved_gitops_config.d.ts +0 -9
- package/dist/resolved_gitops_config.d.ts.map +0 -1
- package/dist/resolved_gitops_config.js +0 -12
- package/src/lib/config_reconcile.ts +0 -94
- package/src/lib/paths.ts +0 -6
- package/src/lib/repo_ops.ts +0 -216
- package/src/lib/resolved_gitops_config.ts +0 -27
|
@@ -1,38 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Production implementations of operations interfaces.
|
|
3
3
|
*
|
|
4
|
-
* Provides real git, npm, fs, and
|
|
4
|
+
* Provides real git, npm, fs, build, and `repos` operations for production use.
|
|
5
5
|
* For interface definitions and dependency injection pattern, see `operations.ts`.
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {spawn_out} from '@fuzdev/fuz_util/process.ts';
|
|
11
|
-
import {readFile, writeFile, mkdir, stat} from 'node:fs/promises';
|
|
12
|
-
import {
|
|
13
|
-
import {fs_classify_error} from '@fuzdev/fuz_util/fs.ts';
|
|
14
|
-
import {EMPTY_OBJECT} from '@fuzdev/fuz_util/object.ts';
|
|
10
|
+
import { spawn_out, spawn_process, spawn_result_to_message } from '@fuzdev/fuz_util/process.ts';
|
|
11
|
+
import { readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
12
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
13
|
+
import { fs_classify_error } from '@fuzdev/fuz_util/fs.ts';
|
|
14
|
+
import { EMPTY_OBJECT } from '@fuzdev/fuz_util/object.ts';
|
|
15
15
|
|
|
16
|
-
import {has_changesets, read_changesets, predict_next_version} from './changeset_reader.ts';
|
|
17
|
-
import {wait_for_package} from './npm_registry.ts';
|
|
18
|
-
import {run_preflight_checks} from './preflight_checks.ts';
|
|
19
|
-
import {
|
|
20
|
-
git_add,
|
|
21
|
-
git_commit,
|
|
22
|
-
git_add_and_commit,
|
|
23
|
-
git_tag,
|
|
24
|
-
git_push_tag,
|
|
25
|
-
git_has_changes,
|
|
26
|
-
git_list_uncommitted_files,
|
|
27
|
-
git_has_file_changed,
|
|
28
|
-
git_stash,
|
|
29
|
-
git_stash_pop,
|
|
30
|
-
git_switch_branch,
|
|
31
|
-
git_current_branch_name_required,
|
|
32
|
-
git_current_commit_hash_required,
|
|
33
|
-
git_check_clean_workspace_as_boolean,
|
|
34
|
-
git_has_remote,
|
|
35
|
-
} from './git_operations.ts';
|
|
16
|
+
import { has_changesets, read_changesets, predict_next_version } from './changeset_reader.ts';
|
|
17
|
+
import { wait_for_package } from './npm_registry.ts';
|
|
18
|
+
import { run_preflight_checks } from './preflight_checks.ts';
|
|
19
|
+
import { git_add, git_commit, git_current_commit_hash_required } from './git_operations.ts';
|
|
36
20
|
import type {
|
|
37
21
|
ChangesetOperations,
|
|
38
22
|
GitOperations,
|
|
@@ -42,199 +26,181 @@ import type {
|
|
|
42
26
|
FsOperations,
|
|
43
27
|
BuildOperations,
|
|
44
28
|
GitopsOperations,
|
|
29
|
+
ReposOperations
|
|
45
30
|
} from './operations.ts';
|
|
46
31
|
|
|
47
32
|
/** Wrap an async function that returns a value */
|
|
48
33
|
const wrap_with_value = async <T>(
|
|
49
|
-
fn: () => Promise<T
|
|
50
|
-
): Promise<{ok: true; value: T} | {ok: false; message: string}> => {
|
|
34
|
+
fn: () => Promise<T>
|
|
35
|
+
): Promise<{ ok: true; value: T } | { ok: false; message: string }> => {
|
|
51
36
|
try {
|
|
52
37
|
const value = await fn();
|
|
53
|
-
return {ok: true, value};
|
|
38
|
+
return { ok: true, value };
|
|
54
39
|
} catch (error) {
|
|
55
|
-
return {ok: false, message: String(error)};
|
|
40
|
+
return { ok: false, message: String(error) };
|
|
56
41
|
}
|
|
57
42
|
};
|
|
58
43
|
|
|
59
44
|
/** Wrap an async function, ignoring its return value */
|
|
60
45
|
const wrap_void = async (
|
|
61
|
-
fn: () => Promise<unknown
|
|
62
|
-
): Promise<{ok: true} | {ok: false; message: string}> => {
|
|
46
|
+
fn: () => Promise<unknown>
|
|
47
|
+
): Promise<{ ok: true } | { ok: false; message: string }> => {
|
|
63
48
|
try {
|
|
64
49
|
await fn();
|
|
65
|
-
return {ok: true};
|
|
50
|
+
return { ok: true };
|
|
66
51
|
} catch (error) {
|
|
67
|
-
return {ok: false, message: String(error)};
|
|
52
|
+
return { ok: false, message: String(error) };
|
|
68
53
|
}
|
|
69
54
|
};
|
|
70
55
|
|
|
71
56
|
export const default_changeset_operations: ChangesetOperations = {
|
|
72
57
|
has_changesets: async (options) => {
|
|
73
|
-
const {repo} = options;
|
|
58
|
+
const { repo } = options;
|
|
74
59
|
return wrap_with_value(() => has_changesets(repo));
|
|
75
60
|
},
|
|
76
61
|
|
|
77
62
|
read_changesets: async (options) => {
|
|
78
|
-
const {repo, log} = options;
|
|
63
|
+
const { repo, log } = options;
|
|
79
64
|
return wrap_with_value(() => read_changesets(repo, log));
|
|
80
65
|
},
|
|
81
66
|
|
|
82
67
|
predict_next_version: async (options) => {
|
|
83
|
-
const {repo, log} = options;
|
|
68
|
+
const { repo, log } = options;
|
|
84
69
|
try {
|
|
85
70
|
const result = await predict_next_version(repo, log);
|
|
86
71
|
if (result === null) {
|
|
87
72
|
return null;
|
|
88
73
|
}
|
|
89
|
-
return {ok: true, ...result};
|
|
74
|
+
return { ok: true, ...result };
|
|
90
75
|
} catch (error) {
|
|
91
|
-
return {ok: false, message: String(error)};
|
|
76
|
+
return { ok: false, message: String(error) };
|
|
92
77
|
}
|
|
93
|
-
}
|
|
78
|
+
}
|
|
94
79
|
};
|
|
95
80
|
|
|
96
81
|
export const default_git_operations: GitOperations = {
|
|
97
|
-
// Core git info
|
|
98
|
-
current_branch_name: async (options) => {
|
|
99
|
-
const {cwd} = options ?? EMPTY_OBJECT;
|
|
100
|
-
return wrap_with_value(() => git_current_branch_name_required(cwd ? {cwd} : undefined));
|
|
101
|
-
},
|
|
102
|
-
|
|
103
82
|
current_commit_hash: async (options) => {
|
|
104
|
-
const {
|
|
105
|
-
return wrap_with_value(() => git_current_commit_hash_required(
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
check_clean_workspace: async (options) => {
|
|
109
|
-
const {cwd} = options ?? EMPTY_OBJECT;
|
|
110
|
-
return wrap_with_value(() => git_check_clean_workspace_as_boolean(cwd ? {cwd} : undefined));
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
// Branch operations
|
|
114
|
-
checkout: async (options) => {
|
|
115
|
-
const {branch, cwd} = options;
|
|
116
|
-
return wrap_void(() => git_checkout(branch, cwd ? {cwd} : undefined));
|
|
83
|
+
const { cwd } = options ?? EMPTY_OBJECT;
|
|
84
|
+
return wrap_with_value(() => git_current_commit_hash_required(cwd ? { cwd } : undefined));
|
|
117
85
|
},
|
|
118
86
|
|
|
119
|
-
pull: async (options) => {
|
|
120
|
-
const {origin, branch, cwd} = options ?? EMPTY_OBJECT;
|
|
121
|
-
try {
|
|
122
|
-
// Omit the branch arg when absent: a trailing `''` makes git pull the remote's
|
|
123
|
-
// default branch (origin/HEAD) instead of the current branch's upstream.
|
|
124
|
-
const spawned = await spawn_out(
|
|
125
|
-
'git',
|
|
126
|
-
['pull', origin || 'origin', ...(branch ? [branch] : [])],
|
|
127
|
-
cwd ? {cwd} : undefined,
|
|
128
|
-
);
|
|
129
|
-
if (spawned.result.ok) {
|
|
130
|
-
return {ok: true};
|
|
131
|
-
} else {
|
|
132
|
-
return {ok: false, message: spawned.stderr || 'Pull failed'};
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
return {ok: false, message: String(error)};
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
switch_branch: async (options) => {
|
|
140
|
-
const {branch, pull, cwd} = options;
|
|
141
|
-
return wrap_void(() => git_switch_branch(branch as GitBranch, pull, cwd ? {cwd} : undefined));
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
has_remote: async (options) => {
|
|
145
|
-
const {remote, cwd} = options ?? EMPTY_OBJECT;
|
|
146
|
-
return wrap_with_value(() => git_has_remote(remote, cwd ? {cwd} : undefined));
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
// Staging and committing
|
|
150
87
|
add: async (options) => {
|
|
151
|
-
const {files, cwd} = options;
|
|
152
|
-
return wrap_void(() => git_add(files, cwd ? {cwd} : undefined));
|
|
88
|
+
const { files, cwd } = options;
|
|
89
|
+
return wrap_void(() => git_add(files, cwd ? { cwd } : undefined));
|
|
153
90
|
},
|
|
154
91
|
|
|
155
92
|
commit: async (options) => {
|
|
156
|
-
const {message, cwd} = options;
|
|
157
|
-
return wrap_void(() => git_commit(message, cwd ? {cwd} : undefined));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
add_and_commit: async (options) => {
|
|
161
|
-
const {files, message, cwd} = options;
|
|
162
|
-
return wrap_void(() => git_add_and_commit(files, message, cwd ? {cwd} : undefined));
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
has_changes: async (options) => {
|
|
166
|
-
const {cwd} = options ?? EMPTY_OBJECT;
|
|
167
|
-
return wrap_with_value(() => git_has_changes(cwd ? {cwd} : undefined));
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
list_uncommitted_files: async (options) => {
|
|
171
|
-
const {cwd} = options ?? EMPTY_OBJECT;
|
|
172
|
-
return wrap_with_value(() => git_list_uncommitted_files(cwd ? {cwd} : undefined));
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
// Tagging
|
|
176
|
-
tag: async (options) => {
|
|
177
|
-
const {tag_name, message, cwd} = options;
|
|
178
|
-
return wrap_void(() => git_tag(tag_name, message, cwd ? {cwd} : undefined));
|
|
179
|
-
},
|
|
93
|
+
const { message, files, cwd } = options;
|
|
94
|
+
return wrap_void(() => git_commit(message, files, cwd ? { cwd } : undefined));
|
|
95
|
+
}
|
|
96
|
+
};
|
|
180
97
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return wrap_void(() => git_push_tag(tag_name, origin as GitOrigin, cwd ? {cwd} : undefined));
|
|
184
|
-
},
|
|
98
|
+
/** The most lines of a child's stderr a failure carries. */
|
|
99
|
+
export const OUTPUT_TAIL_MAX_LINES = 20;
|
|
185
100
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const {message, cwd} = options ?? EMPTY_OBJECT;
|
|
189
|
-
return wrap_void(() => git_stash(message, cwd ? {cwd} : undefined));
|
|
190
|
-
},
|
|
101
|
+
/** The most characters of a child's stderr a failure carries. */
|
|
102
|
+
export const OUTPUT_TAIL_MAX_CHARS = 4096;
|
|
191
103
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The end of a process's output, for a failure message: its last lines, then
|
|
106
|
+
* its last characters, with terminal escape sequences stripped and trailing
|
|
107
|
+
* whitespace trimmed.
|
|
108
|
+
*
|
|
109
|
+
* @param text - the output, or as much of its end as was kept
|
|
110
|
+
* @param max_lines - the most lines to keep
|
|
111
|
+
* @param max_chars - the most characters to keep, applied after `max_lines`
|
|
112
|
+
*/
|
|
113
|
+
export const output_tail = (
|
|
114
|
+
text: string,
|
|
115
|
+
max_lines: number = OUTPUT_TAIL_MAX_LINES,
|
|
116
|
+
max_chars: number = OUTPUT_TAIL_MAX_CHARS
|
|
117
|
+
): string => {
|
|
118
|
+
const lines = stripVTControlCharacters(text).replace(/\r\n?/g, '\n').trimEnd().split('\n');
|
|
119
|
+
const tail = lines.slice(-max_lines).join('\n');
|
|
120
|
+
if (tail.length <= max_chars) return drop_leading_blank_lines(tail);
|
|
121
|
+
// a character cut can split a line, and with it a secret masking would no longer
|
|
122
|
+
// recognize, so the partial first line goes
|
|
123
|
+
const cut = tail.slice(-max_chars);
|
|
124
|
+
if (tail[tail.length - max_chars - 1] === '\n') return drop_leading_blank_lines(cut);
|
|
125
|
+
const nl = cut.indexOf('\n');
|
|
126
|
+
return nl === -1 ? '' : drop_leading_blank_lines(cut.slice(nl + 1));
|
|
204
127
|
};
|
|
205
128
|
|
|
129
|
+
// not `trimStart`, which would strip the first line's indentation
|
|
130
|
+
const drop_leading_blank_lines = (text: string): string => text.replace(/^\n+/, '');
|
|
131
|
+
|
|
206
132
|
export const default_process_operations: ProcessOperations = {
|
|
207
|
-
|
|
208
|
-
const {cmd, args, cwd} = options;
|
|
133
|
+
run_interactive: async (options) => {
|
|
134
|
+
const { cmd, args, cwd, stdout = 'stdout' } = options;
|
|
209
135
|
try {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
136
|
+
// stdin and stdout go straight to the child (fd 2 for stdout routed to our stderr), so
|
|
137
|
+
// they stay a TTY when ours are — npm prompts for a one-time password only on a TTY.
|
|
138
|
+
// Only stderr is piped, to keep its end for the failure message while echoing it live.
|
|
139
|
+
const { child, closed } = spawn_process(cmd, args, {
|
|
140
|
+
cwd,
|
|
141
|
+
stdio: ['inherit', stdout === 'stderr' ? 2 : 'inherit', 'pipe']
|
|
142
|
+
});
|
|
143
|
+
// a rolling window over stderr's end, bounded however much the child writes
|
|
144
|
+
let kept = '';
|
|
145
|
+
child.stderr?.setEncoding('utf8');
|
|
146
|
+
child.stderr?.on('data', (chunk: string) => {
|
|
147
|
+
process.stderr.write(chunk);
|
|
148
|
+
kept += chunk;
|
|
149
|
+
if (kept.length > OUTPUT_TAIL_MAX_CHARS * 4) kept = kept.slice(-OUTPUT_TAIL_MAX_CHARS * 2);
|
|
150
|
+
});
|
|
151
|
+
const result = await closed;
|
|
152
|
+
if (result.ok) return { ok: true };
|
|
153
|
+
const stderr_tail = output_tail(kept);
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
message: `\`${[cmd, ...args].join(' ')}\` failed (${spawn_result_to_message(result)})`,
|
|
157
|
+
stderr_tail: stderr_tail || undefined
|
|
158
|
+
};
|
|
224
159
|
} catch (error) {
|
|
225
|
-
return {ok: false, message: String(error)};
|
|
160
|
+
return { ok: false, message: String(error) };
|
|
226
161
|
}
|
|
227
|
-
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const default_repos_operations: ReposOperations = {
|
|
166
|
+
status: async (options) => {
|
|
167
|
+
const { keys, registry, fetch } = options;
|
|
168
|
+
const args = [...(registry === undefined ? [] : ['--registry', registry]), 'status'];
|
|
169
|
+
if (fetch) args.push('--fetch');
|
|
170
|
+
// `--` so a key can never read as a flag
|
|
171
|
+
args.push('--json', '--', ...keys);
|
|
172
|
+
const spawned = await spawn_out('repos', args);
|
|
173
|
+
const { result } = spawned;
|
|
174
|
+
if (result.kind === 'error') {
|
|
175
|
+
const not_found = (result.error as NodeJS.ErrnoException).code === 'ENOENT';
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
kind: not_found ? 'not_found' : 'failed',
|
|
179
|
+
message: result.error.message
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (result.kind === 'signaled') {
|
|
183
|
+
return { ok: false, kind: 'failed', message: `repos was killed by ${result.signal}` };
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
ok: true,
|
|
187
|
+
output: {
|
|
188
|
+
stdout: spawned.stdout ?? '',
|
|
189
|
+
stderr: spawned.stderr ?? '',
|
|
190
|
+
exit_code: result.code
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
228
194
|
};
|
|
229
195
|
|
|
230
196
|
export const default_npm_operations: NpmOperations = {
|
|
231
197
|
wait_for_package: async (options) => {
|
|
232
|
-
const {pkg, version, wait_options, log} = options;
|
|
198
|
+
const { pkg, version, wait_options, log } = options;
|
|
233
199
|
try {
|
|
234
|
-
await wait_for_package(pkg, version, {...wait_options, log});
|
|
235
|
-
return {ok: true};
|
|
200
|
+
await wait_for_package(pkg, version, { ...wait_options, log });
|
|
201
|
+
return { ok: true };
|
|
236
202
|
} catch (error) {
|
|
237
|
-
return {ok: false, message: String(error)
|
|
203
|
+
return { ok: false, message: String(error) };
|
|
238
204
|
}
|
|
239
205
|
},
|
|
240
206
|
|
|
@@ -244,76 +210,66 @@ export const default_npm_operations: NpmOperations = {
|
|
|
244
210
|
if (result.stdout) {
|
|
245
211
|
const username = result.stdout.trim();
|
|
246
212
|
if (username) {
|
|
247
|
-
return {ok: true, username};
|
|
213
|
+
return { ok: true, username };
|
|
248
214
|
}
|
|
249
215
|
}
|
|
250
|
-
return {ok: false, message: 'Not logged in to npm'};
|
|
216
|
+
return { ok: false, message: 'Not logged in to npm' };
|
|
251
217
|
} catch (error) {
|
|
252
|
-
return {ok: false, message: String(error)};
|
|
218
|
+
return { ok: false, message: String(error) };
|
|
253
219
|
}
|
|
254
220
|
},
|
|
255
221
|
|
|
256
222
|
check_registry: async () => {
|
|
257
223
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return {ok: false, message: 'Failed to ping npm registry'};
|
|
263
|
-
} catch (error) {
|
|
264
|
-
return {ok: false, message: String(error)};
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
install: async (options) => {
|
|
269
|
-
const {cwd} = options ?? EMPTY_OBJECT;
|
|
270
|
-
try {
|
|
271
|
-
const spawned = await spawn_out('npm', ['install'], cwd ? {cwd} : undefined);
|
|
272
|
-
if (spawned.result.ok) {
|
|
273
|
-
return {ok: true};
|
|
274
|
-
} else {
|
|
275
|
-
return {ok: false, message: 'Install failed', stderr: spawned.stderr || undefined};
|
|
224
|
+
// the exit status alone: `npm ping` reports on stderr, writing nothing to stdout
|
|
225
|
+
const { result } = await spawn_out('npm', ['ping']);
|
|
226
|
+
if (result.ok) {
|
|
227
|
+
return { ok: true };
|
|
276
228
|
}
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
message: `Failed to ping npm registry (${spawn_result_to_message(result)})`
|
|
232
|
+
};
|
|
277
233
|
} catch (error) {
|
|
278
|
-
return {ok: false, message: String(error)};
|
|
234
|
+
return { ok: false, message: String(error) };
|
|
279
235
|
}
|
|
280
|
-
}
|
|
236
|
+
}
|
|
281
237
|
};
|
|
282
238
|
|
|
283
239
|
export const default_preflight_operations: PreflightOperations = {
|
|
284
240
|
run_preflight_checks: async (options) => {
|
|
285
241
|
return run_preflight_checks(options);
|
|
286
|
-
}
|
|
242
|
+
}
|
|
287
243
|
};
|
|
288
244
|
|
|
289
245
|
export const default_fs_operations: FsOperations = {
|
|
290
246
|
readFile: async (options) => {
|
|
291
|
-
const {path, encoding} = options;
|
|
247
|
+
const { path, encoding } = options;
|
|
292
248
|
try {
|
|
293
249
|
const value = await readFile(path, encoding);
|
|
294
|
-
return {ok: true, value};
|
|
250
|
+
return { ok: true, value };
|
|
295
251
|
} catch (error) {
|
|
296
|
-
return {ok: false, ...fs_classify_error(error)};
|
|
252
|
+
return { ok: false, ...fs_classify_error(error) };
|
|
297
253
|
}
|
|
298
254
|
},
|
|
299
255
|
|
|
300
256
|
writeFile: async (options) => {
|
|
301
|
-
const {path, content} = options;
|
|
257
|
+
const { path, content } = options;
|
|
302
258
|
try {
|
|
303
259
|
await writeFile(path, content);
|
|
304
|
-
return {ok: true};
|
|
260
|
+
return { ok: true };
|
|
305
261
|
} catch (error) {
|
|
306
|
-
return {ok: false, ...fs_classify_error(error)};
|
|
262
|
+
return { ok: false, ...fs_classify_error(error) };
|
|
307
263
|
}
|
|
308
264
|
},
|
|
309
265
|
|
|
310
266
|
mkdir: async (options) => {
|
|
311
|
-
const {path, recursive} = options;
|
|
267
|
+
const { path, recursive } = options;
|
|
312
268
|
try {
|
|
313
|
-
await mkdir(path, {recursive});
|
|
314
|
-
return {ok: true};
|
|
269
|
+
await mkdir(path, { recursive });
|
|
270
|
+
return { ok: true };
|
|
315
271
|
} catch (error) {
|
|
316
|
-
return {ok: false, ...fs_classify_error(error)};
|
|
272
|
+
return { ok: false, ...fs_classify_error(error) };
|
|
317
273
|
}
|
|
318
274
|
},
|
|
319
275
|
|
|
@@ -324,28 +280,27 @@ export const default_fs_operations: FsOperations = {
|
|
|
324
280
|
} catch {
|
|
325
281
|
return false;
|
|
326
282
|
}
|
|
327
|
-
}
|
|
283
|
+
}
|
|
328
284
|
};
|
|
329
285
|
|
|
330
286
|
export const default_build_operations: BuildOperations = {
|
|
331
287
|
build_package: async (options) => {
|
|
332
|
-
const {repo
|
|
288
|
+
const { repo } = options;
|
|
333
289
|
try {
|
|
334
|
-
|
|
335
|
-
const spawned = await spawn_out('gro', ['build'], {cwd: repo.repo_dir});
|
|
290
|
+
const spawned = await spawn_out('gro', ['build'], { cwd: repo.repo_dir });
|
|
336
291
|
if (spawned.result.ok) {
|
|
337
|
-
return {ok: true};
|
|
292
|
+
return { ok: true };
|
|
338
293
|
} else {
|
|
339
294
|
return {
|
|
340
295
|
ok: false,
|
|
341
296
|
message: 'Build failed',
|
|
342
|
-
output: spawned.stderr || spawned.stdout || 'Build failed'
|
|
297
|
+
output: spawned.stderr || spawned.stdout || 'Build failed'
|
|
343
298
|
};
|
|
344
299
|
}
|
|
345
300
|
} catch (error) {
|
|
346
|
-
return {ok: false, message: String(error)};
|
|
301
|
+
return { ok: false, message: String(error) };
|
|
347
302
|
}
|
|
348
|
-
}
|
|
303
|
+
}
|
|
349
304
|
};
|
|
350
305
|
|
|
351
306
|
/**
|
|
@@ -359,4 +314,5 @@ export const default_gitops_operations: GitopsOperations = {
|
|
|
359
314
|
preflight: default_preflight_operations,
|
|
360
315
|
fs: default_fs_operations,
|
|
361
316
|
build: default_build_operations,
|
|
317
|
+
repos: default_repos_operations
|
|
362
318
|
};
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
import type {Logger} from '@fuzdev/fuz_util/log.ts';
|
|
2
|
-
import {writeFile} from 'node:fs/promises';
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
3
|
|
|
4
4
|
export type OutputFormat = 'stdout' | 'json' | 'markdown';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Writes one machine-readable document, or one line of a stream, to stdout,
|
|
8
|
+
* ending it with a newline.
|
|
9
|
+
*/
|
|
10
|
+
export type WriteStdout = (content: string) => void;
|
|
11
|
+
|
|
6
12
|
export interface OutputOptions {
|
|
7
13
|
format: OutputFormat;
|
|
8
14
|
outfile?: string;
|
|
9
15
|
log?: Logger;
|
|
16
|
+
/**
|
|
17
|
+
* Where a `json` or `markdown` document goes without `outfile`; the one
|
|
18
|
+
* `route_human_output` returns, so the document reaches stdout while the
|
|
19
|
+
* logger's lines go to stderr.
|
|
20
|
+
*
|
|
21
|
+
* @default `console.log`
|
|
22
|
+
*/
|
|
23
|
+
write_stdout?: WriteStdout;
|
|
10
24
|
}
|
|
11
25
|
|
|
12
26
|
export interface OutputFormatters<T> {
|
|
@@ -18,6 +32,42 @@ export interface OutputFormatters<T> {
|
|
|
18
32
|
stdout: (data: T, log: Logger) => void;
|
|
19
33
|
}
|
|
20
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Whether a task's stdout carries a machine-readable document: a `json` or
|
|
37
|
+
* `markdown` report not sent to `--outfile`.
|
|
38
|
+
*
|
|
39
|
+
* @param format - the task's `--format`
|
|
40
|
+
* @param outfile - the task's `--outfile`, if any
|
|
41
|
+
*/
|
|
42
|
+
export const output_is_machine = (format: string, outfile: string | undefined): boolean =>
|
|
43
|
+
(format === 'json' || format === 'markdown') && !outfile;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Keeps a task's stdout for its machine-readable document or stream when
|
|
47
|
+
* `machine` is set: the logger's `info` and `debug` lines, which it writes with
|
|
48
|
+
* `console.log`, go where its errors go — stderr — and child loggers inherit
|
|
49
|
+
* that. Gro hands a task the logger its own lines go through, so gro's lines
|
|
50
|
+
* after the task runs (`✓`, the timings) follow to stderr; the two it prints
|
|
51
|
+
* before the task runs (`invoking`, `→ <task>`) stay on stdout, out of the
|
|
52
|
+
* task's reach.
|
|
53
|
+
*
|
|
54
|
+
* @param log - the task's logger
|
|
55
|
+
* @param machine - whether stdout carries a machine-readable document or stream
|
|
56
|
+
* @returns writes to the stdout the logger wrote to before, for the document
|
|
57
|
+
* @mutates log - overrides its `console` when `machine` is set
|
|
58
|
+
*/
|
|
59
|
+
export const route_human_output = (log: Logger, machine: boolean): WriteStdout => {
|
|
60
|
+
const stdout = log.console;
|
|
61
|
+
if (machine) {
|
|
62
|
+
log.console = {
|
|
63
|
+
log: (...args) => stdout.error(...args),
|
|
64
|
+
warn: (...args) => stdout.warn(...args),
|
|
65
|
+
error: (...args) => stdout.error(...args)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return (content) => stdout.log(content);
|
|
69
|
+
};
|
|
70
|
+
|
|
21
71
|
/**
|
|
22
72
|
* Formats data and outputs to file or stdout based on options.
|
|
23
73
|
*
|
|
@@ -31,9 +81,9 @@ export interface OutputFormatters<T> {
|
|
|
31
81
|
export const format_and_output = async <T>(
|
|
32
82
|
data: T,
|
|
33
83
|
formatters: OutputFormatters<T>,
|
|
34
|
-
options: OutputOptions
|
|
84
|
+
options: OutputOptions
|
|
35
85
|
): Promise<void> => {
|
|
36
|
-
const {format, outfile, log} = options;
|
|
86
|
+
const { format, outfile, log, write_stdout = write_console_log } = options;
|
|
37
87
|
|
|
38
88
|
// Handle stdout format (special case - uses logger directly)
|
|
39
89
|
if (format === 'stdout') {
|
|
@@ -59,7 +109,9 @@ export const format_and_output = async <T>(
|
|
|
59
109
|
// line with `[taskname]`, corrupting JSON and cluttering markdown — keep the
|
|
60
110
|
// machine-readable formats unprefixed and pipeable (use `--outfile` for output
|
|
61
111
|
// fully free of Gro's own preamble).
|
|
62
|
-
|
|
63
|
-
console.log(content);
|
|
112
|
+
write_stdout(content);
|
|
64
113
|
}
|
|
65
114
|
};
|
|
115
|
+
|
|
116
|
+
// eslint-disable-next-line no-console
|
|
117
|
+
const write_console_log: WriteStdout = (content) => console.log(content);
|