forked from anomalyco/opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadd-package.ts
More file actions
174 lines (150 loc) · 5.67 KB
/
add-package.ts
File metadata and controls
174 lines (150 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import { join } from "path"
import { readdirSync, statSync, existsSync } from "fs"
import { z } from "zod"
import { spawn } from "bun"
import { Tool } from "openagent-ai"
function getAvailableWorkspaces(config: WorkspaceConfig): string[] {
const packagesDir = join(process.cwd(), "packages")
const workspaces = ["root"] // Always include root
if (existsSync(packagesDir)) {
try {
const entries = readdirSync(packagesDir)
for (const entry of entries) {
const entryPath = join(packagesDir, entry)
if (
statSync(entryPath).isDirectory() &&
existsSync(join(entryPath, "package.json"))
) {
workspaces.push(entry)
}
}
} catch (error) {
console.warn("Could not scan packages directory:", error)
}
}
let filteredWorkspaces = workspaces
if (config.include && config.include.length > 0) {
filteredWorkspaces = workspaces.filter((ws) => config.include!.includes(ws))
}
if (config.exclude && config.exclude.length > 0) {
filteredWorkspaces = filteredWorkspaces.filter(
(ws) => !config.exclude!.includes(ws),
)
}
return filteredWorkspaces
}
interface WorkspaceConfig {
workspacesDirectory: string
include?: string[]
exclude?: string[]
}
export function savePackageTool(
config: WorkspaceConfig = { workspacesDirectory: "packages" },
) {
const filteredWorkspaces = getAvailableWorkspaces(config)
// Build parameter schema based on available workspaces
const baseParameters = {
packageName: z
.string()
.describe(
"The npm package name to install (e.g., 'lodash', '@types/node'). Versions will be automatically stripped (e.g., 'lodash@4.17.21' becomes 'lodash')",
),
dev: z
.boolean()
.optional()
.describe(
"Install as dev dependency. Defaults to false (regular dependency).",
),
}
// Only add workspace parameter if there are multiple workspaces
const parametersSchema =
filteredWorkspaces.length === 1
? z.object(baseParameters)
: z.object({
...baseParameters,
workspace: z
.string()
.refine((val) => filteredWorkspaces.includes(val), {
message: `Workspace must be one of: ${filteredWorkspaces.join(", ")}`,
})
.describe(
`The workspace where the package should be installed. 'root' installs at project level. Available workspaces: ${filteredWorkspaces.join(", ")}.`,
),
})
return Tool.define({
id: "add-package",
description: `Adds npm packages to specific workspaces in the monorepo
- Installs packages using pnpm in the correct workspace directory
- Supports both regular dependencies and dev dependencies
- Works with configured workspace restrictions
- Validates package names and provides installation feedback
Use this tool when you need to add dependencies to any part of the monorepo`,
parameters: parametersSchema,
async execute(params, ctx) {
const { packageName: rawPackageName, dev = false } = params
// Use the single available workspace if workspace parameter is not present
const workspace =
filteredWorkspaces.length === 1
? filteredWorkspaces[0]
: (params as any).workspace
// Strip version from package name if present (e.g., "lodash@4.17.21" -> "lodash")
const packageName =
rawPackageName.includes("@") && !rawPackageName.startsWith("@")
? rawPackageName.split("@")[0] // Regular package like lodash@4.17.21
: rawPackageName.split("@").length > 2
? `@${rawPackageName.split("@")[1]}` // Scoped package like @types/node@1.0.0
: rawPackageName // No version specified
// Validate package name format (without version)
if (
!packageName.match(
/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,
)
) {
throw new Error(`Invalid package name format: ${packageName}`)
}
const workspacePath = workspace === "root" ? "." : `packages/${workspace}`
// Build pnpm command
const args = ["add"]
if (dev) args.push("--save-dev")
args.push(packageName)
// Add workspace filter if not root
if (workspace !== "root") {
args.unshift("--filter", workspace)
} else {
args.unshift("--workspace-root")
}
const proc = spawn(["pnpm", ...args], {
cwd: process.cwd(),
stdout: "pipe",
stderr: "pipe",
})
const stdout = await new Response(proc.stdout).text()
const stderr = await new Response(proc.stderr).text()
const exitCode = await proc.exited
if (exitCode !== 0) {
throw new Error(`pnpm failed with exit code ${exitCode}: ${stderr}`)
}
const alreadyInstalled =
stderr.includes("Already up to date") ||
stdout.includes("Already up to date")
const metadata = {
packageName,
originalInput: rawPackageName,
workspace,
isDev: dev,
alreadyInstalled,
workspacePath,
title: `Package ${dev ? "(dev) " : ""}added to ${workspace}`,
availableWorkspaces: filteredWorkspaces,
singleWorkspace: filteredWorkspaces.length === 1,
}
ctx.metadata(metadata)
return {
metadata,
output: alreadyInstalled
? `Package ${packageName} was already installed in ${workspace}${rawPackageName !== packageName ? ` (version stripped from ${rawPackageName})` : ""}`
: `Successfully added ${packageName} ${dev ? "as dev dependency " : ""}to ${workspace} workspace${rawPackageName !== packageName ? ` (version stripped from ${rawPackageName})` : ""}\n\n${stdout}`,
}
},
})
}