@syntesseraai/opencode-feature-factory 0.2.3 → 0.2.4
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/dist/discovery.d.ts +18 -0
- package/dist/discovery.js +189 -0
- package/dist/discovery.test.d.ts +10 -0
- package/dist/discovery.test.js +95 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +81 -0
- package/dist/output.d.ts +17 -0
- package/dist/output.js +48 -0
- package/dist/output.test.d.ts +8 -0
- package/dist/output.test.js +205 -0
- package/dist/quality-gate-config.d.ts +37 -0
- package/dist/quality-gate-config.js +84 -0
- package/dist/quality-gate-config.test.d.ts +9 -0
- package/dist/quality-gate-config.test.js +164 -0
- package/dist/stop-quality-gate.d.ts +16 -0
- package/dist/stop-quality-gate.js +378 -0
- package/dist/stop-quality-gate.test.d.ts +8 -0
- package/dist/stop-quality-gate.test.js +549 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.js +1 -0
- package/package.json +8 -6
- package/src/discovery.test.ts +0 -114
- package/src/discovery.ts +0 -242
- package/src/index.ts +0 -105
- package/src/output.test.ts +0 -233
- package/src/output.ts +0 -55
- package/src/quality-gate-config.test.ts +0 -186
- package/src/quality-gate-config.ts +0 -106
- package/src/stop-quality-gate.test.ts +0 -655
- package/src/stop-quality-gate.ts +0 -450
- package/src/types.ts +0 -72
package/src/output.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regex pattern for detecting error-like lines in command output
|
|
3
|
-
*/
|
|
4
|
-
const ERROR_PATTERNS =
|
|
5
|
-
/(error|failed|failure|panic|assert|exception|traceback|TypeError|ReferenceError|SyntaxError|FAILED|FAIL|ERR!)/i;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Extract lines that likely contain error information from command output.
|
|
9
|
-
* Includes 1-2 lines of context after each error line.
|
|
10
|
-
*
|
|
11
|
-
* @param output - The command output to search
|
|
12
|
-
* @param maxLines - Maximum number of error lines to extract
|
|
13
|
-
* @returns Array of error-like lines (up to maxLines)
|
|
14
|
-
*/
|
|
15
|
-
export function extractErrorLines(output: string, maxLines: number): string[] {
|
|
16
|
-
const lines = output.split('\n');
|
|
17
|
-
const errorLines: string[] = [];
|
|
18
|
-
|
|
19
|
-
for (let i = 0; i < lines.length && errorLines.length < maxLines; i++) {
|
|
20
|
-
const line = lines[i];
|
|
21
|
-
if (line && ERROR_PATTERNS.test(line)) {
|
|
22
|
-
errorLines.push(line);
|
|
23
|
-
// Include 1-2 lines of context after the error
|
|
24
|
-
const nextLine = lines[i + 1];
|
|
25
|
-
const nextNextLine = lines[i + 2];
|
|
26
|
-
if (nextLine !== undefined) {
|
|
27
|
-
errorLines.push(nextLine);
|
|
28
|
-
}
|
|
29
|
-
if (nextNextLine !== undefined) {
|
|
30
|
-
errorLines.push(nextNextLine);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return errorLines.slice(0, maxLines);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Return the last N lines of output, with a truncation notice if needed.
|
|
40
|
-
*
|
|
41
|
-
* @param output - The full command output
|
|
42
|
-
* @param maxLines - Maximum number of lines to return
|
|
43
|
-
* @returns Truncated output with notice, or full output if short enough
|
|
44
|
-
*/
|
|
45
|
-
export function tailLines(output: string, maxLines: number): string {
|
|
46
|
-
const lines = output.split('\n');
|
|
47
|
-
if (lines.length <= maxLines) {
|
|
48
|
-
return output;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const truncatedCount = lines.length - maxLines;
|
|
52
|
-
const tail = lines.slice(-maxLines).join('\n');
|
|
53
|
-
|
|
54
|
-
return `... (${truncatedCount} lines truncated)\n${tail}`;
|
|
55
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for quality-gate-config module
|
|
3
|
-
*
|
|
4
|
-
* Tests focus on pure functions:
|
|
5
|
-
* - mergeQualityGateConfig: merges two config objects with deep merge for 'include'
|
|
6
|
-
* - hasConfiguredCommands: checks if explicit commands are configured
|
|
7
|
-
* - DEFAULT_QUALITY_GATE: verify default values
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
mergeQualityGateConfig,
|
|
12
|
-
hasConfiguredCommands,
|
|
13
|
-
DEFAULT_QUALITY_GATE,
|
|
14
|
-
} from './quality-gate-config';
|
|
15
|
-
import type { QualityGateConfig } from './types';
|
|
16
|
-
|
|
17
|
-
describe('DEFAULT_QUALITY_GATE', () => {
|
|
18
|
-
it('should have correct default values', () => {
|
|
19
|
-
expect(DEFAULT_QUALITY_GATE.steps).toEqual(['lint', 'build', 'test']);
|
|
20
|
-
expect(DEFAULT_QUALITY_GATE.useCiSh).toBe('auto');
|
|
21
|
-
expect(DEFAULT_QUALITY_GATE.packageManager).toBe('auto');
|
|
22
|
-
expect(DEFAULT_QUALITY_GATE.cacheSeconds).toBe(30);
|
|
23
|
-
expect(DEFAULT_QUALITY_GATE.maxOutputLines).toBe(160);
|
|
24
|
-
expect(DEFAULT_QUALITY_GATE.maxErrorLines).toBe(60);
|
|
25
|
-
expect(DEFAULT_QUALITY_GATE.include.rustClippy).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('mergeQualityGateConfig', () => {
|
|
30
|
-
it('should return empty config when both inputs are undefined', () => {
|
|
31
|
-
const result = mergeQualityGateConfig(undefined, undefined);
|
|
32
|
-
expect(result).toEqual({ include: {} });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should return root config when dotOpencode is undefined', () => {
|
|
36
|
-
const root: QualityGateConfig = {
|
|
37
|
-
lint: 'pnpm lint',
|
|
38
|
-
cacheSeconds: 60,
|
|
39
|
-
};
|
|
40
|
-
const result = mergeQualityGateConfig(root, undefined);
|
|
41
|
-
expect(result).toEqual({ lint: 'pnpm lint', cacheSeconds: 60, include: {} });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should return dotOpencode config when root is undefined', () => {
|
|
45
|
-
const dotOpencode: QualityGateConfig = {
|
|
46
|
-
build: 'npm run build',
|
|
47
|
-
maxOutputLines: 200,
|
|
48
|
-
};
|
|
49
|
-
const result = mergeQualityGateConfig(undefined, dotOpencode);
|
|
50
|
-
expect(result).toEqual({ build: 'npm run build', maxOutputLines: 200, include: {} });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should override root values with dotOpencode values', () => {
|
|
54
|
-
const root: QualityGateConfig = {
|
|
55
|
-
lint: 'pnpm lint',
|
|
56
|
-
build: 'pnpm build',
|
|
57
|
-
cacheSeconds: 30,
|
|
58
|
-
};
|
|
59
|
-
const dotOpencode: QualityGateConfig = {
|
|
60
|
-
lint: 'npm run lint:strict',
|
|
61
|
-
cacheSeconds: 60,
|
|
62
|
-
};
|
|
63
|
-
const result = mergeQualityGateConfig(root, dotOpencode);
|
|
64
|
-
expect(result.lint).toBe('npm run lint:strict');
|
|
65
|
-
expect(result.build).toBe('pnpm build');
|
|
66
|
-
expect(result.cacheSeconds).toBe(60);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should deep merge include objects', () => {
|
|
70
|
-
const root: QualityGateConfig = {
|
|
71
|
-
include: {
|
|
72
|
-
rustClippy: false,
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
const dotOpencode: QualityGateConfig = {
|
|
76
|
-
include: {
|
|
77
|
-
rustClippy: true,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
const result = mergeQualityGateConfig(root, dotOpencode);
|
|
81
|
-
expect(result.include?.rustClippy).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should preserve root include values not overridden by dotOpencode', () => {
|
|
85
|
-
const root: QualityGateConfig = {
|
|
86
|
-
include: {
|
|
87
|
-
rustClippy: true,
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
const dotOpencode: QualityGateConfig = {
|
|
91
|
-
lint: 'custom lint',
|
|
92
|
-
// No include specified
|
|
93
|
-
};
|
|
94
|
-
const result = mergeQualityGateConfig(root, dotOpencode);
|
|
95
|
-
expect(result.include?.rustClippy).toBe(true);
|
|
96
|
-
expect(result.lint).toBe('custom lint');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should handle all config properties', () => {
|
|
100
|
-
const root: QualityGateConfig = {
|
|
101
|
-
lint: 'lint-root',
|
|
102
|
-
build: 'build-root',
|
|
103
|
-
test: 'test-root',
|
|
104
|
-
cwd: './root',
|
|
105
|
-
steps: ['lint'],
|
|
106
|
-
useCiSh: 'auto',
|
|
107
|
-
packageManager: 'npm',
|
|
108
|
-
cacheSeconds: 30,
|
|
109
|
-
maxOutputLines: 100,
|
|
110
|
-
maxErrorLines: 50,
|
|
111
|
-
include: { rustClippy: false },
|
|
112
|
-
};
|
|
113
|
-
const dotOpencode: QualityGateConfig = {
|
|
114
|
-
lint: 'lint-override',
|
|
115
|
-
steps: ['lint', 'test'],
|
|
116
|
-
useCiSh: 'never',
|
|
117
|
-
include: { rustClippy: true },
|
|
118
|
-
};
|
|
119
|
-
const result = mergeQualityGateConfig(root, dotOpencode);
|
|
120
|
-
|
|
121
|
-
expect(result.lint).toBe('lint-override');
|
|
122
|
-
expect(result.build).toBe('build-root');
|
|
123
|
-
expect(result.test).toBe('test-root');
|
|
124
|
-
expect(result.cwd).toBe('./root');
|
|
125
|
-
expect(result.steps).toEqual(['lint', 'test']);
|
|
126
|
-
expect(result.useCiSh).toBe('never');
|
|
127
|
-
expect(result.packageManager).toBe('npm');
|
|
128
|
-
expect(result.cacheSeconds).toBe(30);
|
|
129
|
-
expect(result.maxOutputLines).toBe(100);
|
|
130
|
-
expect(result.maxErrorLines).toBe(50);
|
|
131
|
-
expect(result.include?.rustClippy).toBe(true);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe('hasConfiguredCommands', () => {
|
|
136
|
-
it('should return false for empty config', () => {
|
|
137
|
-
expect(hasConfiguredCommands({})).toBe(false);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should return false when only non-command properties are set', () => {
|
|
141
|
-
const config: QualityGateConfig = {
|
|
142
|
-
cacheSeconds: 60,
|
|
143
|
-
maxOutputLines: 200,
|
|
144
|
-
useCiSh: 'always',
|
|
145
|
-
packageManager: 'pnpm',
|
|
146
|
-
};
|
|
147
|
-
expect(hasConfiguredCommands(config)).toBe(false);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should return true when lint is configured', () => {
|
|
151
|
-
const config: QualityGateConfig = {
|
|
152
|
-
lint: 'pnpm lint',
|
|
153
|
-
};
|
|
154
|
-
expect(hasConfiguredCommands(config)).toBe(true);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should return true when build is configured', () => {
|
|
158
|
-
const config: QualityGateConfig = {
|
|
159
|
-
build: 'pnpm build',
|
|
160
|
-
};
|
|
161
|
-
expect(hasConfiguredCommands(config)).toBe(true);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('should return true when test is configured', () => {
|
|
165
|
-
const config: QualityGateConfig = {
|
|
166
|
-
test: 'pnpm test',
|
|
167
|
-
};
|
|
168
|
-
expect(hasConfiguredCommands(config)).toBe(true);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should return true when multiple commands are configured', () => {
|
|
172
|
-
const config: QualityGateConfig = {
|
|
173
|
-
lint: 'pnpm lint',
|
|
174
|
-
build: 'pnpm build',
|
|
175
|
-
test: 'pnpm test',
|
|
176
|
-
};
|
|
177
|
-
expect(hasConfiguredCommands(config)).toBe(true);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should return false for empty string commands', () => {
|
|
181
|
-
const config: QualityGateConfig = {
|
|
182
|
-
lint: '',
|
|
183
|
-
};
|
|
184
|
-
expect(hasConfiguredCommands(config)).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import type { QualityGateConfig } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Default configuration values for StopQualityGate
|
|
5
|
-
*/
|
|
6
|
-
export const DEFAULT_QUALITY_GATE: Required<
|
|
7
|
-
Omit<QualityGateConfig, 'lint' | 'build' | 'test' | 'cwd'>
|
|
8
|
-
> = {
|
|
9
|
-
steps: ['lint', 'build', 'test'],
|
|
10
|
-
useCiSh: 'auto',
|
|
11
|
-
packageManager: 'auto',
|
|
12
|
-
cacheSeconds: 30,
|
|
13
|
-
maxOutputLines: 160,
|
|
14
|
-
maxErrorLines: 60,
|
|
15
|
-
include: {
|
|
16
|
-
rustClippy: true,
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type BunShell = any;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Read a JSON file safely, returning null if not found or invalid
|
|
24
|
-
*/
|
|
25
|
-
export async function readJsonFile(
|
|
26
|
-
$: BunShell,
|
|
27
|
-
path: string
|
|
28
|
-
): Promise<Record<string, unknown> | null> {
|
|
29
|
-
try {
|
|
30
|
-
const result = await $`cat ${path}`.quiet();
|
|
31
|
-
return JSON.parse(result.text()) as Record<string, unknown>;
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Check if a file exists
|
|
39
|
-
*/
|
|
40
|
-
export async function fileExists($: BunShell, path: string): Promise<boolean> {
|
|
41
|
-
try {
|
|
42
|
-
await $`test -f ${path}`.quiet();
|
|
43
|
-
return true;
|
|
44
|
-
} catch {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Merge two qualityGate config objects.
|
|
51
|
-
*
|
|
52
|
-
* Precedence: `.opencode/opencode.json` overrides `opencode.json` (root).
|
|
53
|
-
* Deep-merges nested objects like `include`.
|
|
54
|
-
*/
|
|
55
|
-
export function mergeQualityGateConfig(
|
|
56
|
-
root: QualityGateConfig | undefined,
|
|
57
|
-
dotOpencode: QualityGateConfig | undefined
|
|
58
|
-
): QualityGateConfig {
|
|
59
|
-
const base = root ?? {};
|
|
60
|
-
const override = dotOpencode ?? {};
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
...base,
|
|
64
|
-
...override,
|
|
65
|
-
// Deep merge the `include` object
|
|
66
|
-
include: {
|
|
67
|
-
...(base.include ?? {}),
|
|
68
|
-
...(override.include ?? {}),
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Load and merge qualityGate config from both config file locations.
|
|
75
|
-
*
|
|
76
|
-
* Reads from:
|
|
77
|
-
* - `${directory}/opencode.json`
|
|
78
|
-
* - `${directory}/.opencode/opencode.json`
|
|
79
|
-
*
|
|
80
|
-
* Merges ONLY the `qualityGate` key. `.opencode/opencode.json` takes precedence.
|
|
81
|
-
*/
|
|
82
|
-
export async function loadQualityGateConfig(
|
|
83
|
-
$: BunShell,
|
|
84
|
-
directory: string
|
|
85
|
-
): Promise<QualityGateConfig> {
|
|
86
|
-
const rootConfigPath = `${directory}/opencode.json`;
|
|
87
|
-
const dotOpencodeConfigPath = `${directory}/.opencode/opencode.json`;
|
|
88
|
-
|
|
89
|
-
const [rootJson, dotOpencodeJson] = await Promise.all([
|
|
90
|
-
readJsonFile($, rootConfigPath),
|
|
91
|
-
readJsonFile($, dotOpencodeConfigPath),
|
|
92
|
-
]);
|
|
93
|
-
|
|
94
|
-
const rootQualityGate = rootJson?.qualityGate as QualityGateConfig | undefined;
|
|
95
|
-
const dotOpencodeQualityGate = dotOpencodeJson?.qualityGate as QualityGateConfig | undefined;
|
|
96
|
-
|
|
97
|
-
return mergeQualityGateConfig(rootQualityGate, dotOpencodeQualityGate);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Check if any explicit commands are configured (lint/build/test).
|
|
102
|
-
* If so, these take priority over ci.sh and discovery.
|
|
103
|
-
*/
|
|
104
|
-
export function hasConfiguredCommands(config: QualityGateConfig): boolean {
|
|
105
|
-
return !!(config.lint || config.build || config.test);
|
|
106
|
-
}
|