@krr2020/taskflow-core 0.1.0-beta.3 → 0.1.0-beta.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 +1 -1
- package/dist/cli/index.js +42 -4
- package/dist/commands/base.d.ts +41 -0
- package/dist/commands/base.js +141 -0
- package/dist/commands/configure.d.ts +29 -0
- package/dist/commands/configure.js +187 -0
- package/dist/commands/init.js +21 -7
- package/dist/commands/prd/create.d.ts +1 -1
- package/dist/commands/prd/create.js +29 -11
- package/dist/commands/prd/generate-arch.d.ts +1 -1
- package/dist/commands/prd/generate-arch.js +6 -5
- package/dist/commands/retro/list.js +6 -5
- package/dist/commands/tasks/generate.d.ts +1 -1
- package/dist/commands/tasks/generate.js +83 -56
- package/dist/commands/upgrade.js +49 -16
- package/dist/commands/workflow/check.d.ts +17 -0
- package/dist/commands/workflow/check.js +482 -35
- package/dist/commands/workflow/commit.js +117 -60
- package/dist/commands/workflow/do.d.ts +1 -0
- package/dist/commands/workflow/do.js +206 -13
- package/dist/commands/workflow/next.js +4 -4
- package/dist/commands/workflow/resume.js +9 -6
- package/dist/commands/workflow/start.js +11 -11
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/lib/config-paths.d.ts +15 -15
- package/dist/lib/config-paths.js +20 -15
- package/dist/lib/file-validator.d.ts +119 -0
- package/dist/lib/file-validator.js +291 -0
- package/dist/lib/git.js +4 -2
- package/dist/lib/log-parser.d.ts +91 -0
- package/dist/lib/log-parser.js +178 -0
- package/dist/lib/retrospective.d.ts +27 -0
- package/dist/lib/retrospective.js +111 -1
- package/dist/lib/types.d.ts +19 -6
- package/dist/lib/types.js +20 -1
- package/dist/lib/validation.d.ts +0 -3
- package/dist/lib/validation.js +1 -15
- package/dist/llm/base.d.ts +52 -0
- package/dist/llm/base.js +35 -0
- package/dist/llm/factory.d.ts +39 -0
- package/dist/llm/factory.js +102 -0
- package/dist/llm/index.d.ts +7 -0
- package/dist/llm/index.js +7 -0
- package/dist/llm/model-selector.d.ts +71 -0
- package/dist/llm/model-selector.js +139 -0
- package/dist/llm/providers/anthropic.d.ts +31 -0
- package/dist/llm/providers/anthropic.js +116 -0
- package/dist/llm/providers/index.d.ts +6 -0
- package/dist/llm/providers/index.js +6 -0
- package/dist/llm/providers/ollama.d.ts +28 -0
- package/dist/llm/providers/ollama.js +91 -0
- package/dist/llm/providers/openai-compatible.d.ts +30 -0
- package/dist/llm/providers/openai-compatible.js +93 -0
- package/dist/schemas/config.d.ts +82 -0
- package/dist/schemas/config.js +35 -0
- package/dist/schemas/task.d.ts +2 -2
- package/dist/state-machine.d.ts +12 -0
- package/dist/state-machine.js +2 -2
- package/package.json +1 -1
- package/dist/lib/package-manager.d.ts +0 -17
- package/dist/lib/package-manager.js +0 -53
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible LLM Provider
|
|
3
|
+
* Supports: OpenAI, Azure OpenAI, Together AI, Groq, DeepSeek, and any OpenAI-compatible API
|
|
4
|
+
*/
|
|
5
|
+
import { LLMProvider, LLMProviderType, } from "../base.js";
|
|
6
|
+
export class OpenAICompatibleProvider extends LLMProvider {
|
|
7
|
+
config;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
super(LLMProviderType.OpenAICompatible, config.model);
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate text using OpenAI-compatible API
|
|
14
|
+
*/
|
|
15
|
+
async generate(messages, options) {
|
|
16
|
+
if (!this.isConfigured()) {
|
|
17
|
+
throw new Error("OpenAI-compatible provider is not configured properly");
|
|
18
|
+
}
|
|
19
|
+
const response = await fetch(`${this.config.baseUrl}/chat/completions`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
model: this.config.model,
|
|
27
|
+
messages,
|
|
28
|
+
max_tokens: options?.maxTokens,
|
|
29
|
+
temperature: options?.temperature,
|
|
30
|
+
top_p: options?.topP,
|
|
31
|
+
stream: false,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const error = await response.text();
|
|
36
|
+
throw new Error(`OpenAI-compatible API error: ${response.status} - ${error}`);
|
|
37
|
+
}
|
|
38
|
+
const data = (await response.json());
|
|
39
|
+
const choice = data.choices[0];
|
|
40
|
+
if (!choice) {
|
|
41
|
+
throw new Error("No choice returned from OpenAI API");
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
content: choice.message.content,
|
|
45
|
+
model: data.model,
|
|
46
|
+
tokensUsed: data.usage?.total_tokens ?? 0,
|
|
47
|
+
finishReason: choice.finish_reason,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if provider is configured
|
|
52
|
+
*/
|
|
53
|
+
isConfigured() {
|
|
54
|
+
return !!(this.config.apiKey && this.config.baseUrl);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create provider from environment variables
|
|
58
|
+
*/
|
|
59
|
+
static fromEnv(config) {
|
|
60
|
+
const baseUrl = config.baseUrl ||
|
|
61
|
+
process.env.OPENAI_BASE_URL ||
|
|
62
|
+
process.env.AI_BASE_URL ||
|
|
63
|
+
"https://api.openai.com/v1";
|
|
64
|
+
const apiKey = config.apiKey ||
|
|
65
|
+
process.env.OPENAI_API_KEY ||
|
|
66
|
+
process.env.AI_API_KEY ||
|
|
67
|
+
"";
|
|
68
|
+
const model = config.model || process.env.AI_MODEL || "gpt-4o-mini";
|
|
69
|
+
if (!apiKey) {
|
|
70
|
+
console.warn("Warning: OpenAI-compatible provider missing API key");
|
|
71
|
+
}
|
|
72
|
+
return new OpenAICompatibleProvider({
|
|
73
|
+
baseUrl,
|
|
74
|
+
apiKey: expandEnvVar(apiKey),
|
|
75
|
+
model,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Expand environment variable in string (e.g., "${VAR_NAME}" -> actual value)
|
|
81
|
+
*/
|
|
82
|
+
function expandEnvVar(value) {
|
|
83
|
+
if (!value) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
const envVarMatch = value.match(/^\$\{([^}]+)\}$/);
|
|
87
|
+
if (envVarMatch?.[1]) {
|
|
88
|
+
const envVar = envVarMatch[1];
|
|
89
|
+
const envValue = process.env[envVar];
|
|
90
|
+
return envValue ?? value;
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
package/dist/schemas/config.d.ts
CHANGED
|
@@ -14,6 +14,48 @@ export declare const GatesSchema: z.ZodObject<{
|
|
|
14
14
|
requirePlanApproval: z.ZodDefault<z.ZodBoolean>;
|
|
15
15
|
requireTestPass: z.ZodDefault<z.ZodBoolean>;
|
|
16
16
|
}, z.core.$strip>;
|
|
17
|
+
declare const LLMProviderTypeSchema: z.ZodEnum<{
|
|
18
|
+
"openai-compatible": "openai-compatible";
|
|
19
|
+
anthropic: "anthropic";
|
|
20
|
+
ollama: "ollama";
|
|
21
|
+
}>;
|
|
22
|
+
declare const AIConfigSchema: z.ZodOptional<z.ZodObject<{
|
|
23
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
provider: z.ZodDefault<z.ZodEnum<{
|
|
25
|
+
"openai-compatible": "openai-compatible";
|
|
26
|
+
anthropic: "anthropic";
|
|
27
|
+
ollama: "ollama";
|
|
28
|
+
}>>;
|
|
29
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
30
|
+
models: z.ZodDefault<z.ZodObject<{
|
|
31
|
+
default: z.ZodDefault<z.ZodString>;
|
|
32
|
+
planning: z.ZodOptional<z.ZodString>;
|
|
33
|
+
execution: z.ZodOptional<z.ZodString>;
|
|
34
|
+
analysis: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>>;
|
|
36
|
+
planningProvider: z.ZodOptional<z.ZodEnum<{
|
|
37
|
+
"openai-compatible": "openai-compatible";
|
|
38
|
+
anthropic: "anthropic";
|
|
39
|
+
ollama: "ollama";
|
|
40
|
+
}>>;
|
|
41
|
+
planningApiKey: z.ZodOptional<z.ZodString>;
|
|
42
|
+
executionProvider: z.ZodOptional<z.ZodEnum<{
|
|
43
|
+
"openai-compatible": "openai-compatible";
|
|
44
|
+
anthropic: "anthropic";
|
|
45
|
+
ollama: "ollama";
|
|
46
|
+
}>>;
|
|
47
|
+
executionApiKey: z.ZodOptional<z.ZodString>;
|
|
48
|
+
analysisProvider: z.ZodOptional<z.ZodEnum<{
|
|
49
|
+
"openai-compatible": "openai-compatible";
|
|
50
|
+
anthropic: "anthropic";
|
|
51
|
+
ollama: "ollama";
|
|
52
|
+
}>>;
|
|
53
|
+
analysisApiKey: z.ZodOptional<z.ZodString>;
|
|
54
|
+
ollamaBaseUrl: z.ZodDefault<z.ZodString>;
|
|
55
|
+
openaiBaseUrl: z.ZodDefault<z.ZodString>;
|
|
56
|
+
autoContinueTask: z.ZodDefault<z.ZodBoolean>;
|
|
57
|
+
clearContextOnComplete: z.ZodDefault<z.ZodBoolean>;
|
|
58
|
+
}, z.core.$strip>>;
|
|
17
59
|
export declare const TaskflowConfigSchema: z.ZodObject<{
|
|
18
60
|
version: z.ZodDefault<z.ZodString>;
|
|
19
61
|
projectType: z.ZodDefault<z.ZodString>;
|
|
@@ -36,5 +78,45 @@ export declare const TaskflowConfigSchema: z.ZodObject<{
|
|
|
36
78
|
validate: z.ZodOptional<z.ZodString>;
|
|
37
79
|
test: z.ZodOptional<z.ZodString>;
|
|
38
80
|
}, z.core.$strip>>;
|
|
81
|
+
ai: z.ZodOptional<z.ZodObject<{
|
|
82
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
83
|
+
provider: z.ZodDefault<z.ZodEnum<{
|
|
84
|
+
"openai-compatible": "openai-compatible";
|
|
85
|
+
anthropic: "anthropic";
|
|
86
|
+
ollama: "ollama";
|
|
87
|
+
}>>;
|
|
88
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
89
|
+
models: z.ZodDefault<z.ZodObject<{
|
|
90
|
+
default: z.ZodDefault<z.ZodString>;
|
|
91
|
+
planning: z.ZodOptional<z.ZodString>;
|
|
92
|
+
execution: z.ZodOptional<z.ZodString>;
|
|
93
|
+
analysis: z.ZodOptional<z.ZodString>;
|
|
94
|
+
}, z.core.$strip>>;
|
|
95
|
+
planningProvider: z.ZodOptional<z.ZodEnum<{
|
|
96
|
+
"openai-compatible": "openai-compatible";
|
|
97
|
+
anthropic: "anthropic";
|
|
98
|
+
ollama: "ollama";
|
|
99
|
+
}>>;
|
|
100
|
+
planningApiKey: z.ZodOptional<z.ZodString>;
|
|
101
|
+
executionProvider: z.ZodOptional<z.ZodEnum<{
|
|
102
|
+
"openai-compatible": "openai-compatible";
|
|
103
|
+
anthropic: "anthropic";
|
|
104
|
+
ollama: "ollama";
|
|
105
|
+
}>>;
|
|
106
|
+
executionApiKey: z.ZodOptional<z.ZodString>;
|
|
107
|
+
analysisProvider: z.ZodOptional<z.ZodEnum<{
|
|
108
|
+
"openai-compatible": "openai-compatible";
|
|
109
|
+
anthropic: "anthropic";
|
|
110
|
+
ollama: "ollama";
|
|
111
|
+
}>>;
|
|
112
|
+
analysisApiKey: z.ZodOptional<z.ZodString>;
|
|
113
|
+
ollamaBaseUrl: z.ZodDefault<z.ZodString>;
|
|
114
|
+
openaiBaseUrl: z.ZodDefault<z.ZodString>;
|
|
115
|
+
autoContinueTask: z.ZodDefault<z.ZodBoolean>;
|
|
116
|
+
clearContextOnComplete: z.ZodDefault<z.ZodBoolean>;
|
|
117
|
+
}, z.core.$strip>>;
|
|
39
118
|
}, z.core.$strip>;
|
|
40
119
|
export type TaskflowConfig = z.infer<typeof TaskflowConfigSchema>;
|
|
120
|
+
export type AIConfig = z.infer<typeof AIConfigSchema>;
|
|
121
|
+
export type LLMProviderType = z.infer<typeof LLMProviderTypeSchema>;
|
|
122
|
+
export {};
|
package/dist/schemas/config.js
CHANGED
|
@@ -12,6 +12,40 @@ export const GatesSchema = z.object({
|
|
|
12
12
|
requirePlanApproval: z.boolean().default(true),
|
|
13
13
|
requireTestPass: z.boolean().default(true),
|
|
14
14
|
});
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// AI Configuration Schema
|
|
17
|
+
// ============================================================================
|
|
18
|
+
const LLMProviderTypeSchema = z.enum([
|
|
19
|
+
"openai-compatible",
|
|
20
|
+
"anthropic",
|
|
21
|
+
"ollama",
|
|
22
|
+
]);
|
|
23
|
+
const AIModelsSchema = z.object({
|
|
24
|
+
default: z.string().default("gpt-4o-mini"),
|
|
25
|
+
planning: z.string().optional(),
|
|
26
|
+
execution: z.string().optional(),
|
|
27
|
+
analysis: z.string().optional(),
|
|
28
|
+
});
|
|
29
|
+
const AIConfigSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
enabled: z.boolean().default(false),
|
|
32
|
+
provider: LLMProviderTypeSchema.default("openai-compatible"),
|
|
33
|
+
apiKey: z.string().optional(),
|
|
34
|
+
models: AIModelsSchema.default({
|
|
35
|
+
default: "gpt-4o-mini",
|
|
36
|
+
}),
|
|
37
|
+
planningProvider: LLMProviderTypeSchema.optional(),
|
|
38
|
+
planningApiKey: z.string().optional(),
|
|
39
|
+
executionProvider: LLMProviderTypeSchema.optional(),
|
|
40
|
+
executionApiKey: z.string().optional(),
|
|
41
|
+
analysisProvider: LLMProviderTypeSchema.optional(),
|
|
42
|
+
analysisApiKey: z.string().optional(),
|
|
43
|
+
ollamaBaseUrl: z.string().default("http://localhost:11434"),
|
|
44
|
+
openaiBaseUrl: z.string().default("https://api.openai.com/v1"),
|
|
45
|
+
autoContinueTask: z.boolean().default(false),
|
|
46
|
+
clearContextOnComplete: z.boolean().default(true),
|
|
47
|
+
})
|
|
48
|
+
.optional();
|
|
15
49
|
export const TaskflowConfigSchema = z.object({
|
|
16
50
|
version: z.string().default("2.0"),
|
|
17
51
|
projectType: z.string().default("custom"),
|
|
@@ -31,4 +65,5 @@ export const TaskflowConfigSchema = z.object({
|
|
|
31
65
|
test: z.string().optional(),
|
|
32
66
|
})
|
|
33
67
|
.optional(),
|
|
68
|
+
ai: AIConfigSchema,
|
|
34
69
|
});
|
package/dist/schemas/task.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const TaskStatusSchema: z.ZodEnum<{
|
|
3
|
+
planning: "planning";
|
|
3
4
|
implementing: "implementing";
|
|
4
5
|
verifying: "verifying";
|
|
5
6
|
completed: "completed";
|
|
6
7
|
blocked: "blocked";
|
|
7
8
|
todo: "todo";
|
|
8
9
|
in_progress: "in_progress";
|
|
9
|
-
planning: "planning";
|
|
10
10
|
}>;
|
|
11
11
|
export declare const SubtaskSchema: z.ZodObject<{
|
|
12
12
|
id: z.ZodString;
|
|
@@ -21,13 +21,13 @@ export declare const TaskSchema: z.ZodObject<{
|
|
|
21
21
|
title: z.ZodString;
|
|
22
22
|
description: z.ZodString;
|
|
23
23
|
status: z.ZodDefault<z.ZodEnum<{
|
|
24
|
+
planning: "planning";
|
|
24
25
|
implementing: "implementing";
|
|
25
26
|
verifying: "verifying";
|
|
26
27
|
completed: "completed";
|
|
27
28
|
blocked: "blocked";
|
|
28
29
|
todo: "todo";
|
|
29
30
|
in_progress: "in_progress";
|
|
30
|
-
planning: "planning";
|
|
31
31
|
}>>;
|
|
32
32
|
type: z.ZodDefault<z.ZodEnum<{
|
|
33
33
|
feat: "feat";
|
package/dist/state-machine.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow State Machine
|
|
3
|
+
*
|
|
4
|
+
* This class provides an in-memory state machine for managing task workflow.
|
|
5
|
+
* It operates at a higher level of abstraction than task files - it manages
|
|
6
|
+
* the developer's session state rather than persistent task state.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This is currently NOT used by the CLI commands. CLI commands
|
|
9
|
+
* (StartCommand, CheckCommand, etc.) use task files directly via the
|
|
10
|
+
* data-access module. This class can be used as an alternative
|
|
11
|
+
* programmatic API or for future session management features.
|
|
12
|
+
*/
|
|
1
13
|
import type { ConfigLoader } from "./config.js";
|
|
2
14
|
import type { GitManager } from "./git.js";
|
|
3
15
|
export type MachineState = "IDLE" | "PLANNING" | "EXECUTION" | "VERIFICATION" | "COMPLETED";
|
package/dist/state-machine.js
CHANGED
|
@@ -52,7 +52,7 @@ export class StateMachine {
|
|
|
52
52
|
// In a real impl, we would find the task file.
|
|
53
53
|
this.activeTaskId = taskId;
|
|
54
54
|
this.currentState = "PLANNING";
|
|
55
|
-
//
|
|
55
|
+
// FUTURE: Return initial context (Rules, Retro) when this is actively used
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* Transition: PLANNING -> EXECUTION
|
|
@@ -82,7 +82,7 @@ export class StateMachine {
|
|
|
82
82
|
if (this.currentState !== "VERIFICATION") {
|
|
83
83
|
throw new Error("Cannot complete task: Not in VERIFICATION state.");
|
|
84
84
|
}
|
|
85
|
-
//
|
|
85
|
+
// FUTURE: Verify checks passed? For now, rely on caller to ensure this
|
|
86
86
|
this.currentState = "COMPLETED";
|
|
87
87
|
this.activeTaskId = null;
|
|
88
88
|
// Auto-transition back to IDLE? Or stay in COMPLETED until explicit 'next'?
|
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
|
2
|
-
export interface PackageManagerCommands {
|
|
3
|
-
run: string;
|
|
4
|
-
exec: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Detect the package manager used in the project
|
|
8
|
-
*/
|
|
9
|
-
export declare function detectPackageManager(projectRoot: string): PackageManager;
|
|
10
|
-
/**
|
|
11
|
-
* Get the run command prefix for the detected package manager
|
|
12
|
-
*/
|
|
13
|
-
export declare function getRunCommand(projectRoot: string): string;
|
|
14
|
-
/**
|
|
15
|
-
* Get the exec command prefix for the detected package manager
|
|
16
|
-
*/
|
|
17
|
-
export declare function getExecCommand(projectRoot: string): string;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
const COMMANDS = {
|
|
4
|
-
npm: {
|
|
5
|
-
run: "npm run",
|
|
6
|
-
exec: "npx",
|
|
7
|
-
},
|
|
8
|
-
pnpm: {
|
|
9
|
-
run: "pnpm run",
|
|
10
|
-
exec: "pnpm exec",
|
|
11
|
-
},
|
|
12
|
-
yarn: {
|
|
13
|
-
run: "yarn run",
|
|
14
|
-
exec: "yarn run",
|
|
15
|
-
},
|
|
16
|
-
bun: {
|
|
17
|
-
run: "bun run",
|
|
18
|
-
exec: "bun x",
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Detect the package manager used in the project
|
|
23
|
-
*/
|
|
24
|
-
export function detectPackageManager(projectRoot) {
|
|
25
|
-
if (fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) {
|
|
26
|
-
return "pnpm";
|
|
27
|
-
}
|
|
28
|
-
if (fs.existsSync(path.join(projectRoot, "yarn.lock"))) {
|
|
29
|
-
return "yarn";
|
|
30
|
-
}
|
|
31
|
-
if (fs.existsSync(path.join(projectRoot, "bun.lockb"))) {
|
|
32
|
-
return "bun";
|
|
33
|
-
}
|
|
34
|
-
if (fs.existsSync(path.join(projectRoot, "package-lock.json"))) {
|
|
35
|
-
return "npm";
|
|
36
|
-
}
|
|
37
|
-
// Default to npm if no lock file found
|
|
38
|
-
return "npm";
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Get the run command prefix for the detected package manager
|
|
42
|
-
*/
|
|
43
|
-
export function getRunCommand(projectRoot) {
|
|
44
|
-
const pm = detectPackageManager(projectRoot);
|
|
45
|
-
return COMMANDS[pm].run;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Get the exec command prefix for the detected package manager
|
|
49
|
-
*/
|
|
50
|
-
export function getExecCommand(projectRoot) {
|
|
51
|
-
const pm = detectPackageManager(projectRoot);
|
|
52
|
-
return COMMANDS[pm].exec;
|
|
53
|
-
}
|