@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,163 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
detectPackageManagerFromUserAgent,
|
|
4
|
+
detectPackageManagerFromLockFiles,
|
|
5
|
+
detectPackageManager,
|
|
6
|
+
getPackageManagerInfo,
|
|
7
|
+
getUpgradeCommand,
|
|
8
|
+
} from '../package-manager-detector';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
|
|
13
|
+
describe('Package Manager Detection', () => {
|
|
14
|
+
const originalEnv = process.env;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
process.env = { ...originalEnv };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
process.env = originalEnv;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('detectPackageManagerFromUserAgent', () => {
|
|
25
|
+
it('should detect bun from user agent', () => {
|
|
26
|
+
process.env.npm_config_user_agent = 'bun/1.0.0';
|
|
27
|
+
expect(detectPackageManagerFromUserAgent()).toBe('bun');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should detect pnpm from user agent', () => {
|
|
31
|
+
process.env.npm_config_user_agent = 'pnpm/8.0.0 npm/? node/v18.0.0';
|
|
32
|
+
expect(detectPackageManagerFromUserAgent()).toBe('pnpm');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should detect yarn from user agent', () => {
|
|
36
|
+
process.env.npm_config_user_agent = 'yarn/1.22.0 npm/? node/v18.0.0';
|
|
37
|
+
expect(detectPackageManagerFromUserAgent()).toBe('yarn');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should detect npm from user agent', () => {
|
|
41
|
+
process.env.npm_config_user_agent = 'npm/9.0.0 node/v18.0.0';
|
|
42
|
+
expect(detectPackageManagerFromUserAgent()).toBe('npm');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should return null when no user agent', () => {
|
|
46
|
+
delete process.env.npm_config_user_agent;
|
|
47
|
+
expect(detectPackageManagerFromUserAgent()).toBe(null);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('detectPackageManagerFromLockFiles', () => {
|
|
52
|
+
let tempDir: string;
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pm-test-'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should detect bun from bun.lockb', () => {
|
|
63
|
+
fs.writeFileSync(path.join(tempDir, 'bun.lockb'), '');
|
|
64
|
+
expect(detectPackageManagerFromLockFiles(tempDir)).toBe('bun');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should detect bun from bun.lock', () => {
|
|
68
|
+
fs.writeFileSync(path.join(tempDir, 'bun.lock'), '');
|
|
69
|
+
expect(detectPackageManagerFromLockFiles(tempDir)).toBe('bun');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should detect pnpm from pnpm-lock.yaml', () => {
|
|
73
|
+
fs.writeFileSync(path.join(tempDir, 'pnpm-lock.yaml'), '');
|
|
74
|
+
expect(detectPackageManagerFromLockFiles(tempDir)).toBe('pnpm');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should detect yarn from yarn.lock', () => {
|
|
78
|
+
fs.writeFileSync(path.join(tempDir, 'yarn.lock'), '');
|
|
79
|
+
expect(detectPackageManagerFromLockFiles(tempDir)).toBe('yarn');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should detect npm from package-lock.json', () => {
|
|
83
|
+
fs.writeFileSync(path.join(tempDir, 'package-lock.json'), '');
|
|
84
|
+
expect(detectPackageManagerFromLockFiles(tempDir)).toBe('npm');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should prioritize bun over others', () => {
|
|
88
|
+
fs.writeFileSync(path.join(tempDir, 'bun.lock'), '');
|
|
89
|
+
fs.writeFileSync(path.join(tempDir, 'package-lock.json'), '');
|
|
90
|
+
expect(detectPackageManagerFromLockFiles(tempDir)).toBe('bun');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return null when no lock files', () => {
|
|
94
|
+
expect(detectPackageManagerFromLockFiles(tempDir)).toBe(null);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('detectPackageManager', () => {
|
|
99
|
+
it('should prioritize user agent over lock files', () => {
|
|
100
|
+
process.env.npm_config_user_agent = 'pnpm/8.0.0';
|
|
101
|
+
const result = detectPackageManager();
|
|
102
|
+
expect(result).toBe('pnpm');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should default to npm when no detection methods work', () => {
|
|
106
|
+
delete process.env.npm_config_user_agent;
|
|
107
|
+
const result = detectPackageManager('/nonexistent');
|
|
108
|
+
expect(result).toBe('npm');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('getPackageManagerInfo', () => {
|
|
113
|
+
it('should return correct info for npm', () => {
|
|
114
|
+
const info = getPackageManagerInfo('npm');
|
|
115
|
+
expect(info.name).toBe('npm');
|
|
116
|
+
expect(info.installCommand).toBe('npm install');
|
|
117
|
+
expect(info.globalInstallCommand('foo')).toBe('npm install -g foo');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should return correct info for bun', () => {
|
|
121
|
+
const info = getPackageManagerInfo('bun');
|
|
122
|
+
expect(info.name).toBe('bun');
|
|
123
|
+
expect(info.installCommand).toBe('bun install');
|
|
124
|
+
expect(info.globalInstallCommand('foo')).toBe('bun install -g foo');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should return correct info for pnpm', () => {
|
|
128
|
+
const info = getPackageManagerInfo('pnpm');
|
|
129
|
+
expect(info.name).toBe('pnpm');
|
|
130
|
+
expect(info.installCommand).toBe('pnpm install');
|
|
131
|
+
expect(info.globalInstallCommand('foo')).toBe('pnpm install -g foo');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should return correct info for yarn', () => {
|
|
135
|
+
const info = getPackageManagerInfo('yarn');
|
|
136
|
+
expect(info.name).toBe('yarn');
|
|
137
|
+
expect(info.installCommand).toBe('yarn install');
|
|
138
|
+
expect(info.globalInstallCommand('foo')).toBe('yarn global add foo');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('getUpgradeCommand', () => {
|
|
143
|
+
it('should return correct upgrade command for npm', () => {
|
|
144
|
+
const cmd = getUpgradeCommand('my-package', 'npm');
|
|
145
|
+
expect(cmd).toBe('npm install -g my-package@latest');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return correct upgrade command for bun', () => {
|
|
149
|
+
const cmd = getUpgradeCommand('my-package', 'bun');
|
|
150
|
+
expect(cmd).toBe('bun install -g my-package@latest');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should return correct upgrade command for pnpm', () => {
|
|
154
|
+
const cmd = getUpgradeCommand('my-package', 'pnpm');
|
|
155
|
+
expect(cmd).toBe('pnpm install -g my-package@latest');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should return correct upgrade command for yarn', () => {
|
|
159
|
+
const cmd = getUpgradeCommand('my-package', 'yarn');
|
|
160
|
+
expect(cmd).toBe('yarn global add my-package@latest');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -18,17 +18,20 @@ import { yamlUtils } from './config/target-utils.js';
|
|
|
18
18
|
/**
|
|
19
19
|
* Load and combine rules and output styles
|
|
20
20
|
*/
|
|
21
|
-
export async function loadRulesAndStyles(
|
|
21
|
+
export async function loadRulesAndStyles(
|
|
22
|
+
ruleNames?: string[],
|
|
23
|
+
outputStyleNames?: string[]
|
|
24
|
+
): Promise<string> {
|
|
22
25
|
const sections: string[] = [];
|
|
23
26
|
|
|
24
|
-
// Load rules (either specified rules or default to
|
|
27
|
+
// Load rules (either specified rules or default to all)
|
|
25
28
|
const rulesContent = await loadRules(ruleNames);
|
|
26
29
|
if (rulesContent) {
|
|
27
30
|
sections.push(rulesContent);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
// Load output styles
|
|
31
|
-
const stylesContent = await loadOutputStyles();
|
|
33
|
+
// Load output styles (either specified or all)
|
|
34
|
+
const stylesContent = await loadOutputStyles(outputStyleNames);
|
|
32
35
|
if (stylesContent) {
|
|
33
36
|
sections.push(stylesContent);
|
|
34
37
|
}
|
|
@@ -69,26 +72,36 @@ async function loadRules(ruleNames?: string[]): Promise<string> {
|
|
|
69
72
|
|
|
70
73
|
/**
|
|
71
74
|
* Load output styles from assets/output-styles/
|
|
75
|
+
* @param styleNames - Array of style file names (without .md extension). If not provided, loads all styles.
|
|
72
76
|
*/
|
|
73
|
-
async function loadOutputStyles(): Promise<string> {
|
|
77
|
+
async function loadOutputStyles(styleNames?: string[]): Promise<string> {
|
|
74
78
|
try {
|
|
75
79
|
const outputStylesDir = getOutputStylesDir();
|
|
76
|
-
const files = await fs.readdir(outputStylesDir);
|
|
77
|
-
const mdFiles = files.filter((f) => f.endsWith('.md'));
|
|
78
|
-
|
|
79
|
-
if (mdFiles.length === 0) {
|
|
80
|
-
return '';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
80
|
const sections: string[] = [];
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
// If specific styles are requested, load only those
|
|
83
|
+
if (styleNames && styleNames.length > 0) {
|
|
84
|
+
for (const styleName of styleNames) {
|
|
85
|
+
const filePath = path.join(outputStylesDir, `${styleName}.md`);
|
|
86
|
+
try {
|
|
87
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
88
|
+
const stripped = await yamlUtils.stripFrontMatter(content);
|
|
89
|
+
sections.push(stripped);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.warn(`Warning: Output style file not found: ${styleName}.md`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// Load all styles
|
|
96
|
+
const files = await fs.readdir(outputStylesDir);
|
|
97
|
+
const mdFiles = files.filter((f) => f.endsWith('.md'));
|
|
98
|
+
|
|
99
|
+
for (const file of mdFiles) {
|
|
100
|
+
const filePath = path.join(outputStylesDir, file);
|
|
101
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
102
|
+
const stripped = await yamlUtils.stripFrontMatter(content);
|
|
103
|
+
sections.push(stripped);
|
|
104
|
+
}
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
return sections.join('\n\n');
|
|
@@ -101,10 +114,15 @@ async function loadOutputStyles(): Promise<string> {
|
|
|
101
114
|
/**
|
|
102
115
|
* Enhance agent content by appending rules and output styles
|
|
103
116
|
* @param agentContent - The agent markdown content
|
|
104
|
-
* @param ruleNames - Optional array of rule file names to include
|
|
117
|
+
* @param ruleNames - Optional array of rule file names to include
|
|
118
|
+
* @param outputStyleNames - Optional array of output style file names to include
|
|
105
119
|
*/
|
|
106
|
-
export async function enhanceAgentContent(
|
|
107
|
-
|
|
120
|
+
export async function enhanceAgentContent(
|
|
121
|
+
agentContent: string,
|
|
122
|
+
ruleNames?: string[],
|
|
123
|
+
outputStyleNames?: string[]
|
|
124
|
+
): Promise<string> {
|
|
125
|
+
const rulesAndStyles = await loadRulesAndStyles(ruleNames, outputStyleNames);
|
|
108
126
|
|
|
109
127
|
if (!rulesAndStyles) {
|
|
110
128
|
return agentContent;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Manager Detection
|
|
3
|
+
* Detects which package manager is being used (npm, bun, pnpm, yarn)
|
|
4
|
+
* Based on lock files and environment variables
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
export type PackageManager = 'npm' | 'bun' | 'pnpm' | 'yarn';
|
|
11
|
+
|
|
12
|
+
export interface PackageManagerInfo {
|
|
13
|
+
name: PackageManager;
|
|
14
|
+
installCommand: string;
|
|
15
|
+
globalInstallCommand: (packageName: string) => string;
|
|
16
|
+
version?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect package manager from environment variable (npm_config_user_agent)
|
|
21
|
+
* This is set when running through npm/bun/pnpm/yarn scripts
|
|
22
|
+
*/
|
|
23
|
+
export function detectPackageManagerFromUserAgent(): PackageManager | null {
|
|
24
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
25
|
+
|
|
26
|
+
if (!userAgent) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (userAgent.includes('bun')) return 'bun';
|
|
31
|
+
if (userAgent.includes('pnpm')) return 'pnpm';
|
|
32
|
+
if (userAgent.includes('yarn')) return 'yarn';
|
|
33
|
+
if (userAgent.includes('npm')) return 'npm';
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Detect package manager from lock files in directory
|
|
40
|
+
*/
|
|
41
|
+
export function detectPackageManagerFromLockFiles(dir: string = process.cwd()): PackageManager | null {
|
|
42
|
+
const lockFiles: Record<PackageManager, string[]> = {
|
|
43
|
+
bun: ['bun.lockb', 'bun.lock'],
|
|
44
|
+
pnpm: ['pnpm-lock.yaml'],
|
|
45
|
+
yarn: ['yarn.lock'],
|
|
46
|
+
npm: ['package-lock.json'],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Check in priority order: bun > pnpm > yarn > npm
|
|
50
|
+
const priority: PackageManager[] = ['bun', 'pnpm', 'yarn', 'npm'];
|
|
51
|
+
|
|
52
|
+
for (const pm of priority) {
|
|
53
|
+
const files = lockFiles[pm];
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
if (fs.existsSync(path.join(dir, file))) {
|
|
56
|
+
return pm;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Detect which package manager to use
|
|
66
|
+
* Priority: user agent > lock files > npm (default)
|
|
67
|
+
*/
|
|
68
|
+
export function detectPackageManager(dir: string = process.cwd()): PackageManager {
|
|
69
|
+
// 1. Try user agent (most reliable when running as script)
|
|
70
|
+
const fromUserAgent = detectPackageManagerFromUserAgent();
|
|
71
|
+
if (fromUserAgent) {
|
|
72
|
+
return fromUserAgent;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Try lock files
|
|
76
|
+
const fromLockFiles = detectPackageManagerFromLockFiles(dir);
|
|
77
|
+
if (fromLockFiles) {
|
|
78
|
+
return fromLockFiles;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 3. Default to npm
|
|
82
|
+
return 'npm';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get package manager info with commands
|
|
87
|
+
*/
|
|
88
|
+
export function getPackageManagerInfo(pm?: PackageManager): PackageManagerInfo {
|
|
89
|
+
const detected = pm || detectPackageManager();
|
|
90
|
+
|
|
91
|
+
const info: Record<PackageManager, PackageManagerInfo> = {
|
|
92
|
+
npm: {
|
|
93
|
+
name: 'npm',
|
|
94
|
+
installCommand: 'npm install',
|
|
95
|
+
globalInstallCommand: (pkg) => `npm install -g ${pkg}`,
|
|
96
|
+
},
|
|
97
|
+
bun: {
|
|
98
|
+
name: 'bun',
|
|
99
|
+
installCommand: 'bun install',
|
|
100
|
+
globalInstallCommand: (pkg) => `bun install -g ${pkg}`,
|
|
101
|
+
},
|
|
102
|
+
pnpm: {
|
|
103
|
+
name: 'pnpm',
|
|
104
|
+
installCommand: 'pnpm install',
|
|
105
|
+
globalInstallCommand: (pkg) => `pnpm install -g ${pkg}`,
|
|
106
|
+
},
|
|
107
|
+
yarn: {
|
|
108
|
+
name: 'yarn',
|
|
109
|
+
installCommand: 'yarn install',
|
|
110
|
+
globalInstallCommand: (pkg) => `yarn global add ${pkg}`,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return info[detected];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get upgrade command for a package
|
|
119
|
+
*/
|
|
120
|
+
export function getUpgradeCommand(packageName: string, pm?: PackageManager): string {
|
|
121
|
+
const pmInfo = getPackageManagerInfo(pm);
|
|
122
|
+
return pmInfo.globalInstallCommand(`${packageName}@latest`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if package manager is available in system
|
|
127
|
+
*/
|
|
128
|
+
export async function isPackageManagerAvailable(pm: PackageManager): Promise<boolean> {
|
|
129
|
+
const { exec } = await import('node:child_process');
|
|
130
|
+
const { promisify } = await import('node:util');
|
|
131
|
+
const execAsync = promisify(exec);
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await execAsync(`${pm} --version`);
|
|
135
|
+
return true;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|