@sweny-ai/core 0.1.53 → 0.1.55

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.
Files changed (45) hide show
  1. package/dist/__tests__/executor.test.js +2 -1
  2. package/dist/__tests__/integration/sources.integration.test.d.ts +1 -0
  3. package/dist/__tests__/integration/sources.integration.test.js +123 -0
  4. package/dist/__tests__/workflow-yaml.test.js +12 -5
  5. package/dist/cli/__tests__/report-cloud.test.d.ts +1 -0
  6. package/dist/cli/__tests__/report-cloud.test.js +165 -0
  7. package/dist/cli/cloud-report.d.ts +12 -0
  8. package/dist/cli/cloud-report.js +79 -0
  9. package/dist/cli/config-file.d.ts +3 -3
  10. package/dist/cli/config-file.js +18 -0
  11. package/dist/cli/config.d.ts +3 -0
  12. package/dist/cli/config.js +18 -2
  13. package/dist/cli/config.test.js +65 -1
  14. package/dist/cli/e2e.js +4 -2
  15. package/dist/cli/e2e.test.js +9 -2
  16. package/dist/cli/main.js +46 -19
  17. package/dist/cli/new.d.ts +0 -5
  18. package/dist/cli/new.js +0 -5
  19. package/dist/cli/new.test.js +1 -9
  20. package/dist/cli/output.test.js +1 -0
  21. package/dist/cli/publish.js +23 -12
  22. package/dist/cli/publish.test.d.ts +1 -0
  23. package/dist/cli/publish.test.js +610 -0
  24. package/dist/executor.d.ts +8 -0
  25. package/dist/executor.js +21 -3
  26. package/dist/executor.test.js +56 -1
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +1 -1
  29. package/dist/mermaid.test.js +2 -0
  30. package/dist/schema.d.ts +174 -46
  31. package/dist/schema.js +31 -2
  32. package/dist/sources.d.ts +70 -0
  33. package/dist/sources.js +194 -0
  34. package/dist/sources.test.d.ts +1 -0
  35. package/dist/sources.test.js +264 -0
  36. package/dist/sources.validation.test.d.ts +12 -0
  37. package/dist/sources.validation.test.js +360 -0
  38. package/dist/templates.d.ts +6 -2
  39. package/dist/templates.js +36 -46
  40. package/dist/testing.js +4 -1
  41. package/dist/types.d.ts +8 -1
  42. package/dist/types.js +0 -5
  43. package/dist/workflows/browser.js +1 -1
  44. package/dist/workflows/triage.yml +1 -23
  45. package/package.json +1 -5
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { validateInputs } from "./config.js";
2
+ import { validateInputs, parseCliInputs } from "./config.js";
3
3
  /**
4
4
  * Focused tests for validateInputs() — specifically the notification-provider
5
5
  * switch. Regressions here break every `swenyai/triage` action run because
@@ -65,6 +65,9 @@ function baseConfig(overrides = {}) {
65
65
  workspaceTools: [],
66
66
  rules: [],
67
67
  context: [],
68
+ offline: false,
69
+ fetchAuth: {},
70
+ cloudToken: "",
68
71
  ...overrides,
69
72
  };
70
73
  }
@@ -109,3 +112,64 @@ describe("validateInputs — notification provider", () => {
109
112
  expect(err).toContain("console");
110
113
  });
111
114
  });
115
+ describe("fetch.auth + offline parsing", () => {
116
+ it("parses offline flag", () => {
117
+ const config = parseCliInputs({ offline: true }, {});
118
+ expect(config.offline).toBe(true);
119
+ });
120
+ it("defaults fetchAuth to {} and offline to false", () => {
121
+ const config = parseCliInputs({}, {});
122
+ expect(config.offline).toBe(false);
123
+ expect(config.fetchAuth).toEqual({});
124
+ });
125
+ it("parses fetch.auth from file config", () => {
126
+ const config = parseCliInputs({}, { "fetch.auth": { "api.example.com": "MY_TOKEN" } });
127
+ expect(config.fetchAuth).toEqual({ "api.example.com": "MY_TOKEN" });
128
+ });
129
+ });
130
+ describe("parseCliInputs — cloud token", () => {
131
+ it("reads SWENY_CLOUD_TOKEN from env", () => {
132
+ const original = process.env.SWENY_CLOUD_TOKEN;
133
+ process.env.SWENY_CLOUD_TOKEN = "sweny_pk_test123";
134
+ try {
135
+ const config = parseCliInputs({});
136
+ expect(config.cloudToken).toBe("sweny_pk_test123");
137
+ }
138
+ finally {
139
+ if (original === undefined)
140
+ delete process.env.SWENY_CLOUD_TOKEN;
141
+ else
142
+ process.env.SWENY_CLOUD_TOKEN = original;
143
+ }
144
+ });
145
+ it("reads cloud-token from config file", () => {
146
+ const config = parseCliInputs({}, { "cloud-token": "sweny_pk_fromfile" });
147
+ expect(config.cloudToken).toBe("sweny_pk_fromfile");
148
+ });
149
+ it("env var overrides config file", () => {
150
+ const original = process.env.SWENY_CLOUD_TOKEN;
151
+ process.env.SWENY_CLOUD_TOKEN = "sweny_pk_env";
152
+ try {
153
+ const config = parseCliInputs({}, { "cloud-token": "sweny_pk_file" });
154
+ expect(config.cloudToken).toBe("sweny_pk_env");
155
+ }
156
+ finally {
157
+ if (original === undefined)
158
+ delete process.env.SWENY_CLOUD_TOKEN;
159
+ else
160
+ process.env.SWENY_CLOUD_TOKEN = original;
161
+ }
162
+ });
163
+ it("defaults to empty string", () => {
164
+ const original = process.env.SWENY_CLOUD_TOKEN;
165
+ delete process.env.SWENY_CLOUD_TOKEN;
166
+ try {
167
+ const config = parseCliInputs({});
168
+ expect(config.cloudToken).toBe("");
169
+ }
170
+ finally {
171
+ if (original !== undefined)
172
+ process.env.SWENY_CLOUD_TOKEN = original;
173
+ }
174
+ });
175
+ });
package/dist/cli/e2e.js CHANGED
@@ -830,9 +830,11 @@ export async function runE2eRun(options) {
830
830
  runResults.push({ file: fileName, passed: false });
831
831
  continue;
832
832
  }
833
- // Replace template vars in node instructions
833
+ // Replace template vars in node instructions (e2e workflows always use string instructions)
834
834
  for (const node of Object.values(workflow.nodes)) {
835
- node.instruction = resolveTemplateVars(node.instruction, vars);
835
+ if (typeof node.instruction === "string") {
836
+ node.instruction = resolveTemplateVars(node.instruction, vars);
837
+ }
836
838
  }
837
839
  // Build skills + Claude client
838
840
  const skills = createSkillMap(configuredSkills(process.env, cwd));
@@ -247,7 +247,13 @@ describe("buildFlowNodes", () => {
247
247
  const { nodes } = buildFlowNodes(flow);
248
248
  for (const [id, node] of Object.entries(nodes)) {
249
249
  expect(node.name, `${id}.name`).toBeTruthy();
250
- expect(node.instruction.length, `${id}.instruction`).toBeGreaterThan(0);
250
+ const instr = node.instruction;
251
+ if (typeof instr === "string") {
252
+ expect(instr.length, `${id}.instruction`).toBeGreaterThan(0);
253
+ }
254
+ else {
255
+ expect(instr, `${id}.instruction`).toBeTruthy();
256
+ }
251
257
  expect(Array.isArray(node.skills), `${id}.skills`).toBe(true);
252
258
  }
253
259
  });
@@ -607,7 +613,8 @@ describe("template variable completeness — all {vars} in instructions are reso
607
613
  it(`${flow.type}: all template vars in instructions resolve to values`, () => {
608
614
  const wf = buildFlowWorkflow(flow, "http://localhost:3000");
609
615
  for (const [nodeId, node] of Object.entries(wf.nodes)) {
610
- const resolved = resolveTemplateVars(node.instruction, vars);
616
+ const instrText = typeof node.instruction === "string" ? node.instruction : "";
617
+ const resolved = resolveTemplateVars(instrText, vars);
611
618
  // After resolution, no {word} placeholders should remain
612
619
  // (except for agent-browser refs like @<ref> which are not {vars})
613
620
  const unresolvedVars = resolved.match(/\{(\w+)\}/g) || [];
package/dist/cli/main.js CHANGED
@@ -23,12 +23,13 @@ import * as readline from "node:readline";
23
23
  import { loadDotenv, loadConfigFile } from "./config-file.js";
24
24
  import { buildCredentialMap } from "./credentials.js";
25
25
  import { runNew } from "./new.js";
26
- import { runE2eInit, runE2eRun } from "./e2e.js";
26
+ import { runE2eRun } from "./e2e.js";
27
27
  import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs, validateWarnings, } from "./config.js";
28
28
  import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, formatResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
29
29
  import { checkProviderConnectivity } from "./check.js";
30
30
  import { registerSetupCommand } from "./setup.js";
31
31
  import { registerPublishCommand } from "./publish.js";
32
+ import { reportToCloud } from "./cloud-report.js";
32
33
  // ── Stream observer (NDJSON) ────────────────────────────────────────
33
34
  /**
34
35
  * Create an observer that writes NDJSON ExecutionEvents to stdout.
@@ -64,23 +65,8 @@ program
64
65
  .action(async () => {
65
66
  await runNew();
66
67
  });
67
- // ── sweny init (deprecated alias) ─────────────────────────────────────
68
- program
69
- .command("init", { hidden: true })
70
- .description("[DEPRECATED] Use `sweny new` instead")
71
- .action(async () => {
72
- console.warn("\x1B[33m ⚠ `sweny init` is deprecated and will be removed in v6. Use `sweny new` instead.\x1B[0m\n");
73
- await runNew();
74
- });
75
68
  // ── sweny e2e ────────────────────────────────────────────────────────
76
69
  const e2eCmd = program.command("e2e").description("End-to-end browser testing");
77
- e2eCmd
78
- .command("init")
79
- .description("[DEPRECATED] Use `sweny new` and pick 'End-to-end browser testing'")
80
- .action(async () => {
81
- console.warn("\x1B[33m ⚠ `sweny e2e init` is deprecated and will be removed in v6. Use `sweny new` and pick 'End-to-end browser testing'.\x1B[0m\n");
82
- await runE2eInit();
83
- });
84
70
  e2eCmd
85
71
  .command("run [file]")
86
72
  .description("Run e2e workflow files from .sweny/e2e/")
@@ -339,10 +325,9 @@ triageCmd.action(async (options) => {
339
325
  ...(config.observabilityCredentials.tableName && {
340
326
  betterstackTableName: config.observabilityCredentials.tableName,
341
327
  }),
342
- // Structured rules/context for executor
328
+ // Structured rules/context for executor (URLs resolved eagerly by loadAdditionalContext)
343
329
  rules,
344
330
  context: fullContext,
345
- // URLs for the prepare node to fetch at runtime
346
331
  rulesUrls,
347
332
  contextUrls,
348
333
  };
@@ -352,6 +337,10 @@ triageCmd.action(async (options) => {
352
337
  claude,
353
338
  observer,
354
339
  logger: consoleLogger,
340
+ cwd: process.cwd(),
341
+ env: process.env,
342
+ fetchAuth: config.fetchAuth,
343
+ offline: config.offline,
355
344
  });
356
345
  const durationMs = Date.now() - runStart;
357
346
  // Output
@@ -375,6 +364,13 @@ triageCmd.action(async (options) => {
375
364
  console.error(c.subtle(` ⚠ could not write GITHUB_STEP_SUMMARY: ${err instanceof Error ? err.message : err}`));
376
365
  }
377
366
  }
367
+ // Report to SWEny Cloud (best-effort, never block the run)
368
+ try {
369
+ await reportToCloud(results, durationMs, config, "triage");
370
+ }
371
+ catch {
372
+ // silent — cloud reporting is optional
373
+ }
378
374
  // Terminal bell
379
375
  if (config.bell)
380
376
  process.stderr.write("\x07");
@@ -435,6 +431,7 @@ implementCmd.action(async (issueId, options) => {
435
431
  mcpServers,
436
432
  });
437
433
  console.log(chalk.cyan(`\n sweny implement ${issueId}\n`));
434
+ const implRunStart = Date.now();
438
435
  const isTTY = process.stderr.isTTY ?? false;
439
436
  const implProgressObserver = (event) => {
440
437
  switch (event.type) {
@@ -492,6 +489,10 @@ implementCmd.action(async (issueId, options) => {
492
489
  claude,
493
490
  observer,
494
491
  logger: consoleLogger,
492
+ cwd: process.cwd(),
493
+ env: process.env,
494
+ fetchAuth: config.fetchAuth,
495
+ offline: config.offline,
495
496
  });
496
497
  const hasFailed = [...results.values()].some((r) => r.status === "failed");
497
498
  if (hasFailed) {
@@ -506,6 +507,13 @@ implementCmd.action(async (issueId, options) => {
506
507
  else {
507
508
  console.log(chalk.green(`\n Implement workflow completed\n`));
508
509
  }
510
+ // Report to SWEny Cloud (best-effort)
511
+ try {
512
+ await reportToCloud(results, Date.now() - implRunStart, config, "implement");
513
+ }
514
+ catch {
515
+ // silent
516
+ }
509
517
  process.exit(0);
510
518
  }
511
519
  catch (err) {
@@ -555,6 +563,7 @@ export async function workflowRunAction(file, options) {
555
563
  }
556
564
  process.exit(0);
557
565
  }
566
+ const runStart = Date.now();
558
567
  const fileConfig = loadConfigFile();
559
568
  const config = parseCliInputs(options, fileConfig);
560
569
  const isJson = Boolean(options.json);
@@ -674,6 +683,10 @@ export async function workflowRunAction(file, options) {
674
683
  claude,
675
684
  observer,
676
685
  logger: consoleLogger,
686
+ cwd: process.cwd(),
687
+ env: process.env,
688
+ fetchAuth: config.fetchAuth,
689
+ offline: config.offline,
677
690
  });
678
691
  if (isJson) {
679
692
  process.stdout.write(JSON.stringify(Object.fromEntries(results), null, 2) + "\n");
@@ -689,7 +702,21 @@ export async function workflowRunAction(file, options) {
689
702
  }
690
703
  process.stdout.write(toMermaidBlock(workflow, { state, trace, title: workflow.name }) + "\n");
691
704
  }
705
+ // Report to SWEny Cloud (best-effort)
706
+ try {
707
+ await reportToCloud(results, Date.now() - runStart, config, workflow.id);
708
+ }
709
+ catch {
710
+ // silent
711
+ }
692
712
  const hasFailed = [...results.values()].some((r) => r.status === "failed");
713
+ // Report to SWEny Cloud (best-effort)
714
+ try {
715
+ await reportToCloud(results, 0, config, workflow.id);
716
+ }
717
+ catch {
718
+ // silent
719
+ }
693
720
  if (hasFailed) {
694
721
  console.error(chalk.red(` Workflow failed\n`));
695
722
  process.exit(1);
@@ -823,7 +850,7 @@ workflowCmd
823
850
  .option("--json", "Output workflow JSON to stdout (no interactive prompt)")
824
851
  .action(async (description, options) => {
825
852
  if (!options.json) {
826
- console.warn("\x1B[33m ⚠ `sweny workflow create` is deprecated and will be removed in v6. Use `sweny new` and pick 'Describe your own'.\x1B[0m\n");
853
+ console.warn("\x1B[33m ⚠ `sweny workflow create` is deprecated. Use `sweny new` instead.\x1B[0m\n");
827
854
  }
828
855
  const skills = configuredSkills();
829
856
  const claude = new ClaudeClient({
package/dist/cli/new.d.ts CHANGED
@@ -94,8 +94,3 @@ export declare function buildActionWorkflow(credentials: Credential[], cronExpre
94
94
  * Safe to re-run in an existing project to add additional workflows.
95
95
  */
