@sylphx/flow 1.8.2 → 2.0.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/CHANGELOG.md +59 -0
- package/UPGRADE.md +140 -0
- package/package.json +2 -1
- package/src/commands/flow/execute-v2.ts +278 -0
- package/src/commands/flow/execute.ts +1 -18
- package/src/commands/flow/types.ts +3 -2
- package/src/commands/flow-command.ts +32 -69
- package/src/commands/flow-orchestrator.ts +18 -55
- package/src/commands/run-command.ts +12 -6
- package/src/commands/settings-command.ts +529 -0
- package/src/core/attach-manager.ts +482 -0
- package/src/core/backup-manager.ts +308 -0
- package/src/core/cleanup-handler.ts +166 -0
- package/src/core/flow-executor.ts +323 -0
- package/src/core/git-stash-manager.ts +133 -0
- package/src/core/project-manager.ts +274 -0
- package/src/core/secrets-manager.ts +229 -0
- package/src/core/session-manager.ts +268 -0
- package/src/core/template-loader.ts +189 -0
- package/src/core/upgrade-manager.ts +79 -47
- package/src/index.ts +13 -27
- package/src/services/first-run-setup.ts +220 -0
- package/src/services/global-config.ts +337 -0
- package/src/utils/__tests__/package-manager-detector.test.ts +163 -0
- package/src/utils/agent-enhancer.ts +40 -22
- package/src/utils/errors.ts +9 -0
- package/src/utils/package-manager-detector.ts +139 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach Manager
|
|
3
|
+
* Handles merging Flow templates into user's project environment
|
|
4
|
+
* Strategy: Direct override with backup, restore on cleanup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs/promises';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { ProjectManager } from './project-manager.js';
|
|
12
|
+
import type { BackupManifest } from './backup-manager.js';
|
|
13
|
+
import { GlobalConfigService } from '../services/global-config.js';
|
|
14
|
+
import { MCP_SERVER_REGISTRY } from '../config/servers.js';
|
|
15
|
+
|
|
16
|
+
export interface AttachResult {
|
|
17
|
+
agentsAdded: string[];
|
|
18
|
+
agentsOverridden: string[];
|
|
19
|
+
commandsAdded: string[];
|
|
20
|
+
commandsOverridden: string[];
|
|
21
|
+
rulesAppended: boolean;
|
|
22
|
+
mcpServersAdded: string[];
|
|
23
|
+
mcpServersOverridden: string[];
|
|
24
|
+
singleFilesMerged: string[];
|
|
25
|
+
hooksAdded: string[];
|
|
26
|
+
hooksOverridden: string[];
|
|
27
|
+
conflicts: ConflictInfo[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ConflictInfo {
|
|
31
|
+
type: 'agent' | 'command' | 'mcp' | 'hook';
|
|
32
|
+
name: string;
|
|
33
|
+
action: 'overridden' | 'merged';
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FlowTemplates {
|
|
38
|
+
agents: Array<{ name: string; content: string }>;
|
|
39
|
+
commands: Array<{ name: string; content: string }>;
|
|
40
|
+
rules?: string;
|
|
41
|
+
mcpServers: Array<{ name: string; config: any }>;
|
|
42
|
+
hooks: Array<{ name: string; content: string }>;
|
|
43
|
+
singleFiles: Array<{ path: string; content: string }>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class AttachManager {
|
|
47
|
+
private projectManager: ProjectManager;
|
|
48
|
+
private configService: GlobalConfigService;
|
|
49
|
+
|
|
50
|
+
constructor(projectManager: ProjectManager) {
|
|
51
|
+
this.projectManager = projectManager;
|
|
52
|
+
this.configService = new GlobalConfigService();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get target-specific directory names
|
|
57
|
+
*/
|
|
58
|
+
private getTargetDirs(target: 'claude-code' | 'opencode'): {
|
|
59
|
+
agents: string;
|
|
60
|
+
commands: string;
|
|
61
|
+
} {
|
|
62
|
+
return target === 'claude-code'
|
|
63
|
+
? { agents: 'agents', commands: 'commands' }
|
|
64
|
+
: { agents: 'agent', commands: 'command' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Load global MCP servers from ~/.sylphx-flow/mcp-config.json
|
|
69
|
+
*/
|
|
70
|
+
private async loadGlobalMCPServers(
|
|
71
|
+
target: 'claude-code' | 'opencode'
|
|
72
|
+
): Promise<Array<{ name: string; config: any }>> {
|
|
73
|
+
try {
|
|
74
|
+
const enabledServers = await this.configService.getEnabledMCPServers();
|
|
75
|
+
const servers: Array<{ name: string; config: any }> = [];
|
|
76
|
+
|
|
77
|
+
for (const [serverKey, serverConfig] of Object.entries(enabledServers)) {
|
|
78
|
+
// Lookup server definition in registry
|
|
79
|
+
const serverDef = MCP_SERVER_REGISTRY[serverKey];
|
|
80
|
+
|
|
81
|
+
if (!serverDef) {
|
|
82
|
+
console.warn(`MCP server '${serverKey}' not found in registry, skipping`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Clone the server config from registry
|
|
87
|
+
let config: any = { ...serverDef.config };
|
|
88
|
+
|
|
89
|
+
// Merge environment variables from global config
|
|
90
|
+
if (serverConfig.env && Object.keys(serverConfig.env).length > 0) {
|
|
91
|
+
if (config.type === 'stdio' || config.type === 'local') {
|
|
92
|
+
config.env = { ...config.env, ...serverConfig.env };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
servers.push({ name: serverDef.name, config });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return servers;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// If global config doesn't exist or fails to load, return empty array
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Attach Flow templates to project
|
|
108
|
+
* Strategy: Override with warning, backup handles restoration
|
|
109
|
+
*/
|
|
110
|
+
async attach(
|
|
111
|
+
projectPath: string,
|
|
112
|
+
projectHash: string,
|
|
113
|
+
target: 'claude-code' | 'opencode',
|
|
114
|
+
templates: FlowTemplates,
|
|
115
|
+
manifest: BackupManifest
|
|
116
|
+
): Promise<AttachResult> {
|
|
117
|
+
const targetDir = this.projectManager.getTargetConfigDir(projectPath, target);
|
|
118
|
+
|
|
119
|
+
const result: AttachResult = {
|
|
120
|
+
agentsAdded: [],
|
|
121
|
+
agentsOverridden: [],
|
|
122
|
+
commandsAdded: [],
|
|
123
|
+
commandsOverridden: [],
|
|
124
|
+
rulesAppended: false,
|
|
125
|
+
mcpServersAdded: [],
|
|
126
|
+
mcpServersOverridden: [],
|
|
127
|
+
singleFilesMerged: [],
|
|
128
|
+
hooksAdded: [],
|
|
129
|
+
hooksOverridden: [],
|
|
130
|
+
conflicts: [],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Ensure target directory exists
|
|
134
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
135
|
+
|
|
136
|
+
// 1. Attach agents
|
|
137
|
+
await this.attachAgents(targetDir, target, templates.agents, result, manifest);
|
|
138
|
+
|
|
139
|
+
// 2. Attach commands
|
|
140
|
+
await this.attachCommands(targetDir, target, templates.commands, result, manifest);
|
|
141
|
+
|
|
142
|
+
// 3. Attach rules (if applicable)
|
|
143
|
+
if (templates.rules) {
|
|
144
|
+
await this.attachRules(targetDir, target, templates.rules, result, manifest);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 4. Attach MCP servers (merge global + template servers)
|
|
148
|
+
const globalMCPServers = await this.loadGlobalMCPServers(target);
|
|
149
|
+
const allMCPServers = [...globalMCPServers, ...templates.mcpServers];
|
|
150
|
+
|
|
151
|
+
if (allMCPServers.length > 0) {
|
|
152
|
+
await this.attachMCPServers(
|
|
153
|
+
targetDir,
|
|
154
|
+
target,
|
|
155
|
+
allMCPServers,
|
|
156
|
+
result,
|
|
157
|
+
manifest
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 5. Attach hooks
|
|
162
|
+
if (templates.hooks.length > 0) {
|
|
163
|
+
await this.attachHooks(targetDir, templates.hooks, result, manifest);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 6. Attach single files
|
|
167
|
+
if (templates.singleFiles.length > 0) {
|
|
168
|
+
await this.attachSingleFiles(projectPath, templates.singleFiles, result, manifest);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Show conflict warnings
|
|
172
|
+
this.showConflictWarnings(result);
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Attach agents (override strategy)
|
|
179
|
+
*/
|
|
180
|
+
private async attachAgents(
|
|
181
|
+
targetDir: string,
|
|
182
|
+
target: 'claude-code' | 'opencode',
|
|
183
|
+
agents: Array<{ name: string; content: string }>,
|
|
184
|
+
result: AttachResult,
|
|
185
|
+
manifest: BackupManifest
|
|
186
|
+
): Promise<void> {
|
|
187
|
+
const dirs = this.getTargetDirs(target);
|
|
188
|
+
const agentsDir = path.join(targetDir, dirs.agents);
|
|
189
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
190
|
+
|
|
191
|
+
for (const agent of agents) {
|
|
192
|
+
const agentPath = path.join(agentsDir, agent.name);
|
|
193
|
+
const existed = existsSync(agentPath);
|
|
194
|
+
|
|
195
|
+
if (existed) {
|
|
196
|
+
// Conflict: user has same agent
|
|
197
|
+
result.agentsOverridden.push(agent.name);
|
|
198
|
+
result.conflicts.push({
|
|
199
|
+
type: 'agent',
|
|
200
|
+
name: agent.name,
|
|
201
|
+
action: 'overridden',
|
|
202
|
+
message: `Agent '${agent.name}' overridden (will be restored on exit)`,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Track in manifest
|
|
206
|
+
manifest.backup.agents.user.push(agent.name);
|
|
207
|
+
} else {
|
|
208
|
+
result.agentsAdded.push(agent.name);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Write Flow agent (override)
|
|
212
|
+
await fs.writeFile(agentPath, agent.content);
|
|
213
|
+
|
|
214
|
+
// Track Flow agent
|
|
215
|
+
manifest.backup.agents.flow.push(agent.name);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Attach commands (override strategy)
|
|
221
|
+
*/
|
|
222
|
+
private async attachCommands(
|
|
223
|
+
targetDir: string,
|
|
224
|
+
target: 'claude-code' | 'opencode',
|
|
225
|
+
commands: Array<{ name: string; content: string }>,
|
|
226
|
+
result: AttachResult,
|
|
227
|
+
manifest: BackupManifest
|
|
228
|
+
): Promise<void> {
|
|
229
|
+
const dirs = this.getTargetDirs(target);
|
|
230
|
+
const commandsDir = path.join(targetDir, dirs.commands);
|
|
231
|
+
await fs.mkdir(commandsDir, { recursive: true });
|
|
232
|
+
|
|
233
|
+
for (const command of commands) {
|
|
234
|
+
const commandPath = path.join(commandsDir, command.name);
|
|
235
|
+
const existed = existsSync(commandPath);
|
|
236
|
+
|
|
237
|
+
if (existed) {
|
|
238
|
+
// Conflict: user has same command
|
|
239
|
+
result.commandsOverridden.push(command.name);
|
|
240
|
+
result.conflicts.push({
|
|
241
|
+
type: 'command',
|
|
242
|
+
name: command.name,
|
|
243
|
+
action: 'overridden',
|
|
244
|
+
message: `Command '${command.name}' overridden (will be restored on exit)`,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Track in manifest
|
|
248
|
+
manifest.backup.commands.user.push(command.name);
|
|
249
|
+
} else {
|
|
250
|
+
result.commandsAdded.push(command.name);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Write Flow command (override)
|
|
254
|
+
await fs.writeFile(commandPath, command.content);
|
|
255
|
+
|
|
256
|
+
// Track Flow command
|
|
257
|
+
manifest.backup.commands.flow.push(command.name);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Attach rules (append strategy for AGENTS.md)
|
|
263
|
+
*/
|
|
264
|
+
private async attachRules(
|
|
265
|
+
targetDir: string,
|
|
266
|
+
target: 'claude-code' | 'opencode',
|
|
267
|
+
rules: string,
|
|
268
|
+
result: AttachResult,
|
|
269
|
+
manifest: BackupManifest
|
|
270
|
+
): Promise<void> {
|
|
271
|
+
// Claude Code: .claude/agents/AGENTS.md
|
|
272
|
+
// OpenCode: .opencode/AGENTS.md
|
|
273
|
+
const dirs = this.getTargetDirs(target);
|
|
274
|
+
const rulesPath =
|
|
275
|
+
target === 'claude-code'
|
|
276
|
+
? path.join(targetDir, dirs.agents, 'AGENTS.md')
|
|
277
|
+
: path.join(targetDir, 'AGENTS.md');
|
|
278
|
+
|
|
279
|
+
if (existsSync(rulesPath)) {
|
|
280
|
+
// User has AGENTS.md, append Flow rules
|
|
281
|
+
const userRules = await fs.readFile(rulesPath, 'utf-8');
|
|
282
|
+
|
|
283
|
+
// Check if already appended (avoid duplicates)
|
|
284
|
+
if (userRules.includes('<!-- Sylphx Flow Rules -->')) {
|
|
285
|
+
// Already appended, skip
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const merged = `${userRules}
|
|
290
|
+
|
|
291
|
+
<!-- ========== Sylphx Flow Rules (Auto-injected) ========== -->
|
|
292
|
+
|
|
293
|
+
${rules}
|
|
294
|
+
|
|
295
|
+
<!-- ========== End of Sylphx Flow Rules ========== -->
|
|
296
|
+
`;
|
|
297
|
+
|
|
298
|
+
await fs.writeFile(rulesPath, merged);
|
|
299
|
+
|
|
300
|
+
manifest.backup.rules = {
|
|
301
|
+
path: rulesPath,
|
|
302
|
+
originalSize: userRules.length,
|
|
303
|
+
flowContentAdded: true,
|
|
304
|
+
};
|
|
305
|
+
} else {
|
|
306
|
+
// User doesn't have AGENTS.md, create new
|
|
307
|
+
await fs.mkdir(path.dirname(rulesPath), { recursive: true });
|
|
308
|
+
await fs.writeFile(rulesPath, rules);
|
|
309
|
+
|
|
310
|
+
manifest.backup.rules = {
|
|
311
|
+
path: rulesPath,
|
|
312
|
+
originalSize: 0,
|
|
313
|
+
flowContentAdded: true,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
result.rulesAppended = true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Attach MCP servers (merge strategy)
|
|
322
|
+
*/
|
|
323
|
+
private async attachMCPServers(
|
|
324
|
+
targetDir: string,
|
|
325
|
+
target: 'claude-code' | 'opencode',
|
|
326
|
+
mcpServers: Array<{ name: string; config: any }>,
|
|
327
|
+
result: AttachResult,
|
|
328
|
+
manifest: BackupManifest
|
|
329
|
+
): Promise<void> {
|
|
330
|
+
// Claude Code: .claude/settings.json (mcp.servers)
|
|
331
|
+
// OpenCode: .opencode/.mcp.json
|
|
332
|
+
const configPath =
|
|
333
|
+
target === 'claude-code'
|
|
334
|
+
? path.join(targetDir, 'settings.json')
|
|
335
|
+
: path.join(targetDir, '.mcp.json');
|
|
336
|
+
|
|
337
|
+
let config: any = {};
|
|
338
|
+
|
|
339
|
+
if (existsSync(configPath)) {
|
|
340
|
+
config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Ensure mcp.servers exists
|
|
344
|
+
if (!config.mcp) config.mcp = {};
|
|
345
|
+
if (!config.mcp.servers) config.mcp.servers = {};
|
|
346
|
+
|
|
347
|
+
// Add Flow MCP servers
|
|
348
|
+
for (const server of mcpServers) {
|
|
349
|
+
if (config.mcp.servers[server.name]) {
|
|
350
|
+
// Conflict: user has same MCP server
|
|
351
|
+
result.mcpServersOverridden.push(server.name);
|
|
352
|
+
result.conflicts.push({
|
|
353
|
+
type: 'mcp',
|
|
354
|
+
name: server.name,
|
|
355
|
+
action: 'overridden',
|
|
356
|
+
message: `MCP server '${server.name}' overridden (will be restored on exit)`,
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
result.mcpServersAdded.push(server.name);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Override with Flow config
|
|
363
|
+
config.mcp.servers[server.name] = server.config;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Write updated config
|
|
367
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
368
|
+
|
|
369
|
+
// Track in manifest
|
|
370
|
+
manifest.backup.config = {
|
|
371
|
+
path: configPath,
|
|
372
|
+
hash: '', // TODO: calculate hash
|
|
373
|
+
mcpServersCount: Object.keys(config.mcp.servers).length,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Attach hooks (override strategy)
|
|
379
|
+
*/
|
|
380
|
+
private async attachHooks(
|
|
381
|
+
targetDir: string,
|
|
382
|
+
hooks: Array<{ name: string; content: string }>,
|
|
383
|
+
result: AttachResult,
|
|
384
|
+
manifest: BackupManifest
|
|
385
|
+
): Promise<void> {
|
|
386
|
+
const hooksDir = path.join(targetDir, 'hooks');
|
|
387
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
388
|
+
|
|
389
|
+
for (const hook of hooks) {
|
|
390
|
+
const hookPath = path.join(hooksDir, hook.name);
|
|
391
|
+
const existed = existsSync(hookPath);
|
|
392
|
+
|
|
393
|
+
if (existed) {
|
|
394
|
+
result.hooksOverridden.push(hook.name);
|
|
395
|
+
result.conflicts.push({
|
|
396
|
+
type: 'hook',
|
|
397
|
+
name: hook.name,
|
|
398
|
+
action: 'overridden',
|
|
399
|
+
message: `Hook '${hook.name}' overridden (will be restored on exit)`,
|
|
400
|
+
});
|
|
401
|
+
} else {
|
|
402
|
+
result.hooksAdded.push(hook.name);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
await fs.writeFile(hookPath, hook.content);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Attach single files (CLAUDE.md, .cursorrules, etc.)
|
|
411
|
+
*/
|
|
412
|
+
private async attachSingleFiles(
|
|
413
|
+
projectPath: string,
|
|
414
|
+
singleFiles: Array<{ path: string; content: string }>,
|
|
415
|
+
result: AttachResult,
|
|
416
|
+
manifest: BackupManifest
|
|
417
|
+
): Promise<void> {
|
|
418
|
+
for (const file of singleFiles) {
|
|
419
|
+
const filePath = path.join(projectPath, file.path);
|
|
420
|
+
const existed = existsSync(filePath);
|
|
421
|
+
|
|
422
|
+
if (existed) {
|
|
423
|
+
// User has file, append Flow content
|
|
424
|
+
const userContent = await fs.readFile(filePath, 'utf-8');
|
|
425
|
+
|
|
426
|
+
// Check if already appended
|
|
427
|
+
if (userContent.includes('<!-- Sylphx Flow Enhancement -->')) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const merged = `${userContent}
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
**Sylphx Flow Enhancement:**
|
|
436
|
+
|
|
437
|
+
${file.content}
|
|
438
|
+
`;
|
|
439
|
+
|
|
440
|
+
await fs.writeFile(filePath, merged);
|
|
441
|
+
|
|
442
|
+
manifest.backup.singleFiles[file.path] = {
|
|
443
|
+
existed: true,
|
|
444
|
+
originalSize: userContent.length,
|
|
445
|
+
flowContentAdded: true,
|
|
446
|
+
};
|
|
447
|
+
} else {
|
|
448
|
+
// Create new file
|
|
449
|
+
await fs.writeFile(filePath, file.content);
|
|
450
|
+
|
|
451
|
+
manifest.backup.singleFiles[file.path] = {
|
|
452
|
+
existed: false,
|
|
453
|
+
originalSize: 0,
|
|
454
|
+
flowContentAdded: true,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
result.singleFilesMerged.push(file.path);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Show conflict warnings to user
|
|
464
|
+
*/
|
|
465
|
+
private showConflictWarnings(result: AttachResult): void {
|
|
466
|
+
if (result.conflicts.length === 0) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
console.log(chalk.yellow('\n⚠️ Conflicts detected:\n'));
|
|
471
|
+
|
|
472
|
+
for (const conflict of result.conflicts) {
|
|
473
|
+
console.log(
|
|
474
|
+
chalk.yellow(` • ${conflict.type}: ${conflict.name} - ${conflict.action}`)
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
console.log(
|
|
479
|
+
chalk.dim('\n Don\'t worry! All overridden content will be restored on exit.\n')
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|