bun-workspaces 1.0.0-alpha.13 → 1.0.0-alpha.15
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/package.json +4 -3
- package/src/cli/createCli.d.ts +2 -0
- package/src/cli/createCli.mjs +14 -10
- package/src/cli/fatalErrorLogger.d.ts +1 -0
- package/src/cli/fatalErrorLogger.mjs +4 -0
- package/src/cli/globalOptions/globalOptions.mjs +18 -10
- package/src/cli/globalOptions/globalOptionsConfig.d.ts +1 -0
- package/src/cli/globalOptions/globalOptionsConfig.mjs +2 -1
- package/src/cli/projectCommands/projectCommandHandlers.d.ts +1 -0
- package/src/cli/projectCommands/projectCommandHandlers.mjs +58 -46
- package/src/cli/projectCommands/projectCommandsConfig.d.ts +5 -4
- package/src/cli/projectCommands/projectCommandsConfig.mjs +4 -3
- package/src/config/bunWorkspacesConfig.mjs +8 -6
- package/src/config/configFile.mjs +7 -4
- package/src/config/errors.d.ts +1 -0
- package/src/config/errors.mjs +3 -0
- package/src/internal/logger.d.ts +1 -1
- package/src/internal/logger.mjs +3 -1
- package/src/project/implementations/fileSystemProject.d.ts +6 -0
- package/src/project/implementations/fileSystemProject.mjs +19 -0
- package/src/project/implementations/memoryProject.d.ts +8 -0
- package/src/project/implementations/memoryProject.mjs +14 -0
- package/src/project/{project.d.ts → implementations/projectBase.d.ts} +14 -6
- package/src/project/{project.mjs → implementations/projectBase.mjs} +8 -26
- package/src/project/index.d.ts +2 -1
- package/src/project/index.mjs +2 -2
- package/src/workspaces/findWorkspaces.mjs +1 -2
- package/src/workspaces/index.d.ts +1 -1
- package/src/workspaces/packageJson.d.ts +1 -1
- package/src/workspaces/packageJson.mjs +1 -1
- package/src/workspaces/workspace.d.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-workspaces",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.15",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.mjs",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
"url": "https://github.com/ScottMorse/bun-workspaces.git"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"bun-workspaces": "bin/cli.js"
|
|
13
|
+
"bun-workspaces": "bin/cli.js",
|
|
14
|
+
"bw": "bun-workspaces"
|
|
14
15
|
},
|
|
15
16
|
"custom": {
|
|
16
17
|
"bunVersion": {
|
|
17
|
-
"build": "1.3.
|
|
18
|
+
"build": "1.3.1",
|
|
18
19
|
"libraryConsumer": "^1.1.x"
|
|
19
20
|
}
|
|
20
21
|
},
|
package/src/cli/createCli.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Command } from "commander";
|
|
2
2
|
export interface RunCliOptions {
|
|
3
3
|
argv?: string | string[];
|
|
4
|
+
/** Should be `true` if args do not include the binary name (e.g. `bunx bun-workspaces`) */
|
|
5
|
+
programmatic?: true;
|
|
4
6
|
}
|
|
5
7
|
export interface CliProgram {
|
|
6
8
|
run: (options?: RunCliOptions) => Promise<void>;
|
package/src/cli/createCli.mjs
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { createCommand } from "commander";
|
|
2
2
|
import package_0 from "../../package.json";
|
|
3
3
|
import { getRequiredBunVersion, validateCurrentBunVersion } from "../internal/bunVersion.mjs";
|
|
4
|
+
import { BunWorkspacesError } from "../internal/error.mjs";
|
|
4
5
|
import { logger } from "../internal/logger.mjs";
|
|
6
|
+
import { fatalErrorLogger } from "./fatalErrorLogger.mjs";
|
|
5
7
|
import { initializeWithGlobalOptions } from "./globalOptions/index.mjs";
|
|
6
8
|
import { defineProjectCommands } from "./projectCommands/index.mjs";
|
|
7
9
|
const createCli = ({ handleError, postInit, defaultCwd = process.cwd() } = {})=>{
|
|
8
|
-
const run = async ({ argv = process.argv } = {})=>{
|
|
10
|
+
const run = async ({ argv = process.argv, programmatic } = {})=>{
|
|
9
11
|
const errorListener = handleError ?? ((error)=>{
|
|
10
|
-
|
|
11
|
-
logger.error("Unhandled rejection");
|
|
12
|
+
fatalErrorLogger.error(error);
|
|
12
13
|
process.exit(1);
|
|
13
14
|
});
|
|
14
15
|
process.on("unhandledRejection", errorListener);
|
|
15
16
|
try {
|
|
16
|
-
const program = createCommand("bunx bun-workspaces").description("CLI
|
|
17
|
-
writeOut: (s)=>logger.info(s),
|
|
18
|
-
writeErr: (s)=>s.startsWith("Usage") ? logger.info(s) : logger.error(s)
|
|
19
|
-
});
|
|
17
|
+
const program = createCommand("bunx bun-workspaces").description("A CLI on top of native Bun workspaces").version(package_0.version).showHelpAfterError(true);
|
|
20
18
|
postInit?.(program);
|
|
21
19
|
if (!validateCurrentBunVersion()) {
|
|
22
|
-
|
|
20
|
+
fatalErrorLogger.error(`Bun version mismatch. Required: ${getRequiredBunVersion()}, Found: ${Bun.version}`);
|
|
23
21
|
process.exit(1);
|
|
24
22
|
}
|
|
25
23
|
const args = tempFixCamelCaseOptions("string" == typeof argv ? argv.split(/s+/) : argv);
|
|
@@ -29,9 +27,15 @@ const createCli = ({ handleError, postInit, defaultCwd = process.cwd() } = {})=>
|
|
|
29
27
|
program,
|
|
30
28
|
project
|
|
31
29
|
});
|
|
32
|
-
await program.parseAsync(args
|
|
30
|
+
await program.parseAsync(args, {
|
|
31
|
+
from: programmatic ? "user" : "node"
|
|
32
|
+
});
|
|
33
33
|
} catch (error) {
|
|
34
|
-
|
|
34
|
+
if (error instanceof BunWorkspacesError) {
|
|
35
|
+
logger.debug(error);
|
|
36
|
+
fatalErrorLogger.error(error.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
} else errorListener(error);
|
|
35
39
|
} finally{
|
|
36
40
|
process.off("unhandledRejection", errorListener);
|
|
37
41
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const fatalErrorLogger: import("../internal/logger").Logger;
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
import node_fs from "node:fs";
|
|
2
|
+
import node_path from "node:path";
|
|
2
3
|
import { Option } from "commander";
|
|
3
4
|
import { loadConfigFile } from "../../config/index.mjs";
|
|
5
|
+
import { defineErrors } from "../../internal/error.mjs";
|
|
4
6
|
import { logger } from "../../internal/logger.mjs";
|
|
5
|
-
import {
|
|
7
|
+
import { createFileSystemProject } from "../../project/index.mjs";
|
|
6
8
|
import { getCliGlobalOptionConfig } from "./globalOptionsConfig.mjs";
|
|
9
|
+
const ERRORS = defineErrors("WorkingDirectoryNotFound", "WorkingDirectoryNotADirectory");
|
|
7
10
|
const addGlobalOption = (program, optionName, defaultOverride)=>{
|
|
8
11
|
const { mainOption, shortOption, description, param, values, defaultValue } = getCliGlobalOptionConfig(optionName);
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
let option = new Option(`${shortOption} ${mainOption}${param ? ` <${param}>` : ""}`, description);
|
|
13
|
+
const effectiveDefaultValue = defaultOverride ?? defaultValue;
|
|
14
|
+
if (effectiveDefaultValue) option = option.default(effectiveDefaultValue);
|
|
15
|
+
if (values?.length) option = option.choices(values);
|
|
16
|
+
program.addOption(option);
|
|
11
17
|
};
|
|
12
|
-
const
|
|
18
|
+
const getWorkingDirectoryFromArgs = (program, args, defaultCwd)=>{
|
|
13
19
|
addGlobalOption(program, "cwd", defaultCwd);
|
|
14
20
|
program.parseOptions(args);
|
|
15
21
|
return program.opts().cwd;
|
|
16
22
|
};
|
|
17
|
-
const
|
|
23
|
+
const getConfigFileFromArgs = (program, args)=>{
|
|
18
24
|
addGlobalOption(program, "configFile");
|
|
19
25
|
program.parseOptions(args);
|
|
20
26
|
return program.opts().configFile;
|
|
21
27
|
};
|
|
22
28
|
const defineGlobalOptions = (program, args, defaultCwd)=>{
|
|
23
|
-
const cwd =
|
|
24
|
-
|
|
29
|
+
const cwd = getWorkingDirectoryFromArgs(program, args, defaultCwd);
|
|
30
|
+
if (!node_fs.existsSync(cwd)) throw new ERRORS.WorkingDirectoryNotFound(`Working directory not found at path "${cwd}"`);
|
|
31
|
+
if (!node_fs.statSync(cwd).isDirectory()) throw new ERRORS.WorkingDirectoryNotADirectory(`Working directory is not a directory at path "${cwd}"`);
|
|
32
|
+
const configFilePath = getConfigFileFromArgs(program, args);
|
|
25
33
|
const config = loadConfigFile(configFilePath, cwd);
|
|
26
34
|
addGlobalOption(program, "logLevel");
|
|
27
35
|
return {
|
|
@@ -32,12 +40,12 @@ const defineGlobalOptions = (program, args, defaultCwd)=>{
|
|
|
32
40
|
const applyGlobalOptions = (options, config)=>{
|
|
33
41
|
logger.printLevel = options.logLevel;
|
|
34
42
|
logger.debug("Log level: " + options.logLevel);
|
|
35
|
-
const project =
|
|
43
|
+
const project = createFileSystemProject({
|
|
36
44
|
rootDir: options.cwd,
|
|
37
45
|
workspaceAliases: config?.project?.workspaceAliases ?? {}
|
|
38
46
|
});
|
|
39
47
|
logger.debug(`Project: ${JSON.stringify(project.name)} (${project.workspaces.length} workspace${1 === project.workspaces.length ? "" : "s"})`);
|
|
40
|
-
logger.debug("Project root: " +
|
|
48
|
+
logger.debug("Project root: " + node_path.resolve(project.rootDir));
|
|
41
49
|
return {
|
|
42
50
|
project
|
|
43
51
|
};
|
|
@@ -29,4 +29,5 @@ const CLI_GLOBAL_OPTIONS_CONFIG = {
|
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
31
|
const getCliGlobalOptionConfig = (optionName)=>CLI_GLOBAL_OPTIONS_CONFIG[optionName];
|
|
32
|
-
|
|
32
|
+
const getCliGlobalOptionNames = ()=>Object.keys(CLI_GLOBAL_OPTIONS_CONFIG);
|
|
33
|
+
export { getCliGlobalOptionConfig, getCliGlobalOptionNames };
|
|
@@ -6,7 +6,7 @@ const createWorkspaceInfoLines = (workspace)=>[
|
|
|
6
6
|
` - Aliases: ${workspace.aliases.join(", ")}`,
|
|
7
7
|
` - Path: ${workspace.path}`,
|
|
8
8
|
` - Glob Match: ${workspace.matchPattern}`,
|
|
9
|
-
` - Scripts: ${
|
|
9
|
+
` - Scripts: ${workspace.scripts.join(", ")}`
|
|
10
10
|
];
|
|
11
11
|
const createScriptInfoLines = (script, workspaces)=>[
|
|
12
12
|
`Script: ${script}`,
|
|
@@ -36,37 +36,42 @@ const listWorkspaces = handleCommand("listWorkspaces", ({ project }, pattern, op
|
|
|
36
36
|
if (options.nameOnly) lines.push(workspace.name);
|
|
37
37
|
else lines.push(...createWorkspaceInfoLines(workspace));
|
|
38
38
|
});
|
|
39
|
-
if (!lines.length) logger.info("No workspaces found");
|
|
39
|
+
if (!lines.length && !options.nameOnly) logger.info("No workspaces found");
|
|
40
40
|
if (lines.length) commandOutputLogger.info(lines.join("\n"));
|
|
41
41
|
});
|
|
42
42
|
const listScripts = handleCommand("listScripts", ({ project }, options)=>{
|
|
43
43
|
logger.debug(`Command: List scripts (options: ${JSON.stringify(options)})`);
|
|
44
44
|
const scripts = project.listScriptsWithWorkspaces();
|
|
45
45
|
const lines = [];
|
|
46
|
+
if (!project.workspaces.length && !options.nameOnly) return void logger.info("No workspaces found");
|
|
47
|
+
if (!Object.keys(scripts).length && !options.nameOnly) return void logger.info("No scripts found");
|
|
46
48
|
if (options.json) lines.push(...createJsonLines(options.nameOnly ? Object.keys(scripts) : Object.values(scripts).map(({ workspaces, ...rest })=>({
|
|
47
49
|
...rest,
|
|
48
50
|
workspaces: workspaces.map(({ name })=>name)
|
|
49
51
|
})), options));
|
|
50
|
-
else {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
if (!lines.length) logger.info("No scripts found");
|
|
56
|
-
}
|
|
52
|
+
else Object.values(scripts).sort(({ name: nameA }, { name: nameB })=>nameA.localeCompare(nameB)).forEach(({ name, workspaces })=>{
|
|
53
|
+
if (options.nameOnly) lines.push(name);
|
|
54
|
+
else lines.push(...createScriptInfoLines(name, workspaces));
|
|
55
|
+
});
|
|
57
56
|
if (lines.length) commandOutputLogger.info(lines.join("\n"));
|
|
58
57
|
});
|
|
59
58
|
const workspaceInfo = handleCommand("workspaceInfo", ({ project }, workspaceName, options)=>{
|
|
60
59
|
logger.debug(`Command: Workspace info for ${workspaceName} (options: ${JSON.stringify(options)})`);
|
|
61
60
|
const workspace = project.findWorkspaceByNameOrAlias(workspaceName);
|
|
62
|
-
if (!workspace)
|
|
61
|
+
if (!workspace) {
|
|
62
|
+
logger.error(`Workspace ${JSON.stringify(workspaceName)} not found`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
63
65
|
commandOutputLogger.info((options.json ? createJsonLines(workspace, options) : createWorkspaceInfoLines(workspace)).join("\n"));
|
|
64
66
|
});
|
|
65
67
|
const scriptInfo = handleCommand("scriptInfo", ({ project }, script, options)=>{
|
|
66
68
|
logger.debug(`Command: Script info for ${script} (options: ${JSON.stringify(options)})`);
|
|
67
69
|
const scripts = project.listScriptsWithWorkspaces();
|
|
68
70
|
const scriptMetadata = scripts[script];
|
|
69
|
-
if (!scriptMetadata)
|
|
71
|
+
if (!scriptMetadata) {
|
|
72
|
+
logger.error(`Script not found: ${JSON.stringify(script)}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
70
75
|
commandOutputLogger.info((options.json ? createJsonLines(options.workspacesOnly ? scriptMetadata.workspaces.map(({ name })=>name) : {
|
|
71
76
|
name: scriptMetadata.name,
|
|
72
77
|
workspaces: scriptMetadata.workspaces.map(({ name })=>name)
|
|
@@ -75,98 +80,105 @@ const scriptInfo = handleCommand("scriptInfo", ({ project }, script, options)=>{
|
|
|
75
80
|
const runScript = handleCommand("runScript", async ({ project }, script, _workspaces, options)=>{
|
|
76
81
|
logger.debug(`Command: Run script ${JSON.stringify(script)} for ${_workspaces.length ? "workspaces " + _workspaces.join(", ") : "all workspaces"} (parallel: ${!!options.parallel}, args: ${JSON.stringify(options.args)})`);
|
|
77
82
|
const workspaces = _workspaces.length ? _workspaces.flatMap((workspacePattern)=>{
|
|
78
|
-
if (workspacePattern.includes("*")) return project.findWorkspacesByPattern(workspacePattern).filter(({
|
|
83
|
+
if (workspacePattern.includes("*")) return project.findWorkspacesByPattern(workspacePattern).filter(({ scripts })=>scripts.includes(script)).map(({ name })=>name);
|
|
79
84
|
return [
|
|
80
85
|
workspacePattern
|
|
81
86
|
];
|
|
82
|
-
}).
|
|
87
|
+
}).map((workspaceName)=>project.findWorkspaceByNameOrAlias(workspaceName)).filter(Boolean) : project.listWorkspacesWithScript(script);
|
|
83
88
|
if (!workspaces.length) {
|
|
84
|
-
if (1
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
throw new Error(message);
|
|
88
|
-
}
|
|
89
|
-
const message = `No ${_workspaces.length ? "matching " : ""}workspaces found for script ${JSON.stringify(script)}`;
|
|
90
|
-
logger.error(message);
|
|
91
|
-
throw new Error(message);
|
|
89
|
+
if (1 !== _workspaces.length || _workspaces[0].includes("*")) logger.error(`No ${_workspaces.length ? "matching " : ""}workspaces found with script ${JSON.stringify(script)}`);
|
|
90
|
+
else logger.error(`Workspace not found: ${JSON.stringify(_workspaces[0])}`);
|
|
91
|
+
process.exit(1);
|
|
92
92
|
}
|
|
93
|
-
const scriptCommands = workspaces.map((
|
|
93
|
+
const scriptCommands = workspaces.map((workspace)=>project.createScriptCommand({
|
|
94
94
|
scriptName: script,
|
|
95
|
-
workspaceName,
|
|
95
|
+
workspaceName: workspace.name,
|
|
96
96
|
method: "cd",
|
|
97
|
-
args: options.args?.replace(/<workspace>/g,
|
|
97
|
+
args: options.args?.replace(/<workspace>/g, workspace.name) ?? ""
|
|
98
98
|
}));
|
|
99
99
|
const runCommand = async ({ command, scriptName, workspace })=>{
|
|
100
|
-
const commandLogger = createLogger(`${workspace.name}:${scriptName}`);
|
|
101
100
|
const splitCommand = command.command.split(/\s+/g);
|
|
102
|
-
|
|
103
|
-
const isSilent = "silent" === logger.printLevel;
|
|
101
|
+
logger.debug(`Running script ${scriptName} in workspace ${workspace.name} (cwd: ${command.cwd}): ${splitCommand.join(" ")}`);
|
|
104
102
|
const proc = Bun.spawn(command.command.split(/\s+/g), {
|
|
105
103
|
cwd: command.cwd,
|
|
106
104
|
env: {
|
|
107
105
|
...process.env,
|
|
108
106
|
FORCE_COLOR: "1"
|
|
109
107
|
},
|
|
110
|
-
stdout:
|
|
111
|
-
stderr:
|
|
108
|
+
stdout: "pipe",
|
|
109
|
+
stderr: "pipe"
|
|
112
110
|
});
|
|
113
111
|
const linePrefix = options.noPrefix ? "" : `[${workspace.name}:${scriptName}] `;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
const pipeOutput = async (streamName)=>{
|
|
113
|
+
const stream = proc[streamName];
|
|
114
|
+
if (stream && "silent" !== logger.printLevel) for await (const chunk of stream)commandOutputLogger.logOutput(chunk, "info", process[streamName], linePrefix);
|
|
115
|
+
};
|
|
116
|
+
await Promise.all([
|
|
117
|
+
pipeOutput("stdout"),
|
|
118
|
+
pipeOutput("stderr"),
|
|
119
|
+
proc.exited
|
|
120
|
+
]);
|
|
117
121
|
return {
|
|
118
122
|
scriptName,
|
|
119
123
|
workspace,
|
|
120
124
|
command,
|
|
121
125
|
success: 0 === proc.exitCode,
|
|
126
|
+
exitCode: proc.exitCode,
|
|
122
127
|
error: 0 === proc.exitCode ? null : new BunWorkspacesError(`Script exited with code ${proc.exitCode}`)
|
|
123
128
|
};
|
|
124
129
|
};
|
|
125
130
|
const results = [];
|
|
126
|
-
|
|
131
|
+
const runParallel = async ()=>{
|
|
127
132
|
let i = 0;
|
|
128
133
|
for await (const result of (await Promise.allSettled(scriptCommands.map(runCommand)))){
|
|
129
134
|
if ("rejected" === result.status) results.push({
|
|
130
135
|
success: false,
|
|
131
|
-
workspaceName: workspaces[i],
|
|
132
|
-
error: result.reason
|
|
136
|
+
workspaceName: workspaces[i].name,
|
|
137
|
+
error: result.reason,
|
|
138
|
+
exitCode: null
|
|
133
139
|
});
|
|
134
140
|
else results.push({
|
|
135
141
|
success: result.value.success,
|
|
136
|
-
workspaceName: workspaces[i],
|
|
137
|
-
error: result.value.error
|
|
142
|
+
workspaceName: workspaces[i].name,
|
|
143
|
+
error: result.value.error,
|
|
144
|
+
exitCode: result.value.exitCode
|
|
138
145
|
});
|
|
139
146
|
i++;
|
|
140
147
|
}
|
|
141
|
-
}
|
|
148
|
+
};
|
|
149
|
+
const runSeries = async ()=>{
|
|
142
150
|
let i = 0;
|
|
143
151
|
for (const command of scriptCommands){
|
|
144
152
|
try {
|
|
145
153
|
const result = await runCommand(command);
|
|
146
154
|
results.push({
|
|
147
155
|
success: result.success,
|
|
148
|
-
workspaceName: workspaces[i],
|
|
149
|
-
error: result.error
|
|
156
|
+
workspaceName: workspaces[i].name,
|
|
157
|
+
error: result.error,
|
|
158
|
+
exitCode: result.exitCode
|
|
150
159
|
});
|
|
151
160
|
} catch (error) {
|
|
152
161
|
results.push({
|
|
153
162
|
success: false,
|
|
154
|
-
workspaceName: workspaces[i],
|
|
155
|
-
error: error
|
|
163
|
+
workspaceName: workspaces[i].name,
|
|
164
|
+
error: error,
|
|
165
|
+
exitCode: null
|
|
156
166
|
});
|
|
157
167
|
}
|
|
158
168
|
i++;
|
|
159
169
|
}
|
|
160
|
-
}
|
|
170
|
+
};
|
|
171
|
+
if (options.parallel) await runParallel();
|
|
172
|
+
else await runSeries();
|
|
161
173
|
let failCount = 0;
|
|
162
|
-
results.forEach(({ success, workspaceName })=>{
|
|
174
|
+
results.forEach(({ success, workspaceName, exitCode })=>{
|
|
163
175
|
if (!success) failCount++;
|
|
164
|
-
logger.info(`${success ? "✅" : "❌"} ${workspaceName}: ${script}`);
|
|
176
|
+
logger.info(`${success ? "✅" : "❌"} ${workspaceName}: ${script}${exitCode ? ` (exited with code ${exitCode})` : ""}`);
|
|
165
177
|
});
|
|
166
178
|
const s = 1 === results.length ? "" : "s";
|
|
167
179
|
if (failCount) {
|
|
168
180
|
const message = `${failCount} of ${results.length} script${s} failed`;
|
|
169
|
-
logger.
|
|
181
|
+
logger.info(message);
|
|
170
182
|
process.exit(1);
|
|
171
183
|
} else logger.info(`${results.length} script${s} ran successfully`);
|
|
172
184
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface CliProjectCommandConfig {
|
|
2
2
|
command: string;
|
|
3
3
|
aliases: string[];
|
|
4
4
|
description: string;
|
|
@@ -7,8 +7,8 @@ export interface ProjectCommandConfig {
|
|
|
7
7
|
description: string;
|
|
8
8
|
}>;
|
|
9
9
|
}
|
|
10
|
-
export type
|
|
11
|
-
declare const
|
|
10
|
+
export type CliProjectCommandName = keyof typeof CLI_PROJECT_COMMANDS_CONFIG;
|
|
11
|
+
declare const CLI_PROJECT_COMMANDS_CONFIG: {
|
|
12
12
|
readonly listWorkspaces: {
|
|
13
13
|
readonly command: "list-workspaces [pattern]";
|
|
14
14
|
readonly aliases: ["ls", "list"];
|
|
@@ -101,7 +101,7 @@ declare const PROJECT_COMMANDS_CONFIG: {
|
|
|
101
101
|
};
|
|
102
102
|
};
|
|
103
103
|
};
|
|
104
|
-
export declare const getProjectCommandConfig: (commandName:
|
|
104
|
+
export declare const getProjectCommandConfig: (commandName: CliProjectCommandName) => {
|
|
105
105
|
readonly command: "list-workspaces [pattern]";
|
|
106
106
|
readonly aliases: ["ls", "list"];
|
|
107
107
|
readonly description: "List all workspaces";
|
|
@@ -188,4 +188,5 @@ export declare const getProjectCommandConfig: (commandName: ProjectCommandName)
|
|
|
188
188
|
};
|
|
189
189
|
};
|
|
190
190
|
};
|
|
191
|
+
export declare const getCliProjectCommandNames: () => CliProjectCommandName[];
|
|
191
192
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const CLI_PROJECT_COMMANDS_CONFIG = {
|
|
2
2
|
listWorkspaces: {
|
|
3
3
|
command: "list-workspaces [pattern]",
|
|
4
4
|
aliases: [
|
|
@@ -100,5 +100,6 @@ const PROJECT_COMMANDS_CONFIG = {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
|
-
const getProjectCommandConfig = (commandName)=>
|
|
104
|
-
|
|
103
|
+
const getProjectCommandConfig = (commandName)=>CLI_PROJECT_COMMANDS_CONFIG[commandName];
|
|
104
|
+
const getCliProjectCommandNames = ()=>Object.keys(CLI_PROJECT_COMMANDS_CONFIG);
|
|
105
|
+
export { getCliProjectCommandNames, getProjectCommandConfig };
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { validateLogLevel } from "../internal/logger.mjs";
|
|
2
|
+
import { ERRORS } from "./errors.mjs";
|
|
3
|
+
const isJsonObject = (value)=>"object" == typeof value && null !== value && !Array.isArray(value);
|
|
2
4
|
const validateCliConfig = (cliConfig)=>{
|
|
3
|
-
if (
|
|
5
|
+
if (!isJsonObject(cliConfig)) throw new ERRORS.InvalidConfigFile('Config file: "cli" must be an object');
|
|
4
6
|
if (cliConfig?.logLevel) validateLogLevel(cliConfig.logLevel);
|
|
5
7
|
};
|
|
6
8
|
const validateProjectConfig = (projectConfig)=>{
|
|
7
|
-
if (
|
|
8
|
-
if (projectConfig?.workspaceAliases) {
|
|
9
|
-
if (
|
|
10
|
-
for (const alias of Object.values(projectConfig.workspaceAliases))if ("string" != typeof alias) throw new
|
|
9
|
+
if (!isJsonObject(projectConfig)) throw new ERRORS.InvalidConfigFile('Config file: "project" must be an object');
|
|
10
|
+
if (projectConfig?.workspaceAliases !== void 0) {
|
|
11
|
+
if (!isJsonObject(projectConfig.workspaceAliases)) throw new ERRORS.InvalidConfigFile("Config file: project.workspaceAliases must be an object");
|
|
12
|
+
for (const alias of Object.values(projectConfig.workspaceAliases))if ("string" != typeof alias) throw new ERRORS.InvalidConfigFile("Config file: project.workspaceAliases must be an object with string keys and values");
|
|
11
13
|
}
|
|
12
14
|
};
|
|
13
15
|
const validateBunWorkspacesConfig = (config)=>{
|
|
14
|
-
if (
|
|
16
|
+
if (!isJsonObject(config)) throw new ERRORS.InvalidConfigFile("Config file: must be an object");
|
|
15
17
|
if (void 0 !== config.cli) validateCliConfig(config.cli);
|
|
16
18
|
if (void 0 !== config.project) validateProjectConfig(config.project);
|
|
17
19
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { validateBunWorkspacesConfig } from "./bunWorkspacesConfig.mjs";
|
|
4
|
+
import { ERRORS } from "./errors.mjs";
|
|
4
5
|
const DEFAULT_CONFIG_FILE_PATH = "bw.json";
|
|
5
6
|
const loadConfigFile = (filePath, rootDir = ".")=>{
|
|
6
7
|
if (!filePath) {
|
|
@@ -9,13 +10,15 @@ const loadConfigFile = (filePath, rootDir = ".")=>{
|
|
|
9
10
|
filePath = defaultFilePath;
|
|
10
11
|
}
|
|
11
12
|
filePath = path.resolve(rootDir, filePath);
|
|
13
|
+
if (!fs.existsSync(filePath)) throw new ERRORS.ConfigFileNotFound(`Config file not found at path "${filePath}"`);
|
|
12
14
|
const configFile = fs.readFileSync(filePath, "utf8");
|
|
15
|
+
let json = null;
|
|
13
16
|
try {
|
|
14
|
-
|
|
15
|
-
validateBunWorkspacesConfig(json);
|
|
16
|
-
return json;
|
|
17
|
+
json = JSON.parse(configFile);
|
|
17
18
|
} catch (error) {
|
|
18
|
-
throw new
|
|
19
|
+
throw new ERRORS.InvalidConfigFileFormat(`Failed to parse config file at path "${filePath}": ${error.message}`);
|
|
19
20
|
}
|
|
21
|
+
if (json) validateBunWorkspacesConfig(json);
|
|
22
|
+
return json;
|
|
20
23
|
};
|
|
21
24
|
export { DEFAULT_CONFIG_FILE_PATH, loadConfigFile };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ERRORS: import("../internal/error").DefinedErrors<"ConfigFileNotFound" | "InvalidConfigFile" | "InvalidConfigFileFormat">;
|
package/src/internal/logger.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
|
|
|
2
2
|
export type LogLevel = (typeof LOG_LEVELS)[number];
|
|
3
3
|
export type LogLevelSetting = LogLevel | "silent";
|
|
4
4
|
export declare const validateLogLevel: (level: LogLevelSetting) => void;
|
|
5
|
-
export type LogMetadata = Record<string,
|
|
5
|
+
export type LogMetadata = Record<string, unknown>;
|
|
6
6
|
export interface Log<Message extends string | Error = string, Metadata extends LogMetadata = LogMetadata> {
|
|
7
7
|
message: Message;
|
|
8
8
|
level: LogLevel;
|
package/src/internal/logger.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IS_PRODUCTION, IS_TEST } from "./env.mjs";
|
|
2
|
+
import { defineErrors } from "./error.mjs";
|
|
2
3
|
const LOG_LEVELS = [
|
|
3
4
|
"debug",
|
|
4
5
|
"info",
|
|
@@ -6,9 +7,10 @@ const LOG_LEVELS = [
|
|
|
6
7
|
"error"
|
|
7
8
|
];
|
|
8
9
|
const getLevelNumber = (level)=>LOG_LEVELS.indexOf(level);
|
|
10
|
+
const ERRORS = defineErrors("InvalidLogLevel");
|
|
9
11
|
const validateLogLevel = (level)=>{
|
|
10
12
|
if ("silent" === level) return;
|
|
11
|
-
if (!LOG_LEVELS.includes(level)) throw new
|
|
13
|
+
if (!LOG_LEVELS.includes(level)) throw new ERRORS.InvalidLogLevel(`Invalid log level: "${level}". Accepted values: ${LOG_LEVELS.join(", ")}`);
|
|
12
14
|
};
|
|
13
15
|
const createLogger = (name)=>new _Logger(name);
|
|
14
16
|
class _Logger {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { findWorkspacesFromPackage } from "../../workspaces/index.mjs";
|
|
2
|
+
import { ProjectBase } from "./projectBase.mjs";
|
|
3
|
+
class FileSystemProject extends ProjectBase {
|
|
4
|
+
rootDir;
|
|
5
|
+
workspaces;
|
|
6
|
+
name;
|
|
7
|
+
constructor(options){
|
|
8
|
+
super();
|
|
9
|
+
this.rootDir = options.rootDir;
|
|
10
|
+
const { name, workspaces } = findWorkspacesFromPackage({
|
|
11
|
+
rootDir: options.rootDir,
|
|
12
|
+
workspaceAliases: options.workspaceAliases
|
|
13
|
+
});
|
|
14
|
+
this.name = name;
|
|
15
|
+
this.workspaces = workspaces;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const createFileSystemProject = (options)=>new FileSystemProject(options);
|
|
19
|
+
export { createFileSystemProject };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Workspace } from "../../workspaces";
|
|
2
|
+
import { type Project } from "./projectBase";
|
|
3
|
+
export interface CreateMemoryProjectOptions {
|
|
4
|
+
name: string;
|
|
5
|
+
workspaces: Workspace[];
|
|
6
|
+
rootDir: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const createMemoryProject: (options: CreateMemoryProjectOptions) => Project;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ProjectBase } from "./projectBase.mjs";
|
|
2
|
+
class MemoryProject extends ProjectBase {
|
|
3
|
+
rootDir;
|
|
4
|
+
workspaces;
|
|
5
|
+
name;
|
|
6
|
+
constructor(options){
|
|
7
|
+
super();
|
|
8
|
+
this.rootDir = options.rootDir;
|
|
9
|
+
this.name = options.name;
|
|
10
|
+
this.workspaces = options.workspaces;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const createMemoryProject = (options)=>new MemoryProject(options);
|
|
14
|
+
export { createMemoryProject };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Workspace } from "
|
|
2
|
-
import { type CreateScriptCommandOptions, type ScriptCommand } from "
|
|
1
|
+
import { type Workspace } from "../../workspaces";
|
|
2
|
+
import { type CreateScriptCommandOptions, type ScriptCommand } from "../scriptCommand";
|
|
3
3
|
export interface ScriptMetadata {
|
|
4
4
|
name: string;
|
|
5
5
|
workspaces: Workspace[];
|
|
@@ -24,8 +24,16 @@ export interface Project {
|
|
|
24
24
|
findWorkspacesByPattern(workspaceName: string): Workspace[];
|
|
25
25
|
createScriptCommand(options: CreateProjectScriptCommandOptions): CreateProjectScriptCommandResult;
|
|
26
26
|
}
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
export declare abstract class ProjectBase implements Project {
|
|
28
|
+
abstract readonly name: string;
|
|
29
|
+
abstract readonly rootDir: string;
|
|
30
|
+
abstract readonly workspaces: Workspace[];
|
|
31
|
+
listWorkspacesWithScript(scriptName: string): Workspace[];
|
|
32
|
+
listScriptsWithWorkspaces(): Record<string, ScriptMetadata>;
|
|
33
|
+
findWorkspaceByName(workspaceName: string): Workspace | null;
|
|
34
|
+
findWorkspaceByAlias(alias: string): Workspace | null;
|
|
35
|
+
findWorkspaceByNameOrAlias(nameOrAlias: string): Workspace | null;
|
|
36
|
+
/** Accepts wildcard for finding a list of workspaces */
|
|
37
|
+
findWorkspacesByPattern(workspacePattern: string): Workspace[];
|
|
38
|
+
createScriptCommand(options: CreateProjectScriptCommandOptions): CreateProjectScriptCommandResult;
|
|
30
39
|
}
|
|
31
|
-
export declare const createProject: (options: CreateProjectOptions) => Project;
|
|
@@ -1,32 +1,15 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import { createWildcardRegex } from "
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
class _Project {
|
|
7
|
-
options;
|
|
8
|
-
rootDir;
|
|
9
|
-
workspaceAliases;
|
|
10
|
-
workspaces;
|
|
11
|
-
name;
|
|
12
|
-
constructor(options){
|
|
13
|
-
this.options = options;
|
|
14
|
-
this.rootDir = options.rootDir;
|
|
15
|
-
this.workspaceAliases = options.workspaceAliases;
|
|
16
|
-
const { name, workspaces } = findWorkspacesFromPackage({
|
|
17
|
-
rootDir: options.rootDir,
|
|
18
|
-
workspaceAliases: options.workspaceAliases
|
|
19
|
-
});
|
|
20
|
-
this.name = name;
|
|
21
|
-
this.workspaces = workspaces;
|
|
22
|
-
}
|
|
2
|
+
import { createWildcardRegex } from "../../internal/regex.mjs";
|
|
3
|
+
import { ERRORS } from "../errors.mjs";
|
|
4
|
+
import { createScriptCommand } from "../scriptCommand.mjs";
|
|
5
|
+
class ProjectBase {
|
|
23
6
|
listWorkspacesWithScript(scriptName) {
|
|
24
|
-
return this.workspaces.filter((workspace)=>workspace.
|
|
7
|
+
return this.workspaces.filter((workspace)=>workspace.scripts.includes(scriptName));
|
|
25
8
|
}
|
|
26
9
|
listScriptsWithWorkspaces() {
|
|
27
10
|
const scripts = new Set();
|
|
28
11
|
this.workspaces.forEach((workspace)=>{
|
|
29
|
-
|
|
12
|
+
workspace.scripts.forEach((script)=>scripts.add(script));
|
|
30
13
|
});
|
|
31
14
|
return Array.from(scripts).sort((a, b)=>a.localeCompare(b)).map((name)=>({
|
|
32
15
|
name,
|
|
@@ -56,7 +39,7 @@ class _Project {
|
|
|
56
39
|
createScriptCommand(options) {
|
|
57
40
|
const workspace = this.findWorkspaceByNameOrAlias(options.workspaceName);
|
|
58
41
|
if (!workspace) throw new ERRORS.ProjectWorkspaceNotFound(`Workspace not found: ${JSON.stringify(options.workspaceName)}`);
|
|
59
|
-
if (!workspace.
|
|
42
|
+
if (!workspace.scripts.includes(options.scriptName)) throw new ERRORS.WorkspaceScriptDoesNotExist(`Script not found in workspace ${JSON.stringify(workspace.name)}: ${JSON.stringify(options.scriptName)} (available: ${workspace.scripts.join(", ") || "none"})`);
|
|
60
43
|
return {
|
|
61
44
|
workspace,
|
|
62
45
|
scriptName: options.scriptName,
|
|
@@ -68,5 +51,4 @@ class _Project {
|
|
|
68
51
|
};
|
|
69
52
|
}
|
|
70
53
|
}
|
|
71
|
-
|
|
72
|
-
export { createProject };
|
|
54
|
+
export { ProjectBase };
|
package/src/project/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { type Project, type
|
|
1
|
+
export { type Project, type CreateProjectScriptCommandOptions, type ScriptMetadata, } from "./implementations/projectBase";
|
|
2
|
+
export { createFileSystemProject as createFileSystemProject, type CreateFileSystemProjectOptions, } from "./implementations/fileSystemProject";
|
package/src/project/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { createFileSystemProject } from "./implementations/fileSystemProject.mjs";
|
|
2
|
+
export { createFileSystemProject };
|
|
@@ -19,7 +19,6 @@ const findWorkspaces = ({ rootDir, workspaceGlobs, workspaceAliases })=>{
|
|
|
19
19
|
const packageJsonPath = resolvePackageJsonPath(item);
|
|
20
20
|
if (packageJsonPath) excludedWorkspacePaths.push(path.relative(rootDir, path.dirname(packageJsonPath)));
|
|
21
21
|
}
|
|
22
|
-
console.log(negativePatterns, excludedWorkspacePaths);
|
|
23
22
|
for (const pattern of positivePatterns)for (const item of scanWorkspaceGlob(pattern.replace(/^!/, ""), rootDir)){
|
|
24
23
|
const packageJsonPath = resolvePackageJsonPath(item);
|
|
25
24
|
if (packageJsonPath) {
|
|
@@ -31,7 +30,7 @@ const findWorkspaces = ({ rootDir, workspaceGlobs, workspaceAliases })=>{
|
|
|
31
30
|
name: packageJsonContent.name ?? "",
|
|
32
31
|
matchPattern: pattern,
|
|
33
32
|
path: path.relative(rootDir, path.dirname(packageJsonPath)),
|
|
34
|
-
|
|
33
|
+
scripts: Object.keys(packageJsonContent.scripts ?? {}).sort(),
|
|
35
34
|
aliases: Object.entries(workspaceAliases ?? {}).filter(([_, value])=>value === packageJsonContent.name).map(([key])=>key)
|
|
36
35
|
};
|
|
37
36
|
if (!excludedWorkspacePaths.includes(workspace.path) && validateWorkspace(workspace, workspaces)) workspaces.push(workspace);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { findWorkspaces, findWorkspacesFromPackage, type FindWorkspacesOptions, } from "./findWorkspaces";
|
|
2
2
|
export { type Workspace } from "./workspace";
|
|
3
|
-
export type { ResolvedPackageJsonContent } from "./packageJson";
|
|
3
|
+
export type { resolvePackageJsonContent, resolvePackageJsonPath, scanWorkspaceGlob, ResolvedPackageJsonContent, } from "./packageJson";
|
|
@@ -4,5 +4,5 @@ export type ResolvedPackageJsonContent = {
|
|
|
4
4
|
workspaces: string[];
|
|
5
5
|
scripts: Record<string, string>;
|
|
6
6
|
} & Record<string, unknown>;
|
|
7
|
-
export declare const scanWorkspaceGlob: (
|
|
7
|
+
export declare const scanWorkspaceGlob: (globPattern: string, rootDir: string) => Generator<string, void, void>;
|
|
8
8
|
export declare const resolvePackageJsonContent: (packageJsonPath: string, rootDir: string, validations: ("workspaces" | "name" | "scripts")[]) => ResolvedPackageJsonContent;
|
|
@@ -8,7 +8,7 @@ const resolvePackageJsonPath = (directoryItem)=>{
|
|
|
8
8
|
if (fs.existsSync(path.join(directoryItem, "package.json"))) return path.join(directoryItem, "package.json");
|
|
9
9
|
return "";
|
|
10
10
|
};
|
|
11
|
-
const scanWorkspaceGlob = (
|
|
11
|
+
const scanWorkspaceGlob = (globPattern, rootDir)=>new Glob(globPattern, {
|
|
12
12
|
absolute: true,
|
|
13
13
|
cwd: rootDir
|
|
14
14
|
}).iterateSync();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/** Metadata about a nested package within a Bun monorepo */
|
|
2
2
|
export interface Workspace {
|
|
3
3
|
/** The name of the workspace from its `package.json` */
|
|
4
4
|
name: string;
|
|
@@ -6,8 +6,8 @@ export interface Workspace {
|
|
|
6
6
|
path: string;
|
|
7
7
|
/** The pattern from `"workspaces"` in the root `package.json`that this workspace was matched from*/
|
|
8
8
|
matchPattern: string;
|
|
9
|
-
/** The
|
|
10
|
-
|
|
9
|
+
/** The scripts available in package.json */
|
|
10
|
+
scripts: string[];
|
|
11
11
|
/** Aliases assigned to the workspace via the `"workspaceAliases"` field in the config */
|
|
12
12
|
aliases: string[];
|
|
13
13
|
}
|