@sweny-ai/core 0.1.63 → 0.1.64
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/cli/main.js +4 -4
- package/dist/cli/marketplace.d.ts +103 -0
- package/dist/cli/marketplace.js +290 -0
- package/dist/cli/marketplace.test.d.ts +1 -0
- package/dist/cli/marketplace.test.js +355 -0
- package/dist/cli/new.d.ts +35 -2
- package/dist/cli/new.js +156 -51
- package/dist/cli/new.test.js +215 -2
- package/dist/executor.js +1 -1
- package/dist/source-resolver.d.ts +22 -0
- package/dist/source-resolver.js +174 -0
- package/dist/sources.d.ts +9 -6
- package/dist/sources.js +13 -146
- package/dist/sources.test.js +2 -1
- package/dist/sources.validation.test.js +2 -1
- package/dist/templates.js +2 -1
- package/package.json +1 -1
package/dist/cli/main.js
CHANGED
|
@@ -60,10 +60,10 @@ const program = new Command()
|
|
|
60
60
|
.version(version);
|
|
61
61
|
// ── sweny new ─────────────────────────────────────────────────────────
|
|
62
62
|
program
|
|
63
|
-
.command("new")
|
|
64
|
-
.description("Create a new workflow
|
|
65
|
-
.action(async () => {
|
|
66
|
-
await runNew();
|
|
63
|
+
.command("new [id]")
|
|
64
|
+
.description("Create a new workflow. With no id, opens the interactive picker. With an id, installs that workflow from the marketplace (swenyai/workflows).")
|
|
65
|
+
.action(async (id) => {
|
|
66
|
+
await runNew(id ? { marketplaceId: id } : undefined);
|
|
67
67
|
});
|
|
68
68
|
// ── sweny e2e ────────────────────────────────────────────────────────
|
|
69
69
|
const e2eCmd = program.command("e2e").description("End-to-end browser testing");
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace install — fetch workflows from swenyai/workflows and
|
|
3
|
+
* adapt them to the user's .sweny.yml providers.
|
|
4
|
+
*
|
|
5
|
+
* Pure functions for fetch/mismatch/adapt; file writes delegate to
|
|
6
|
+
* helpers in ./new.ts.
|
|
7
|
+
*/
|
|
8
|
+
import type { Skill, SkillCategory, Workflow, Claude, Logger } from "../types.js";
|
|
9
|
+
import type { FileConfig } from "./config-file.js";
|
|
10
|
+
export declare const MARKETPLACE_REPO = "swenyai/workflows";
|
|
11
|
+
export declare const MARKETPLACE_RAW_BASE = "https://raw.githubusercontent.com/swenyai/workflows/main";
|
|
12
|
+
export interface MarketplaceEntry {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
skills: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface FetchError extends Error {
|
|
19
|
+
kind: "not-found" | "rate-limit" | "network" | "bad-yaml" | "unknown";
|
|
20
|
+
retryAfter?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface FetchedWorkflow {
|
|
23
|
+
id: string;
|
|
24
|
+
yaml: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function fetchMarketplaceWorkflow(id: string): Promise<FetchedWorkflow>;
|
|
27
|
+
export declare function fetchMarketplaceIndex(): Promise<MarketplaceEntry[]>;
|
|
28
|
+
export interface ProviderMismatch {
|
|
29
|
+
category: SkillCategory;
|
|
30
|
+
configKey: string;
|
|
31
|
+
workflowSkill: string;
|
|
32
|
+
userProvider: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function computeProviderMismatch(workflowSkills: string[], fileConfig: FileConfig, availableSkills: Skill[]): ProviderMismatch[];
|
|
35
|
+
export declare function buildAdaptPrompt(mismatches: ProviderMismatch[]): string;
|
|
36
|
+
export interface AdaptOptions {
|
|
37
|
+
claude: Claude;
|
|
38
|
+
skills: Skill[];
|
|
39
|
+
logger: Logger;
|
|
40
|
+
/** Optional pre-filled first refinement (e.g. buildAdaptPrompt output). */
|
|
41
|
+
initialRefinement?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Render the workflow DAG and run an accept/refine/cancel loop.
|
|
45
|
+
* Returns the accepted workflow or null on cancel.
|
|
46
|
+
*
|
|
47
|
+
* Applies `initialRefinement` (if provided) before the first render.
|
|
48
|
+
*/
|
|
49
|
+
export declare function adaptWorkflowInteractive(workflow: Workflow, options: AdaptOptions): Promise<Workflow | null>;
|
|
50
|
+
export interface InstallOptions {
|
|
51
|
+
cwd: string;
|
|
52
|
+
availableSkills: Skill[];
|
|
53
|
+
/** Claude client for optional LLM adaptation. If null, we install as-is with a warning on mismatch. */
|
|
54
|
+
claude: Claude | null;
|
|
55
|
+
logger: Logger;
|
|
56
|
+
/**
|
|
57
|
+
* Pre-computed source-control provider (e.g. from git-remote detection).
|
|
58
|
+
* When provided, takes precedence over the skill-based fallback inference.
|
|
59
|
+
*/
|
|
60
|
+
inferredSourceControl?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Pre-computed issue-tracker provider.
|
|
63
|
+
* When provided, takes precedence over the skill-based fallback inference.
|
|
64
|
+
*/
|
|
65
|
+
inferredIssueTracker?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Pre-computed observability provider (or null if none).
|
|
68
|
+
* When provided, takes precedence over the skill-based fallback inference.
|
|
69
|
+
*/
|
|
70
|
+
inferredObservability?: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* Whether to overwrite an existing workflow file.
|
|
73
|
+
* Defaults to false. When false and the file already exists,
|
|
74
|
+
* `installMarketplaceWorkflow` returns `{ installed: false, alreadyExists: true }`.
|
|
75
|
+
*/
|
|
76
|
+
overwrite?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export interface InstallResult {
|
|
79
|
+
installed: boolean;
|
|
80
|
+
adapted: boolean;
|
|
81
|
+
workflowPath: string;
|
|
82
|
+
mismatches: ProviderMismatch[];
|
|
83
|
+
addedEnvKeys: number;
|
|
84
|
+
/**
|
|
85
|
+
* True when `overwrite` was false and a workflow file already existed at the
|
|
86
|
+
* target path. The caller is responsible for prompting the user if desired.
|
|
87
|
+
*/
|
|
88
|
+
alreadyExists?: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Fetch a workflow from swenyai/workflows, adapt if provider mismatch is detected
|
|
92
|
+
* AND an agent is available, then write files idempotently.
|
|
93
|
+
*
|
|
94
|
+
* When `overwrite` is false (default) and the workflow file already exists,
|
|
95
|
+
* returns `{ installed: false, alreadyExists: true, workflowPath }` without
|
|
96
|
+
* touching the file. The caller (runNew) is responsible for prompting the user.
|
|
97
|
+
*
|
|
98
|
+
* Pre-computed provider fields (`inferredSourceControl`, `inferredIssueTracker`,
|
|
99
|
+
* `inferredObservability`) take precedence over skill-based inference when
|
|
100
|
+
* provided. Omit them to use the original skill-based fallback (backward-compat
|
|
101
|
+
* for direct callers that don't have git-remote information).
|
|
102
|
+
*/
|
|
103
|
+
export declare function installMarketplaceWorkflow(id: string, options: InstallOptions): Promise<InstallResult>;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// packages/core/src/cli/marketplace.ts
|
|
2
|
+
/**
|
|
3
|
+
* Marketplace install — fetch workflows from swenyai/workflows and
|
|
4
|
+
* adapt them to the user's .sweny.yml providers.
|
|
5
|
+
*
|
|
6
|
+
* Pure functions for fetch/mismatch/adapt; file writes delegate to
|
|
7
|
+
* helpers in ./new.ts.
|
|
8
|
+
*/
|
|
9
|
+
import * as p from "@clack/prompts";
|
|
10
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
11
|
+
import { refineWorkflow } from "../workflow-builder.js";
|
|
12
|
+
import { DagRenderer } from "./renderer.js";
|
|
13
|
+
import { workflowZ, validateWorkflow } from "../schema.js";
|
|
14
|
+
import { loadConfigFile } from "./config-file.js";
|
|
15
|
+
import { extractSkillsFromYaml, collectCredentialsForSkills, writeSwenyYmlIfMissing, appendMissingEnvKeys, writeWorkflowFile, } from "./new.js";
|
|
16
|
+
export const MARKETPLACE_REPO = "swenyai/workflows";
|
|
17
|
+
export const MARKETPLACE_RAW_BASE = `https://raw.githubusercontent.com/${MARKETPLACE_REPO}/main`;
|
|
18
|
+
export async function fetchMarketplaceWorkflow(id) {
|
|
19
|
+
const url = `${MARKETPLACE_RAW_BASE}/workflows/${id}.yml`;
|
|
20
|
+
let res;
|
|
21
|
+
try {
|
|
22
|
+
res = await fetch(url);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
const err = new Error(`Could not reach github.com — check your connection`);
|
|
26
|
+
err.kind = "network";
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
if (res.status === 404) {
|
|
30
|
+
const err = new Error(`Workflow "${id}" not found in ${MARKETPLACE_REPO}. See https://marketplace.sweny.ai for available workflows.`);
|
|
31
|
+
err.kind = "not-found";
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
if (res.status === 403 && res.headers.get("X-RateLimit-Remaining") === "0") {
|
|
35
|
+
const reset = res.headers.get("X-RateLimit-Reset");
|
|
36
|
+
const err = new Error(`GitHub rate limit hit. Set GITHUB_TOKEN to raise the limit, or retry later.`);
|
|
37
|
+
err.kind = "rate-limit";
|
|
38
|
+
if (reset)
|
|
39
|
+
err.retryAfter = parseInt(reset, 10);
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const err = new Error(`Fetch failed with status ${res.status}`);
|
|
44
|
+
err.kind = "unknown";
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
return { id, yaml: await res.text() };
|
|
48
|
+
}
|
|
49
|
+
export async function fetchMarketplaceIndex() {
|
|
50
|
+
const url = `${MARKETPLACE_RAW_BASE}/index.json`;
|
|
51
|
+
let res;
|
|
52
|
+
try {
|
|
53
|
+
res = await fetch(url);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
const err = new Error(`Could not reach github.com`);
|
|
57
|
+
err.kind = "network";
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
if (res.status === 404) {
|
|
61
|
+
const err = new Error(`Marketplace index not found`);
|
|
62
|
+
err.kind = "not-found";
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const err = new Error(`Fetch failed with status ${res.status}`);
|
|
67
|
+
err.kind = "unknown";
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
const raw = (await res.json());
|
|
71
|
+
if (!Array.isArray(raw)) {
|
|
72
|
+
const err = new Error(`Marketplace index is not an array`);
|
|
73
|
+
err.kind = "bad-yaml";
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
return raw;
|
|
77
|
+
}
|
|
78
|
+
const CATEGORY_TO_CONFIG_KEY = {
|
|
79
|
+
git: "source-control-provider",
|
|
80
|
+
tasks: "issue-tracker-provider",
|
|
81
|
+
observability: "observability-provider",
|
|
82
|
+
notification: "notification-provider",
|
|
83
|
+
};
|
|
84
|
+
export function computeProviderMismatch(workflowSkills, fileConfig, availableSkills) {
|
|
85
|
+
const skillMap = new Map(availableSkills.map((s) => [s.id, s]));
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const skillId of workflowSkills) {
|
|
88
|
+
const skill = skillMap.get(skillId);
|
|
89
|
+
if (!skill)
|
|
90
|
+
continue;
|
|
91
|
+
const configKey = CATEGORY_TO_CONFIG_KEY[skill.category];
|
|
92
|
+
if (!configKey)
|
|
93
|
+
continue;
|
|
94
|
+
const userProvider = fileConfig[configKey];
|
|
95
|
+
if (typeof userProvider !== "string")
|
|
96
|
+
continue;
|
|
97
|
+
if (userProvider === skillId)
|
|
98
|
+
continue;
|
|
99
|
+
out.push({
|
|
100
|
+
category: skill.category,
|
|
101
|
+
configKey,
|
|
102
|
+
workflowSkill: skillId,
|
|
103
|
+
userProvider,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
export function buildAdaptPrompt(mismatches) {
|
|
109
|
+
const swaps = mismatches
|
|
110
|
+
.map((m) => `- Replace the \`${m.workflowSkill}\` skill (${m.configKey}) with \`${m.userProvider}\`, preserving the intent of each node.`)
|
|
111
|
+
.join("\n");
|
|
112
|
+
return [
|
|
113
|
+
`The target project's .sweny.yml declares different providers than this workflow uses.`,
|
|
114
|
+
`Rewrite the workflow so every node uses the target project's providers:`,
|
|
115
|
+
"",
|
|
116
|
+
swaps,
|
|
117
|
+
"",
|
|
118
|
+
`Keep node IDs, edge structure, and instruction intent. Only change skill references and any node instructions that name the old provider by name.`,
|
|
119
|
+
].join("\n");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Render the workflow DAG and run an accept/refine/cancel loop.
|
|
123
|
+
* Returns the accepted workflow or null on cancel.
|
|
124
|
+
*
|
|
125
|
+
* Applies `initialRefinement` (if provided) before the first render.
|
|
126
|
+
*/
|
|
127
|
+
export async function adaptWorkflowInteractive(workflow, options) {
|
|
128
|
+
const { claude, skills, logger, initialRefinement } = options;
|
|
129
|
+
let current = workflow;
|
|
130
|
+
if (initialRefinement) {
|
|
131
|
+
const spinner = p.spinner();
|
|
132
|
+
spinner.start("Adapting workflow to your project…");
|
|
133
|
+
try {
|
|
134
|
+
current = await refineWorkflow(current, initialRefinement, { claude, skills, logger });
|
|
135
|
+
spinner.stop("Adapted");
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
spinner.stop("Adaptation failed");
|
|
139
|
+
p.log.error(` ${err instanceof Error ? err.message : String(err)}`);
|
|
140
|
+
// fall through — user sees the un-adapted workflow
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
while (true) {
|
|
144
|
+
console.log("");
|
|
145
|
+
console.log(new DagRenderer(current, { animate: false }).renderToString());
|
|
146
|
+
console.log("");
|
|
147
|
+
const action = await p.select({
|
|
148
|
+
message: "Looks good?",
|
|
149
|
+
options: [
|
|
150
|
+
{ value: "accept", label: "Yes — use this workflow" },
|
|
151
|
+
{ value: "refine", label: "Refine — describe what to change" },
|
|
152
|
+
{ value: "cancel", label: "Cancel" },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
if (p.isCancel(action) || action === "cancel")
|
|
156
|
+
return null;
|
|
157
|
+
if (action === "accept")
|
|
158
|
+
return current;
|
|
159
|
+
if (action === "refine") {
|
|
160
|
+
const refinement = await p.text({
|
|
161
|
+
message: "What would you like to change?",
|
|
162
|
+
validate: (v) => (v && v.trim().length > 0 ? undefined : "Refinement is required"),
|
|
163
|
+
});
|
|
164
|
+
if (p.isCancel(refinement))
|
|
165
|
+
return null;
|
|
166
|
+
const rspin = p.spinner();
|
|
167
|
+
rspin.start("Refining…");
|
|
168
|
+
try {
|
|
169
|
+
current = await refineWorkflow(current, refinement, { claude, skills, logger });
|
|
170
|
+
rspin.stop("Refined");
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
rspin.stop("Failed");
|
|
174
|
+
p.log.error(` ${err instanceof Error ? err.message : String(err)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Fetch a workflow from swenyai/workflows, adapt if provider mismatch is detected
|
|
181
|
+
* AND an agent is available, then write files idempotently.
|
|
182
|
+
*
|
|
183
|
+
* When `overwrite` is false (default) and the workflow file already exists,
|
|
184
|
+
* returns `{ installed: false, alreadyExists: true, workflowPath }` without
|
|
185
|
+
* touching the file. The caller (runNew) is responsible for prompting the user.
|
|
186
|
+
*
|
|
187
|
+
* Pre-computed provider fields (`inferredSourceControl`, `inferredIssueTracker`,
|
|
188
|
+
* `inferredObservability`) take precedence over skill-based inference when
|
|
189
|
+
* provided. Omit them to use the original skill-based fallback (backward-compat
|
|
190
|
+
* for direct callers that don't have git-remote information).
|
|
191
|
+
*/
|
|
192
|
+
export async function installMarketplaceWorkflow(id, options) {
|
|
193
|
+
const { cwd, availableSkills, claude, logger } = options;
|
|
194
|
+
const overwrite = options.overwrite ?? false;
|
|
195
|
+
// 1. Fetch
|
|
196
|
+
const fetched = await fetchMarketplaceWorkflow(id);
|
|
197
|
+
// 2. Load existing config to detect mismatches
|
|
198
|
+
const fileConfig = loadConfigFile(cwd);
|
|
199
|
+
const workflowSkills = extractSkillsFromYaml(fetched.yaml);
|
|
200
|
+
const mismatches = computeProviderMismatch(workflowSkills, fileConfig, availableSkills);
|
|
201
|
+
// 3. Adapt if needed + possible
|
|
202
|
+
let finalYaml = fetched.yaml;
|
|
203
|
+
let adapted = false;
|
|
204
|
+
if (mismatches.length > 0 && claude) {
|
|
205
|
+
try {
|
|
206
|
+
const parsed = workflowZ.parse(parseYaml(fetched.yaml));
|
|
207
|
+
const errs = validateWorkflow(parsed);
|
|
208
|
+
if (errs.length > 0) {
|
|
209
|
+
throw new Error(`Marketplace workflow has schema errors: ${errs.map((e) => e.message).join("; ")}`);
|
|
210
|
+
}
|
|
211
|
+
const refined = await adaptWorkflowInteractive(parsed, {
|
|
212
|
+
claude,
|
|
213
|
+
skills: availableSkills,
|
|
214
|
+
logger,
|
|
215
|
+
initialRefinement: buildAdaptPrompt(mismatches),
|
|
216
|
+
});
|
|
217
|
+
if (refined === null) {
|
|
218
|
+
// user cancelled — signal no install
|
|
219
|
+
return {
|
|
220
|
+
installed: false,
|
|
221
|
+
adapted: false,
|
|
222
|
+
workflowPath: "",
|
|
223
|
+
mismatches,
|
|
224
|
+
addedEnvKeys: 0,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
finalYaml = stringifyYaml(refined, { indent: 2, lineWidth: 120 });
|
|
228
|
+
adapted = true;
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
logger.warn(`Adaptation failed, installing as-is: ${err instanceof Error ? err.message : String(err)}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (mismatches.length > 0) {
|
|
235
|
+
const swapDesc = mismatches.map((m) => `${m.workflowSkill} → ${m.userProvider}`).join(", ");
|
|
236
|
+
logger.warn(`Workflow uses providers different from your .sweny.yml (${swapDesc}). ` +
|
|
237
|
+
`Install as-is and edit manually, or set ANTHROPIC_API_KEY and re-run to adapt automatically.`);
|
|
238
|
+
}
|
|
239
|
+
// 4. Infer providers for fresh-project .sweny.yml.
|
|
240
|
+
// Pre-computed values from the caller (git-remote-based) take precedence;
|
|
241
|
+
// fall back to skill-based inference for backward-compat with direct callers.
|
|
242
|
+
const finalSkills = extractSkillsFromYaml(finalYaml);
|
|
243
|
+
const sourceControl = options.inferredSourceControl !== undefined
|
|
244
|
+
? options.inferredSourceControl
|
|
245
|
+
: finalSkills.includes("gitlab")
|
|
246
|
+
? "gitlab"
|
|
247
|
+
: "github";
|
|
248
|
+
const issueTracker = options.inferredIssueTracker !== undefined
|
|
249
|
+
? options.inferredIssueTracker
|
|
250
|
+
: finalSkills.includes("linear")
|
|
251
|
+
? "linear"
|
|
252
|
+
: finalSkills.includes("jira")
|
|
253
|
+
? "jira"
|
|
254
|
+
: "github-issues";
|
|
255
|
+
const observability = options.inferredObservability !== undefined
|
|
256
|
+
? options.inferredObservability
|
|
257
|
+
: finalSkills.includes("datadog")
|
|
258
|
+
? "datadog"
|
|
259
|
+
: finalSkills.includes("sentry")
|
|
260
|
+
? "sentry"
|
|
261
|
+
: finalSkills.includes("betterstack")
|
|
262
|
+
? "betterstack"
|
|
263
|
+
: finalSkills.includes("newrelic")
|
|
264
|
+
? "newrelic"
|
|
265
|
+
: null;
|
|
266
|
+
writeSwenyYmlIfMissing(cwd, sourceControl, observability, issueTracker);
|
|
267
|
+
// 5. Credentials
|
|
268
|
+
const creds = collectCredentialsForSkills(finalSkills, availableSkills);
|
|
269
|
+
const addedEnvKeys = appendMissingEnvKeys(cwd, creds);
|
|
270
|
+
// 6. Workflow file — respect the overwrite flag.
|
|
271
|
+
const write = writeWorkflowFile(cwd, id, finalYaml, { overwrite });
|
|
272
|
+
if (!write.written) {
|
|
273
|
+
// File exists and overwrite was false — signal the caller.
|
|
274
|
+
return {
|
|
275
|
+
installed: false,
|
|
276
|
+
adapted,
|
|
277
|
+
workflowPath: write.path,
|
|
278
|
+
mismatches,
|
|
279
|
+
addedEnvKeys,
|
|
280
|
+
alreadyExists: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
installed: true,
|
|
285
|
+
adapted,
|
|
286
|
+
workflowPath: write.path,
|
|
287
|
+
mismatches,
|
|
288
|
+
addedEnvKeys,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|