oh-my-harness 0.2.1 → 0.3.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 +7 -2
- package/dist/cli/commands/init.js +10 -1
- package/dist/cli/tool-checker.d.ts +2 -2
- package/dist/cli/tool-checker.js +51 -12
- package/dist/cli/tui/init-flow.d.ts +2 -0
- package/dist/cli/tui/init-flow.js +39 -1
- package/dist/core/harness-schema.d.ts +4 -4
- package/dist/detector/detectors/cpp.d.ts +2 -0
- package/dist/detector/detectors/cpp.js +44 -0
- package/dist/detector/detectors/dart.d.ts +2 -0
- package/dist/detector/detectors/dart.js +44 -0
- package/dist/detector/detectors/dotnet.d.ts +2 -0
- package/dist/detector/detectors/dotnet.js +43 -0
- package/dist/detector/detectors/elixir.d.ts +2 -0
- package/dist/detector/detectors/elixir.js +22 -0
- package/dist/detector/detectors/go.d.ts +2 -0
- package/dist/detector/detectors/go.js +22 -0
- package/dist/detector/detectors/index.d.ts +2 -0
- package/dist/detector/detectors/index.js +30 -0
- package/dist/detector/detectors/java.d.ts +2 -0
- package/dist/detector/detectors/java.js +59 -0
- package/dist/detector/detectors/node.d.ts +2 -0
- package/dist/detector/detectors/node.js +130 -0
- package/dist/detector/detectors/php.d.ts +2 -0
- package/dist/detector/detectors/php.js +63 -0
- package/dist/detector/detectors/python.d.ts +2 -0
- package/dist/detector/detectors/python.js +142 -0
- package/dist/detector/detectors/ruby.d.ts +2 -0
- package/dist/detector/detectors/ruby.js +67 -0
- package/dist/detector/detectors/rust.d.ts +2 -0
- package/dist/detector/detectors/rust.js +23 -0
- package/dist/detector/detectors/scala.d.ts +2 -0
- package/dist/detector/detectors/scala.js +22 -0
- package/dist/detector/detectors/swift.d.ts +2 -0
- package/dist/detector/detectors/swift.js +55 -0
- package/dist/detector/detectors/zig.d.ts +2 -0
- package/dist/detector/detectors/zig.js +21 -0
- package/dist/detector/project-detector.d.ts +3 -0
- package/dist/detector/project-detector.js +33 -0
- package/dist/detector/types.d.ts +17 -0
- package/dist/detector/types.js +26 -0
- package/dist/nl/parse-intent.d.ts +2 -1
- package/dist/nl/parse-intent.js +2 -2
- package/dist/nl/prompt-templates.d.ts +2 -1
- package/dist/nl/prompt-templates.js +32 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -241,7 +241,7 @@ rules:
|
|
|
241
241
|
priority: 10
|
|
242
242
|
|
|
243
243
|
enforcement:
|
|
244
|
-
preCommit: ["test", "
|
|
244
|
+
preCommit: ["pnpm test", "npx eslint .", "npx tsc --noEmit"]
|
|
245
245
|
blockedPaths: [".next/", "node_modules/"]
|
|
246
246
|
blockedCommands: ["rm -rf", "sudo"]
|
|
247
247
|
postSave:
|
|
@@ -290,6 +290,10 @@ oh-my-harness/
|
|
|
290
290
|
│ │ ├── hooks.ts # Executable hook scripts
|
|
291
291
|
│ │ ├── settings.ts # .claude/settings.json
|
|
292
292
|
│ │ └── gitignore.ts # .gitignore updater
|
|
293
|
+
│ ├── detector/
|
|
294
|
+
│ │ ├── project-detector.ts # Deterministic project detection
|
|
295
|
+
│ │ ├── types.ts # ProjectFacts, Detector interface
|
|
296
|
+
│ │ └── detectors/ # 14 language detectors
|
|
293
297
|
│ ├── nl/
|
|
294
298
|
│ │ ├── parse-intent.ts # claude -p integration
|
|
295
299
|
│ │ └── prompt-templates.ts # LLM prompt construction
|
|
@@ -301,7 +305,7 @@ oh-my-harness/
|
|
|
301
305
|
│ ├── nextjs/
|
|
302
306
|
│ ├── fastapi/
|
|
303
307
|
│ └── nextjs-fastapi/
|
|
304
|
-
└── tests/ #
|
|
308
|
+
└── tests/ # 422 tests (unit + integration)
|
|
305
309
|
```
|
|
306
310
|
|
|
307
311
|
---
|
|
@@ -354,6 +358,7 @@ No code changes required. The registry auto-discovers it.
|
|
|
354
358
|
- [x] `npx oh-my-harness` — zero-install usage
|
|
355
359
|
- [x] `oh-my-harness sync` — regenerate from harness.yaml
|
|
356
360
|
- [x] Building block catalog — 10 verified hook templates
|
|
361
|
+
- [x] Project detector — 14 language auto-detection for accurate NL generation
|
|
357
362
|
- [ ] Cursor (`.cursor/rules/`) emitter
|
|
358
363
|
- [ ] Codex (`AGENTS.md`) emitter
|
|
359
364
|
- [ ] GitHub Copilot emitter
|
|
@@ -5,6 +5,7 @@ import { PresetRegistry } from "../../core/preset-registry.js";
|
|
|
5
5
|
import { mergePresets } from "../../core/config-merger.js";
|
|
6
6
|
import { generate } from "../../core/generator.js";
|
|
7
7
|
import { generateHarnessConfig } from "../../nl/parse-intent.js";
|
|
8
|
+
import { detectProject } from "../../detector/project-detector.js";
|
|
8
9
|
import { harnessToMergedConfigV2 } from "../../core/harness-converter-v2.js";
|
|
9
10
|
import { createDefaultRegistry } from "../../catalog/registry.js";
|
|
10
11
|
function getDefaultPresetsDir() {
|
|
@@ -107,6 +108,14 @@ async function initWithNL(projectDir, presetsDir, options) {
|
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
109
110
|
console.log(`Generating harness config for: "${description}"`);
|
|
111
|
+
// Detect project facts for richer prompt context
|
|
112
|
+
let facts;
|
|
113
|
+
try {
|
|
114
|
+
facts = await detectProject(projectDir);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Non-fatal: continue with no facts
|
|
118
|
+
}
|
|
110
119
|
// Load catalog blocks so LLM knows available building blocks
|
|
111
120
|
const registry = await createDefaultRegistry();
|
|
112
121
|
const catalogBlocks = registry.list().map((b) => ({
|
|
@@ -117,7 +126,7 @@ async function initWithNL(projectDir, presetsDir, options) {
|
|
|
117
126
|
matcher: b.matcher,
|
|
118
127
|
params: b.params.map((p) => ({ name: p.name, type: p.type, description: p.description, required: p.required, default: p.default })),
|
|
119
128
|
}));
|
|
120
|
-
const harness = await generateHarnessConfig(description, options.nlRunner, catalogBlocks);
|
|
129
|
+
const harness = await generateHarnessConfig(description, options.nlRunner, catalogBlocks, facts);
|
|
121
130
|
// Show summary
|
|
122
131
|
const stackNames = harness.project.stacks.map((s) => `${s.name} (${s.framework})`).join(", ");
|
|
123
132
|
console.log(`\nStacks: ${stackNames}`);
|
|
@@ -6,10 +6,10 @@ export interface ToolCheck {
|
|
|
6
6
|
installCmd: string;
|
|
7
7
|
installed: boolean;
|
|
8
8
|
}
|
|
9
|
-
interface ToolRef {
|
|
9
|
+
export interface ToolRef {
|
|
10
10
|
name: string;
|
|
11
11
|
source: string;
|
|
12
|
+
lookupCommand: string;
|
|
12
13
|
}
|
|
13
14
|
export declare function extractToolNames(config: HarnessConfig): ToolRef[];
|
|
14
15
|
export declare function checkReferencedTools(config: HarnessConfig): Promise<ToolCheck[]>;
|
|
15
|
-
export {};
|
package/dist/cli/tool-checker.js
CHANGED
|
@@ -17,27 +17,66 @@ function extractBinary(command) {
|
|
|
17
17
|
const trimmed = command.trim();
|
|
18
18
|
if (!trimmed)
|
|
19
19
|
return undefined;
|
|
20
|
-
|
|
20
|
+
const parts = trimmed.split(/\s+/);
|
|
21
|
+
// Skip gradle wrapper commands (managed by project)
|
|
22
|
+
if (parts[0] === "./gradlew" || parts[0] === "gradlew")
|
|
23
|
+
return undefined;
|
|
24
|
+
// npx [options] <tool> ... → skip option flags, extract first non-option token
|
|
25
|
+
if (parts[0] === "npx") {
|
|
26
|
+
let i = 1;
|
|
27
|
+
while (i < parts.length && parts[i].startsWith("-")) {
|
|
28
|
+
if (parts[i] === "-p" || parts[i] === "--package")
|
|
29
|
+
i += 2;
|
|
30
|
+
else
|
|
31
|
+
i += 1;
|
|
32
|
+
}
|
|
33
|
+
return i < parts.length ? { name: parts[i], lookupCommand: "npx" } : undefined;
|
|
34
|
+
}
|
|
35
|
+
// npm exec <tool> → extract tool; other npm subcommands → skip
|
|
36
|
+
if (parts[0] === "npm") {
|
|
37
|
+
if (parts[1] === "exec" && parts.length > 2)
|
|
38
|
+
return { name: parts[2], lookupCommand: "npm" };
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
// pnpm exec <tool> → wrapper lookup; pnpm dlx <tool> → direct lookup
|
|
42
|
+
if (parts[0] === "pnpm") {
|
|
43
|
+
if (parts[1] === "exec" && parts.length > 2)
|
|
44
|
+
return { name: parts[2], lookupCommand: "pnpm" };
|
|
45
|
+
if (parts[1] === "dlx" && parts.length > 2)
|
|
46
|
+
return { name: parts[2], lookupCommand: "pnpm" };
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
// yarn dlx <tool> → direct lookup
|
|
50
|
+
if (parts[0] === "yarn") {
|
|
51
|
+
if (parts[1] === "dlx" && parts.length > 2)
|
|
52
|
+
return { name: parts[2], lookupCommand: "yarn" };
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
// poetry run <tool> → check poetry existence
|
|
56
|
+
if (parts[0] === "poetry" && parts[1] === "run" && parts.length > 2) {
|
|
57
|
+
return { name: parts[2], lookupCommand: "poetry" };
|
|
58
|
+
}
|
|
59
|
+
return { name: parts[0], lookupCommand: parts[0] };
|
|
21
60
|
}
|
|
22
61
|
export function extractToolNames(config) {
|
|
23
62
|
const seen = new Set();
|
|
24
63
|
const tools = [];
|
|
25
64
|
for (const cmd of config.enforcement.preCommit) {
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
65
|
+
const result = extractBinary(cmd);
|
|
66
|
+
if (!result)
|
|
28
67
|
continue;
|
|
29
|
-
if (!seen.has(name)) {
|
|
30
|
-
seen.add(name);
|
|
31
|
-
tools.push({ name, source: "pre-commit" });
|
|
68
|
+
if (!seen.has(result.name)) {
|
|
69
|
+
seen.add(result.name);
|
|
70
|
+
tools.push({ name: result.name, lookupCommand: result.lookupCommand, source: "pre-commit" });
|
|
32
71
|
}
|
|
33
72
|
}
|
|
34
73
|
for (const ps of config.enforcement.postSave) {
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
74
|
+
const result = extractBinary(ps.command);
|
|
75
|
+
if (!result)
|
|
37
76
|
continue;
|
|
38
|
-
if (!seen.has(name)) {
|
|
39
|
-
seen.add(name);
|
|
40
|
-
tools.push({ name, source: "post-save hook" });
|
|
77
|
+
if (!seen.has(result.name)) {
|
|
78
|
+
seen.add(result.name);
|
|
79
|
+
tools.push({ name: result.name, lookupCommand: result.lookupCommand, source: "post-save hook" });
|
|
41
80
|
}
|
|
42
81
|
}
|
|
43
82
|
return tools;
|
|
@@ -58,7 +97,7 @@ export async function checkReferencedTools(config) {
|
|
|
58
97
|
const refs = extractToolNames(config);
|
|
59
98
|
const results = [];
|
|
60
99
|
for (const ref of refs) {
|
|
61
|
-
const installed = await commandExists(ref.
|
|
100
|
+
const installed = await commandExists(ref.lookupCommand);
|
|
62
101
|
results.push({
|
|
63
102
|
name: ref.name,
|
|
64
103
|
command: ref.name,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { DepCheck } from "../deps-checker.js";
|
|
2
2
|
import type { HarnessConfig } from "../../core/harness-schema.js";
|
|
3
|
+
import type { ProjectFacts } from "../../detector/types.js";
|
|
3
4
|
export declare function formatDepResults(deps: DepCheck[]): string;
|
|
4
5
|
export declare function formatConfigSummary(config: HarnessConfig): string;
|
|
6
|
+
export declare function formatProjectFacts(facts: ProjectFacts): string;
|
|
5
7
|
export declare function runInitTUI(options?: {
|
|
6
8
|
projectDir?: string;
|
|
7
9
|
presetsDir?: string;
|
|
@@ -12,6 +12,7 @@ import { generate } from "../../core/generator.js";
|
|
|
12
12
|
import { generateHarnessConfig } from "../../nl/parse-intent.js";
|
|
13
13
|
import { harnessToMergedConfig } from "../../core/harness-converter.js";
|
|
14
14
|
import { HarnessConfigSchema } from "../../core/harness-schema.js";
|
|
15
|
+
import { detectProject } from "../../detector/project-detector.js";
|
|
15
16
|
function getDefaultPresetsDir() {
|
|
16
17
|
return path.resolve(import.meta.dirname, "../../../presets");
|
|
17
18
|
}
|
|
@@ -79,6 +80,25 @@ export function formatConfigSummary(config) {
|
|
|
79
80
|
}
|
|
80
81
|
return lines.join("\n");
|
|
81
82
|
}
|
|
83
|
+
export function formatProjectFacts(facts) {
|
|
84
|
+
const lines = [];
|
|
85
|
+
const entries = [
|
|
86
|
+
["Languages", facts.languages],
|
|
87
|
+
["Frameworks", facts.frameworks],
|
|
88
|
+
["Package managers", facts.packageManagers],
|
|
89
|
+
["Test commands", facts.testCommands],
|
|
90
|
+
["Lint commands", facts.lintCommands],
|
|
91
|
+
["Build commands", facts.buildCommands],
|
|
92
|
+
["Typecheck", facts.typecheckCommands],
|
|
93
|
+
["Blocked paths", facts.blockedPaths],
|
|
94
|
+
];
|
|
95
|
+
for (const [label, values] of entries) {
|
|
96
|
+
if (values.length > 0) {
|
|
97
|
+
lines.push(` ${chalk.bold(label)}: ${values.join(", ")}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return lines.length > 0 ? lines.join("\n") : ` ${chalk.dim("No project signals detected")}`;
|
|
101
|
+
}
|
|
82
102
|
function handleCancel(value) {
|
|
83
103
|
if (p.isCancel(value)) {
|
|
84
104
|
p.cancel("Operation cancelled.");
|
|
@@ -114,6 +134,24 @@ export async function runInitTUI(options) {
|
|
|
114
134
|
if (!claudeInstalled) {
|
|
115
135
|
p.log.warn("claude CLI not installed. AI-powered mode will not be available.");
|
|
116
136
|
}
|
|
137
|
+
// Step 2.5: Project Detection
|
|
138
|
+
const detectSpinner = p.spinner();
|
|
139
|
+
detectSpinner.start("Detecting project type...");
|
|
140
|
+
let projectFacts;
|
|
141
|
+
try {
|
|
142
|
+
const facts = await detectProject(projectDir);
|
|
143
|
+
const hasAnyFacts = facts.languages.length > 0 || facts.frameworks.length > 0;
|
|
144
|
+
if (hasAnyFacts) {
|
|
145
|
+
projectFacts = facts;
|
|
146
|
+
}
|
|
147
|
+
detectSpinner.stop("Project detected");
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
detectSpinner.stop("Project detection skipped");
|
|
151
|
+
}
|
|
152
|
+
if (projectFacts) {
|
|
153
|
+
p.note(formatProjectFacts(projectFacts), "Detected Project");
|
|
154
|
+
}
|
|
117
155
|
const modeOptions = [];
|
|
118
156
|
if (claudeInstalled) {
|
|
119
157
|
modeOptions.push({
|
|
@@ -163,7 +201,7 @@ export async function runInitTUI(options) {
|
|
|
163
201
|
const genSpinner = p.spinner();
|
|
164
202
|
genSpinner.start("Generating harness configuration...");
|
|
165
203
|
try {
|
|
166
|
-
harnessConfig = await generateHarnessConfig(description);
|
|
204
|
+
harnessConfig = await generateHarnessConfig(description, undefined, undefined, projectFacts);
|
|
167
205
|
genSpinner.stop("Configuration generated");
|
|
168
206
|
}
|
|
169
207
|
catch (err) {
|
|
@@ -80,16 +80,16 @@ export declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
80
80
|
pattern: string;
|
|
81
81
|
}>, "many">>;
|
|
82
82
|
}, "strip", z.ZodTypeAny, {
|
|
83
|
-
preCommit: string[];
|
|
84
83
|
blockedPaths: string[];
|
|
84
|
+
preCommit: string[];
|
|
85
85
|
blockedCommands: string[];
|
|
86
86
|
postSave: {
|
|
87
87
|
command: string;
|
|
88
88
|
pattern: string;
|
|
89
89
|
}[];
|
|
90
90
|
}, {
|
|
91
|
-
preCommit?: string[] | undefined;
|
|
92
91
|
blockedPaths?: string[] | undefined;
|
|
92
|
+
preCommit?: string[] | undefined;
|
|
93
93
|
blockedCommands?: string[] | undefined;
|
|
94
94
|
postSave?: {
|
|
95
95
|
command: string;
|
|
@@ -145,8 +145,8 @@ export declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
145
145
|
priority: number;
|
|
146
146
|
}[];
|
|
147
147
|
enforcement: {
|
|
148
|
-
preCommit: string[];
|
|
149
148
|
blockedPaths: string[];
|
|
149
|
+
preCommit: string[];
|
|
150
150
|
blockedCommands: string[];
|
|
151
151
|
postSave: {
|
|
152
152
|
command: string;
|
|
@@ -173,8 +173,8 @@ export declare const HarnessConfigSchema: z.ZodObject<{
|
|
|
173
173
|
priority?: number | undefined;
|
|
174
174
|
}[];
|
|
175
175
|
enforcement: {
|
|
176
|
-
preCommit?: string[] | undefined;
|
|
177
176
|
blockedPaths?: string[] | undefined;
|
|
177
|
+
preCommit?: string[] | undefined;
|
|
178
178
|
blockedCommands?: string[] | undefined;
|
|
179
179
|
postSave?: {
|
|
180
180
|
command: string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export const cppDetector = {
|
|
4
|
+
name: "cpp",
|
|
5
|
+
detect: async (projectDir) => {
|
|
6
|
+
const cmakeFile = path.join(projectDir, "CMakeLists.txt");
|
|
7
|
+
const makeFile = path.join(projectDir, "Makefile");
|
|
8
|
+
const mesonFile = path.join(projectDir, "meson.build");
|
|
9
|
+
const [hasCMake, hasMakefile, hasMeson] = await Promise.all([
|
|
10
|
+
fs.access(cmakeFile).then(() => true).catch(() => false),
|
|
11
|
+
fs.access(makeFile).then(() => true).catch(() => false),
|
|
12
|
+
fs.access(mesonFile).then(() => true).catch(() => false),
|
|
13
|
+
]);
|
|
14
|
+
const blockedPaths = ["build/", "cmake-build-*/"];
|
|
15
|
+
if (hasCMake) {
|
|
16
|
+
return {
|
|
17
|
+
languages: ["c", "cpp"],
|
|
18
|
+
buildCommands: ["cmake --build build"],
|
|
19
|
+
testCommands: ["ctest --test-dir build"],
|
|
20
|
+
blockedPaths,
|
|
21
|
+
detectedFiles: ["CMakeLists.txt"],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (hasMeson) {
|
|
25
|
+
return {
|
|
26
|
+
languages: ["c", "cpp"],
|
|
27
|
+
buildCommands: ["meson compile -C build"],
|
|
28
|
+
testCommands: ["meson test -C build"],
|
|
29
|
+
blockedPaths,
|
|
30
|
+
detectedFiles: ["meson.build"],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (hasMakefile) {
|
|
34
|
+
return {
|
|
35
|
+
languages: ["c"],
|
|
36
|
+
buildCommands: ["make"],
|
|
37
|
+
testCommands: ["make test"],
|
|
38
|
+
blockedPaths,
|
|
39
|
+
detectedFiles: ["Makefile"],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export const dartDetector = {
|
|
4
|
+
name: "dart",
|
|
5
|
+
detect: async (projectDir) => {
|
|
6
|
+
let entries;
|
|
7
|
+
try {
|
|
8
|
+
entries = await fs.readdir(projectDir);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
if (!entries.includes("pubspec.yaml"))
|
|
14
|
+
return {};
|
|
15
|
+
let pubspecContent = "";
|
|
16
|
+
try {
|
|
17
|
+
pubspecContent = await fs.readFile(path.join(projectDir, "pubspec.yaml"), "utf-8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
const isFlutter = pubspecContent.includes("flutter:");
|
|
23
|
+
const languages = ["dart"];
|
|
24
|
+
const frameworks = [];
|
|
25
|
+
const packageManagers = [];
|
|
26
|
+
const testCommands = [];
|
|
27
|
+
const buildCommands = [];
|
|
28
|
+
const lintCommands = ["dart analyze"];
|
|
29
|
+
const blockedPaths = [".dart_tool/", "build/"];
|
|
30
|
+
const detectedFiles = ["pubspec.yaml"];
|
|
31
|
+
if (isFlutter) {
|
|
32
|
+
frameworks.push("flutter");
|
|
33
|
+
packageManagers.push("flutter");
|
|
34
|
+
testCommands.push("flutter test");
|
|
35
|
+
buildCommands.push("flutter build");
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
packageManagers.push("pub");
|
|
39
|
+
testCommands.push("dart test");
|
|
40
|
+
buildCommands.push("dart compile exe");
|
|
41
|
+
}
|
|
42
|
+
return { languages, frameworks, packageManagers, testCommands, buildCommands, lintCommands, blockedPaths, detectedFiles };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
export const dotnetDetector = {
|
|
3
|
+
name: "dotnet",
|
|
4
|
+
detect: async (projectDir) => {
|
|
5
|
+
let entries;
|
|
6
|
+
try {
|
|
7
|
+
entries = await fs.readdir(projectDir);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
const sorted = [...entries].sort();
|
|
13
|
+
const csprojFiles = sorted.filter((e) => e.endsWith(".csproj"));
|
|
14
|
+
const fsprojFiles = sorted.filter((e) => e.endsWith(".fsproj"));
|
|
15
|
+
const slnFiles = sorted.filter((e) => e.endsWith(".sln"));
|
|
16
|
+
if (!csprojFiles.length && !fsprojFiles.length && !slnFiles.length) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
const detectedFiles = [];
|
|
20
|
+
const languages = [];
|
|
21
|
+
if (csprojFiles.length > 0) {
|
|
22
|
+
detectedFiles.push(...csprojFiles);
|
|
23
|
+
languages.push("csharp");
|
|
24
|
+
}
|
|
25
|
+
if (fsprojFiles.length > 0) {
|
|
26
|
+
detectedFiles.push(...fsprojFiles);
|
|
27
|
+
languages.push("fsharp");
|
|
28
|
+
}
|
|
29
|
+
if (slnFiles.length > 0) {
|
|
30
|
+
detectedFiles.push(...slnFiles);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...(languages.length > 0 ? { languages } : {}),
|
|
34
|
+
frameworks: ["dotnet"],
|
|
35
|
+
packageManagers: ["nuget"],
|
|
36
|
+
buildCommands: ["dotnet build"],
|
|
37
|
+
testCommands: ["dotnet test"],
|
|
38
|
+
lintCommands: ["dotnet format"],
|
|
39
|
+
blockedPaths: ["bin/", "obj/"],
|
|
40
|
+
detectedFiles,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export const elixirDetector = {
|
|
4
|
+
name: "elixir",
|
|
5
|
+
detect: async (projectDir) => {
|
|
6
|
+
try {
|
|
7
|
+
await fs.access(path.join(projectDir, "mix.exs"));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
languages: ["elixir"],
|
|
14
|
+
packageManagers: ["mix"],
|
|
15
|
+
testCommands: ["mix test"],
|
|
16
|
+
lintCommands: ["mix credo"],
|
|
17
|
+
buildCommands: ["mix compile"],
|
|
18
|
+
blockedPaths: ["_build/", "deps/"],
|
|
19
|
+
detectedFiles: ["mix.exs"],
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export const goDetector = {
|
|
4
|
+
name: "go",
|
|
5
|
+
detect: async (projectDir) => {
|
|
6
|
+
try {
|
|
7
|
+
await fs.access(path.join(projectDir, "go.mod"));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
languages: ["go"],
|
|
14
|
+
packageManagers: ["go modules"],
|
|
15
|
+
testCommands: ["go test ./..."],
|
|
16
|
+
lintCommands: ["golangci-lint run"],
|
|
17
|
+
buildCommands: ["go build ./..."],
|
|
18
|
+
blockedPaths: ["vendor/"],
|
|
19
|
+
detectedFiles: ["go.mod"],
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { nodeDetector } from "./node.js";
|
|
2
|
+
import { pythonDetector } from "./python.js";
|
|
3
|
+
import { swiftDetector } from "./swift.js";
|
|
4
|
+
import { goDetector } from "./go.js";
|
|
5
|
+
import { rustDetector } from "./rust.js";
|
|
6
|
+
import { javaDetector } from "./java.js";
|
|
7
|
+
import { cppDetector } from "./cpp.js";
|
|
8
|
+
import { dotnetDetector } from "./dotnet.js";
|
|
9
|
+
import { phpDetector } from "./php.js";
|
|
10
|
+
import { rubyDetector } from "./ruby.js";
|
|
11
|
+
import { dartDetector } from "./dart.js";
|
|
12
|
+
import { elixirDetector } from "./elixir.js";
|
|
13
|
+
import { scalaDetector } from "./scala.js";
|
|
14
|
+
import { zigDetector } from "./zig.js";
|
|
15
|
+
export const allDetectors = [
|
|
16
|
+
nodeDetector,
|
|
17
|
+
pythonDetector,
|
|
18
|
+
swiftDetector,
|
|
19
|
+
goDetector,
|
|
20
|
+
rustDetector,
|
|
21
|
+
javaDetector,
|
|
22
|
+
cppDetector,
|
|
23
|
+
dotnetDetector,
|
|
24
|
+
phpDetector,
|
|
25
|
+
rubyDetector,
|
|
26
|
+
dartDetector,
|
|
27
|
+
elixirDetector,
|
|
28
|
+
scalaDetector,
|
|
29
|
+
zigDetector,
|
|
30
|
+
];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
async function fileExists(filePath) {
|
|
4
|
+
try {
|
|
5
|
+
await fs.access(filePath);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const javaDetector = {
|
|
13
|
+
name: "java",
|
|
14
|
+
detect: async (projectDir) => {
|
|
15
|
+
const pomPath = path.join(projectDir, "pom.xml");
|
|
16
|
+
const gradlePath = path.join(projectDir, "build.gradle");
|
|
17
|
+
const gradleKtsPath = path.join(projectDir, "build.gradle.kts");
|
|
18
|
+
const gradlewPath = path.join(projectDir, "gradlew");
|
|
19
|
+
const [hasPom, hasGradle, hasGradleKts, hasGradlew] = await Promise.all([
|
|
20
|
+
fileExists(pomPath),
|
|
21
|
+
fileExists(gradlePath),
|
|
22
|
+
fileExists(gradleKtsPath),
|
|
23
|
+
fileExists(gradlewPath),
|
|
24
|
+
]);
|
|
25
|
+
if (hasPom) {
|
|
26
|
+
return {
|
|
27
|
+
languages: ["java"],
|
|
28
|
+
packageManagers: ["maven"],
|
|
29
|
+
testCommands: ["mvn test"],
|
|
30
|
+
buildCommands: ["mvn compile"],
|
|
31
|
+
blockedPaths: ["target/"],
|
|
32
|
+
detectedFiles: ["pom.xml"],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const gradleCmd = hasGradlew ? "./gradlew" : "gradle";
|
|
36
|
+
const gradlewFiles = hasGradlew ? ["gradlew"] : [];
|
|
37
|
+
if (hasGradleKts) {
|
|
38
|
+
return {
|
|
39
|
+
languages: ["java", "kotlin"],
|
|
40
|
+
packageManagers: ["gradle"],
|
|
41
|
+
testCommands: [`${gradleCmd} test`],
|
|
42
|
+
buildCommands: [`${gradleCmd} build`],
|
|
43
|
+
blockedPaths: ["build/", ".gradle/"],
|
|
44
|
+
detectedFiles: ["build.gradle.kts", ...gradlewFiles],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (hasGradle) {
|
|
48
|
+
return {
|
|
49
|
+
languages: ["java"],
|
|
50
|
+
packageManagers: ["gradle"],
|
|
51
|
+
testCommands: [`${gradleCmd} test`],
|
|
52
|
+
buildCommands: [`${gradleCmd} build`],
|
|
53
|
+
blockedPaths: ["build/", ".gradle/"],
|
|
54
|
+
detectedFiles: ["build.gradle", ...gradlewFiles],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
},
|
|
59
|
+
};
|