@bastani/atomic 0.5.1 → 0.5.2-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 +79 -183
- package/package.json +6 -4
- package/src/commands/cli/chat/index.ts +2 -2
- package/src/commands/cli/workflow.ts +6 -6
- package/src/scripts/constants.ts +2 -2
- package/src/sdk/components/session-graph-panel.tsx +1 -3
- package/src/sdk/runtime/discovery.ts +51 -3
- package/src/sdk/runtime/executor-entry.ts +16 -0
- package/src/sdk/runtime/executor.ts +31 -17
- package/src/sdk/runtime/loader.ts +0 -127
- package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/claude/index.ts +1 -1
- package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/copilot/index.ts +1 -1
- package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/opencode/index.ts +1 -1
- package/src/sdk/{workflows.ts → workflows/index.ts} +14 -14
- package/src/services/system/auto-sync.ts +0 -2
- package/.atomic/workflows/hello/claude/index.ts +0 -40
- package/.atomic/workflows/hello/copilot/index.ts +0 -53
- package/.atomic/workflows/hello/opencode/index.ts +0 -52
- package/.atomic/workflows/hello-parallel/claude/index.ts +0 -73
- package/.atomic/workflows/hello-parallel/copilot/index.ts +0 -103
- package/.atomic/workflows/hello-parallel/opencode/index.ts +0 -105
- package/.atomic/workflows/tsconfig.json +0 -22
- package/src/services/system/workflows.ts +0 -240
- /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/git.ts +0 -0
- /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/prompts.ts +0 -0
- /package/{.atomic/workflows → src/sdk/workflows/builtin}/ralph/helpers/review.ts +0 -0
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hello-parallel workflow for Copilot — parallel session example.
|
|
3
|
-
*
|
|
4
|
-
* Session 1 (sequential): Ask the agent to describe the project.
|
|
5
|
-
* Sessions 2+3 (parallel): Two agents summarize session 1 concurrently.
|
|
6
|
-
* Session 4 (sequential): Merge both summaries into a final output.
|
|
7
|
-
*
|
|
8
|
-
* Run: atomic workflow -n hello-parallel -a copilot "describe this project"
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* `CopilotSession.sendAndWait` defaults to a 60s timeout and THROWS on
|
|
15
|
-
* expiry, which crashes the workflow mid-stage. Override with a generous
|
|
16
|
-
* 30-minute budget so legitimate long-running agent work completes.
|
|
17
|
-
*/
|
|
18
|
-
const SEND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
19
|
-
|
|
20
|
-
export default defineWorkflow<"copilot">({
|
|
21
|
-
name: "hello-parallel",
|
|
22
|
-
description: "Parallel Copilot demo: describe → [summarize-a, summarize-b] → merge",
|
|
23
|
-
})
|
|
24
|
-
.run(async (ctx) => {
|
|
25
|
-
// Sequential: describe
|
|
26
|
-
const describe = await ctx.stage(
|
|
27
|
-
{ name: "describe", description: "Ask the agent to describe the project" },
|
|
28
|
-
{},
|
|
29
|
-
{},
|
|
30
|
-
async (s) => {
|
|
31
|
-
await s.session.sendAndWait({ prompt: s.userPrompt }, SEND_TIMEOUT_MS);
|
|
32
|
-
|
|
33
|
-
s.save(await s.session.getMessages());
|
|
34
|
-
},
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// Parallel: summarize-a + summarize-b
|
|
38
|
-
const [summarizeA, summarizeB] = await Promise.all([
|
|
39
|
-
ctx.stage(
|
|
40
|
-
{ name: "summarize-a", description: "Summarize the description as bullet points" },
|
|
41
|
-
{},
|
|
42
|
-
{},
|
|
43
|
-
async (s) => {
|
|
44
|
-
const research = await s.transcript(describe);
|
|
45
|
-
|
|
46
|
-
await s.session.sendAndWait(
|
|
47
|
-
{
|
|
48
|
-
prompt: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
|
|
49
|
-
},
|
|
50
|
-
SEND_TIMEOUT_MS,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
s.save(await s.session.getMessages());
|
|
54
|
-
},
|
|
55
|
-
),
|
|
56
|
-
ctx.stage(
|
|
57
|
-
{ name: "summarize-b", description: "Summarize the description as a one-liner" },
|
|
58
|
-
{},
|
|
59
|
-
{},
|
|
60
|
-
async (s) => {
|
|
61
|
-
const research = await s.transcript(describe);
|
|
62
|
-
|
|
63
|
-
await s.session.sendAndWait(
|
|
64
|
-
{
|
|
65
|
-
prompt: `Summarize the following in a single sentence:\n\n${research.content}`,
|
|
66
|
-
},
|
|
67
|
-
SEND_TIMEOUT_MS,
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
s.save(await s.session.getMessages());
|
|
71
|
-
},
|
|
72
|
-
),
|
|
73
|
-
]);
|
|
74
|
-
|
|
75
|
-
// Sequential: merge
|
|
76
|
-
await ctx.stage(
|
|
77
|
-
{ name: "merge", description: "Merge both summaries into a final output" },
|
|
78
|
-
{},
|
|
79
|
-
{},
|
|
80
|
-
async (s) => {
|
|
81
|
-
const bullets = await s.transcript(summarizeA);
|
|
82
|
-
const oneliner = await s.transcript(summarizeB);
|
|
83
|
-
|
|
84
|
-
await s.session.sendAndWait(
|
|
85
|
-
{
|
|
86
|
-
prompt: [
|
|
87
|
-
"Combine the following two summaries into one concise paragraph:",
|
|
88
|
-
"",
|
|
89
|
-
"## Bullet points",
|
|
90
|
-
bullets.content,
|
|
91
|
-
"",
|
|
92
|
-
"## One-liner",
|
|
93
|
-
oneliner.content,
|
|
94
|
-
].join("\n"),
|
|
95
|
-
},
|
|
96
|
-
SEND_TIMEOUT_MS,
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
s.save(await s.session.getMessages());
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
})
|
|
103
|
-
.compile();
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hello-parallel workflow for OpenCode — parallel session example.
|
|
3
|
-
*
|
|
4
|
-
* Session 1 (sequential): Ask the agent to describe the project.
|
|
5
|
-
* Sessions 2+3 (parallel): Two agents summarize session 1 concurrently.
|
|
6
|
-
* Session 4 (sequential): Merge both summaries into a final output.
|
|
7
|
-
*
|
|
8
|
-
* Run: atomic workflow -n hello-parallel -a opencode "describe this project"
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
|
-
|
|
13
|
-
export default defineWorkflow<"opencode">({
|
|
14
|
-
name: "hello-parallel",
|
|
15
|
-
description: "Parallel OpenCode demo: describe → [summarize-a, summarize-b] → merge",
|
|
16
|
-
})
|
|
17
|
-
.run(async (ctx) => {
|
|
18
|
-
const describe = await ctx.stage(
|
|
19
|
-
{ name: "describe", description: "Ask the agent to describe the project" },
|
|
20
|
-
{},
|
|
21
|
-
{ title: "describe" },
|
|
22
|
-
async (s) => {
|
|
23
|
-
const result = await s.client.session.prompt({
|
|
24
|
-
sessionID: s.session.id,
|
|
25
|
-
parts: [{ type: "text", text: s.userPrompt }],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
s.save(result.data!);
|
|
29
|
-
},
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const [summarizeA, summarizeB] = await Promise.all([
|
|
33
|
-
ctx.stage(
|
|
34
|
-
{ name: "summarize-a", description: "Summarize the description as bullet points" },
|
|
35
|
-
{},
|
|
36
|
-
{ title: "summarize-a" },
|
|
37
|
-
async (s) => {
|
|
38
|
-
const research = await s.transcript(describe);
|
|
39
|
-
|
|
40
|
-
const result = await s.client.session.prompt({
|
|
41
|
-
sessionID: s.session.id,
|
|
42
|
-
parts: [
|
|
43
|
-
{
|
|
44
|
-
type: "text",
|
|
45
|
-
text: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
s.save(result.data!);
|
|
51
|
-
},
|
|
52
|
-
),
|
|
53
|
-
ctx.stage(
|
|
54
|
-
{ name: "summarize-b", description: "Summarize the description as a one-liner" },
|
|
55
|
-
{},
|
|
56
|
-
{ title: "summarize-b" },
|
|
57
|
-
async (s) => {
|
|
58
|
-
const research = await s.transcript(describe);
|
|
59
|
-
|
|
60
|
-
const result = await s.client.session.prompt({
|
|
61
|
-
sessionID: s.session.id,
|
|
62
|
-
parts: [
|
|
63
|
-
{
|
|
64
|
-
type: "text",
|
|
65
|
-
text: `Summarize the following in a single sentence:\n\n${research.content}`,
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
s.save(result.data!);
|
|
71
|
-
},
|
|
72
|
-
),
|
|
73
|
-
]);
|
|
74
|
-
|
|
75
|
-
await ctx.stage(
|
|
76
|
-
{ name: "merge", description: "Merge both summaries into a final output" },
|
|
77
|
-
{},
|
|
78
|
-
{ title: "merge" },
|
|
79
|
-
async (s) => {
|
|
80
|
-
const bullets = await s.transcript(summarizeA);
|
|
81
|
-
const oneliner = await s.transcript(summarizeB);
|
|
82
|
-
|
|
83
|
-
const result = await s.client.session.prompt({
|
|
84
|
-
sessionID: s.session.id,
|
|
85
|
-
parts: [
|
|
86
|
-
{
|
|
87
|
-
type: "text",
|
|
88
|
-
text: [
|
|
89
|
-
"Combine the following two summaries into one concise paragraph:",
|
|
90
|
-
"",
|
|
91
|
-
"## Bullet points",
|
|
92
|
-
bullets.content,
|
|
93
|
-
"",
|
|
94
|
-
"## One-liner",
|
|
95
|
-
oneliner.content,
|
|
96
|
-
].join("\n"),
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
s.save(result.data!);
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
})
|
|
105
|
-
.compile();
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"allowImportingTsExtensions": true,
|
|
7
|
-
"noEmit": true,
|
|
8
|
-
"verbatimModuleSyntax": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"types": ["bun"],
|
|
12
|
-
"paths": {
|
|
13
|
-
"@bastani/atomic/workflows": ["../../src/sdk/workflows.ts"]
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"include": [
|
|
17
|
-
"**/claude/**/*.ts",
|
|
18
|
-
"**/copilot/**/*.ts",
|
|
19
|
-
"**/opencode/**/*.ts",
|
|
20
|
-
"**/helpers/**/*.ts"
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sync bundled Atomic workflow templates from the installed package into
|
|
3
|
-
* the user's global `~/.atomic/workflows/` directory.
|
|
4
|
-
*
|
|
5
|
-
* Each bundled workflow directory (`hello/`, `hello-parallel/`, `ralph/`,
|
|
6
|
-
* etc.) is a full overwrite of its destination — `rm -rf` followed by a
|
|
7
|
-
* fresh copy — so files removed upstream don't linger after an upgrade.
|
|
8
|
-
* User-created workflows whose names don't collide with bundled names are
|
|
9
|
-
* left untouched (we only iterate the bundled source, never the user's
|
|
10
|
-
* destination).
|
|
11
|
-
*
|
|
12
|
-
* Root-level files (`tsconfig.json`, etc.) are also overwritten on each
|
|
13
|
-
* sync.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { join, sep } from "path";
|
|
17
|
-
import { lstat, readdir, rm, symlink, unlink } from "fs/promises";
|
|
18
|
-
import { homedir } from "os";
|
|
19
|
-
import {
|
|
20
|
-
copyDir,
|
|
21
|
-
copyFile,
|
|
22
|
-
ensureDir,
|
|
23
|
-
pathExists,
|
|
24
|
-
} from "@/services/system/copy.ts";
|
|
25
|
-
import { assertPathWithinRoot } from "@/lib/path-root-guard.ts";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Reject any entry name that could redirect a write outside destRoot.
|
|
29
|
-
* `readdir` should never return `.`, `..`, or names with separators, but
|
|
30
|
-
* this is a cheap belt-and-braces check in case the installed package has
|
|
31
|
-
* been tampered with or sits on an exotic filesystem.
|
|
32
|
-
*/
|
|
33
|
-
function isSafeEntryName(name: string): boolean {
|
|
34
|
-
if (name === "" || name === "." || name === "..") return false;
|
|
35
|
-
if (name.includes("/") || name.includes("\\")) return false;
|
|
36
|
-
if (sep !== "/" && name.includes(sep)) return false;
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Locate the package root by walking up from this module. Both in installed
|
|
42
|
-
* (`<pkg>/src/services/system/workflows.ts`) and dev checkout layouts the
|
|
43
|
-
* package root is three directories up.
|
|
44
|
-
*/
|
|
45
|
-
function packageRoot(): string {
|
|
46
|
-
return join(import.meta.dir, "..", "..", "..");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Safely remove a symlink or junction before re-creating it.
|
|
51
|
-
*
|
|
52
|
-
* On Windows, Bun's `rm({ recursive: true })` follows NTFS junctions and
|
|
53
|
-
* can delete the **target directory's contents** (oven-sh/bun#27233).
|
|
54
|
-
* `unlink` is safe: it opens with `FILE_FLAG_OPEN_REPARSE_POINT` and
|
|
55
|
-
* removes only the link entry, never following it.
|
|
56
|
-
*
|
|
57
|
-
* Falls back to `rm` only when the path is a real directory (not a link),
|
|
58
|
-
* which can happen if a previous version created the path as a plain copy
|
|
59
|
-
* instead of a symlink.
|
|
60
|
-
*/
|
|
61
|
-
async function removeLinkOrDir(path: string): Promise<void> {
|
|
62
|
-
try {
|
|
63
|
-
const stats = await lstat(path);
|
|
64
|
-
if (stats.isSymbolicLink()) {
|
|
65
|
-
await unlink(path);
|
|
66
|
-
} else if (stats.isDirectory()) {
|
|
67
|
-
await rm(path, { recursive: true, force: true });
|
|
68
|
-
} else {
|
|
69
|
-
await unlink(path);
|
|
70
|
-
}
|
|
71
|
-
} catch (error: unknown) {
|
|
72
|
-
// ENOENT — path doesn't exist; nothing to remove.
|
|
73
|
-
if (
|
|
74
|
-
error instanceof Error &&
|
|
75
|
-
"code" in error &&
|
|
76
|
-
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
77
|
-
) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Honors ATOMIC_SETTINGS_HOME so tests can point at a temp dir. */
|
|
85
|
-
function homeRoot(): string {
|
|
86
|
-
return process.env.ATOMIC_SETTINGS_HOME ?? homedir();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Sync bundled workflow templates to `~/.atomic/workflows/`. Returns the
|
|
91
|
-
* number of bundled workflows installed (for logging). Best-effort on
|
|
92
|
-
* individual entries — readdir failures throw, per-entry copy failures
|
|
93
|
-
* propagate up to the caller (auto-sync's runStep).
|
|
94
|
-
*/
|
|
95
|
-
export async function installGlobalWorkflows(): Promise<void> {
|
|
96
|
-
const srcRoot = join(packageRoot(), ".atomic", "workflows");
|
|
97
|
-
const destRoot = join(homeRoot(), ".atomic", "workflows");
|
|
98
|
-
|
|
99
|
-
if (!(await pathExists(srcRoot))) {
|
|
100
|
-
// Treat a missing bundled source as a non-fatal skip: dev checkouts
|
|
101
|
-
// and partial installs legitimately hit this path. Surfaced via a
|
|
102
|
-
// thrown error so the spinner UI marks the step red with context.
|
|
103
|
-
throw new Error(`bundled workflows missing at ${srcRoot} — skipping ${destRoot}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
await ensureDir(destRoot);
|
|
107
|
-
|
|
108
|
-
// Safety invariant: we enumerate the BUNDLED source, never the user's
|
|
109
|
-
// destination. This guarantees that `rm(dest)` can only ever target a
|
|
110
|
-
// path whose basename exists in the bundled workflows — user-created
|
|
111
|
-
// workflows with different names are structurally invisible to this loop.
|
|
112
|
-
const entries = await readdir(srcRoot, { withFileTypes: true });
|
|
113
|
-
|
|
114
|
-
for (const entry of entries) {
|
|
115
|
-
if (!isSafeEntryName(entry.name)) {
|
|
116
|
-
console.warn(
|
|
117
|
-
` skipping unsafe bundled workflow entry name: ${JSON.stringify(entry.name)}`,
|
|
118
|
-
);
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const src = join(srcRoot, entry.name);
|
|
123
|
-
const dest = join(destRoot, entry.name);
|
|
124
|
-
|
|
125
|
-
// Belt-and-braces: confirm the computed destination actually lives
|
|
126
|
-
// inside destRoot. Throws if something conspired to produce an escape.
|
|
127
|
-
assertPathWithinRoot(destRoot, dest, "Workflow destination");
|
|
128
|
-
|
|
129
|
-
if (entry.isFile()) {
|
|
130
|
-
// Root files (tsconfig.json, etc.) — overwrite in place.
|
|
131
|
-
await copyFile(src, dest);
|
|
132
|
-
} else if (entry.isDirectory()) {
|
|
133
|
-
// Bundled workflow — full overwrite of the destination directory so
|
|
134
|
-
// files removed upstream don't linger across upgrades. User-created
|
|
135
|
-
// workflows under names that don't collide are untouched.
|
|
136
|
-
await rm(dest, { recursive: true, force: true });
|
|
137
|
-
await copyDir(src, dest);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ── Type-resolution setup for workflow authors ─────────────────────
|
|
142
|
-
//
|
|
143
|
-
// The bundled tsconfig.json uses relative `paths` that only resolve
|
|
144
|
-
// correctly inside the package's own directory tree. Once the files
|
|
145
|
-
// are copied to `~/.atomic/workflows/`, those relative paths break.
|
|
146
|
-
//
|
|
147
|
-
// Strategy: symlink `node_modules/@bastani/atomic` in the destination
|
|
148
|
-
// back to the running package root. TypeScript's standard module
|
|
149
|
-
// resolution then finds `@bastani/atomic/workflows` (and its
|
|
150
|
-
// transitive deps) automatically — no `paths` override needed.
|
|
151
|
-
//
|
|
152
|
-
// If symlink creation fails (permissions, unsupported FS), we fall
|
|
153
|
-
// back to a tsconfig with an absolute `paths` entry pointing at the
|
|
154
|
-
// package's SDK source. Either way the workflow author gets types
|
|
155
|
-
// with zero manual configuration.
|
|
156
|
-
await setupWorkflowTypes(destRoot);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Wire up TypeScript type resolution for a global workflows directory.
|
|
161
|
-
*
|
|
162
|
-
* Creates a `node_modules/@bastani/atomic` symlink → the installed
|
|
163
|
-
* package root and generates a tsconfig.json that lets standard module
|
|
164
|
-
* resolution do the work. Falls back to absolute `paths` in the
|
|
165
|
-
* tsconfig if symlinking isn't possible.
|
|
166
|
-
*/
|
|
167
|
-
export async function setupWorkflowTypes(destRoot: string): Promise<void> {
|
|
168
|
-
const pkgRoot = packageRoot();
|
|
169
|
-
let usedSymlink = false;
|
|
170
|
-
|
|
171
|
-
// 1. Symlink the package itself
|
|
172
|
-
try {
|
|
173
|
-
const scopeDir = join(destRoot, "node_modules", "@bastani");
|
|
174
|
-
await ensureDir(scopeDir);
|
|
175
|
-
|
|
176
|
-
const link = join(scopeDir, "atomic");
|
|
177
|
-
await removeLinkOrDir(link);
|
|
178
|
-
|
|
179
|
-
// Junctions on Windows need no elevated privileges.
|
|
180
|
-
const type = process.platform === "win32" ? "junction" : "dir";
|
|
181
|
-
await symlink(pkgRoot, link, type);
|
|
182
|
-
usedSymlink = true;
|
|
183
|
-
} catch {
|
|
184
|
-
// Swallow — falls back to paths-based tsconfig below.
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 2. Symlink @types/bun so `Bun.*` APIs have types in workflows
|
|
188
|
-
try {
|
|
189
|
-
const bunTypes = join(pkgRoot, "node_modules", "@types", "bun");
|
|
190
|
-
if (await pathExists(bunTypes)) {
|
|
191
|
-
const typesDir = join(destRoot, "node_modules", "@types");
|
|
192
|
-
await ensureDir(typesDir);
|
|
193
|
-
|
|
194
|
-
const link = join(typesDir, "bun");
|
|
195
|
-
await removeLinkOrDir(link);
|
|
196
|
-
|
|
197
|
-
const type = process.platform === "win32" ? "junction" : "dir";
|
|
198
|
-
await symlink(bunTypes, link, type);
|
|
199
|
-
}
|
|
200
|
-
} catch {
|
|
201
|
-
// Best effort — Bun APIs in workflows lack types but runtime is fine.
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// 3. Generate a clean tsconfig for the destination
|
|
205
|
-
const compilerOptions: Record<string, unknown> = {
|
|
206
|
-
target: "ESNext",
|
|
207
|
-
module: "ESNext",
|
|
208
|
-
moduleResolution: "bundler",
|
|
209
|
-
allowImportingTsExtensions: true,
|
|
210
|
-
noEmit: true,
|
|
211
|
-
verbatimModuleSyntax: true,
|
|
212
|
-
strict: true,
|
|
213
|
-
skipLibCheck: true,
|
|
214
|
-
types: ["bun"],
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
if (!usedSymlink) {
|
|
218
|
-
// Fallback: absolute paths so TypeScript can still resolve the SDK
|
|
219
|
-
// source from the installed package location.
|
|
220
|
-
compilerOptions.paths = {
|
|
221
|
-
"@bastani/atomic": [join(pkgRoot, "src", "sdk", "index.ts")],
|
|
222
|
-
"@bastani/atomic/workflows": [join(pkgRoot, "src", "sdk", "workflows.ts")],
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const tsconfig = { compilerOptions, include: GLOBAL_TSCONFIG_INCLUDE };
|
|
227
|
-
|
|
228
|
-
await Bun.write(
|
|
229
|
-
join(destRoot, "tsconfig.json"),
|
|
230
|
-
JSON.stringify(tsconfig, null, 2) + "\n",
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Include globs shared by every generated global workflows tsconfig. */
|
|
235
|
-
const GLOBAL_TSCONFIG_INCLUDE = [
|
|
236
|
-
"**/claude/**/*.ts",
|
|
237
|
-
"**/copilot/**/*.ts",
|
|
238
|
-
"**/opencode/**/*.ts",
|
|
239
|
-
"**/helpers/**/*.ts",
|
|
240
|
-
];
|
|
File without changes
|
|
File without changes
|
|
File without changes
|