bun-workspaces 1.0.0-alpha.4 → 1.0.0-alpha.7
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 +4 -0
- package/bin/cli.js +0 -1
- package/package.json +5 -24
- package/src/cli/cli.d.ts +13 -0
- package/src/cli/cli.mjs +43 -0
- package/src/cli/globalOptions.d.ts +11 -0
- package/src/cli/globalOptions.mjs +65 -0
- package/src/cli/index.mjs +2 -0
- package/src/cli/projectCommands.d.ts +8 -0
- package/src/cli/projectCommands.mjs +179 -0
- package/src/config/bunWorkspacesConfig.d.ts +13 -0
- package/src/config/bunWorkspacesConfig.mjs +18 -0
- package/src/config/configFile.d.ts +3 -0
- package/src/config/configFile.mjs +21 -0
- package/src/config/index.d.ts +2 -0
- package/src/config/index.mjs +3 -0
- package/src/index.mjs +3 -0
- package/src/internal/bunVersion.d.ts +14 -0
- package/src/internal/bunVersion.mjs +8 -0
- package/src/internal/env.d.ts +5 -0
- package/src/internal/env.mjs +12 -0
- package/src/internal/error.d.ts +7 -0
- package/src/internal/error.mjs +26 -0
- package/src/internal/logger.d.ts +21 -0
- package/src/internal/logger.mjs +85 -0
- package/src/internal/regex.d.ts +2 -0
- package/src/internal/regex.mjs +3 -0
- package/src/project/errors.d.ts +1 -0
- package/src/project/errors.mjs +3 -0
- package/src/project/index.d.ts +1 -0
- package/src/project/index.mjs +2 -0
- package/src/project/project.d.ts +31 -0
- package/src/project/project.mjs +72 -0
- package/src/project/scriptCommand.d.ts +15 -0
- package/src/project/scriptCommand.mjs +18 -0
- package/src/workspaces/errors.d.ts +1 -0
- package/src/workspaces/errors.mjs +3 -0
- package/src/workspaces/findWorkspaces.d.ts +17 -0
- package/src/workspaces/findWorkspaces.mjs +66 -0
- package/src/workspaces/index.d.ts +3 -0
- package/src/workspaces/index.mjs +2 -0
- package/src/workspaces/packageJson.d.ts +8 -0
- package/src/workspaces/packageJson.mjs +65 -0
- package/src/workspaces/workspace.d.ts +13 -0
- package/src/workspaces/workspace.mjs +0 -0
- package/LICENSE.md +0 -21
- package/bun.lock +0 -573
- package/src/cli/cli.ts +0 -87
- package/src/cli/globalOptions.ts +0 -122
- package/src/cli/projectCommands.ts +0 -397
- package/src/config/bunWorkspacesConfig.ts +0 -62
- package/src/config/configFile.ts +0 -33
- package/src/config/index.ts +0 -7
- package/src/internal/bunVersion.ts +0 -26
- package/src/internal/env.ts +0 -25
- package/src/internal/error.ts +0 -38
- package/src/internal/logger.ts +0 -180
- package/src/internal/regex.ts +0 -5
- package/src/project/errors.ts +0 -6
- package/src/project/index.ts +0 -6
- package/src/project/project.ts +0 -155
- package/src/project/scriptCommand.ts +0 -40
- package/src/workspaces/errors.ts +0 -14
- package/src/workspaces/findWorkspaces.ts +0 -137
- package/src/workspaces/index.ts +0 -7
- package/src/workspaces/packageJson.ts +0 -166
- package/src/workspaces/workspace.ts +0 -14
- package/tsconfig.json +0 -28
- /package/src/cli/{index.ts → index.d.ts} +0 -0
- /package/src/{index.ts → index.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -79,6 +79,10 @@ bw script-info my-script --json
|
|
|
79
79
|
# in their `scripts` field
|
|
80
80
|
bw run my-script
|
|
81
81
|
|
|
82
|
+
# By default, a prefix is added to the script output with the workspace name
|
|
83
|
+
# This can be disabled with the --noPrefix option
|
|
84
|
+
bw run my-script --noPrefix
|
|
85
|
+
|
|
82
86
|
# Run a script for a specific workspace by its package.json name or alias from the config
|
|
83
87
|
bw run my-script my-workspace
|
|
84
88
|
|
package/bin/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-workspaces",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
4
|
-
"main": "src/index.
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0-alpha.7",
|
|
4
|
+
"main": "src/index.mjs",
|
|
5
|
+
"types": "src/index.d.ts",
|
|
6
|
+
"homepage": "https://github.com/ScottMorse/bun-workspaces",
|
|
6
7
|
"bin": {
|
|
7
8
|
"bun-workspaces": "bin/cli.js"
|
|
8
9
|
},
|
|
@@ -12,28 +13,8 @@
|
|
|
12
13
|
"libraryConsumer": "^1.1.x"
|
|
13
14
|
}
|
|
14
15
|
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"cli": "bun run bin/cli.js",
|
|
17
|
-
"cli:dev": "_BW_RUNTIME_MODE=development bun run bin/cli.js",
|
|
18
|
-
"type-check": "tsc --noEmit",
|
|
19
|
-
"lint": "eslint .",
|
|
20
|
-
"format": "prettier --write .",
|
|
21
|
-
"format-check": "prettier --check ."
|
|
22
|
-
},
|
|
23
|
-
"devDependencies": {
|
|
24
|
-
"@types/bun": "^1.3.0",
|
|
25
|
-
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
26
|
-
"@typescript-eslint/parser": "^8.46.0",
|
|
27
|
-
"eslint": "^9.37.0",
|
|
28
|
-
"eslint-plugin-import": "^2.32.0",
|
|
29
|
-
"prettier": "^3.6.2",
|
|
30
|
-
"typescript-eslint": "^8.46.0"
|
|
31
|
-
},
|
|
32
16
|
"dependencies": {
|
|
33
17
|
"commander": "^12.1.0",
|
|
34
18
|
"glob": "^11.0.3"
|
|
35
|
-
},
|
|
36
|
-
"peerDependencies": {
|
|
37
|
-
"typescript": "^5.9.3"
|
|
38
19
|
}
|
|
39
|
-
}
|
|
20
|
+
}
|
package/src/cli/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
export interface RunCliOptions {
|
|
3
|
+
argv?: string | string[];
|
|
4
|
+
}
|
|
5
|
+
export interface CliProgram {
|
|
6
|
+
run: (options?: RunCliOptions) => Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateCliProgramOptions {
|
|
9
|
+
handleError?: (error: Error) => void;
|
|
10
|
+
postInit?: (program: Command) => unknown;
|
|
11
|
+
defaultCwd?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const createCliProgram: ({ handleError, postInit, defaultCwd, }?: CreateCliProgramOptions) => CliProgram;
|
package/src/cli/cli.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createCommand } from "commander";
|
|
2
|
+
import package_0 from "../../package.json";
|
|
3
|
+
import { getRequiredBunVersion, validateCurrentBunVersion } from "../internal/bunVersion.mjs";
|
|
4
|
+
import { logger } from "../internal/logger.mjs";
|
|
5
|
+
import { initializeWithGlobalOptions } from "./globalOptions.mjs";
|
|
6
|
+
import { defineProjectCommands } from "./projectCommands.mjs";
|
|
7
|
+
const createCliProgram = ({ handleError, postInit, defaultCwd = process.cwd() } = {})=>{
|
|
8
|
+
const run = async ({ argv = process.argv } = {})=>{
|
|
9
|
+
const errorListener = handleError ?? ((error)=>{
|
|
10
|
+
logger.error(error);
|
|
11
|
+
logger.error("Unhandled rejection");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
|
14
|
+
process.on("unhandledRejection", errorListener);
|
|
15
|
+
try {
|
|
16
|
+
const program = createCommand("bunx bun-workspaces").description("CLI for utilities for Bun workspaces").version(package_0.version).configureOutput({
|
|
17
|
+
writeOut: (s)=>logger.info(s),
|
|
18
|
+
writeErr: (s)=>s.startsWith("Usage") ? logger.info(s) : logger.error(s)
|
|
19
|
+
});
|
|
20
|
+
postInit?.(program);
|
|
21
|
+
if (!validateCurrentBunVersion()) {
|
|
22
|
+
logger.error(`Bun version mismatch. Required: ${getRequiredBunVersion()}, Found: ${Bun.version}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const args = "string" == typeof argv ? argv.split(" ") : argv;
|
|
26
|
+
const { project } = initializeWithGlobalOptions(program, args, defaultCwd);
|
|
27
|
+
if (!project) return;
|
|
28
|
+
defineProjectCommands({
|
|
29
|
+
program,
|
|
30
|
+
project
|
|
31
|
+
});
|
|
32
|
+
await program.parseAsync(args);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
errorListener(error);
|
|
35
|
+
} finally{
|
|
36
|
+
process.off("unhandledRejection", errorListener);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
run
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export { createCliProgram };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
import { type LogLevelSetting } from "../internal/logger";
|
|
3
|
+
export interface CliGlobalOptions {
|
|
4
|
+
logLevel: LogLevelSetting;
|
|
5
|
+
cwd: string;
|
|
6
|
+
configFile?: string;
|
|
7
|
+
}
|
|
8
|
+
export type CliGlobalOptionName = keyof CliGlobalOptions;
|
|
9
|
+
export declare const initializeWithGlobalOptions: (program: Command, args: string[], defaultCwd: string) => {
|
|
10
|
+
project: import("..").Project;
|
|
11
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { Option } from "commander";
|
|
3
|
+
import { loadConfigFile } from "../config/index.mjs";
|
|
4
|
+
import { LOG_LEVELS, logger } from "../internal/logger.mjs";
|
|
5
|
+
import { createProject } from "../project/index.mjs";
|
|
6
|
+
const getWorkingDirectory = (program, args, defaultCwd)=>{
|
|
7
|
+
program.addOption(new Option("-d --cwd <path>", "Working directory").default(defaultCwd));
|
|
8
|
+
program.parseOptions(args);
|
|
9
|
+
return program.opts().cwd;
|
|
10
|
+
};
|
|
11
|
+
const getConfig = (program, args)=>{
|
|
12
|
+
program.addOption(new Option("-c --configFile <path>", "Config file"));
|
|
13
|
+
program.parseOptions(args);
|
|
14
|
+
return program.opts().configFile;
|
|
15
|
+
};
|
|
16
|
+
const defineGlobalOptions = (program, args, defaultCwd)=>{
|
|
17
|
+
const cwd = getWorkingDirectory(program, args, defaultCwd);
|
|
18
|
+
const configFilePath = getConfig(program, args);
|
|
19
|
+
const config = loadConfigFile(configFilePath, cwd);
|
|
20
|
+
const globalOptions = {
|
|
21
|
+
logLevel: {
|
|
22
|
+
shortName: "l",
|
|
23
|
+
description: "Log levels",
|
|
24
|
+
defaultValue: config?.cli?.logLevel ?? logger.printLevel,
|
|
25
|
+
values: [
|
|
26
|
+
...LOG_LEVELS,
|
|
27
|
+
"silent"
|
|
28
|
+
],
|
|
29
|
+
param: "level"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
Object.entries(globalOptions).forEach(([name, { shortName, description, param, values, defaultValue }])=>{
|
|
33
|
+
const option = new Option(`-${shortName} --${name}${param ? ` <${param}>` : ""}`, description).default(defaultValue);
|
|
34
|
+
program.addOption(values?.length ? option.choices(values) : option);
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
cwd,
|
|
38
|
+
config
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const applyGlobalOptions = (options, config)=>{
|
|
42
|
+
logger.printLevel = options.logLevel;
|
|
43
|
+
logger.debug("Log level: " + options.logLevel);
|
|
44
|
+
const project = createProject({
|
|
45
|
+
rootDir: options.cwd,
|
|
46
|
+
workspaceAliases: config?.project?.workspaceAliases ?? {}
|
|
47
|
+
});
|
|
48
|
+
logger.debug(`Project: ${JSON.stringify(project.name)} (${project.workspaces.length} workspace${1 === project.workspaces.length ? "" : "s"})`);
|
|
49
|
+
logger.debug("Project root: " + path.resolve(project.rootDir));
|
|
50
|
+
return {
|
|
51
|
+
project
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const initializeWithGlobalOptions = (program, args, defaultCwd)=>{
|
|
55
|
+
program.allowUnknownOption(true);
|
|
56
|
+
const { cwd, config } = defineGlobalOptions(program, args, defaultCwd);
|
|
57
|
+
program.parseOptions(args);
|
|
58
|
+
program.allowUnknownOption(false);
|
|
59
|
+
const options = program.opts();
|
|
60
|
+
return applyGlobalOptions({
|
|
61
|
+
...options,
|
|
62
|
+
cwd
|
|
63
|
+
}, config);
|
|
64
|
+
};
|
|
65
|
+
export { initializeWithGlobalOptions };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
import type { Project } from "../project";
|
|
3
|
+
export interface ProjectCommandsContext {
|
|
4
|
+
project: Project;
|
|
5
|
+
program: Command;
|
|
6
|
+
}
|
|
7
|
+
export declare const commandOutputLogger: import("../internal/logger").Logger;
|
|
8
|
+
export declare const defineProjectCommands: (context: ProjectCommandsContext) => void;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { BunWorkspacesError } from "../internal/error.mjs";
|
|
2
|
+
import { createLogger, logger } from "../internal/logger.mjs";
|
|
3
|
+
const createWorkspaceInfoLines = (workspace)=>[
|
|
4
|
+
`Workspace: ${workspace.name}`,
|
|
5
|
+
` - Aliases: ${workspace.aliases.join(", ")}`,
|
|
6
|
+
` - Path: ${workspace.path}`,
|
|
7
|
+
` - Glob Match: ${workspace.matchPattern}`,
|
|
8
|
+
` - Scripts: ${Object.keys(workspace.packageJson.scripts).sort().join(", ")}`
|
|
9
|
+
];
|
|
10
|
+
const createScriptInfoLines = (script, workspaces)=>[
|
|
11
|
+
`Script: ${script}`,
|
|
12
|
+
...workspaces.map((workspace)=>` - ${workspace.name}`)
|
|
13
|
+
];
|
|
14
|
+
const createJsonLines = (data, options)=>JSON.stringify(data, null, options.pretty ? 2 : void 0).split("\n");
|
|
15
|
+
const commandOutputLogger = createLogger("");
|
|
16
|
+
commandOutputLogger.printLevel = "info";
|
|
17
|
+
const listWorkspaces = ({ program, project })=>{
|
|
18
|
+
program.command("list-workspaces [pattern]").aliases([
|
|
19
|
+
"ls",
|
|
20
|
+
"list"
|
|
21
|
+
]).description("List all workspaces").option("--name-only", "Only show workspace names").option("--json", "Output as JSON").option("--pretty", "Pretty print JSON").action((pattern, options)=>{
|
|
22
|
+
logger.debug(`Command: List workspaces (options: ${JSON.stringify(options)})`);
|
|
23
|
+
const lines = [];
|
|
24
|
+
const workspaces = pattern ? project.findWorkspacesByPattern(pattern) : project.workspaces;
|
|
25
|
+
if (options.json) lines.push(...createJsonLines(options.nameOnly ? workspaces.map(({ name })=>name) : workspaces, options));
|
|
26
|
+
else workspaces.forEach((workspace)=>{
|
|
27
|
+
if (options.nameOnly) lines.push(workspace.name);
|
|
28
|
+
else lines.push(...createWorkspaceInfoLines(workspace));
|
|
29
|
+
});
|
|
30
|
+
if (!lines.length) logger.info("No workspaces found");
|
|
31
|
+
if (lines.length) commandOutputLogger.info(lines.join("\n"));
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const listScripts = ({ program, project })=>{
|
|
35
|
+
program.command("list-scripts").description("List all scripts available with their workspaces").option("--name-only", "Only show script names").option("--json", "Output as JSON").option("--pretty", "Pretty print JSON").action((options)=>{
|
|
36
|
+
logger.debug(`Command: List scripts (options: ${JSON.stringify(options)})`);
|
|
37
|
+
const scripts = project.listScriptsWithWorkspaces();
|
|
38
|
+
const lines = [];
|
|
39
|
+
if (options.json) lines.push(...createJsonLines(options.nameOnly ? Object.keys(scripts) : Object.values(scripts).map(({ workspaces, ...rest })=>({
|
|
40
|
+
...rest,
|
|
41
|
+
workspaces: workspaces.map(({ name })=>name)
|
|
42
|
+
})), options));
|
|
43
|
+
else {
|
|
44
|
+
Object.values(scripts).sort(({ name: nameA }, { name: nameB })=>nameA.localeCompare(nameB)).forEach(({ name, workspaces })=>{
|
|
45
|
+
if (options.nameOnly) lines.push(name);
|
|
46
|
+
else lines.push(...createScriptInfoLines(name, workspaces));
|
|
47
|
+
});
|
|
48
|
+
if (!lines.length) logger.info("No scripts found");
|
|
49
|
+
}
|
|
50
|
+
if (lines.length) commandOutputLogger.info(lines.join("\n"));
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const workspaceInfo = ({ program, project })=>{
|
|
54
|
+
program.command("workspace-info <workspace>").aliases([
|
|
55
|
+
"info"
|
|
56
|
+
]).description("Show information about a workspace").option("--json", "Output as JSON").option("--pretty", "Pretty print JSON").action((workspaceName, options)=>{
|
|
57
|
+
logger.debug(`Command: Workspace info for ${workspaceName} (options: ${JSON.stringify(options)})`);
|
|
58
|
+
const workspace = project.findWorkspaceByNameOrAlias(workspaceName);
|
|
59
|
+
if (!workspace) return void logger.error(`Workspace not found: (options: ${JSON.stringify(workspaceName)})`);
|
|
60
|
+
commandOutputLogger.info((options.json ? createJsonLines(workspace, options) : createWorkspaceInfoLines(workspace)).join("\n"));
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const scriptInfo = ({ program, project })=>{
|
|
64
|
+
program.command("script-info <script>").description("Show information about a script").option("--workspaces-only", "Only show script's workspace names").option("--json", "Output as JSON").option("--pretty", "Pretty print JSON").action((script, options)=>{
|
|
65
|
+
logger.debug(`Command: Script info for ${script} (options: ${JSON.stringify(options)})`);
|
|
66
|
+
const scripts = project.listScriptsWithWorkspaces();
|
|
67
|
+
const scriptMetadata = scripts[script];
|
|
68
|
+
if (!scriptMetadata) return void logger.error(`Script not found: ${JSON.stringify(script)} (available: ${Object.keys(scripts).join(", ")})`);
|
|
69
|
+
commandOutputLogger.info((options.json ? createJsonLines(options.workspacesOnly ? scriptMetadata.workspaces.map(({ name })=>name) : {
|
|
70
|
+
name: scriptMetadata.name,
|
|
71
|
+
workspaces: scriptMetadata.workspaces.map(({ name })=>name)
|
|
72
|
+
}, options) : options.workspacesOnly ? scriptMetadata.workspaces.map(({ name })=>name) : createScriptInfoLines(script, scriptMetadata.workspaces)).join("\n"));
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const runScript = ({ program, project })=>{
|
|
76
|
+
program.command("run <script> [workspaces...]").description("Run a script in all workspaces").option("--parallel", "Run the scripts in parallel").option("--args <args>", "Args to append to the script command", "").option("--noPrefix", "Do not prefix the workspace name to the script output", "").action(async (script, _workspaces, options)=>{
|
|
77
|
+
logger.debug(`Command: Run script ${JSON.stringify(script)} for ${_workspaces.length ? "workspaces " + _workspaces.join(", ") : "all workspaces"} (parallel: ${!!options.parallel}, method: ${JSON.stringify(options.method)}, args: ${JSON.stringify(options.args)})`);
|
|
78
|
+
const workspaces = _workspaces.length ? _workspaces.flatMap((workspacePattern)=>{
|
|
79
|
+
if (workspacePattern.includes("*")) return project.findWorkspacesByPattern(workspacePattern).filter(({ packageJson: { scripts } })=>scripts?.[script]).map(({ name })=>name);
|
|
80
|
+
return [
|
|
81
|
+
workspacePattern
|
|
82
|
+
];
|
|
83
|
+
}) : project.listWorkspacesWithScript(script).map(({ name })=>name);
|
|
84
|
+
if (!workspaces.length) program.error(`No ${_workspaces.length ? "matching " : ""}workspaces found for script ${JSON.stringify(script)}`);
|
|
85
|
+
let scriptCommands;
|
|
86
|
+
try {
|
|
87
|
+
scriptCommands = workspaces.map((workspaceName)=>project.createScriptCommand({
|
|
88
|
+
scriptName: script,
|
|
89
|
+
workspaceName,
|
|
90
|
+
method: "cd",
|
|
91
|
+
args: options.args.replace(/<workspace>/g, workspaceName)
|
|
92
|
+
}));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
program.error(error.message);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
const runCommand = async ({ command, scriptName, workspace })=>{
|
|
98
|
+
const commandLogger = createLogger(`${workspace.name}:${scriptName}`);
|
|
99
|
+
const splitCommand = command.command.split(/\s+/g);
|
|
100
|
+
commandLogger.debug(`Running script ${scriptName} in workspace ${workspace.name} (cwd: ${command.cwd}): ${splitCommand.join(" ")}`);
|
|
101
|
+
const isSilent = "silent" === logger.printLevel;
|
|
102
|
+
const proc = Bun.spawn(command.command.split(/\s+/g), {
|
|
103
|
+
cwd: command.cwd,
|
|
104
|
+
env: {
|
|
105
|
+
...process.env,
|
|
106
|
+
FORCE_COLOR: "1"
|
|
107
|
+
},
|
|
108
|
+
stdout: isSilent ? "ignore" : "pipe",
|
|
109
|
+
stderr: isSilent ? "ignore" : "pipe"
|
|
110
|
+
});
|
|
111
|
+
const linePrefix = options.noPrefix ? "" : `[${workspace.name}:${scriptName}] `;
|
|
112
|
+
if (proc.stdout) for await (const chunk of proc.stdout)commandLogger.logOutput(chunk, "info", process.stdout, linePrefix);
|
|
113
|
+
if (proc.stderr) for await (const chunk of proc.stderr)commandLogger.logOutput(chunk, "error", process.stderr, linePrefix);
|
|
114
|
+
await proc.exited;
|
|
115
|
+
return {
|
|
116
|
+
scriptName,
|
|
117
|
+
workspace,
|
|
118
|
+
command,
|
|
119
|
+
success: 0 === proc.exitCode,
|
|
120
|
+
error: 0 === proc.exitCode ? null : new BunWorkspacesError(`Script exited with code ${proc.exitCode}`)
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const results = [];
|
|
124
|
+
if (options.parallel) {
|
|
125
|
+
let i = 0;
|
|
126
|
+
for await (const result of (await Promise.allSettled(scriptCommands.map(runCommand)))){
|
|
127
|
+
if ("rejected" === result.status) results.push({
|
|
128
|
+
success: false,
|
|
129
|
+
workspaceName: workspaces[i],
|
|
130
|
+
error: result.reason
|
|
131
|
+
});
|
|
132
|
+
else results.push({
|
|
133
|
+
success: result.value.success,
|
|
134
|
+
workspaceName: workspaces[i],
|
|
135
|
+
error: result.value.error
|
|
136
|
+
});
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
let i = 0;
|
|
141
|
+
for (const command of scriptCommands){
|
|
142
|
+
try {
|
|
143
|
+
const result = await runCommand(command);
|
|
144
|
+
results.push({
|
|
145
|
+
success: result.success,
|
|
146
|
+
workspaceName: workspaces[i],
|
|
147
|
+
error: result.error
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
results.push({
|
|
151
|
+
success: false,
|
|
152
|
+
workspaceName: workspaces[i],
|
|
153
|
+
error: error
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
let failCount = 0;
|
|
160
|
+
results.forEach(({ success, workspaceName })=>{
|
|
161
|
+
if (!success) failCount++;
|
|
162
|
+
logger.info(`${success ? "✅" : "❌"} ${workspaceName}: ${script}`);
|
|
163
|
+
});
|
|
164
|
+
const s = 1 === results.length ? "" : "s";
|
|
165
|
+
if (failCount) {
|
|
166
|
+
const message = `${failCount} of ${results.length} script${s} failed`;
|
|
167
|
+
logger.error(message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
} else logger.info(`${results.length} script${s} ran successfully`);
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
const defineProjectCommands = (context)=>{
|
|
173
|
+
listWorkspaces(context);
|
|
174
|
+
listScripts(context);
|
|
175
|
+
workspaceInfo(context);
|
|
176
|
+
scriptInfo(context);
|
|
177
|
+
runScript(context);
|
|
178
|
+
};
|
|
179
|
+
export { commandOutputLogger, defineProjectCommands };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type LogLevelSetting } from "../internal/logger";
|
|
2
|
+
export interface CliConfig {
|
|
3
|
+
logLevel?: LogLevelSetting;
|
|
4
|
+
}
|
|
5
|
+
export interface ProjectConfig {
|
|
6
|
+
/** A map of aliases to a workspace name */
|
|
7
|
+
workspaceAliases?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export interface BunWorkspacesConfig {
|
|
10
|
+
cli?: CliConfig;
|
|
11
|
+
project?: ProjectConfig;
|
|
12
|
+
}
|
|
13
|
+
export declare const validateBunWorkspacesConfig: (config: BunWorkspacesConfig) => void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { validateLogLevel } from "../internal/logger.mjs";
|
|
2
|
+
const validateCliConfig = (cliConfig)=>{
|
|
3
|
+
if ("object" != typeof cliConfig || Array.isArray(cliConfig)) throw new Error('Config file: "cli" must be an object');
|
|
4
|
+
if (cliConfig?.logLevel) validateLogLevel(cliConfig.logLevel);
|
|
5
|
+
};
|
|
6
|
+
const validateProjectConfig = (projectConfig)=>{
|
|
7
|
+
if ("object" != typeof projectConfig || Array.isArray(projectConfig)) throw new Error('Config file: "project" must be an object');
|
|
8
|
+
if (projectConfig?.workspaceAliases) {
|
|
9
|
+
if ("object" != typeof projectConfig.workspaceAliases || Array.isArray(projectConfig.workspaceAliases)) throw new Error("Config file: project.workspaceAliases must be an object");
|
|
10
|
+
for (const alias of Object.values(projectConfig.workspaceAliases))if ("string" != typeof alias) throw new Error("Config file: project.workspaceAliases must be an object with string keys and values");
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const validateBunWorkspacesConfig = (config)=>{
|
|
14
|
+
if ("object" != typeof config || Array.isArray(config)) throw new Error("Config file: must be an object");
|
|
15
|
+
if (void 0 !== config.cli) validateCliConfig(config.cli);
|
|
16
|
+
if (void 0 !== config.project) validateProjectConfig(config.project);
|
|
17
|
+
};
|
|
18
|
+
export { validateBunWorkspacesConfig };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { validateBunWorkspacesConfig } from "./bunWorkspacesConfig.mjs";
|
|
4
|
+
const DEFAULT_CONFIG_FILE_PATH = "bw.json";
|
|
5
|
+
const loadConfigFile = (filePath, rootDir = ".")=>{
|
|
6
|
+
if (!filePath) {
|
|
7
|
+
const defaultFilePath = path.resolve(rootDir, DEFAULT_CONFIG_FILE_PATH);
|
|
8
|
+
if (!fs.existsSync(defaultFilePath)) return null;
|
|
9
|
+
filePath = defaultFilePath;
|
|
10
|
+
}
|
|
11
|
+
filePath = path.resolve(rootDir, filePath);
|
|
12
|
+
const configFile = fs.readFileSync(filePath, "utf8");
|
|
13
|
+
try {
|
|
14
|
+
const json = JSON.parse(configFile);
|
|
15
|
+
validateBunWorkspacesConfig(json);
|
|
16
|
+
return json;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error(`Config file: "${filePath}" is not a valid JSON file: ${error}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export { DEFAULT_CONFIG_FILE_PATH, loadConfigFile };
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const LIBRARY_CONSUMER_BUN_VERSION: string;
|
|
2
|
+
export declare const BUILD_BUN_VERSION: string;
|
|
3
|
+
export declare const getRequiredBunVersion: (build?: boolean | undefined) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Validates that the provided version satisfies the required Bun version
|
|
6
|
+
* specified in the root `package.json`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const validateBunVersion: (version: string, build?: boolean | undefined) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* Validates that the Bun version of the current script satisfies the
|
|
12
|
+
* required Bun version specified in the root `package.json`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const validateCurrentBunVersion: (build?: boolean | undefined) => boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import package_0 from "../../package.json";
|
|
2
|
+
const LIBRARY_CONSUMER_BUN_VERSION = package_0.custom.bunVersion.libraryConsumer;
|
|
3
|
+
const BUILD_BUN_VERSION = package_0.custom.bunVersion.build;
|
|
4
|
+
const getRequiredBunVersion = (build)=>build ? BUILD_BUN_VERSION : LIBRARY_CONSUMER_BUN_VERSION;
|
|
5
|
+
const _Bun = "undefined" == typeof Bun ? null : Bun;
|
|
6
|
+
const validateBunVersion = (version, build)=>_Bun ? _Bun.semver.satisfies(version, getRequiredBunVersion(build)) : false;
|
|
7
|
+
const validateCurrentBunVersion = (build)=>validateBunVersion(_Bun?.version ?? "(Error: not Bun runtime)", build);
|
|
8
|
+
export { BUILD_BUN_VERSION, LIBRARY_CONSUMER_BUN_VERSION, getRequiredBunVersion, validateBunVersion, validateCurrentBunVersion };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const RUNTIME_MODE_VALUES = [
|
|
2
|
+
"development",
|
|
3
|
+
"production",
|
|
4
|
+
"test"
|
|
5
|
+
];
|
|
6
|
+
const _RUNTIME_MODE = process.env._BW_RUNTIME_MODE || (process.env.NODE_ENV?.match(/test(ing)?/) ? "test" : "development" === process.env.NODE_ENV ? "development" : "production");
|
|
7
|
+
const RUNTIME_MODE = RUNTIME_MODE_VALUES.includes(_RUNTIME_MODE) ? _RUNTIME_MODE : "production";
|
|
8
|
+
if (RUNTIME_MODE !== _RUNTIME_MODE) console.error(`Env var RUNTIME_MODE has an invalid value: "${_RUNTIME_MODE}". Defaulting to "${RUNTIME_MODE}". Accepted values: ${RUNTIME_MODE_VALUES.join(", ")}.`);
|
|
9
|
+
const IS_TEST = "test" === RUNTIME_MODE;
|
|
10
|
+
const IS_PRODUCTION = "production" === RUNTIME_MODE;
|
|
11
|
+
const IS_DEVELOPMENT = "development" === RUNTIME_MODE;
|
|
12
|
+
export { IS_DEVELOPMENT, IS_PRODUCTION, IS_TEST, RUNTIME_MODE };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class BunWorkspacesError extends Error {
|
|
2
|
+
name: string;
|
|
3
|
+
}
|
|
4
|
+
export type DefinedErrors<ErrorName extends string> = {
|
|
5
|
+
[name in ErrorName]: typeof BunWorkspacesError;
|
|
6
|
+
};
|
|
7
|
+
export declare const defineErrors: <ErrorName extends string>(...errors: ErrorName[]) => DefinedErrors<ErrorName>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class BunWorkspacesError extends Error {
|
|
2
|
+
name = "BunWorkspacesError";
|
|
3
|
+
}
|
|
4
|
+
const defineErrors = (...errors)=>errors.reduce((acc, error)=>{
|
|
5
|
+
acc[error] = class extends BunWorkspacesError {
|
|
6
|
+
constructor(message){
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = error;
|
|
9
|
+
}
|
|
10
|
+
name = error;
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(acc[error].prototype.constructor, "name", {
|
|
13
|
+
value: error
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(acc[error].constructor, "name", {
|
|
16
|
+
value: error
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(acc[error].prototype, "name", {
|
|
19
|
+
value: error
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(acc[error], "name", {
|
|
22
|
+
value: error
|
|
23
|
+
});
|
|
24
|
+
return acc;
|
|
25
|
+
}, {});
|
|
26
|
+
export { BunWorkspacesError, defineErrors };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
|
|
2
|
+
export type LogLevel = (typeof LOG_LEVELS)[number];
|
|
3
|
+
export type LogLevelSetting = LogLevel | "silent";
|
|
4
|
+
export declare const validateLogLevel: (level: LogLevelSetting) => void;
|
|
5
|
+
export type LogMetadata = Record<string, any>;
|
|
6
|
+
export interface Log<Message extends string | Error = string, Metadata extends LogMetadata = LogMetadata> {
|
|
7
|
+
message: Message;
|
|
8
|
+
level: LogLevel;
|
|
9
|
+
metadata: Metadata;
|
|
10
|
+
time: Date;
|
|
11
|
+
}
|
|
12
|
+
export type Logger = {
|
|
13
|
+
name: string;
|
|
14
|
+
log<Message extends string | Error = string, Metadata extends LogMetadata = LogMetadata>(message: Message, level: LogLevel, metadata?: Metadata): Log<Message, Metadata>;
|
|
15
|
+
logOutput(chunk: Uint8Array, level: LogLevel, stream: NodeJS.WriteStream, prefix: string): Log<string, Record<string, unknown>>;
|
|
16
|
+
printLevel: LogLevelSetting;
|
|
17
|
+
} & {
|
|
18
|
+
[Level in LogLevel]: <Message extends string | Error = string, Metadata extends LogMetadata = LogMetadata>(message: Message, metadata?: Metadata) => Log<Message, Metadata>;
|
|
19
|
+
};
|
|
20
|
+
export declare const createLogger: (name: string) => Logger;
|
|
21
|
+
export declare const logger: Logger;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { IS_PRODUCTION, IS_TEST } from "./env.mjs";
|
|
2
|
+
const LOG_LEVELS = [
|
|
3
|
+
"debug",
|
|
4
|
+
"info",
|
|
5
|
+
"warn",
|
|
6
|
+
"error"
|
|
7
|
+
];
|
|
8
|
+
const getLevelNumber = (level)=>LOG_LEVELS.indexOf(level);
|
|
9
|
+
const validateLogLevel = (level)=>{
|
|
10
|
+
if ("silent" === level) return;
|
|
11
|
+
if (!LOG_LEVELS.includes(level)) throw new Error(`Invalid log level: "${level}". Accepted values: ${LOG_LEVELS.join(", ")}`);
|
|
12
|
+
};
|
|
13
|
+
const createLogger = (name)=>new _Logger(name);
|
|
14
|
+
class _Logger {
|
|
15
|
+
name;
|
|
16
|
+
constructor(name){
|
|
17
|
+
this.name = name;
|
|
18
|
+
}
|
|
19
|
+
log(message, level, metadata) {
|
|
20
|
+
const log = {
|
|
21
|
+
message,
|
|
22
|
+
level,
|
|
23
|
+
metadata: metadata ?? {},
|
|
24
|
+
time: new Date()
|
|
25
|
+
};
|
|
26
|
+
if (this.shouldPrint(level)) {
|
|
27
|
+
const formattedMessage = this.formatLogMessage(message, level);
|
|
28
|
+
if (message instanceof Error) message.message = formattedMessage;
|
|
29
|
+
console[level](message instanceof Error ? message : formattedMessage, ...metadata ? [
|
|
30
|
+
{
|
|
31
|
+
metadata
|
|
32
|
+
}
|
|
33
|
+
] : []);
|
|
34
|
+
}
|
|
35
|
+
return log;
|
|
36
|
+
}
|
|
37
|
+
logOutput(chunk, level, stream, prefix) {
|
|
38
|
+
const message = new TextDecoder().decode(chunk).trim();
|
|
39
|
+
const log = {
|
|
40
|
+
message,
|
|
41
|
+
level,
|
|
42
|
+
metadata: {
|
|
43
|
+
stream,
|
|
44
|
+
isLogOutput: true
|
|
45
|
+
},
|
|
46
|
+
time: new Date()
|
|
47
|
+
};
|
|
48
|
+
if (!this.shouldPrint(level)) return log;
|
|
49
|
+
const linePrefix = `${prefix}\x1b[0m`;
|
|
50
|
+
const lines = message.split(/\r?\n/);
|
|
51
|
+
lines.forEach((line)=>{
|
|
52
|
+
if (line) stream.write(linePrefix + line + "\n");
|
|
53
|
+
});
|
|
54
|
+
return log;
|
|
55
|
+
}
|
|
56
|
+
debug(message, metadata) {
|
|
57
|
+
return this.log(message, "debug", metadata);
|
|
58
|
+
}
|
|
59
|
+
info(message, metadata) {
|
|
60
|
+
return this.log(message, "info", metadata);
|
|
61
|
+
}
|
|
62
|
+
warn(message, metadata) {
|
|
63
|
+
return this.log(message, "warn", metadata);
|
|
64
|
+
}
|
|
65
|
+
error(message, metadata) {
|
|
66
|
+
return this.log(message, "error", metadata);
|
|
67
|
+
}
|
|
68
|
+
get printLevel() {
|
|
69
|
+
return this._printLevel;
|
|
70
|
+
}
|
|
71
|
+
set printLevel(level) {
|
|
72
|
+
this._printLevel = level;
|
|
73
|
+
}
|
|
74
|
+
formatLogMessage(message, level) {
|
|
75
|
+
const content = message instanceof Error ? message.message : message;
|
|
76
|
+
return "debug" === level || "warn" === level ? `[${this.name} ${level.toUpperCase()}]: ${content}` : content;
|
|
77
|
+
}
|
|
78
|
+
_printLevel = IS_PRODUCTION ? "info" : IS_TEST ? "silent" : "debug";
|
|
79
|
+
shouldPrint(level) {
|
|
80
|
+
if ("silent" === this.printLevel) return false;
|
|
81
|
+
return getLevelNumber(level) >= getLevelNumber(this.printLevel);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const logger = createLogger("bun-workspaces");
|
|
85
|
+
export { LOG_LEVELS, createLogger, logger, validateLogLevel };
|