bun-workspaces 0.3.0 → 1.0.1-alpha
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 +32 -4
- package/bun.lock +576 -0
- package/package.json +13 -15
- package/src/cli/cli.ts +3 -8
- package/src/cli/globalOptions.ts +61 -23
- package/src/cli/projectCommands.ts +42 -41
- package/src/config/bunWorkspacesConfig.ts +62 -0
- package/src/config/configFile.ts +33 -0
- package/src/config/index.ts +7 -0
- package/src/internal/env.ts +25 -1
- package/src/internal/logger.ts +143 -19
- package/src/project/project.ts +24 -1
- package/src/workspaces/errors.ts +2 -0
- package/src/workspaces/findWorkspaces.ts +36 -8
- package/src/workspaces/index.ts +0 -1
- package/src/workspaces/packageJson.ts +1 -1
- package/src/workspaces/workspace.ts +2 -0
- package/.vscode/extensions.json +0 -12
- package/.vscode/settings.json +0 -23
- package/bun-workspaces-0.1.0-alpha-test-publish-2.tgz +0 -0
- package/eslint.config.mjs +0 -45
- package/ignore-me-CHANGELOG_TEMPLATE.md +0 -264
- package/ignore-me-test-projects/no-fail/applications/applicationA/package.json +0 -8
- package/ignore-me-test-projects/no-fail/applications/applicationB/package.json +0 -8
- package/ignore-me-test-projects/no-fail/libraries/libraryA/package.json +0 -8
- package/ignore-me-test-projects/no-fail/libraries/libraryB/package.json +0 -8
- package/ignore-me-test-projects/no-fail/libraries/nested/libraryC/package.json +0 -8
- package/ignore-me-test-projects/no-fail/package.json +0 -7
- package/ignore-me-test-projects/one-fail/applications/applicationA/package.json +0 -8
- package/ignore-me-test-projects/one-fail/applications/applicationB/package.json +0 -8
- package/ignore-me-test-projects/one-fail/libraries/libraryA/package.json +0 -8
- package/ignore-me-test-projects/one-fail/libraries/libraryB/package.json +0 -8
- package/ignore-me-test-projects/one-fail/libraries/nested/libraryC/package.json +0 -8
- package/ignore-me-test-projects/one-fail/package.json +0 -7
- package/ignore-me-test-projects/two-fail/applications/applicationA/package.json +0 -8
- package/ignore-me-test-projects/two-fail/applications/applicationB/package.json +0 -8
- package/ignore-me-test-projects/two-fail/libraries/libraryA/package.json +0 -8
- package/ignore-me-test-projects/two-fail/libraries/libraryB/package.json +0 -8
- package/ignore-me-test-projects/two-fail/libraries/nested/libraryC/package.json +0 -8
- package/ignore-me-test-projects/two-fail/package.json +0 -7
- package/src/cli/output.ts +0 -6
package/src/internal/logger.ts
CHANGED
|
@@ -1,21 +1,145 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { IS_PRODUCTION, IS_TEST } from "./env";
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export const LOG_LEVELS = ["debug", "info", "warn", "error"] as const;
|
|
4
|
+
|
|
5
|
+
const getLevelNumber = (level: LogLevel) => LOG_LEVELS.indexOf(level);
|
|
6
|
+
|
|
7
|
+
export type LogLevel = (typeof LOG_LEVELS)[number];
|
|
8
|
+
|
|
9
|
+
export type LogLevelSetting = LogLevel | "silent";
|
|
10
|
+
|
|
11
|
+
export const validateLogLevel = (level: LogLevelSetting) => {
|
|
12
|
+
if (level === "silent") return;
|
|
13
|
+
if (!LOG_LEVELS.includes(level)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Invalid log level: "${level}". Accepted values: ${LOG_LEVELS.join(", ")}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type LogMetadata = Record<string, any>;
|
|
21
|
+
|
|
22
|
+
export interface Log<
|
|
23
|
+
Message extends string | Error = string,
|
|
24
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
25
|
+
> {
|
|
26
|
+
message: Message;
|
|
27
|
+
level: LogLevel;
|
|
28
|
+
metadata: Metadata;
|
|
29
|
+
time: Date;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type Logger = {
|
|
33
|
+
name: string;
|
|
34
|
+
|
|
35
|
+
log<
|
|
36
|
+
Message extends string | Error = string,
|
|
37
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
38
|
+
>(
|
|
39
|
+
message: Message,
|
|
40
|
+
level: LogLevel,
|
|
41
|
+
metadata?: Metadata,
|
|
42
|
+
): Log<Message, Metadata>;
|
|
43
|
+
|
|
44
|
+
printLevel: LogLevelSetting;
|
|
45
|
+
} & {
|
|
46
|
+
[Level in LogLevel]: <
|
|
47
|
+
Message extends string | Error = string,
|
|
48
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
49
|
+
>(
|
|
50
|
+
message: Message,
|
|
51
|
+
metadata?: Metadata,
|
|
52
|
+
) => Log<Message, Metadata>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const createLogger = (name: string): Logger => new _Logger(name);
|
|
56
|
+
|
|
57
|
+
class _Logger implements Logger {
|
|
58
|
+
constructor(public name: string) {}
|
|
59
|
+
|
|
60
|
+
log<
|
|
61
|
+
Message extends string | Error = string,
|
|
62
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
63
|
+
>(
|
|
64
|
+
message: Message,
|
|
65
|
+
level: LogLevel,
|
|
66
|
+
metadata?: Metadata,
|
|
67
|
+
): Log<Message, Metadata> {
|
|
68
|
+
const log: Log<Message, Metadata> = {
|
|
69
|
+
message,
|
|
70
|
+
level,
|
|
71
|
+
metadata: metadata ?? ({} as Metadata),
|
|
72
|
+
time: new Date(),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (this.shouldPrint(level)) {
|
|
76
|
+
const formattedMessage = this.formatLogMessage(message, level);
|
|
77
|
+
if (message instanceof Error) {
|
|
78
|
+
message.message = formattedMessage;
|
|
79
|
+
}
|
|
80
|
+
console[level](
|
|
81
|
+
message instanceof Error ? message : formattedMessage,
|
|
82
|
+
...(metadata ? [{ metadata }] : []),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return log;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
debug<
|
|
90
|
+
Message extends string | Error = string,
|
|
91
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
92
|
+
>(message: Message, metadata?: Metadata): Log<Message, Metadata> {
|
|
93
|
+
return this.log(message, "debug", metadata);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
info<
|
|
97
|
+
Message extends string | Error = string,
|
|
98
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
99
|
+
>(message: Message, metadata?: Metadata): Log<Message, Metadata> {
|
|
100
|
+
return this.log(message, "info", metadata);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
warn<
|
|
104
|
+
Message extends string | Error = string,
|
|
105
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
106
|
+
>(message: Message, metadata?: Metadata): Log<Message, Metadata> {
|
|
107
|
+
return this.log(message, "warn", metadata);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
error<
|
|
111
|
+
Message extends string | Error = string,
|
|
112
|
+
Metadata extends LogMetadata = LogMetadata,
|
|
113
|
+
>(message: Message, metadata?: Metadata): Log<Message, Metadata> {
|
|
114
|
+
return this.log(message, "error", metadata);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get printLevel() {
|
|
118
|
+
return this._printLevel;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
set printLevel(level: LogLevelSetting) {
|
|
122
|
+
this._printLevel = level;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Info prints normally for standard user-facing logs. Debug and Warn are highlighted with a prefix. Errors print as Error instances
|
|
126
|
+
private formatLogMessage(message: Error | string, level: LogLevel): string {
|
|
127
|
+
const content = message instanceof Error ? message.message : message;
|
|
128
|
+
return level === "debug" || level === "warn"
|
|
129
|
+
? `[${this.name} ${level.toUpperCase()}]: ${content}`
|
|
130
|
+
: content;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private _printLevel: LogLevelSetting = IS_PRODUCTION
|
|
134
|
+
? "info"
|
|
135
|
+
: IS_TEST
|
|
7
136
|
? "silent"
|
|
8
|
-
:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export const createLogger = (prefixContent: string) =>
|
|
21
|
-
logger.child({}, { msgPrefix: `[${prefixContent}] ` });
|
|
137
|
+
: "debug";
|
|
138
|
+
|
|
139
|
+
private shouldPrint(level: LogLevel): boolean {
|
|
140
|
+
if (this.printLevel === "silent") return false;
|
|
141
|
+
return getLevelNumber(level) >= getLevelNumber(this.printLevel);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const logger = createLogger("bun-workspaces");
|
package/src/project/project.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface Project {
|
|
|
33
33
|
listWorkspacesWithScript(scriptName: string): Workspace[];
|
|
34
34
|
listScriptsWithWorkspaces(): Record<string, ScriptMetadata>;
|
|
35
35
|
findWorkspaceByName(workspaceName: string): Workspace | null;
|
|
36
|
+
findWorkspaceByAlias(alias: string): Workspace | null;
|
|
37
|
+
findWorkspaceByNameOrAlias(nameOrAlias: string): Workspace | null;
|
|
36
38
|
findWorkspacesByPattern(workspaceName: string): Workspace[];
|
|
37
39
|
createScriptCommand(
|
|
38
40
|
options: CreateProjectScriptCommandOptions,
|
|
@@ -41,17 +43,23 @@ export interface Project {
|
|
|
41
43
|
|
|
42
44
|
export interface CreateProjectOptions {
|
|
43
45
|
rootDir: string;
|
|
46
|
+
workspaceAliases?: Record<string, string>;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
class _Project implements Project {
|
|
47
50
|
public readonly rootDir: string;
|
|
51
|
+
public readonly workspaceAliases?: Record<string, string>;
|
|
48
52
|
public readonly workspaces: Workspace[];
|
|
49
53
|
public readonly name: string;
|
|
50
54
|
constructor(private options: CreateProjectOptions) {
|
|
51
55
|
this.rootDir = options.rootDir;
|
|
56
|
+
this.workspaceAliases = options.workspaceAliases;
|
|
57
|
+
|
|
52
58
|
const { name, workspaces } = findWorkspacesFromPackage({
|
|
53
59
|
rootDir: options.rootDir,
|
|
60
|
+
workspaceAliases: options.workspaceAliases,
|
|
54
61
|
});
|
|
62
|
+
|
|
55
63
|
this.name = name;
|
|
56
64
|
this.workspaces = workspaces;
|
|
57
65
|
}
|
|
@@ -91,6 +99,20 @@ class _Project implements Project {
|
|
|
91
99
|
);
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
findWorkspaceByAlias(alias: string): Workspace | null {
|
|
103
|
+
return (
|
|
104
|
+
this.workspaces.find((workspace) => workspace.aliases.includes(alias)) ??
|
|
105
|
+
null
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
findWorkspaceByNameOrAlias(nameOrAlias: string): Workspace | null {
|
|
110
|
+
return (
|
|
111
|
+
this.findWorkspaceByName(nameOrAlias) ||
|
|
112
|
+
this.findWorkspaceByAlias(nameOrAlias)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
94
116
|
/** Accepts wildcard for finding a list of workspaces */
|
|
95
117
|
findWorkspacesByPattern(workspacePattern: string): Workspace[] {
|
|
96
118
|
if (!workspacePattern) return [];
|
|
@@ -101,7 +123,8 @@ class _Project implements Project {
|
|
|
101
123
|
createScriptCommand(
|
|
102
124
|
options: CreateProjectScriptCommandOptions,
|
|
103
125
|
): CreateProjectScriptCommandResult {
|
|
104
|
-
const workspace = this.
|
|
126
|
+
const workspace = this.findWorkspaceByNameOrAlias(options.workspaceName);
|
|
127
|
+
|
|
105
128
|
if (!workspace) {
|
|
106
129
|
throw new ERRORS.ProjectWorkspaceNotFound(
|
|
107
130
|
`Workspace not found: ${JSON.stringify(options.workspaceName)}`,
|
package/src/workspaces/errors.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import type { ProjectConfig } from "../config";
|
|
3
4
|
import { logger } from "../internal/logger";
|
|
4
5
|
import { ERRORS } from "./errors";
|
|
5
6
|
import {
|
|
@@ -12,6 +13,7 @@ import type { Workspace } from "./workspace";
|
|
|
12
13
|
export interface FindWorkspacesOptions {
|
|
13
14
|
rootDir: string;
|
|
14
15
|
workspaceGlobs: string[];
|
|
16
|
+
workspaceAliases?: ProjectConfig["workspaceAliases"];
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
const validatePattern = (pattern: string) => {
|
|
@@ -43,6 +45,7 @@ const validateWorkspace = (workspace: Workspace, workspaces: Workspace[]) => {
|
|
|
43
45
|
export const findWorkspaces = ({
|
|
44
46
|
rootDir,
|
|
45
47
|
workspaceGlobs,
|
|
48
|
+
workspaceAliases,
|
|
46
49
|
}: FindWorkspacesOptions) => {
|
|
47
50
|
rootDir = path.resolve(rootDir);
|
|
48
51
|
|
|
@@ -65,6 +68,9 @@ export const findWorkspaces = ({
|
|
|
65
68
|
matchPattern: pattern,
|
|
66
69
|
path: path.relative(rootDir, path.dirname(packageJsonPath)),
|
|
67
70
|
packageJson: packageJsonContent,
|
|
71
|
+
aliases: Object.entries(workspaceAliases ?? {})
|
|
72
|
+
.filter(([_, value]) => value === packageJsonContent.name)
|
|
73
|
+
.map(([key]) => key),
|
|
68
74
|
};
|
|
69
75
|
|
|
70
76
|
if (validateWorkspace(workspace, workspaces)) {
|
|
@@ -81,13 +87,30 @@ export const findWorkspaces = ({
|
|
|
81
87
|
return { workspaces };
|
|
82
88
|
};
|
|
83
89
|
|
|
84
|
-
export
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
export const validateWorkspaceAliases = (
|
|
91
|
+
workspaces: Workspace[],
|
|
92
|
+
workspaceAliases: ProjectConfig["workspaceAliases"],
|
|
93
|
+
) => {
|
|
94
|
+
for (const [alias, name] of Object.entries(workspaceAliases ?? {})) {
|
|
95
|
+
if (workspaces.find((ws) => ws.name === alias)) {
|
|
96
|
+
throw new ERRORS.AliasConflict(
|
|
97
|
+
`Alias ${JSON.stringify(alias)} conflicts with workspace name ${JSON.stringify(name)}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (!workspaces.find((ws) => ws.name === name)) {
|
|
101
|
+
throw new ERRORS.AliasedWorkspaceNotFound(
|
|
102
|
+
`Workspace ${JSON.stringify(name)} was aliased by ${JSON.stringify(
|
|
103
|
+
alias,
|
|
104
|
+
)} but was not found`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
87
109
|
|
|
88
110
|
export const findWorkspacesFromPackage = ({
|
|
89
111
|
rootDir,
|
|
90
|
-
|
|
112
|
+
workspaceAliases,
|
|
113
|
+
}: ProjectConfig & { rootDir: string }) => {
|
|
91
114
|
const packageJsonPath = path.join(rootDir, "package.json");
|
|
92
115
|
if (!fs.existsSync(packageJsonPath)) {
|
|
93
116
|
throw new ERRORS.PackageNotFound(
|
|
@@ -99,11 +122,16 @@ export const findWorkspacesFromPackage = ({
|
|
|
99
122
|
"workspaces",
|
|
100
123
|
]);
|
|
101
124
|
|
|
125
|
+
const result = findWorkspaces({
|
|
126
|
+
rootDir,
|
|
127
|
+
workspaceGlobs: packageJson.workspaces ?? [],
|
|
128
|
+
workspaceAliases,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
validateWorkspaceAliases(result.workspaces, workspaceAliases);
|
|
132
|
+
|
|
102
133
|
return {
|
|
103
|
-
...
|
|
104
|
-
rootDir,
|
|
105
|
-
workspaceGlobs: packageJson.workspaces ?? [],
|
|
106
|
-
}),
|
|
134
|
+
...result,
|
|
107
135
|
name: packageJson.name ?? "",
|
|
108
136
|
};
|
|
109
137
|
};
|
package/src/workspaces/index.ts
CHANGED
|
@@ -141,7 +141,7 @@ export const resolvePackageJsonContent = (
|
|
|
141
141
|
try {
|
|
142
142
|
json = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
143
143
|
} catch (error) {
|
|
144
|
-
logger.error(error);
|
|
144
|
+
logger.error(error as Error);
|
|
145
145
|
throw new ERRORS.InvalidPackageJson(
|
|
146
146
|
`Failed to read and parse package.json at ${packageJsonPath}: ${
|
|
147
147
|
(error as Error).message
|
|
@@ -9,4 +9,6 @@ export interface Workspace {
|
|
|
9
9
|
matchPattern: string;
|
|
10
10
|
/** The contents of the workspace's package.json, with `"workspaces"` and `"scripts"` resolved */
|
|
11
11
|
packageJson: ResolvedPackageJsonContent;
|
|
12
|
+
/** Aliases assigned to the workspace via the `"workspaceAliases"` field in the config */
|
|
13
|
+
aliases: string[];
|
|
12
14
|
}
|
package/.vscode/extensions.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"recommendations": [
|
|
3
|
-
"esbenp.prettier-vscode",
|
|
4
|
-
"oven.bun-vscode",
|
|
5
|
-
"streetsidesoftware.code-spell-checker",
|
|
6
|
-
"jasonnutter.vscode-codeowners",
|
|
7
|
-
"EditorConfig.EditorConfig",
|
|
8
|
-
"dbaeumer.vscode-eslint",
|
|
9
|
-
"github.vscode-github-actions",
|
|
10
|
-
"aaron-bond.better-comments"
|
|
11
|
-
]
|
|
12
|
-
}
|
package/.vscode/settings.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"editor.codeActionsOnSave": {
|
|
3
|
-
"source.fixAll.eslint": "explicit"
|
|
4
|
-
},
|
|
5
|
-
"eslint.codeActionsOnSave.rules": ["import/order"],
|
|
6
|
-
"eslint.validate": ["javascript", "typescript"],
|
|
7
|
-
"[typescript]": {
|
|
8
|
-
"editor.formatOnSave": true,
|
|
9
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
10
|
-
},
|
|
11
|
-
"[javascript]": {
|
|
12
|
-
"editor.formatOnSave": true,
|
|
13
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
14
|
-
},
|
|
15
|
-
"[json]": {
|
|
16
|
-
"editor.formatOnSave": true,
|
|
17
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
18
|
-
},
|
|
19
|
-
"[jsonc]": {
|
|
20
|
-
"editor.formatOnSave": true,
|
|
21
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
22
|
-
}
|
|
23
|
-
}
|
|
Binary file
|
package/eslint.config.mjs
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import js from "@eslint/js";
|
|
2
|
-
import importPlugin from "eslint-plugin-import";
|
|
3
|
-
import typescriptEslint from "typescript-eslint";
|
|
4
|
-
|
|
5
|
-
export default [
|
|
6
|
-
...typescriptEslint.config(
|
|
7
|
-
js.configs.recommended,
|
|
8
|
-
typescriptEslint.configs.recommended,
|
|
9
|
-
),
|
|
10
|
-
{
|
|
11
|
-
plugins: {
|
|
12
|
-
import: importPlugin,
|
|
13
|
-
},
|
|
14
|
-
rules: {
|
|
15
|
-
"@typescript-eslint/no-empty-interface": "off",
|
|
16
|
-
"@typescript-eslint/no-empty-function": "off",
|
|
17
|
-
"no-empty": "warn",
|
|
18
|
-
"@typescript-eslint/no-extra-semi": "off",
|
|
19
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
20
|
-
|
|
21
|
-
"@typescript-eslint/no-unused-vars": [
|
|
22
|
-
"warn",
|
|
23
|
-
{
|
|
24
|
-
varsIgnorePattern: "^_",
|
|
25
|
-
argsIgnorePattern: "^_",
|
|
26
|
-
destructuredArrayIgnorePattern: "^_",
|
|
27
|
-
caughtErrorsIgnorePattern: "^_",
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
|
|
31
|
-
eqeqeq: "error",
|
|
32
|
-
"prefer-const": "error",
|
|
33
|
-
|
|
34
|
-
"import/order": [
|
|
35
|
-
"warn",
|
|
36
|
-
{
|
|
37
|
-
alphabetize: {
|
|
38
|
-
order: "asc",
|
|
39
|
-
caseInsensitive: true,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
];
|