96
96
  export declare function runNew(): Promise<void>;
97
- /**
98
- * @deprecated Use `runNew` instead. Kept as a named re-export so external
99
- * callers (e.g. create-sweny, plugin skills) don't break during the migration.
100
- */
101
- export declare const runInit: typeof runNew;
package/dist/cli/new.js CHANGED
@@ -578,11 +578,6 @@ export async function runNew() {
578
578
  p.note(steps.join("\n"), "Next steps");
579
579
  p.outro(hasExistingConfig ? "Workflow added!" : "You're all set!");
580
580
  }
581
- /**
582
- * @deprecated Use `runNew` instead. Kept as a named re-export so external
583
- * callers (e.g. create-sweny, plugin skills) don't break during the migration.
584
- */
585
- export const runInit = runNew;
586
581
  // ── Custom workflow branch ────────────────────────────────────────────
587
582
  /**
588
583
  * Prompt the user for a description, generate a workflow via the LLM,
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import * as os from "node:os";
5
5
  import { parse as parseYaml } from "yaml";
6
- import { collectCredentials, collectCredentialsForSkills, extractSkillsFromYaml, detectGitRemote, buildSwenyYml, buildEnvTemplate, buildActionWorkflow, runInit, runNew, PROVIDER_CREDENTIALS, SKILL_CREDENTIALS, } from "./new.js";
6
+ import { collectCredentials, collectCredentialsForSkills, extractSkillsFromYaml, detectGitRemote, buildSwenyYml, buildEnvTemplate, buildActionWorkflow, PROVIDER_CREDENTIALS, SKILL_CREDENTIALS, } from "./new.js";
7
7
  // ── detectGitRemote ────────────────────────────────────────────────────
8
8
  describe("detectGitRemote", () => {
9
9
  const tmpDirs = [];
@@ -763,14 +763,6 @@ describe("extractSkillsFromYaml — block-style arrays", () => {
763
763
  expect(extractSkillsFromYaml(yaml)).toEqual([]);
764
764
  });
765
765
  });
766
- // ── Deprecated alias: runInit === runNew ──────────────────────────────
767
- describe("runInit deprecation alias", () => {
768
- it("re-exports runNew as runInit (identity pin)", () => {
769
- // Callers using `import { runInit } from "@sweny-ai/core/new"` must
770
- // continue to hit the same function during the deprecation window.
771
- expect(runInit).toBe(runNew);
772
- });
773
- });
774
766
  describe("SKILL_CREDENTIALS", () => {
775
767
  it("has entries for all built-in skills", () => {
776
768
  const expected = [
@@ -44,6 +44,7 @@ function fakeTrace(steps) {
44
44
  iteration: s.iteration ?? 1,
45
45
  })),
46
46
  edges: [],
47
+ sources: {},
47
48
  };
48
49
  }
49
50
  describe("formatDagResultMarkdown", () => {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
- import { execSync } from "node:child_process";
11
+ import { execFileSync } from "node:child_process";
12
12
  import * as p from "@clack/prompts";
13
13
  import chalk from "chalk";
14
14
  import { parse as parseYaml } from "yaml";
@@ -108,7 +108,7 @@ export function validateSkillDir(dirPath) {
108
108
  // ── GitHub PR helpers ──────────────────────────────────────────────────
109
109
  function ghAvailable() {
110
110
  try {
111
- execSync("gh --version", { stdio: "pipe" });
111
+ execFileSync("gh", ["--version"], { stdio: "pipe" });
112
112
  return true;
113
113
  }
114
114
  catch {
@@ -117,7 +117,7 @@ function ghAvailable() {
117
117
  }
118
118
  function ghAuthenticated() {
119
119
  try {
120
- execSync("gh auth status", { stdio: "pipe" });
120
+ execFileSync("gh", ["auth", "status"], { stdio: "pipe" });
121
121
  return true;
122
122
  }
123
123
  catch {
@@ -287,21 +287,21 @@ async function openGitHubPR(type, id, sourcePath, meta) {
287
287
  s.start("Forking marketplace repository...");
288
288
  try {
289
289
  // Fork the marketplace repo (idempotent — if already forked, just continues)
290
- execSync("gh repo fork swenyai/marketplace --clone=false 2>&1", { stdio: "pipe" });
290
+ execFileSync("gh", ["repo", "fork", "swenyai/marketplace", "--clone=false"], { stdio: "pipe" });
291
291
  // Get the fork name
292
- const forkInfo = execSync("gh api user", { stdio: "pipe", encoding: "utf-8" });
292
+ const forkInfo = execFileSync("gh", ["api", "user"], { stdio: "pipe", encoding: "utf-8" });
293
293
  const username = JSON.parse(forkInfo).login;
294
294
  const forkRepo = `${username}/marketplace`;
295
295
  s.message("Creating branch...");
296
296
  const branchName = `publish/${type}/${id}`;
297
297
  // Clone to temp, create branch, add file, push, create PR
298
298
  const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR ?? "/tmp", "sweny-publish-"));
299
- execSync(`gh repo clone ${forkRepo} ${tmpDir} -- --depth 1`, { stdio: "pipe" });
300
- execSync(`git -C ${tmpDir} checkout -b ${branchName}`, { stdio: "pipe" });
299
+ execFileSync("gh", ["repo", "clone", forkRepo, tmpDir, "--", "--depth", "1"], { stdio: "pipe" });
300
+ execFileSync("git", ["-C", tmpDir, "checkout", "-b", branchName], { stdio: "pipe" });
301
301
  if (type === "workflow") {
302
302
  const dest = path.join(tmpDir, "workflows", "community", `${id}.yml`);
303
303
  fs.copyFileSync(sourcePath, dest);
304
- execSync(`git -C ${tmpDir} add workflows/community/${id}.yml`, { stdio: "pipe" });
304
+ execFileSync("git", ["-C", tmpDir, "add", `workflows/community/${id}.yml`], { stdio: "pipe" });
305
305
  }
306
306
  else {
307
307
  const destDir = path.join(tmpDir, "skills", "community", id);
@@ -311,17 +311,28 @@ async function openGitHubPR(type, id, sourcePath, meta) {
311
311
  for (const file of files) {
312
312
  fs.copyFileSync(path.join(sourcePath, file), path.join(destDir, file));
313
313
  }
314
- execSync(`git -C ${tmpDir} add skills/community/${id}`, { stdio: "pipe" });
314
+ execFileSync("git", ["-C", tmpDir, "add", `skills/community/${id}`], { stdio: "pipe" });
315
315
  }
316
316
  s.message("Pushing...");
317
317
  const commitMsg = type === "workflow" ? `feat: add ${id} workflow` : `feat: add ${id} skill`;
318
- execSync(`git -C ${tmpDir} commit -m "${commitMsg}"`, { stdio: "pipe" });
319
- execSync(`git -C ${tmpDir} push origin ${branchName}`, { stdio: "pipe" });
318
+ execFileSync("git", ["-C", tmpDir, "commit", "-m", commitMsg], { stdio: "pipe" });
319
+ execFileSync("git", ["-C", tmpDir, "push", "origin", branchName], { stdio: "pipe" });
320
320
  s.message("Creating pull request...");
321
321
  const prBody = type === "workflow"
322
322
  ? `## New Workflow: ${id}\n\n**Author:** ${meta.author}\n**Category:** ${meta.category}\n**Tags:** ${meta.tags?.join(", ")}\n\nSubmitted via \`sweny publish\`.`
323
323
  : `## New Skill: ${id}\n\nSubmitted via \`sweny publish\`.`;
324
- const prResult = execSync(`gh pr create --repo swenyai/marketplace --head ${username}:${branchName} --title "feat: add ${type} ${id}" --body "${prBody.replace(/"/g, '\\"')}"`, { stdio: "pipe", encoding: "utf-8" });
324
+ const prResult = execFileSync("gh", [
325
+ "pr",
326
+ "create",
327
+ "--repo",
328
+ "swenyai/marketplace",
329
+ "--head",
330
+ `${username}:${branchName}`,
331
+ "--title",
332
+ `feat: add ${type} ${id}`,
333
+ "--body",
334
+ prBody,
335
+ ], { stdio: "pipe", encoding: "utf-8" });
325
336
  const prUrl = prResult.trim();
326
337
  // Cleanup temp dir
327
338
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -0,0 +1 @@
1
+ export {};