buncargo 1.0.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/bin.ts +253 -0
- package/cli.ts +238 -0
- package/config.ts +194 -0
- package/core/docker.ts +384 -0
- package/core/index.ts +7 -0
- package/core/network.ts +152 -0
- package/core/ports.ts +253 -0
- package/core/process.ts +253 -0
- package/core/utils.ts +127 -0
- package/core/watchdog-runner.ts +111 -0
- package/core/watchdog.ts +196 -0
- package/dist/bin.d.ts +12 -0
- package/dist/cli.d.ts +22 -0
- package/dist/config.d.ts +72 -0
- package/dist/core/docker.d.ts +74 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/network.d.ts +30 -0
- package/dist/core/ports.d.ts +30 -0
- package/dist/core/process.d.ts +52 -0
- package/dist/core/utils.d.ts +60 -0
- package/dist/core/watchdog-runner.d.ts +14 -0
- package/dist/core/watchdog.d.ts +46 -0
- package/dist/environment.d.ts +23 -0
- package/dist/index.d.ts +12 -0
- package/dist/lint.d.ts +46 -0
- package/dist/loader.d.ts +39 -0
- package/dist/prisma.d.ts +29 -0
- package/dist/types.d.ts +391 -0
- package/environment.ts +604 -0
- package/index.ts +103 -0
- package/lint.ts +277 -0
- package/loader.ts +100 -0
- package/package.json +124 -0
- package/prisma.ts +138 -0
- package/readme.md +198 -0
- package/types.ts +538 -0
package/lint.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { $, Glob } from "bun";
|
|
2
|
+
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
+
// Types
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for running workspace typechecks.
|
|
9
|
+
*/
|
|
10
|
+
export interface WorkspaceTypecheckOptions {
|
|
11
|
+
/** Root directory to search from (defaults to cwd) */
|
|
12
|
+
root?: string;
|
|
13
|
+
/** Glob patterns for workspaces to check (defaults to apps/*, packages/*, modules) */
|
|
14
|
+
patterns?: string[];
|
|
15
|
+
/** Maximum concurrent typecheck processes (defaults to 1) */
|
|
16
|
+
concurrency?: number;
|
|
17
|
+
/** Print output to console (defaults to true) */
|
|
18
|
+
verbose?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Result of a single workspace typecheck.
|
|
23
|
+
*/
|
|
24
|
+
export interface WorkspaceTypecheckResult {
|
|
25
|
+
workspace: string;
|
|
26
|
+
duration: number;
|
|
27
|
+
success: boolean;
|
|
28
|
+
fileCount: number;
|
|
29
|
+
errorOutput?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Overall result of running typechecks across all workspaces.
|
|
34
|
+
*/
|
|
35
|
+
export interface TypecheckResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
totalDuration: number;
|
|
38
|
+
totalFiles: number;
|
|
39
|
+
workspaceCount: number;
|
|
40
|
+
results: WorkspaceTypecheckResult[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Workspace {
|
|
44
|
+
path: string;
|
|
45
|
+
fileCount: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
49
|
+
// Constants
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
|
+
|
|
52
|
+
const DEFAULT_PATTERNS = ["apps/*", "packages/*", "modules"];
|
|
53
|
+
const DEFAULT_CONCURRENCY = 1;
|
|
54
|
+
|
|
55
|
+
// Patterns that indicate a corrupted tsgo cache (deadlock/panic)
|
|
56
|
+
const CORRUPTED_CACHE_PATTERNS = [
|
|
57
|
+
"all goroutines are asleep - deadlock",
|
|
58
|
+
"fatal error:",
|
|
59
|
+
"panic:",
|
|
60
|
+
"github.com/microsoft/typescript-go",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
64
|
+
// Helper Functions
|
|
65
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
66
|
+
|
|
67
|
+
function isCorruptedCacheError(output: string): boolean {
|
|
68
|
+
return CORRUPTED_CACHE_PATTERNS.some((pattern) => output.includes(pattern));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function clearTsBuildInfo(
|
|
72
|
+
workspace: string,
|
|
73
|
+
verbose: boolean,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
if (verbose) {
|
|
76
|
+
console.log(`🧹 Clearing corrupted tsbuildinfo cache for ${workspace}...`);
|
|
77
|
+
}
|
|
78
|
+
// Clear both old tsbuildinfo and new tsgo cache
|
|
79
|
+
await $`find ${workspace} -name '*.tsbuildinfo' -type f -delete`
|
|
80
|
+
.nothrow()
|
|
81
|
+
.quiet();
|
|
82
|
+
await $`find ${workspace} -path '*/.cache/tsbuildinfo.json' -type f -delete`
|
|
83
|
+
.nothrow()
|
|
84
|
+
.quiet();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function countTypeScriptFiles(
|
|
88
|
+
pkgPath: string,
|
|
89
|
+
root: string,
|
|
90
|
+
): Promise<number> {
|
|
91
|
+
let count = 0;
|
|
92
|
+
const tsGlob = new Glob(`${pkgPath}/**/*.{ts,tsx}`);
|
|
93
|
+
for await (const _ of tsGlob.scan(root)) {
|
|
94
|
+
count++;
|
|
95
|
+
}
|
|
96
|
+
return count;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function formatErrorOutput(output: string): string {
|
|
100
|
+
return output
|
|
101
|
+
.split("\n")
|
|
102
|
+
.map((line) => {
|
|
103
|
+
return line
|
|
104
|
+
.replace(/\.\.\/\.\.\/packages\/modules\//g, "")
|
|
105
|
+
.replace(/\((\d+),(\d+)\):?/g, ":$1:$2 -");
|
|
106
|
+
})
|
|
107
|
+
.join("\n")
|
|
108
|
+
.trim();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function runSingleTypecheck(
|
|
112
|
+
workspace: string,
|
|
113
|
+
fileCount: number,
|
|
114
|
+
root: string,
|
|
115
|
+
verbose: boolean,
|
|
116
|
+
isRetry = false,
|
|
117
|
+
): Promise<WorkspaceTypecheckResult> {
|
|
118
|
+
const startTime = performance.now();
|
|
119
|
+
if (verbose) {
|
|
120
|
+
console.log(
|
|
121
|
+
`Running typecheck in ${workspace} (${fileCount} files)${isRetry ? " (retry)" : ""}...`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const workspacePath = `${root}/${workspace}`;
|
|
126
|
+
const result = await $`cd ${workspacePath} && bun run typecheck`
|
|
127
|
+
.nothrow()
|
|
128
|
+
.quiet();
|
|
129
|
+
const duration = Number(((performance.now() - startTime) / 1000).toFixed(2));
|
|
130
|
+
const success = result.exitCode === 0;
|
|
131
|
+
|
|
132
|
+
let errorOutput: string | undefined;
|
|
133
|
+
if (!success) {
|
|
134
|
+
const stdout = result.stdout.toString().trim();
|
|
135
|
+
const stderr = result.stderr.toString().trim();
|
|
136
|
+
const parts = [stdout, stderr].filter(Boolean);
|
|
137
|
+
errorOutput = parts.length > 0 ? parts.join("\n") : undefined;
|
|
138
|
+
|
|
139
|
+
// Check for corrupted cache and retry once
|
|
140
|
+
if (!isRetry && errorOutput && isCorruptedCacheError(errorOutput)) {
|
|
141
|
+
await clearTsBuildInfo(workspacePath, verbose);
|
|
142
|
+
return runSingleTypecheck(workspace, fileCount, root, verbose, true);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { workspace, duration, success, fileCount, errorOutput };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function discoverWorkspaces(
|
|
150
|
+
patterns: string[],
|
|
151
|
+
root: string,
|
|
152
|
+
): Promise<Workspace[]> {
|
|
153
|
+
const workspaces: Workspace[] = [];
|
|
154
|
+
|
|
155
|
+
for (const pattern of patterns) {
|
|
156
|
+
const glob = new Glob(`${pattern}/package.json`);
|
|
157
|
+
for await (const match of glob.scan(root)) {
|
|
158
|
+
const pkgPath = match.replace("/package.json", "");
|
|
159
|
+
const pkgJson = await Bun.file(`${root}/${match}`).json();
|
|
160
|
+
if (pkgJson.scripts?.typecheck) {
|
|
161
|
+
const fileCount = await countTypeScriptFiles(pkgPath, root);
|
|
162
|
+
workspaces.push({ path: pkgPath, fileCount });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Sort by file count (smallest first for faster feedback)
|
|
168
|
+
workspaces.sort((a, b) => a.fileCount - b.fileCount);
|
|
169
|
+
|
|
170
|
+
return workspaces;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
174
|
+
// Main Export
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run TypeScript typechecks across all workspaces that have a `typecheck` script.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* const result = await runWorkspaceTypecheck({ verbose: true })
|
|
183
|
+
* if (!result.success) {
|
|
184
|
+
* console.error('Typecheck failed')
|
|
185
|
+
* process.exit(1)
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export async function runWorkspaceTypecheck(
|
|
190
|
+
options: WorkspaceTypecheckOptions = {},
|
|
191
|
+
): Promise<TypecheckResult> {
|
|
192
|
+
const {
|
|
193
|
+
root = process.cwd(),
|
|
194
|
+
patterns = DEFAULT_PATTERNS,
|
|
195
|
+
concurrency = DEFAULT_CONCURRENCY,
|
|
196
|
+
verbose = true,
|
|
197
|
+
} = options;
|
|
198
|
+
|
|
199
|
+
// Discover workspaces
|
|
200
|
+
const workspaces = await discoverWorkspaces(patterns, root);
|
|
201
|
+
|
|
202
|
+
if (workspaces.length === 0) {
|
|
203
|
+
if (verbose) {
|
|
204
|
+
console.log("No workspaces with typecheck script found.");
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
totalDuration: 0,
|
|
209
|
+
totalFiles: 0,
|
|
210
|
+
workspaceCount: 0,
|
|
211
|
+
results: [],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const totalStartTime = performance.now();
|
|
216
|
+
if (verbose) {
|
|
217
|
+
console.log(
|
|
218
|
+
`Running typecheck across ${workspaces.length} workspaces with concurrency limit of ${concurrency}...\n`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const results: WorkspaceTypecheckResult[] = [];
|
|
223
|
+
const running = new Set<Promise<void>>();
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < workspaces.length; i++) {
|
|
226
|
+
const { path, fileCount } = workspaces[i];
|
|
227
|
+
const promise = runSingleTypecheck(path, fileCount, root, verbose).then(
|
|
228
|
+
(result) => {
|
|
229
|
+
results[i] = result;
|
|
230
|
+
running.delete(promise);
|
|
231
|
+
|
|
232
|
+
if (verbose) {
|
|
233
|
+
const icon = result.success ? "✅" : "❌";
|
|
234
|
+
const log = result.success ? console.log : console.error;
|
|
235
|
+
log(
|
|
236
|
+
`${icon} ${result.workspace} (${result.fileCount} files) ${result.success ? "completed" : "failed"} in ${result.duration.toFixed(2)}s`,
|
|
237
|
+
);
|
|
238
|
+
if (!result.success && result.errorOutput) {
|
|
239
|
+
console.error(`\n${formatErrorOutput(result.errorOutput)}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
running.add(promise);
|
|
246
|
+
|
|
247
|
+
if (running.size >= concurrency) {
|
|
248
|
+
await Promise.race(running);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await Promise.all(running);
|
|
253
|
+
|
|
254
|
+
const totalDuration = Number(
|
|
255
|
+
((performance.now() - totalStartTime) / 1000).toFixed(2),
|
|
256
|
+
);
|
|
257
|
+
const totalFiles = workspaces.reduce((sum, w) => sum + w.fileCount, 0);
|
|
258
|
+
const success = results.every((r) => r.success);
|
|
259
|
+
|
|
260
|
+
if (verbose) {
|
|
261
|
+
if (success) {
|
|
262
|
+
console.log(
|
|
263
|
+
`\nAll typecheck checks passed! Total time: ${totalDuration}s (${totalFiles} files)`,
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
console.error(`\nTypecheck failed. Total time: ${totalDuration}s`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
success,
|
|
272
|
+
totalDuration,
|
|
273
|
+
totalFiles,
|
|
274
|
+
workspaceCount: workspaces.length,
|
|
275
|
+
results,
|
|
276
|
+
};
|
|
277
|
+
}
|
package/loader.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createDevEnvironment } from "./environment";
|
|
2
|
+
import type { AppConfig, DevEnvironment, ServiceConfig } from "./types";
|
|
3
|
+
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
// Config Loader
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
|
|
8
|
+
const CONFIG_FILES = [
|
|
9
|
+
"dev.config.ts",
|
|
10
|
+
"dev.config.js",
|
|
11
|
+
"dev-tools.config.ts",
|
|
12
|
+
"dev-tools.config.js",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
let cachedEnv: DevEnvironment<
|
|
16
|
+
Record<string, ServiceConfig>,
|
|
17
|
+
Record<string, AppConfig>
|
|
18
|
+
> | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load the dev environment from the config file.
|
|
22
|
+
* Caches the result for subsequent calls.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { loadDevEnv } from 'buncargo'
|
|
27
|
+
*
|
|
28
|
+
* const env = await loadDevEnv()
|
|
29
|
+
* console.log(env.ports.postgres) // 5432 (or offset port)
|
|
30
|
+
* console.log(env.urls.api) // http://localhost:3000
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export async function loadDevEnv(options?: {
|
|
34
|
+
/** Directory to search for config file. Defaults to process.cwd() */
|
|
35
|
+
cwd?: string;
|
|
36
|
+
/** Skip cache and reload config */
|
|
37
|
+
reload?: boolean;
|
|
38
|
+
}): Promise<
|
|
39
|
+
DevEnvironment<Record<string, ServiceConfig>, Record<string, AppConfig>>
|
|
40
|
+
> {
|
|
41
|
+
if (cachedEnv && !options?.reload) {
|
|
42
|
+
return cachedEnv;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
46
|
+
|
|
47
|
+
for (const file of CONFIG_FILES) {
|
|
48
|
+
const path = `${cwd}/${file}`;
|
|
49
|
+
const exists = await Bun.file(path).exists();
|
|
50
|
+
|
|
51
|
+
if (exists) {
|
|
52
|
+
const mod = await import(path);
|
|
53
|
+
const config = mod.default;
|
|
54
|
+
|
|
55
|
+
if (!config?.projectPrefix || !config?.services) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Invalid config in "${file}". Use defineDevConfig() and export as default.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
cachedEnv = createDevEnvironment(config);
|
|
62
|
+
return cachedEnv;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
throw new Error(
|
|
67
|
+
`No config file found. Create dev.config.ts with: export default defineDevConfig({ ... })`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the cached dev environment synchronously.
|
|
73
|
+
* Throws if loadDevEnv() hasn't been called yet.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* // First load async
|
|
78
|
+
* await loadDevEnv()
|
|
79
|
+
*
|
|
80
|
+
* // Then use sync getter anywhere
|
|
81
|
+
* import { getDevEnv } from 'buncargo'
|
|
82
|
+
* const env = getDevEnv()
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function getDevEnv(): DevEnvironment<
|
|
86
|
+
Record<string, ServiceConfig>,
|
|
87
|
+
Record<string, AppConfig>
|
|
88
|
+
> {
|
|
89
|
+
if (!cachedEnv) {
|
|
90
|
+
throw new Error("Dev environment not loaded. Call loadDevEnv() first.");
|
|
91
|
+
}
|
|
92
|
+
return cachedEnv;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Clear the cached environment.
|
|
97
|
+
*/
|
|
98
|
+
export function clearDevEnvCache(): void {
|
|
99
|
+
cachedEnv = null;
|
|
100
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buncargo",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "A Bun-powered development environment CLI for managing Docker Compose services, dev servers, and environment variables",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "index.ts",
|
|
7
|
+
"main": "index.ts",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/HansKristoffer/buncargo.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "Kristoffer Hansen",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"bun",
|
|
17
|
+
"dev-tools",
|
|
18
|
+
"docker",
|
|
19
|
+
"docker-compose",
|
|
20
|
+
"development",
|
|
21
|
+
"cli",
|
|
22
|
+
"monorepo",
|
|
23
|
+
"dev-server",
|
|
24
|
+
"environment"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"bun": ">=1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"*.ts",
|
|
31
|
+
"core/*.ts",
|
|
32
|
+
"dist",
|
|
33
|
+
"!*.test.ts",
|
|
34
|
+
"!core/*.test.ts"
|
|
35
|
+
],
|
|
36
|
+
"bin": {
|
|
37
|
+
"dev-tools": "bin.ts",
|
|
38
|
+
"buncargo": "bin.ts"
|
|
39
|
+
},
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"bun": "./index.ts",
|
|
44
|
+
"default": "./index.ts"
|
|
45
|
+
},
|
|
46
|
+
"./types": {
|
|
47
|
+
"types": "./dist/types.d.ts",
|
|
48
|
+
"bun": "./types.ts",
|
|
49
|
+
"default": "./types.ts"
|
|
50
|
+
},
|
|
51
|
+
"./config": {
|
|
52
|
+
"types": "./dist/config.d.ts",
|
|
53
|
+
"bun": "./config.ts",
|
|
54
|
+
"default": "./config.ts"
|
|
55
|
+
},
|
|
56
|
+
"./environment": {
|
|
57
|
+
"types": "./dist/environment.d.ts",
|
|
58
|
+
"bun": "./environment.ts",
|
|
59
|
+
"default": "./environment.ts"
|
|
60
|
+
},
|
|
61
|
+
"./core/ports": {
|
|
62
|
+
"types": "./dist/core/ports.d.ts",
|
|
63
|
+
"bun": "./core/ports.ts",
|
|
64
|
+
"default": "./core/ports.ts"
|
|
65
|
+
},
|
|
66
|
+
"./core/docker": {
|
|
67
|
+
"types": "./dist/core/docker.d.ts",
|
|
68
|
+
"bun": "./core/docker.ts",
|
|
69
|
+
"default": "./core/docker.ts"
|
|
70
|
+
},
|
|
71
|
+
"./core/network": {
|
|
72
|
+
"types": "./dist/core/network.d.ts",
|
|
73
|
+
"bun": "./core/network.ts",
|
|
74
|
+
"default": "./core/network.ts"
|
|
75
|
+
},
|
|
76
|
+
"./core/process": {
|
|
77
|
+
"types": "./dist/core/process.d.ts",
|
|
78
|
+
"bun": "./core/process.ts",
|
|
79
|
+
"default": "./core/process.ts"
|
|
80
|
+
},
|
|
81
|
+
"./core/watchdog": {
|
|
82
|
+
"types": "./dist/core/watchdog.d.ts",
|
|
83
|
+
"bun": "./core/watchdog.ts",
|
|
84
|
+
"default": "./core/watchdog.ts"
|
|
85
|
+
},
|
|
86
|
+
"./core/utils": {
|
|
87
|
+
"types": "./dist/core/utils.d.ts",
|
|
88
|
+
"bun": "./core/utils.ts",
|
|
89
|
+
"default": "./core/utils.ts"
|
|
90
|
+
},
|
|
91
|
+
"./cli": {
|
|
92
|
+
"types": "./dist/cli.d.ts",
|
|
93
|
+
"bun": "./cli.ts",
|
|
94
|
+
"default": "./cli.ts"
|
|
95
|
+
},
|
|
96
|
+
"./lint": {
|
|
97
|
+
"types": "./dist/lint.d.ts",
|
|
98
|
+
"bun": "./lint.ts",
|
|
99
|
+
"default": "./lint.ts"
|
|
100
|
+
},
|
|
101
|
+
"./loader": {
|
|
102
|
+
"types": "./dist/loader.d.ts",
|
|
103
|
+
"bun": "./loader.ts",
|
|
104
|
+
"default": "./loader.ts"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"scripts": {
|
|
108
|
+
"build": "bun run build:types",
|
|
109
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
110
|
+
"prepublishOnly": "bun run build",
|
|
111
|
+
"lint": "bun run typecheck && biome check",
|
|
112
|
+
"lint:write": "bun run typecheck && biome check --fix && biome format",
|
|
113
|
+
"typecheck": "tsgo --incremental"
|
|
114
|
+
},
|
|
115
|
+
"devDependencies": {
|
|
116
|
+
"@types/bun": "1.3.2",
|
|
117
|
+
"@biomejs/biome": "2.3.4",
|
|
118
|
+
"@typescript/native-preview": "7.0.0-dev.20260127.1",
|
|
119
|
+
"typescript": "^5.7.0"
|
|
120
|
+
},
|
|
121
|
+
"dependencies": {
|
|
122
|
+
"picocolors": "^1.1.1"
|
|
123
|
+
}
|
|
124
|
+
}
|
package/prisma.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma integration for dev-tools-v2.
|
|
3
|
+
*
|
|
4
|
+
* When `prisma` is configured in defineDevConfig, `dev.prisma` becomes available
|
|
5
|
+
* with methods to run prisma commands against the Docker development database.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // In dev.config.ts
|
|
10
|
+
* const config = defineDevConfig({
|
|
11
|
+
* projectPrefix: 'myapp',
|
|
12
|
+
* services: { postgres: { port: 5432, healthCheck: 'pg_isready' } },
|
|
13
|
+
* prisma: { cwd: 'packages/prisma' } // Enable prisma integration
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* // Usage
|
|
17
|
+
* await dev.prisma.run(['migrate', 'dev'])
|
|
18
|
+
* await dev.prisma.ensureDatabase()
|
|
19
|
+
* const url = dev.prisma.getDatabaseUrl()
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @internal This module is used internally by createDevEnvironment.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { $ } from "bun";
|
|
26
|
+
import {
|
|
27
|
+
isContainerRunning,
|
|
28
|
+
startService,
|
|
29
|
+
waitForServiceByType,
|
|
30
|
+
} from "./core/docker";
|
|
31
|
+
import type {
|
|
32
|
+
AppConfig,
|
|
33
|
+
BuiltInHealthCheck,
|
|
34
|
+
DevEnvironment,
|
|
35
|
+
PrismaConfig,
|
|
36
|
+
PrismaRunner,
|
|
37
|
+
ServiceConfig,
|
|
38
|
+
} from "./types";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a Prisma runner from config (used internally by createDevEnvironment).
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function createPrismaRunner<
|
|
45
|
+
TServices extends Record<string, ServiceConfig>,
|
|
46
|
+
TApps extends Record<string, AppConfig>,
|
|
47
|
+
>(env: DevEnvironment<TServices, TApps>, config: PrismaConfig): PrismaRunner {
|
|
48
|
+
const {
|
|
49
|
+
cwd = "packages/prisma",
|
|
50
|
+
service = "postgres",
|
|
51
|
+
urlEnvVar = "DATABASE_URL",
|
|
52
|
+
} = config;
|
|
53
|
+
|
|
54
|
+
// Map service names to health check types
|
|
55
|
+
const healthCheckTypes: Record<string, BuiltInHealthCheck> = {
|
|
56
|
+
postgres: "pg_isready",
|
|
57
|
+
redis: "redis-cli",
|
|
58
|
+
clickhouse: "http",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function getDatabaseUrl(): string {
|
|
62
|
+
const envVars = env.buildEnvVars();
|
|
63
|
+
const url = envVars[urlEnvVar];
|
|
64
|
+
if (!url) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Environment variable ${urlEnvVar} not found. Make sure your dev config defines it in envVars.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return url;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function ensureDatabase(): Promise<void> {
|
|
73
|
+
const alreadyRunning = await isContainerRunning(env.projectName, service);
|
|
74
|
+
|
|
75
|
+
if (alreadyRunning) {
|
|
76
|
+
console.log(`✓ ${service} already running`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`🐳 Starting ${service}...`);
|
|
81
|
+
|
|
82
|
+
const envVars = env.buildEnvVars();
|
|
83
|
+
startService(env.root, env.projectName, service, envVars, {
|
|
84
|
+
verbose: false,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const port = (env.ports as Record<string, number>)[service];
|
|
88
|
+
if (!port) {
|
|
89
|
+
throw new Error(`Service ${service} not found in dev environment ports`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Use the appropriate health check for the service
|
|
93
|
+
const healthCheckType = healthCheckTypes[service] ?? "tcp";
|
|
94
|
+
console.log(`⏳ Waiting for ${service} to be healthy...`);
|
|
95
|
+
await waitForServiceByType(service, healthCheckType, port, {
|
|
96
|
+
verbose: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function run(args: string[]): Promise<number> {
|
|
101
|
+
if (args.length === 0) {
|
|
102
|
+
console.log(`
|
|
103
|
+
Usage: bun prisma <command> [args...]
|
|
104
|
+
|
|
105
|
+
Examples:
|
|
106
|
+
bun prisma migrate dev # Create new migration
|
|
107
|
+
bun prisma migrate deploy # Apply migrations
|
|
108
|
+
bun prisma db push # Push schema changes
|
|
109
|
+
bun prisma studio # Open Prisma Studio
|
|
110
|
+
bun prisma migrate reset # Reset database
|
|
111
|
+
`);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const port = (env.ports as Record<string, number>)[service];
|
|
116
|
+
|
|
117
|
+
console.log(`
|
|
118
|
+
🔧 Prisma CLI
|
|
119
|
+
Project: ${env.projectName}
|
|
120
|
+
Database: localhost:${port}
|
|
121
|
+
${env.portOffset > 0 ? `(port offset +${env.portOffset})` : ""}
|
|
122
|
+
`);
|
|
123
|
+
|
|
124
|
+
await ensureDatabase();
|
|
125
|
+
|
|
126
|
+
const envVars = env.buildEnvVars();
|
|
127
|
+
|
|
128
|
+
$.env({ ...process.env, ...envVars, [urlEnvVar]: getDatabaseUrl() });
|
|
129
|
+
$.cwd(`${env.root}/${cwd}`);
|
|
130
|
+
|
|
131
|
+
console.log(`🔄 Running: prisma ${args.join(" ")}\n`);
|
|
132
|
+
|
|
133
|
+
const result = await $`bunx prisma ${args}`.nothrow();
|
|
134
|
+
return result.exitCode;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { run, getDatabaseUrl, ensureDatabase };
|
|
138
|
+
}
|