poe-code 2.0.3 → 2.0.5
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 +17 -30
- package/dist/cli/command-runner.d.ts +1 -1
- package/dist/cli/commands/configure.d.ts +0 -2
- package/dist/cli/commands/configure.js +99 -34
- package/dist/cli/commands/configure.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +4 -0
- package/dist/cli/commands/doctor.js +73 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/remove.d.ts +1 -0
- package/dist/cli/commands/remove.js +41 -28
- package/dist/cli/commands/remove.js.map +1 -1
- package/dist/cli/commands/shared.d.ts +9 -12
- package/dist/cli/commands/shared.js +26 -24
- package/dist/cli/commands/shared.js.map +1 -1
- package/dist/cli/commands/spawn.d.ts +1 -0
- package/dist/cli/commands/spawn.js +14 -5
- package/dist/cli/commands/spawn.js.map +1 -1
- package/dist/cli/commands/test.d.ts +4 -0
- package/dist/cli/commands/test.js +36 -0
- package/dist/cli/commands/test.js.map +1 -0
- package/dist/cli/constants.d.ts +12 -9
- package/dist/cli/constants.js +15 -9
- package/dist/cli/constants.js.map +1 -1
- package/dist/cli/container.d.ts +3 -3
- package/dist/cli/context.d.ts +4 -13
- package/dist/cli/context.js +19 -149
- package/dist/cli/context.js.map +1 -1
- package/dist/cli/error-logger.js +1 -1
- package/dist/cli/error-logger.js.map +1 -1
- package/dist/cli/errors.d.ts +0 -10
- package/dist/cli/errors.js +0 -12
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/logger.js +9 -11
- package/dist/cli/logger.js.map +1 -1
- package/dist/cli/options.d.ts +13 -4
- package/dist/cli/options.js +12 -11
- package/dist/cli/options.js.map +1 -1
- package/dist/cli/program.js +5 -2
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/prompts.d.ts +14 -4
- package/dist/cli/prompts.js +12 -24
- package/dist/cli/prompts.js.map +1 -1
- package/dist/cli/service-registry.d.ts +37 -15
- package/dist/cli/service-registry.js.map +1 -1
- package/dist/cli/telemetry.d.ts +1 -1
- package/dist/cli/ui/service-menu.d.ts +2 -2
- package/dist/cli/ui/theme.js +6 -6
- package/dist/cli/ui/theme.js.map +1 -1
- package/dist/providers/claude-code.d.ts +10 -46
- package/dist/providers/claude-code.js +94 -224
- package/dist/providers/claude-code.js.map +1 -1
- package/dist/providers/codex.d.ts +14 -40
- package/dist/providers/codex.js +74 -177
- package/dist/providers/codex.js.map +1 -1
- package/dist/providers/create-provider.d.ts +26 -0
- package/dist/providers/create-provider.js +120 -0
- package/dist/providers/create-provider.js.map +1 -0
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +4 -10
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/opencode.d.ts +3 -43
- package/dist/providers/opencode.js +112 -173
- package/dist/providers/opencode.js.map +1 -1
- package/dist/providers/provider-helpers.d.ts +10 -0
- package/dist/providers/provider-helpers.js +62 -0
- package/dist/providers/provider-helpers.js.map +1 -0
- package/dist/providers/versioned-provider.d.ts +3 -0
- package/dist/providers/versioned-provider.js +9 -0
- package/dist/providers/versioned-provider.js.map +1 -0
- package/dist/services/credentials.d.ts +14 -0
- package/dist/services/credentials.js +123 -22
- package/dist/services/credentials.js.map +1 -1
- package/dist/services/model-strategy.d.ts +1 -1
- package/dist/services/model-strategy.js +20 -15
- package/dist/services/model-strategy.js.map +1 -1
- package/dist/services/mutation-events.d.ts +4 -0
- package/dist/services/mutation-events.js +56 -0
- package/dist/services/mutation-events.js.map +1 -0
- package/dist/services/service-install.d.ts +3 -4
- package/dist/services/service-install.js +30 -12
- package/dist/services/service-install.js.map +1 -1
- package/dist/services/service-manifest.d.ts +49 -21
- package/dist/services/service-manifest.js +261 -64
- package/dist/services/service-manifest.js.map +1 -1
- package/dist/tools/label-generator.d.ts +2 -2
- package/dist/tools/label-generator.js +1 -1
- package/dist/tools/label-generator.js.map +1 -1
- package/dist/utils/binary-version.d.ts +6 -0
- package/dist/utils/binary-version.js +35 -0
- package/dist/utils/binary-version.js.map +1 -0
- package/dist/utils/command-checks.d.ts +39 -0
- package/dist/utils/command-checks.js +116 -0
- package/dist/utils/command-checks.js.map +1 -0
- package/package.json +6 -8
- package/dist/providers/roo-code.d.ts +0 -41
- package/dist/providers/roo-code.js +0 -201
- package/dist/providers/roo-code.js.map +0 -1
- package/dist/utils/prerequisites.d.ts +0 -41
- package/dist/utils/prerequisites.js +0 -92
- package/dist/utils/prerequisites.js.map +0 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import type { CliEnvironment } from "../cli/environment.js";
|
|
2
|
+
import type { CommandContext } from "../cli/context.js";
|
|
1
3
|
import type { FileSystem } from "../utils/file-system.js";
|
|
2
4
|
import { type JsonObject } from "../utils/json.js";
|
|
5
|
+
import { type TomlTable } from "../utils/toml.js";
|
|
3
6
|
type ValueResolver<Options, Value> = Value | ((context: MutationContext<Options>) => Value);
|
|
4
7
|
interface MutationContext<Options> {
|
|
5
8
|
options: Options;
|
|
6
9
|
fs: FileSystem;
|
|
10
|
+
env: CliEnvironment;
|
|
7
11
|
}
|
|
8
12
|
interface TransformResult {
|
|
9
13
|
content: string | null;
|
|
@@ -11,7 +15,7 @@ interface TransformResult {
|
|
|
11
15
|
}
|
|
12
16
|
interface TransformFileMutation<Options> {
|
|
13
17
|
kind: "transformFile";
|
|
14
|
-
label?: string
|
|
18
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
15
19
|
target: ValueResolver<Options, string>;
|
|
16
20
|
transform(input: {
|
|
17
21
|
content: string | null;
|
|
@@ -20,42 +24,48 @@ interface TransformFileMutation<Options> {
|
|
|
20
24
|
}
|
|
21
25
|
interface EnsureDirectoryMutation<Options> {
|
|
22
26
|
kind: "ensureDirectory";
|
|
23
|
-
label?: string
|
|
27
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
24
28
|
path: ValueResolver<Options, string>;
|
|
25
29
|
}
|
|
26
30
|
interface CreateBackupMutation<Options> {
|
|
27
31
|
kind: "createBackup";
|
|
28
|
-
label?: string
|
|
32
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
29
33
|
target: ValueResolver<Options, string>;
|
|
30
34
|
timestamp?: ValueResolver<Options, (() => string) | undefined>;
|
|
31
35
|
}
|
|
32
36
|
interface WriteTemplateMutation<Options> {
|
|
33
37
|
kind: "writeTemplate";
|
|
34
|
-
label?: string
|
|
38
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
35
39
|
target: ValueResolver<Options, string>;
|
|
36
40
|
templateId: string;
|
|
37
41
|
context?: ValueResolver<Options, JsonObject | undefined>;
|
|
38
42
|
}
|
|
39
43
|
interface RemoveFileMutation<Options> {
|
|
40
44
|
kind: "removeFile";
|
|
41
|
-
label?: string
|
|
45
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
42
46
|
target: ValueResolver<Options, string>;
|
|
43
47
|
whenEmpty?: boolean;
|
|
44
48
|
whenContentMatches?: RegExp;
|
|
45
49
|
}
|
|
46
50
|
export type ServiceMutation<Options> = TransformFileMutation<Options> | EnsureDirectoryMutation<Options> | CreateBackupMutation<Options> | WriteTemplateMutation<Options> | RemoveFileMutation<Options>;
|
|
47
|
-
export interface
|
|
51
|
+
export interface ServiceManifestDefinition<ConfigureOptions, RemoveOptions = ConfigureOptions> {
|
|
48
52
|
id: string;
|
|
49
53
|
summary: string;
|
|
50
|
-
prerequisites?: {
|
|
51
|
-
before?: string[];
|
|
52
|
-
after?: string[];
|
|
53
|
-
};
|
|
54
54
|
configure: ServiceMutation<ConfigureOptions>[];
|
|
55
55
|
remove?: ServiceMutation<RemoveOptions>[];
|
|
56
56
|
}
|
|
57
|
+
export interface ServiceManifest<ConfigureOptions, RemoveOptions = ConfigureOptions> {
|
|
58
|
+
id: string;
|
|
59
|
+
summary: string;
|
|
60
|
+
configureMutations: ServiceMutation<ConfigureOptions>[];
|
|
61
|
+
removeMutations?: ServiceMutation<RemoveOptions>[];
|
|
62
|
+
configure(context: ServiceExecutionContext<ConfigureOptions>, runOptions?: ServiceRunOptions): Promise<void>;
|
|
63
|
+
remove: (context: ServiceExecutionContext<RemoveOptions>, runOptions?: ServiceRunOptions) => Promise<boolean>;
|
|
64
|
+
}
|
|
57
65
|
export interface ServiceExecutionContext<Options> {
|
|
58
66
|
fs: FileSystem;
|
|
67
|
+
env: CliEnvironment;
|
|
68
|
+
command: CommandContext;
|
|
59
69
|
options: Options;
|
|
60
70
|
}
|
|
61
71
|
export interface MutationLogDetails {
|
|
@@ -71,52 +81,70 @@ export interface ServiceMutationOutcome {
|
|
|
71
81
|
detail?: MutationDetail;
|
|
72
82
|
}
|
|
73
83
|
export type MutationEffect = "none" | "mkdir" | "copy" | "write" | "delete";
|
|
74
|
-
export interface
|
|
84
|
+
export interface ServiceMutationObservers {
|
|
75
85
|
onStart?(details: MutationLogDetails): void;
|
|
76
86
|
onComplete?(details: MutationLogDetails, outcome: ServiceMutationOutcome): void;
|
|
77
87
|
onError?(details: MutationLogDetails, error: unknown): void;
|
|
78
88
|
}
|
|
79
89
|
export declare function ensureDirectory<Options>(config: {
|
|
80
90
|
path: ValueResolver<Options, string>;
|
|
81
|
-
label?: string
|
|
91
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
82
92
|
}): ServiceMutation<Options>;
|
|
83
93
|
export declare function createBackupMutation<Options>(config: {
|
|
84
94
|
target: ValueResolver<Options, string>;
|
|
85
95
|
timestamp?: ValueResolver<Options, (() => string) | undefined>;
|
|
86
|
-
label?: string
|
|
96
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
87
97
|
}): ServiceMutation<Options>;
|
|
88
98
|
export declare function writeTemplateMutation<Options>(config: {
|
|
89
99
|
target: ValueResolver<Options, string>;
|
|
90
100
|
templateId: string;
|
|
91
101
|
context?: ValueResolver<Options, JsonObject | undefined>;
|
|
92
|
-
label?: string
|
|
102
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
93
103
|
}): ServiceMutation<Options>;
|
|
94
104
|
export declare function jsonMergeMutation<Options>(config: {
|
|
95
105
|
target: ValueResolver<Options, string>;
|
|
96
106
|
value: ValueResolver<Options, JsonObject>;
|
|
97
|
-
label?: string
|
|
107
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
98
108
|
}): ServiceMutation<Options>;
|
|
99
109
|
export declare function jsonPruneMutation<Options>(config: {
|
|
100
110
|
target: ValueResolver<Options, string>;
|
|
101
111
|
shape: ValueResolver<Options, JsonObject>;
|
|
102
|
-
label?: string
|
|
112
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
113
|
+
}): ServiceMutation<Options>;
|
|
114
|
+
export declare function tomlMergeMutation<Options>(config: {
|
|
115
|
+
target: ValueResolver<Options, string>;
|
|
116
|
+
value: ValueResolver<Options, TomlTable>;
|
|
117
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
118
|
+
}): ServiceMutation<Options>;
|
|
119
|
+
export declare function tomlPruneMutation<Options>(config: {
|
|
120
|
+
target: ValueResolver<Options, string>;
|
|
121
|
+
prune: (document: TomlTable, context: MutationContext<Options>) => {
|
|
122
|
+
changed: boolean;
|
|
123
|
+
result: TomlTable | null;
|
|
124
|
+
};
|
|
125
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
126
|
+
}): ServiceMutation<Options>;
|
|
127
|
+
export declare function tomlTemplateMergeMutation<Options>(config: {
|
|
128
|
+
target: ValueResolver<Options, string>;
|
|
129
|
+
templateId: string;
|
|
130
|
+
context?: ValueResolver<Options, JsonObject | undefined>;
|
|
131
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
103
132
|
}): ServiceMutation<Options>;
|
|
104
133
|
export declare function removePatternMutation<Options>(config: {
|
|
105
134
|
target: ValueResolver<Options, string>;
|
|
106
135
|
pattern: RegExp;
|
|
107
136
|
replacement?: string | ((match: string, context: MutationContext<Options>) => string);
|
|
108
|
-
label?: string
|
|
137
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
109
138
|
}): ServiceMutation<Options>;
|
|
110
139
|
export declare function removeFileMutation<Options>(config: {
|
|
111
140
|
target: ValueResolver<Options, string>;
|
|
112
141
|
whenEmpty?: boolean;
|
|
113
142
|
whenContentMatches?: RegExp;
|
|
114
|
-
label?: string
|
|
143
|
+
label?: ValueResolver<Options, string | undefined>;
|
|
115
144
|
}): ServiceMutation<Options>;
|
|
116
|
-
export declare function
|
|
117
|
-
export declare function runServiceRemove<ConfigureOptions, RemoveOptions = ConfigureOptions>(manifest: ServiceManifest<ConfigureOptions, RemoveOptions>, context: ServiceExecutionContext<RemoveOptions>, runOptions?: ServiceRunOptions): Promise<boolean>;
|
|
145
|
+
export declare function createServiceManifest<ConfigureOptions, RemoveOptions = ConfigureOptions>(definition: ServiceManifestDefinition<ConfigureOptions, RemoveOptions>): ServiceManifest<ConfigureOptions, RemoveOptions>;
|
|
118
146
|
export interface ServiceRunOptions {
|
|
119
|
-
|
|
147
|
+
observers?: ServiceMutationObservers;
|
|
120
148
|
}
|
|
121
149
|
export type ServiceMutationKind = ServiceMutation<unknown>["kind"];
|
|
122
150
|
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { createBackup } from "../utils/backup.js";
|
|
2
3
|
import { renderTemplate } from "../utils/templates.js";
|
|
3
4
|
import { deepMergeJson, isJsonObject, pruneJsonByShape } from "../utils/json.js";
|
|
5
|
+
import { parseTomlDocument, serializeTomlDocument, mergeTomlTables } from "../utils/toml.js";
|
|
4
6
|
export function ensureDirectory(config) {
|
|
5
7
|
return {
|
|
6
8
|
kind: "ensureDirectory",
|
|
@@ -31,7 +33,13 @@ export function jsonMergeMutation(config) {
|
|
|
31
33
|
target: config.target,
|
|
32
34
|
label: config.label,
|
|
33
35
|
async transform({ content, context }) {
|
|
34
|
-
const
|
|
36
|
+
const rawTarget = resolveValue(config.target, context);
|
|
37
|
+
const targetPath = expandHomeShortcut(rawTarget, context.env);
|
|
38
|
+
const current = await parseJsonWithRecovery({
|
|
39
|
+
content,
|
|
40
|
+
fs: context.fs,
|
|
41
|
+
targetPath
|
|
42
|
+
});
|
|
35
43
|
const desired = resolveValue(config.value, context);
|
|
36
44
|
const merged = deepMergeJson(current, desired);
|
|
37
45
|
const serialized = serializeJson(merged);
|
|
@@ -68,6 +76,89 @@ export function jsonPruneMutation(config) {
|
|
|
68
76
|
}
|
|
69
77
|
};
|
|
70
78
|
}
|
|
79
|
+
export function tomlMergeMutation(config) {
|
|
80
|
+
return {
|
|
81
|
+
kind: "transformFile",
|
|
82
|
+
target: config.target,
|
|
83
|
+
label: config.label,
|
|
84
|
+
async transform({ content, context }) {
|
|
85
|
+
const rawTarget = resolveValue(config.target, context);
|
|
86
|
+
const targetPath = expandHomeShortcut(rawTarget, context.env);
|
|
87
|
+
const current = await parseTomlWithRecovery({
|
|
88
|
+
content,
|
|
89
|
+
fs: context.fs,
|
|
90
|
+
targetPath
|
|
91
|
+
});
|
|
92
|
+
const desired = resolveValue(config.value, context);
|
|
93
|
+
const merged = mergeTomlTables(current, desired);
|
|
94
|
+
const serialized = serializeTomlDocument(merged);
|
|
95
|
+
const previous = content ?? "";
|
|
96
|
+
return {
|
|
97
|
+
content: serialized,
|
|
98
|
+
changed: serialized !== previous
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function tomlPruneMutation(config) {
|
|
104
|
+
return {
|
|
105
|
+
kind: "transformFile",
|
|
106
|
+
target: config.target,
|
|
107
|
+
label: config.label,
|
|
108
|
+
async transform({ content, context }) {
|
|
109
|
+
if (content == null) {
|
|
110
|
+
return { content: null, changed: false };
|
|
111
|
+
}
|
|
112
|
+
let document;
|
|
113
|
+
try {
|
|
114
|
+
document = parseTomlDocument(content);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { content, changed: false };
|
|
118
|
+
}
|
|
119
|
+
const outcome = config.prune(document, context);
|
|
120
|
+
if (!outcome.changed) {
|
|
121
|
+
return { content, changed: false };
|
|
122
|
+
}
|
|
123
|
+
if (!outcome.result || Object.keys(outcome.result).length === 0) {
|
|
124
|
+
return { content: null, changed: true };
|
|
125
|
+
}
|
|
126
|
+
const serialized = serializeTomlDocument(outcome.result);
|
|
127
|
+
return {
|
|
128
|
+
content: serialized,
|
|
129
|
+
changed: serialized !== content
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function tomlTemplateMergeMutation(config) {
|
|
135
|
+
return {
|
|
136
|
+
kind: "transformFile",
|
|
137
|
+
target: config.target,
|
|
138
|
+
label: config.label,
|
|
139
|
+
async transform({ content, context }) {
|
|
140
|
+
const rawTarget = resolveValue(config.target, context);
|
|
141
|
+
const targetPath = expandHomeShortcut(rawTarget, context.env);
|
|
142
|
+
const current = await parseTomlWithRecovery({
|
|
143
|
+
content,
|
|
144
|
+
fs: context.fs,
|
|
145
|
+
targetPath
|
|
146
|
+
});
|
|
147
|
+
const templateContext = config.context
|
|
148
|
+
? resolveValue(config.context, context)
|
|
149
|
+
: undefined;
|
|
150
|
+
const rendered = await renderTemplate(config.templateId, templateContext ?? {});
|
|
151
|
+
const templateDocument = parseTomlDocument(rendered);
|
|
152
|
+
const merged = mergeTomlTables(current, templateDocument);
|
|
153
|
+
const serialized = serializeTomlDocument(merged);
|
|
154
|
+
const previous = content ?? "";
|
|
155
|
+
return {
|
|
156
|
+
content: serialized,
|
|
157
|
+
changed: serialized !== previous
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
71
162
|
export function removePatternMutation(config) {
|
|
72
163
|
return {
|
|
73
164
|
kind: "transformFile",
|
|
@@ -102,145 +193,165 @@ export function removeFileMutation(config) {
|
|
|
102
193
|
label: config.label
|
|
103
194
|
};
|
|
104
195
|
}
|
|
105
|
-
export
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
196
|
+
export function createServiceManifest(definition) {
|
|
197
|
+
const configureMutations = definition.configure;
|
|
198
|
+
const removeMutations = definition.remove;
|
|
199
|
+
return {
|
|
200
|
+
id: definition.id,
|
|
201
|
+
summary: definition.summary,
|
|
202
|
+
configureMutations,
|
|
203
|
+
removeMutations,
|
|
204
|
+
async configure(context, runOptions) {
|
|
205
|
+
await runMutations(configureMutations, context, {
|
|
206
|
+
trackChanges: false,
|
|
207
|
+
observers: runOptions?.observers,
|
|
208
|
+
manifestId: definition.id
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
async remove(context, runOptions) {
|
|
212
|
+
if (!removeMutations) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return runMutations(removeMutations, context, {
|
|
216
|
+
trackChanges: true,
|
|
217
|
+
observers: runOptions?.observers,
|
|
218
|
+
manifestId: definition.id
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
};
|
|
118
222
|
}
|
|
119
223
|
async function runMutations(mutations, context, options) {
|
|
120
224
|
let touched = false;
|
|
121
225
|
for (const mutation of mutations) {
|
|
122
|
-
const changed = await applyMutation(mutation, context, options.manifestId, options.
|
|
226
|
+
const changed = await applyMutation(mutation, context, options.manifestId, options.observers);
|
|
123
227
|
if (options.trackChanges && changed) {
|
|
124
228
|
touched = true;
|
|
125
229
|
}
|
|
126
230
|
}
|
|
127
231
|
return touched;
|
|
128
232
|
}
|
|
129
|
-
async function applyMutation(mutation, context, manifestId,
|
|
233
|
+
async function applyMutation(mutation, context, manifestId, observers) {
|
|
130
234
|
switch (mutation.kind) {
|
|
131
235
|
case "ensureDirectory": {
|
|
132
|
-
const targetPath =
|
|
133
|
-
const details = createMutationDetails(mutation, manifestId, targetPath);
|
|
134
|
-
|
|
236
|
+
const targetPath = resolvePath(mutation.path, context);
|
|
237
|
+
const details = createMutationDetails(mutation, manifestId, targetPath, context);
|
|
238
|
+
observers?.onStart?.(details);
|
|
135
239
|
try {
|
|
136
240
|
const existed = await pathExists(context.fs, targetPath);
|
|
137
241
|
await context.fs.mkdir(targetPath, { recursive: true });
|
|
138
|
-
|
|
242
|
+
observers?.onComplete?.(details, {
|
|
139
243
|
changed: !existed,
|
|
140
244
|
effect: "mkdir",
|
|
141
245
|
detail: existed ? "noop" : "create"
|
|
142
246
|
});
|
|
247
|
+
flushCommandDryRun(context);
|
|
143
248
|
return !existed;
|
|
144
249
|
}
|
|
145
250
|
catch (error) {
|
|
146
|
-
|
|
251
|
+
observers?.onError?.(details, error);
|
|
147
252
|
throw error;
|
|
148
253
|
}
|
|
149
254
|
}
|
|
150
255
|
case "createBackup": {
|
|
151
|
-
const targetPath =
|
|
256
|
+
const targetPath = resolvePath(mutation.target, context);
|
|
152
257
|
const timestamp = mutation.timestamp
|
|
153
258
|
? resolveValue(mutation.timestamp, mutationContext(context))
|
|
154
259
|
: undefined;
|
|
155
|
-
const details = createMutationDetails(mutation, manifestId, targetPath);
|
|
156
|
-
|
|
260
|
+
const details = createMutationDetails(mutation, manifestId, targetPath, context);
|
|
261
|
+
observers?.onStart?.(details);
|
|
157
262
|
try {
|
|
158
263
|
const backupPath = await createBackup(context.fs, targetPath, timestamp);
|
|
159
|
-
|
|
264
|
+
observers?.onComplete?.(details, {
|
|
160
265
|
changed: backupPath != null,
|
|
161
266
|
effect: backupPath ? "copy" : "none",
|
|
162
267
|
detail: backupPath ? "backup" : "noop"
|
|
163
268
|
});
|
|
269
|
+
flushCommandDryRun(context);
|
|
164
270
|
}
|
|
165
271
|
catch (error) {
|
|
166
|
-
|
|
272
|
+
observers?.onError?.(details, error);
|
|
167
273
|
throw error;
|
|
168
274
|
}
|
|
169
275
|
return false;
|
|
170
276
|
}
|
|
171
277
|
case "writeTemplate": {
|
|
172
|
-
const targetPath =
|
|
278
|
+
const targetPath = resolvePath(mutation.target, context);
|
|
173
279
|
const renderContext = mutation.context
|
|
174
280
|
? resolveValue(mutation.context, mutationContext(context))
|
|
175
281
|
: undefined;
|
|
176
282
|
const rendered = await renderTemplate(mutation.templateId, renderContext ?? {});
|
|
177
|
-
const details = createMutationDetails(mutation, manifestId, targetPath);
|
|
178
|
-
|
|
283
|
+
const details = createMutationDetails(mutation, manifestId, targetPath, context);
|
|
284
|
+
observers?.onStart?.(details);
|
|
179
285
|
try {
|
|
180
286
|
const existed = await pathExists(context.fs, targetPath);
|
|
181
287
|
await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
|
|
182
|
-
|
|
288
|
+
observers?.onComplete?.(details, {
|
|
183
289
|
changed: true,
|
|
184
290
|
effect: "write",
|
|
185
291
|
detail: existed ? "update" : "create"
|
|
186
292
|
});
|
|
293
|
+
flushCommandDryRun(context);
|
|
187
294
|
}
|
|
188
295
|
catch (error) {
|
|
189
|
-
|
|
296
|
+
observers?.onError?.(details, error);
|
|
190
297
|
throw error;
|
|
191
298
|
}
|
|
192
299
|
return true;
|
|
193
300
|
}
|
|
194
301
|
case "removeFile": {
|
|
195
|
-
const targetPath =
|
|
196
|
-
const details = createMutationDetails(mutation, manifestId, targetPath);
|
|
197
|
-
|
|
302
|
+
const targetPath = resolvePath(mutation.target, context);
|
|
303
|
+
const details = createMutationDetails(mutation, manifestId, targetPath, context);
|
|
304
|
+
observers?.onStart?.(details);
|
|
198
305
|
try {
|
|
199
306
|
const raw = await context.fs.readFile(targetPath, "utf8");
|
|
200
307
|
const trimmed = raw.trim();
|
|
201
308
|
if (mutation.whenContentMatches &&
|
|
202
309
|
!mutation.whenContentMatches.test(trimmed)) {
|
|
203
|
-
|
|
310
|
+
observers?.onComplete?.(details, {
|
|
204
311
|
changed: false,
|
|
205
312
|
effect: "none",
|
|
206
313
|
detail: "noop"
|
|
207
314
|
});
|
|
315
|
+
flushCommandDryRun(context);
|
|
208
316
|
return false;
|
|
209
317
|
}
|
|
210
318
|
if (mutation.whenEmpty && trimmed.length > 0) {
|
|
211
|
-
|
|
319
|
+
observers?.onComplete?.(details, {
|
|
212
320
|
changed: false,
|
|
213
321
|
effect: "none",
|
|
214
322
|
detail: "noop"
|
|
215
323
|
});
|
|
324
|
+
flushCommandDryRun(context);
|
|
216
325
|
return false;
|
|
217
326
|
}
|
|
218
327
|
await context.fs.unlink(targetPath);
|
|
219
|
-
|
|
328
|
+
observers?.onComplete?.(details, {
|
|
220
329
|
changed: true,
|
|
221
330
|
effect: "delete",
|
|
222
331
|
detail: "delete"
|
|
223
332
|
});
|
|
333
|
+
flushCommandDryRun(context);
|
|
224
334
|
return true;
|
|
225
335
|
}
|
|
226
336
|
catch (error) {
|
|
227
337
|
if (isNotFound(error)) {
|
|
228
|
-
|
|
338
|
+
observers?.onComplete?.(details, {
|
|
229
339
|
changed: false,
|
|
230
340
|
effect: "none",
|
|
231
341
|
detail: "noop"
|
|
232
342
|
});
|
|
343
|
+
flushCommandDryRun(context);
|
|
233
344
|
return false;
|
|
234
345
|
}
|
|
235
|
-
|
|
346
|
+
observers?.onError?.(details, error);
|
|
236
347
|
throw error;
|
|
237
348
|
}
|
|
238
349
|
}
|
|
239
350
|
case "transformFile": {
|
|
240
|
-
const targetPath =
|
|
351
|
+
const targetPath = resolvePath(mutation.target, context);
|
|
241
352
|
const current = await readFileIfExists(context.fs, targetPath);
|
|
242
|
-
const details = createMutationDetails(mutation, manifestId, targetPath);
|
|
243
|
-
|
|
353
|
+
const details = createMutationDetails(mutation, manifestId, targetPath, context);
|
|
354
|
+
observers?.onStart?.(details);
|
|
244
355
|
try {
|
|
245
356
|
const result = await mutation.transform({
|
|
246
357
|
content: current,
|
|
@@ -252,11 +363,12 @@ async function applyMutation(mutation, context, manifestId, hooks) {
|
|
|
252
363
|
previousContent: current,
|
|
253
364
|
result
|
|
254
365
|
});
|
|
255
|
-
|
|
366
|
+
observers?.onComplete?.(details, transformOutcome);
|
|
367
|
+
flushCommandDryRun(context);
|
|
256
368
|
return transformOutcome.changed;
|
|
257
369
|
}
|
|
258
370
|
catch (error) {
|
|
259
|
-
|
|
371
|
+
observers?.onError?.(details, error);
|
|
260
372
|
throw error;
|
|
261
373
|
}
|
|
262
374
|
}
|
|
@@ -269,40 +381,81 @@ async function applyMutation(mutation, context, manifestId, hooks) {
|
|
|
269
381
|
function mutationContext(context) {
|
|
270
382
|
return {
|
|
271
383
|
fs: context.fs,
|
|
272
|
-
options: context.options
|
|
384
|
+
options: context.options,
|
|
385
|
+
env: context.env
|
|
273
386
|
};
|
|
274
387
|
}
|
|
275
|
-
function createMutationDetails(mutation, manifestId, targetPath) {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
case "createBackup":
|
|
281
|
-
return mutation.label ?? "Create backup";
|
|
282
|
-
case "writeTemplate":
|
|
283
|
-
return (mutation.label ??
|
|
284
|
-
`Write template ${mutation.templateId}`);
|
|
285
|
-
case "removeFile":
|
|
286
|
-
return mutation.label ?? "Remove file";
|
|
287
|
-
case "transformFile":
|
|
288
|
-
return mutation.label ?? "Transform file";
|
|
289
|
-
default:
|
|
290
|
-
return "Operation";
|
|
291
|
-
}
|
|
292
|
-
})();
|
|
388
|
+
function createMutationDetails(mutation, manifestId, targetPath, context) {
|
|
389
|
+
const customLabel = mutation.label != null
|
|
390
|
+
? resolveValue(mutation.label, mutationContext(context))
|
|
391
|
+
: undefined;
|
|
392
|
+
const label = customLabel ?? describeMutationOperation(mutation.kind, targetPath);
|
|
293
393
|
return {
|
|
294
394
|
manifestId,
|
|
295
395
|
kind: mutation.kind,
|
|
296
|
-
label
|
|
396
|
+
label,
|
|
297
397
|
targetPath
|
|
298
398
|
};
|
|
299
399
|
}
|
|
400
|
+
function describeMutationOperation(kind, targetPath) {
|
|
401
|
+
const displayPath = targetPath ?? "target";
|
|
402
|
+
switch (kind) {
|
|
403
|
+
case "ensureDirectory":
|
|
404
|
+
return `Ensure directory ${displayPath}`;
|
|
405
|
+
case "createBackup":
|
|
406
|
+
return `Create backup ${displayPath}`;
|
|
407
|
+
case "writeTemplate":
|
|
408
|
+
return `Write file ${displayPath}`;
|
|
409
|
+
case "removeFile":
|
|
410
|
+
return `Remove file ${displayPath}`;
|
|
411
|
+
case "transformFile":
|
|
412
|
+
return `Transform file ${displayPath}`;
|
|
413
|
+
default:
|
|
414
|
+
return "Operation";
|
|
415
|
+
}
|
|
416
|
+
}
|
|
300
417
|
function resolveValue(resolver, context) {
|
|
301
418
|
if (typeof resolver === "function") {
|
|
302
419
|
return resolver(context);
|
|
303
420
|
}
|
|
304
421
|
return resolver;
|
|
305
422
|
}
|
|
423
|
+
function resolvePath(resolver, context) {
|
|
424
|
+
const raw = resolveValue(resolver, mutationContext(context));
|
|
425
|
+
return expandHomeShortcut(raw, context.env);
|
|
426
|
+
}
|
|
427
|
+
function flushCommandDryRun(context) {
|
|
428
|
+
context.command.flushDryRun({ emitIfEmpty: false });
|
|
429
|
+
}
|
|
430
|
+
function expandHomeShortcut(targetPath, env) {
|
|
431
|
+
if (!targetPath?.startsWith("~")) {
|
|
432
|
+
return targetPath;
|
|
433
|
+
}
|
|
434
|
+
if (targetPath.startsWith("~./")) {
|
|
435
|
+
targetPath = `~/.${targetPath.slice(3)}`;
|
|
436
|
+
}
|
|
437
|
+
const homeDir = env?.homeDir ?? process.env.HOME ?? process.env.USERPROFILE;
|
|
438
|
+
if (!homeDir) {
|
|
439
|
+
return targetPath;
|
|
440
|
+
}
|
|
441
|
+
let remainder = targetPath.slice(1);
|
|
442
|
+
if (remainder.startsWith("/")) {
|
|
443
|
+
remainder = remainder.slice(1);
|
|
444
|
+
}
|
|
445
|
+
else if (remainder.startsWith("\\")) {
|
|
446
|
+
remainder = remainder.slice(1);
|
|
447
|
+
}
|
|
448
|
+
else if (remainder.startsWith(".")) {
|
|
449
|
+
remainder = remainder.slice(1);
|
|
450
|
+
if (remainder.startsWith("/")) {
|
|
451
|
+
remainder = remainder.slice(1);
|
|
452
|
+
}
|
|
453
|
+
else if (remainder.startsWith("\\")) {
|
|
454
|
+
remainder = remainder.slice(1);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return remainder.length === 0 ? homeDir : path.join(homeDir, remainder);
|
|
458
|
+
}
|
|
306
459
|
function parseJson(content) {
|
|
307
460
|
if (content == null) {
|
|
308
461
|
return {};
|
|
@@ -313,6 +466,50 @@ function parseJson(content) {
|
|
|
313
466
|
}
|
|
314
467
|
return parsed;
|
|
315
468
|
}
|
|
469
|
+
async function parseJsonWithRecovery(input) {
|
|
470
|
+
try {
|
|
471
|
+
return parseJson(input.content);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
await backupInvalidJsonDocument(input);
|
|
475
|
+
return {};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function parseTomlWithRecovery(input) {
|
|
479
|
+
if (input.content == null) {
|
|
480
|
+
return {};
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
return parseTomlDocument(input.content);
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
await backupInvalidTomlDocument(input);
|
|
487
|
+
return {};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async function backupInvalidJsonDocument(input) {
|
|
491
|
+
if (input.content == null) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const backupPath = createInvalidDocumentBackupPath(input.targetPath);
|
|
495
|
+
await input.fs.writeFile(backupPath, input.content, { encoding: "utf8" });
|
|
496
|
+
}
|
|
497
|
+
async function backupInvalidTomlDocument(input) {
|
|
498
|
+
if (input.content == null) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const backupPath = createInvalidDocumentBackupPath(input.targetPath);
|
|
502
|
+
await input.fs.writeFile(backupPath, input.content, { encoding: "utf8" });
|
|
503
|
+
}
|
|
504
|
+
function createInvalidDocumentBackupPath(targetPath) {
|
|
505
|
+
return `${targetPath}.invalid-${createTimestamp()}.json`;
|
|
506
|
+
}
|
|
507
|
+
function createTimestamp() {
|
|
508
|
+
return new Date()
|
|
509
|
+
.toISOString()
|
|
510
|
+
.replaceAll(":", "-")
|
|
511
|
+
.replaceAll(".", "-");
|
|
512
|
+
}
|
|
316
513
|
function serializeJson(value) {
|
|
317
514
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
318
515
|
}
|